[MERGE] merged v7 branch

bzr revid: qdp-launchpad@openerp.com-20130205155556-b4zw4y3gr9v2eom3
This commit is contained in:
Quentin (OpenERP) 2013-02-05 16:55:56 +01:00
commit 552583b9d8
76 changed files with 884 additions and 672 deletions

View File

@ -3521,7 +3521,7 @@ class account_bank_accounts_wizard(osv.osv_memory):
_columns = { _columns = {
'acc_name': fields.char('Account Name.', size=64, required=True), 'acc_name': fields.char('Account Name.', size=64, required=True),
'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True), 'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True, ondelete='cascade'),
'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."), 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32), 'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
} }

View File

@ -41,7 +41,7 @@ class bank(osv.osv):
def _prepare_name(self, bank): def _prepare_name(self, bank):
"Return the name to use when creating a bank journal" "Return the name to use when creating a bank journal"
return (bank.bank_name or '') + ' ' + bank.acc_number return (bank.bank_name or '') + ' ' + (bank.acc_number or '')
def _prepare_name_get(self, cr, uid, bank_dicts, context=None): def _prepare_name_get(self, cr, uid, bank_dicts, context=None):
"""Add ability to have %(currency_name)s in the format_layout of res.partner.bank.type""" """Add ability to have %(currency_name)s in the format_layout of res.partner.bank.type"""

View File

@ -256,7 +256,7 @@
</group> </group>
<group> <group>
<field name="move_id" groups="account.group_account_user"/> <field name="move_id" groups="account.group_account_user"/>
<field name="period_id" domain="[('state', '=', 'draft')]" groups="account.group_account_user" widget="selection"/> <field name="period_id" domain="[('state', '=', 'draft'), ('company_id', '=', company_id)]" groups="account.group_account_user"/>
<field name="company_id" on_change="onchange_company_id(company_id,partner_id,type,invoice_line,currency_id)" widget="selection" groups="base.group_multi_company"/> <field name="company_id" on_change="onchange_company_id(company_id,partner_id,type,invoice_line,currency_id)" widget="selection" groups="base.group_multi_company"/>
</group> </group>
</group> </group>
@ -393,7 +393,7 @@
<field name="company_id" on_change="onchange_company_id(company_id,partner_id,type,invoice_line,currency_id)" widget="selection" groups="base.group_multi_company"/> <field name="company_id" on_change="onchange_company_id(company_id,partner_id,type,invoice_line,currency_id)" widget="selection" groups="base.group_multi_company"/>
<field name="user_id" groups="base.group_user"/> <field name="user_id" groups="base.group_user"/>
<field domain="[('partner_id.ref_companies', 'in', [company_id])]" name="partner_bank_id"/> <field domain="[('partner_id.ref_companies', 'in', [company_id])]" name="partner_bank_id"/>
<field name="period_id" domain="[('state', '=', 'draft')]" <field name="period_id" domain="[('state', '=', 'draft'), ('company_id', '=', company_id)]"
groups="account.group_account_manager" groups="account.group_account_manager"
string="Accounting Period" string="Accounting Period"
placeholder="force period"/> placeholder="force period"/>

View File

@ -347,6 +347,15 @@
res_model="account.move.line" res_model="account.move.line"
src_model="account.account"/> src_model="account.account"/>
<!-- Enable drill-down from Chart Of Accounts tree view -->
<act_window
id="action_account_items"
name="Journal Items"
context="{'search_default_account_id': [active_id]}"
res_model="account.move.line"
src_model="account.account"
key2="tree_but_open"/>
<!-- Account Journal --> <!-- Account Journal -->
<record id="view_account_journal_tree" model="ir.ui.view"> <record id="view_account_journal_tree" model="ir.ui.view">
<field name="name">account.journal.tree</field> <field name="name">account.journal.tree</field>
@ -841,6 +850,16 @@
res_model="account.move.line" res_model="account.move.line"
src_model="account.tax.code"/> src_model="account.tax.code"/>
<!-- Enable drill-down from Chart Of Taxes tree view -->
<act_window
id="action_tax_code_items"
name="Journal Items"
domain="[('tax_code_id','child_of',active_id),('state','!=','draft')]"
res_model="account.move.line"
src_model="account.tax.code"
key2="tree_but_open"/>
<!-- Tax --> <!-- Tax -->
<record id="view_tax_tree" model="ir.ui.view"> <record id="view_tax_tree" model="ir.ui.view">
<field name="name">account.tax.tree</field> <field name="name">account.tax.tree</field>

View File

@ -155,11 +155,13 @@
<record id="mt_invoice_validated" model="mail.message.subtype"> <record id="mt_invoice_validated" model="mail.message.subtype">
<field name="name">Validated</field> <field name="name">Validated</field>
<field name="res_model">account.invoice</field> <field name="res_model">account.invoice</field>
<field name="default" eval="False"/>
<field name="description">Invoice validated</field> <field name="description">Invoice validated</field>
</record> </record>
<record id="mt_invoice_paid" model="mail.message.subtype"> <record id="mt_invoice_paid" model="mail.message.subtype">
<field name="name">Paid</field> <field name="name">Paid</field>
<field name="res_model">account.invoice</field> <field name="res_model">account.invoice</field>
<field name="default" eval="False"/>
<field name="description">Invoice paid</field> <field name="description">Invoice paid</field>
</record> </record>
</data> </data>

View File

@ -12,9 +12,9 @@
<field name="partner_id"/> <field name="partner_id"/>
<field name="code"/> <field name="code"/>
<field name="date"/> <field name="date"/>
<field name="date_start" invisible="1"/> <field name="date_start"/>
<field name="user_id" invisible="1"/> <field name="user_id" invisible="1"/>
<field name="manager_id" invisible="1"/> <field name="manager_id"/>
<field name="parent_id" invisible="1"/> <field name="parent_id" invisible="1"/>
<field name="state" invisible="1"/> <field name="state" invisible="1"/>
<field name="type" invisible="1"/> <field name="type" invisible="1"/>
@ -30,15 +30,18 @@
<search string="Analytic Account"> <search string="Analytic Account">
<field name="name" filter_domain="['|', ('name','ilike',self), ('code','ilike',self)]" string="Analytic Account"/> <field name="name" filter_domain="['|', ('name','ilike',self), ('code','ilike',self)]" string="Analytic Account"/>
<field name="date"/> <field name="date"/>
<filter icon="terp-gtk-media-pause" string="Pending" domain="[('state','=','pending')]" help="Pending Accounts"/>
<filter icon="terp-camera_test" string="Current" domain="[('state','=','open')]" help="Current Accounts"/>
<field name="partner_id"/> <field name="partner_id"/>
<field name="manager_id"/> <field name="manager_id"/>
<field name="parent_id"/>
<field name="user_id"/> <field name="user_id"/>
<filter string="Open" domain="[('state','=','open')]" help="Current Accounts"/>
<filter string="Pending" domain="[('state','=','pending')]" help="Pending Accounts"/>
<group expand="0" string="Group By..."> <group expand="0" string="Group By...">
<filter string="Associated Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/> <filter string="Associated Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Parent Account" icon="terp-folder-green" domain="[]" context="{'group_by':'parent_id'}"/> <filter string="Type" domain="[]" context="{'group_by':'type'}"/>
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" groups="base.group_no_one"/> <filter string="Template" domain="[]" context="{'group_by':'template_id'}"/>
<filter string="Parent Account" domain="[]" context="{'group_by':'parent_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}" groups="base.group_no_one"/>
</group> </group>
</search> </search>
</field> </field>

View File

@ -58,6 +58,7 @@
<separator/> <separator/>
<filter icon="terp-personal" string="Customer" name="customer" domain="['|', ('type','=','out_invoice'),('type','=','out_refund')]" help="Customer Invoices And Refunds"/> <filter icon="terp-personal" string="Customer" name="customer" domain="['|', ('type','=','out_invoice'),('type','=','out_refund')]" help="Customer Invoices And Refunds"/>
<filter icon="terp-personal" string="Supplier" domain="['|', ('type','=','in_invoice'),('type','=','in_refund')]" help="Supplier Invoices And Refunds"/> <filter icon="terp-personal" string="Supplier" domain="['|', ('type','=','in_invoice'),('type','=','in_refund')]" help="Supplier Invoices And Refunds"/>
<separator/>
<filter icon="terp-dolar" string="Invoice" domain="['|', ('type','=','out_invoice'),('type','=','in_invoice')]" help="Customer And Supplier Invoices"/> <filter icon="terp-dolar" string="Invoice" domain="['|', ('type','=','out_invoice'),('type','=','in_invoice')]" help="Customer And Supplier Invoices"/>
<filter icon="terp-dolar_ok!" string="Refund" domain="['|', ('type','=','out_refund'),('type','=','in_refund')]" help="Customer And Supplier Refunds"/> <filter icon="terp-dolar_ok!" string="Refund" domain="['|', ('type','=','out_refund'),('type','=','in_refund')]" help="Customer And Supplier Refunds"/>
<field name="partner_id"/> <field name="partner_id"/>

View File

@ -35,10 +35,7 @@ class third_party_ledger(report_sxw.rml_parse, common_report_header):
'lines': self.lines, 'lines': self.lines,
'sum_debit_partner': self._sum_debit_partner, 'sum_debit_partner': self._sum_debit_partner,
'sum_credit_partner': self._sum_credit_partner, 'sum_credit_partner': self._sum_credit_partner,
# 'sum_debit': self._sum_debit,
# 'sum_credit': self._sum_credit,
'get_currency': self._get_currency, 'get_currency': self._get_currency,
'comma_me': self.comma_me,
'get_start_period': self.get_start_period, 'get_start_period': self.get_start_period,
'get_end_period': self.get_end_period, 'get_end_period': self.get_end_period,
'get_account': self._get_account, 'get_account': self._get_account,
@ -78,11 +75,6 @@ class third_party_ledger(report_sxw.rml_parse, common_report_header):
move_state = ['draft','posted'] move_state = ['draft','posted']
if self.target_move == 'posted': if self.target_move == 'posted':
move_state = ['posted'] move_state = ['posted']
if (data['model'] == 'res.partner'):
## Si on imprime depuis les partenaires
if ids:
PARTNER_REQUEST = "AND line.partner_id IN %s",(tuple(ids),)
if self.result_selection == 'supplier': if self.result_selection == 'supplier':
self.ACCOUNT_TYPE = ['payable'] self.ACCOUNT_TYPE = ['payable']
elif self.result_selection == 'customer': elif self.result_selection == 'customer':
@ -98,7 +90,11 @@ class third_party_ledger(report_sxw.rml_parse, common_report_header):
'WHERE a.type IN %s' \ 'WHERE a.type IN %s' \
"AND a.active", (tuple(self.ACCOUNT_TYPE), )) "AND a.active", (tuple(self.ACCOUNT_TYPE), ))
self.account_ids = [a for (a,) in self.cr.fetchall()] self.account_ids = [a for (a,) in self.cr.fetchall()]
partner_to_use = [] params = [tuple(move_state), tuple(self.account_ids)]
#if we print from the partners, add a clause on active_ids
if (data['model'] == 'res.partner') and ids:
PARTNER_REQUEST = "AND l.partner_id IN %s"
params += [tuple(ids)]
self.cr.execute( self.cr.execute(
"SELECT DISTINCT l.partner_id " \ "SELECT DISTINCT l.partner_id " \
"FROM account_move_line AS l, account_account AS account, " \ "FROM account_move_line AS l, account_account AS account, " \
@ -110,30 +106,10 @@ class third_party_ledger(report_sxw.rml_parse, common_report_header):
# "AND " + self.query +" " \ # "AND " + self.query +" " \
"AND l.account_id IN %s " \ "AND l.account_id IN %s " \
" " + PARTNER_REQUEST + " " \ " " + PARTNER_REQUEST + " " \
"AND account.active ", "AND account.active ", params)
(tuple(move_state), tuple(self.account_ids),)) self.partner_ids = [res['partner_id'] for res in self.cr.dictfetchall()]
objects = obj_partner.browse(self.cr, self.uid, self.partner_ids)
res = self.cr.dictfetchall() return super(third_party_ledger, self).set_context(objects, data, self.partner_ids, report_type)
for res_line in res:
partner_to_use.append(res_line['partner_id'])
new_ids = partner_to_use
self.partner_ids = new_ids
objects = obj_partner.browse(self.cr, self.uid, new_ids)
return super(third_party_ledger, self).set_context(objects, data, new_ids, report_type)
def comma_me(self, amount):
if type(amount) is float:
amount = str('%.2f'%amount)
else:
amount = str(amount)
if (amount == '0'):
return ' '
orig = amount
new = re.sub("^(-?\d+)(\d{3})", "\g<1>'\g<2>", amount)
if orig == new:
return new
else:
return self.comma_me(new)
def lines(self, partner): def lines(self, partner):
move_state = ['draft','posted'] move_state = ['draft','posted']
@ -290,105 +266,6 @@ class third_party_ledger(report_sxw.rml_parse, common_report_header):
result_tmp = result_tmp + 0.0 result_tmp = result_tmp + 0.0
return result_tmp + result_init return result_tmp + result_init
# code is deprecated
# def _sum_debit(self):
# move_state = ['draft','posted']
# if self.target_move == 'posted':
# move_state = ['posted']
#
# if not self.ids:
# return 0.0
# result_tmp = 0.0
# result_init = 0.0
# if self.reconcil:
# RECONCILE_TAG = " "
# else:
# RECONCILE_TAG = "AND reconcile_id IS NULL"
# if self.initial_balance:
# self.cr.execute(
# "SELECT sum(debit) " \
# "FROM account_move_line AS l, " \
# "account_move AS m "
# "WHERE partner_id IN %s" \
# "AND m.id = l.move_id " \
# "AND m.state IN %s "
# "AND account_id IN %s" \
# "AND reconcile_id IS NULL " \
# "AND " + self.init_query + " ",
# (tuple(self.partner_ids), tuple(move_state), tuple(self.account_ids)))
# contemp = self.cr.fetchone()
# if contemp != None:
# result_init = contemp[0] or 0.0
# else:
# result_init = result_tmp + 0.0
#
# self.cr.execute(
# "SELECT sum(debit) " \
# "FROM account_move_line AS l, " \
# "account_move AS m "
# "WHERE partner_id IN %s" \
# "AND m.id = l.move_id " \
# "AND m.state IN %s "
# "AND account_id IN %s" \
# " " + RECONCILE_TAG + " " \
# "AND " + self.query + " ",
# (tuple(self.partner_ids), tuple(move_state) ,tuple(self.account_ids),))
# contemp = self.cr.fetchone()
# if contemp != None:
# result_tmp = contemp[0] or 0.0
# else:
# result_tmp = result_tmp + 0.0
# return result_tmp + result_init
#
# def _sum_credit(self):
# move_state = ['draft','posted']
# if self.target_move == 'posted':
# move_state = ['posted']
#
# if not self.ids:
# return 0.0
# result_tmp = 0.0
# result_init = 0.0
# if self.reconcil:
# RECONCILE_TAG = " "
# else:
# RECONCILE_TAG = "AND reconcile_id IS NULL"
# if self.initial_balance:
# self.cr.execute(
# "SELECT sum(credit) " \
# "FROM account_move_line AS l, " \
# "account_move AS m "
# "WHERE partner_id IN %s" \
# "AND m.id = l.move_id " \
# "AND m.state IN %s "
# "AND account_id IN %s" \
# "AND reconcile_id IS NULL " \
# "AND " + self.init_query + " ",
# (tuple(self.partner_ids), tuple(move_state), tuple(self.account_ids)))
# contemp = self.cr.fetchone()
# if contemp != None:
# result_init = contemp[0] or 0.0
# else:
# result_init = result_tmp + 0.0
#
# self.cr.execute(
# "SELECT sum(credit) " \
# "FROM account_move_line AS l, " \
# "account_move AS m "
# "WHERE partner_id IN %s" \
# "AND m.id = l.move_id " \
# "AND m.state IN %s "
# "AND account_id IN %s" \
# " " + RECONCILE_TAG + " " \
# "AND " + self.query + " ",
# (tuple(self.partner_ids), tuple(move_state), tuple(self.account_ids),))
# contemp = self.cr.fetchone()
# if contemp != None:
# result_tmp = contemp[0] or 0.0
# else:
# result_tmp = result_tmp + 0.0
# return result_tmp + result_init
def _get_partners(self): def _get_partners(self):
if self.result_selection == 'customer': if self.result_selection == 'customer':
return 'Receivable Accounts' return 'Receivable Accounts'

View File

@ -23,7 +23,7 @@
</record> </record>
<record id="action_account_partner_ledger" model="ir.actions.act_window"> <record id="action_account_partner_ledger" model="ir.actions.act_window">
<field name="name">Select Period</field> <field name="name">Partner Ledger</field>
<field name="res_model">account.partner.ledger</field> <field name="res_model">account.partner.ledger</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="view_type">form</field> <field name="view_type">form</field>
@ -33,6 +33,13 @@
<field name="target">new</field> <field name="target">new</field>
</record> </record>
<record model="ir.values" id="ir_values_account_partner_ledger">
<field name="key2" eval="'client_print_multi'"/>
<field name="model" eval="'res.partner'"/>
<field name="name">Print Partner Ledger</field>
<field name="value" eval="'ir.actions.act_window,%d'%action_account_partner_ledger"/>
</record>
<menuitem icon="STOCK_PRINT" <menuitem icon="STOCK_PRINT"
name="Partner Ledger" name="Partner Ledger"
action="action_account_partner_ledger" action="action_account_partner_ledger"

View File

@ -25,19 +25,20 @@
<field name="name">account.analytic.account.search</field> <field name="name">account.analytic.account.search</field>
<field name="model">account.analytic.account</field> <field name="model">account.analytic.account</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Analytic Account"> <search string="Contracts">
<field name="name" filter_domain="['|', ('name','ilike',self),('code','ilike',self)]" string="Contract"/> <field name="name" filter_domain="['|', ('name','ilike',self),('code','ilike',self)]" string="Contract"/>
<field name="date"/> <field name="date"/>
<filter icon="terp-camera_test" name="open" string="Open" domain="[('state','in',('open','draft'))]" help="Contracts in progress"/>
<filter icon="terp-gtk-media-pause" name="pending" string="Pending" domain="[('state','=','pending')]" help="Pending contracts to renew with your customer"/>
<filter icon="terp-go-today" string="To Renew" domain="['|', '&amp;', ('date', '!=', False), ('date', '&lt;=', time.strftime('%%Y-%%m-%%d')), ('is_overdue_quantity', '=', True)]" name="renew"
help="The contracts to be renewed because the deadline is passed or the working hours are higher than the allocated hours" />
<separator/>
<filter string="Customer Contracts" help="A contract in OpenERP is an analytic account having a partner set on it." name="has_partner" domain="[('partner_id', '!=', False)]" icon="terp-partner" />
<separator/>
<filter string="Contracts not assigned" help="Contracts that are not assigned to an account manager." domain="[('manager_id', '=', False)]" icon="terp-personal-" />
<field name="partner_id"/> <field name="partner_id"/>
<field name="manager_id"/> <field name="manager_id"/>
<field name="parent_id"/>
<filter name="open" string="Open" domain="[('state','in',('open','draft'))]" help="Contracts in progress"/>
<filter name="pending" string="Pending" domain="[('state','=','pending')]" help="Pending contracts to renew with your customer"/>
<filter string="To Renew" domain="['|', '&amp;', ('date', '!=', False), ('date', '&lt;=', time.strftime('%%Y-%%m-%%d')), ('is_overdue_quantity', '=', True)]" name="renew"
help="The contracts to be renewed because the deadline is passed or the working hours are higher than the allocated hours" />
<separator/>
<filter string="Customer Contracts" help="Contracts assigned to a customer." name="has_partner" domain="[('partner_id', '!=', False)]"/>
<filter string="Contracts not assigned" help="Contracts that are not assigned to an account manager." domain="[('manager_id', '=', False)]"/>
<separator/>
<group expand="0" string="Group By..."> <group expand="0" string="Group By...">
<filter string="Status" domain="[]" context="{'group_by':'state'}"/> <filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Account Manager" domain="[]" context="{'group_by':'manager_id'}"/> <filter string="Account Manager" domain="[]" context="{'group_by':'manager_id'}"/>

View File

@ -42,7 +42,7 @@ This module manages:
'category': 'Accounting & Finance', 'category': 'Accounting & Finance',
'sequence': 4, 'sequence': 4,
'website' : 'http://openerp.com', 'website' : 'http://openerp.com',
'images' : ['images/customer_payment.jpeg','images/journal_voucher.jpeg','images/sales_receipt.jpeg','images/supplier_voucher.jpeg'], 'images' : ['images/customer_payment.jpeg','images/journal_voucher.jpeg','images/sales_receipt.jpeg','images/supplier_voucher.jpeg','images/customer_invoice.jpeg','images/customer_refunds.jpeg'],
'depends' : ['account'], 'depends' : ['account'],
'demo' : [], 'demo' : [],
'data' : [ 'data' : [

View File

@ -962,7 +962,9 @@ class account_voucher(osv.osv):
if not voucher_brw.journal_id.sequence_id.active: if not voucher_brw.journal_id.sequence_id.active:
raise osv.except_osv(_('Configuration Error !'), raise osv.except_osv(_('Configuration Error !'),
_('Please activate the sequence of selected journal !')) _('Please activate the sequence of selected journal !'))
name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=context) c = dict(context)
c.update({'fiscalyear_id': voucher_brw.period_id.fiscalyear_id.id})
name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id, context=c)
else: else:
raise osv.except_osv(_('Error!'), raise osv.except_osv(_('Error!'),
_('Please define a sequence on the journal.')) _('Please define a sequence on the journal.'))
@ -977,7 +979,7 @@ class account_voucher(osv.osv):
'narration': voucher_brw.narration, 'narration': voucher_brw.narration,
'date': voucher_brw.date, 'date': voucher_brw.date,
'ref': ref, 'ref': ref,
'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False 'period_id': voucher_brw.period_id.id,
} }
return move return move

View File

@ -17,6 +17,7 @@
<record id="mt_voucher_state_change" model="mail.message.subtype"> <record id="mt_voucher_state_change" model="mail.message.subtype">
<field name="name">Status Change</field> <field name="name">Status Change</field>
<field name="res_model">account.voucher</field> <field name="res_model">account.voucher</field>
<field name="default" eval="False"/>
<field name="description">Status changed</field> <field name="description">Status changed</field>
</record> </record>

View File

@ -172,7 +172,7 @@ class account_analytic_account(osv.osv):
_columns = { _columns = {
'name': fields.char('Account/Contract Name', size=128, required=True), 'name': fields.char('Account/Contract Name', size=128, required=True),
'complete_name': fields.function(_get_full_name, type='char', string='Full Account Name'), 'complete_name': fields.function(_get_full_name, type='char', string='Full Name'),
'code': fields.char('Reference', select=True), 'code': fields.char('Reference', select=True),
'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Contract')], 'Type of Account', required=True, 'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Contract')], 'Type of Account', required=True,
help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\ help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\

View File

@ -6,16 +6,19 @@
<record id="mt_account_pending" model="mail.message.subtype"> <record id="mt_account_pending" model="mail.message.subtype">
<field name="name">Contract to Renew</field> <field name="name">Contract to Renew</field>
<field name="res_model">account.analytic.account</field> <field name="res_model">account.analytic.account</field>
<field name="default" eval="False"/>
<field name="description">Contract pending</field> <field name="description">Contract pending</field>
</record> </record>
<record id="mt_account_closed" model="mail.message.subtype"> <record id="mt_account_closed" model="mail.message.subtype">
<field name="name">Contract Finished</field> <field name="name">Contract Finished</field>
<field name="res_model">account.analytic.account</field> <field name="res_model">account.analytic.account</field>
<field name="default" eval="False"/>
<field name="description">Contract closed</field> <field name="description">Contract closed</field>
</record> </record>
<record id="mt_account_opened" model="mail.message.subtype"> <record id="mt_account_opened" model="mail.message.subtype">
<field name="name">Contract Opened</field> <field name="name">Contract Opened</field>
<field name="res_model">account.analytic.account</field> <field name="res_model">account.analytic.account</field>
<field name="default" eval="False"/>
<field name="description">Contract opened</field> <field name="description">Contract opened</field>
</record> </record>

View File

@ -549,7 +549,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory):
key = (line['model_id'], line['field_id']) key = (line['model_id'], line['field_id'])
custom_updates = fixes.get(key) custom_updates = fixes.get(key)
if custom_updates: if custom_updates:
custom_updates.sort(itemgetter('sequence')) custom_updates.sort(key=itemgetter('sequence'))
queries = [(record['query'], record['query_type']) for record in custom_updates if record['query_type']] queries = [(record['query'], record['query_type']) for record in custom_updates if record['query_type']]
elif table_name: elif table_name:
queries = [("update %(table)s set %(field)s = %%(value)s where id = %%(id)s" % { queries = [("update %(table)s set %(field)s = %%(value)s where id = %%(id)s" % {

View File

@ -33,5 +33,39 @@
<p>Note: If you do not expect this, you can safely ignore this email.</p>]]></field> <p>Note: If you do not expect this, you can safely ignore this email.</p>]]></field>
</record> </record>
<!-- Email template for new users -->
<record id="set_password_email" model="email.template">
<field name="name">OpenERP Enterprise Connection</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">${object.email}</field>
<field name="subject"><![CDATA[${object.company_id.name} invitation to connect on OpenERP]]></field>
<field name="body_html">
<![CDATA[
<p>
${object.name},
</p>
<p>
You have been invited to connect to "${object.company_id.name}" in order to get access to your documents in OpenERP.
</p>
<p>
To accept the invitation, click on the following link:
</p>
<ul>
<li><a href="${object.signup_url}">Accept invitation to "${object.company_id.name}"</a></li>
</ul>
<p>
Thanks,
</p>
<pre>
--
${object.company_id.name or ''}
${object.company_id.email or ''}
${object.company_id.phone or ''}
</pre>
]]>
</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -246,8 +246,18 @@ class res_users(osv.Model):
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)] partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)]
res_partner.signup_prepare(cr, uid, partner_ids, signup_type="reset", expiration=now(days=+1), context=context) res_partner.signup_prepare(cr, uid, partner_ids, signup_type="reset", expiration=now(days=+1), context=context)
if not context:
context = {}
# send email to users with their signup url # send email to users with their signup url
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'reset_password_email') template = False
if context.get('create_user'):
try:
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'set_password_email')
except ValueError:
pass
if not bool(template):
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'reset_password_email')
mail_obj = self.pool.get('mail.mail') mail_obj = self.pool.get('mail.mail')
assert template._name == 'email.template' assert template._name == 'email.template'
for user in self.browse(cr, uid, ids, context): for user in self.browse(cr, uid, ids, context):
@ -274,5 +284,6 @@ class res_users(osv.Model):
user_id = super(res_users, self).create(cr, uid, values, context=context) user_id = super(res_users, self).create(cr, uid, values, context=context)
user = self.browse(cr, uid, user_id, context=context) user = self.browse(cr, uid, user_id, context=context)
if context and context.get('reset_password') and user.email: if context and context.get('reset_password') and user.email:
user.action_reset_password() ctx = dict(context, create_user=True)
self.action_reset_password(cr, uid, [user.id], context=ctx)
return user_id return user_id

View File

@ -164,7 +164,10 @@
background: #efc9cb; background: #efc9cb;
} }
.oe_import .select2-results { /* Field dropdown */
font-size: 12px; .oe_import_selector {
font-size: 10px;
/* copied from base.sass:~148 */
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
} }

View File

@ -134,6 +134,7 @@ openerp.base_import = function (instance) {
start: function () { start: function () {
var self = this; var self = this;
this.setup_encoding_picker(); this.setup_encoding_picker();
this.setup_separator_picker();
return $.when( return $.when(
this._super(), this._super(),
@ -164,6 +165,26 @@ openerp.base_import = function (instance) {
} }
}).select2('val', 'utf-8'); }).select2('val', 'utf-8');
}, },
setup_separator_picker: function () {
this.$('input.oe_import_separator').select2({
width: '160px',
query: function (q) {
var suggestions = [
{id: ',', text: _t("Comma")},
{id: ';', text: _t("Semicolon")},
{id: '\t', text: _t("Tab")},
{id: ' ', text: _t("Space")}
];
if (q.term) {
suggestions.unshift({id: q.term, text: q.term});
}
q.callback({results: suggestions});
},
initSelection: function (e, c) {
return c({id: ',', text: _t("Comma")});
},
});
},
import_options: function () { import_options: function () {
var self = this; var self = this;
@ -336,8 +357,18 @@ openerp.base_import = function (instance) {
var fields = this.$('.oe_import_fields input.oe_import_match_field').map(function (index, el) { var fields = this.$('.oe_import_fields input.oe_import_match_field').map(function (index, el) {
return $(el).select2('val') || false; return $(el).select2('val') || false;
}).get(); }).get();
return this.Import.call( return this.Import.call('do', [this.id, fields, this.import_options()], options)
'do', [this.id, fields, this.import_options()], options); .then(undefined, function (error, event) {
// In case of unexpected exception, convert
// "JSON-RPC error" to an import failure, and
// prevent default handling (warning dialog)
if (event) { event.preventDefault(); }
return $.when([{
type: 'error',
record: false,
message: error.data.fault_code,
}]);
}) ;
}, },
onvalidate: function () { onvalidate: function () {
return this.call_import({ dryrun: true }) return this.call_import({ dryrun: true })

View File

@ -36,6 +36,7 @@ You can track your suppliers, customers and other contacts.
'data': [ 'data': [
'contacts_view.xml', 'contacts_view.xml',
], ],
'images': ['images/contacts.jpeg'],
'installable': True, 'installable': True,
'application': True, 'application': True,
'auto_install': False, 'auto_install': False,

View File

@ -54,6 +54,7 @@ Dashboard for CRM will include:
'base_status', 'base_status',
'process', 'process',
'mail', 'mail',
'email_template',
'base_calendar', 'base_calendar',
'resource', 'resource',
'board', 'board',
@ -116,6 +117,6 @@ Dashboard for CRM will include:
'installable': True, 'installable': True,
'application': True, 'application': True,
'auto_install': False, 'auto_install': False,
'images': ['images/sale_crm_crm_dashboard.png', 'images/crm_dashboard.jpeg','images/leads.jpeg','images/meetings.jpeg','images/opportunities.jpeg','images/outbound_calls.jpeg','images/stages.jpeg'], 'images': ['images/crm_dashboard.png', 'images/customers.png','images/leads.png','images/opportunities_kanban.png','images/opportunities_form.png','images/opportunities_calendar.png','images/opportunities_graph.png','images/logged_calls.png','images/scheduled_calls.png','images/stages.png'],
} }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -36,7 +36,6 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name',
'company_id', 'company_id',
'country_id', 'country_id',
'section_id', 'section_id',
'stage_id',
'state_id', 'state_id',
'type_id', 'type_id',
'user_id', 'user_id',
@ -472,6 +471,15 @@ class crm_lead(base_stage, format_address, osv.osv):
return 'lead' return 'lead'
def _merge_get_result_stage(self, cr, uid, opps, context=None):
stage = None
for opp in opps:
if not stage:
stage = opp.stage_id.id
if opp.type == 'opportunity':
return opp.stage_id.id
return stage
def _merge_data(self, cr, uid, ids, oldest, fields, context=None): def _merge_data(self, cr, uid, ids, oldest, fields, context=None):
""" """
Prepare lead/opp data into a dictionary for merging. Different types Prepare lead/opp data into a dictionary for merging. Different types
@ -488,10 +496,8 @@ class crm_lead(base_stage, format_address, osv.osv):
opportunities = self.browse(cr, uid, ids, context=context) opportunities = self.browse(cr, uid, ids, context=context)
def _get_first_not_null(attr): def _get_first_not_null(attr):
if hasattr(oldest, attr):
return getattr(oldest, attr)
for opp in opportunities: for opp in opportunities:
if hasattr(opp, attr): if hasattr(opp, attr) and bool(getattr(opp, attr)):
return getattr(opp, attr) return getattr(opp, attr)
return False return False
@ -520,7 +526,7 @@ class crm_lead(base_stage, format_address, osv.osv):
# Define the resulting type ('lead' or 'opportunity') # Define the resulting type ('lead' or 'opportunity')
data['type'] = self._merge_get_result_type(cr, uid, opportunities, context) data['type'] = self._merge_get_result_type(cr, uid, opportunities, context)
data['stage_id'] = self._merge_get_result_stage(cr, uid, opportunities, context)
return data return data
def _merge_find_oldest(self, cr, uid, ids, context=None): def _merge_find_oldest(self, cr, uid, ids, context=None):
@ -533,9 +539,6 @@ class crm_lead(base_stage, format_address, osv.osv):
if context is None: if context is None:
context = {} context = {}
if context.get('convert'):
ids = list(set(ids) - set(context.get('lead_ids', [])))
# Search opportunities order by create date # Search opportunities order by create date
opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date', context=context) opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date', context=context)
oldest_opp_id = opportunity_ids[0] oldest_opp_id = opportunity_ids[0]
@ -643,19 +646,11 @@ class crm_lead(base_stage, format_address, osv.osv):
if len(ids) <= 1: if len(ids) <= 1:
raise osv.except_osv(_('Warning!'),_('Please select more than one element (lead or opportunity) from the list view.')) raise osv.except_osv(_('Warning!'),_('Please select more than one element (lead or opportunity) from the list view.'))
ids.sort()
lead_ids = context.get('lead_ids', [])
ctx_opportunities = self.browse(cr, uid, lead_ids, context=context)
opportunities = self.browse(cr, uid, ids, context=context)
opportunities_list = list(set(opportunities) - set(ctx_opportunities))
oldest = self._merge_find_oldest(cr, uid, ids, context=context) oldest = self._merge_find_oldest(cr, uid, ids, context=context)
if ctx_opportunities: opportunities_rest = self.browse(cr, uid, list(set(ids) - set([oldest.id])), context=context)
first_opportunity = ctx_opportunities[0] first_opportunity = oldest
tail_opportunities = opportunities_list + ctx_opportunities[1:] tail_opportunities = opportunities_rest
else:
first_opportunity = opportunities_list[0]
tail_opportunities = opportunities_list[1:]
merged_data = self._merge_data(cr, uid, ids, oldest, CRM_LEAD_FIELDS_TO_MERGE, context=context) merged_data = self._merge_data(cr, uid, ids, oldest, CRM_LEAD_FIELDS_TO_MERGE, context=context)
@ -664,14 +659,14 @@ class crm_lead(base_stage, format_address, osv.osv):
self._merge_opportunity_attachments(cr, uid, first_opportunity.id, tail_opportunities, context=context) self._merge_opportunity_attachments(cr, uid, first_opportunity.id, tail_opportunities, context=context)
# Merge notifications about loss of information # Merge notifications about loss of information
opportunities = [oldest]
opportunities.extend(opportunities_rest)
self._merge_notify(cr, uid, first_opportunity, opportunities, context=context) self._merge_notify(cr, uid, first_opportunity, opportunities, context=context)
# Write merged data into first opportunity # Write merged data into first opportunity
self.write(cr, uid, [first_opportunity.id], merged_data, context=context) self.write(cr, uid, [first_opportunity.id], merged_data, context=context)
# Delete tail opportunities # Delete tail opportunities
self.unlink(cr, uid, [x.id for x in tail_opportunities], context=context) self.unlink(cr, uid, [x.id for x in tail_opportunities], context=context)
# Open first opportunity
self.case_open(cr, uid, [first_opportunity.id])
return first_opportunity.id return first_opportunity.id
def _convert_opportunity_data(self, cr, uid, lead, customer, section_id=False, context=None): def _convert_opportunity_data(self, cr, uid, lead, customer, section_id=False, context=None):
@ -716,7 +711,7 @@ class crm_lead(base_stage, format_address, osv.osv):
def _lead_create_contact(self, cr, uid, lead, name, is_company, parent_id=False, context=None): def _lead_create_contact(self, cr, uid, lead, name, is_company, parent_id=False, context=None):
partner = self.pool.get('res.partner') partner = self.pool.get('res.partner')
vals = { 'name': name, vals = {'name': name,
'user_id': lead.user_id.id, 'user_id': lead.user_id.id,
'comment': lead.description, 'comment': lead.description,
'section_id': lead.section_id.id or False, 'section_id': lead.section_id.id or False,
@ -736,11 +731,11 @@ class crm_lead(base_stage, format_address, osv.osv):
'is_company': is_company, 'is_company': is_company,
'type': 'contact' 'type': 'contact'
} }
partner = partner.create(cr, uid,vals, context) partner = partner.create(cr, uid, vals, context=context)
return partner return partner
def _create_lead_partner(self, cr, uid, lead, context=None): def _create_lead_partner(self, cr, uid, lead, context=None):
partner_id = False partner_id = False
if lead.partner_name and lead.contact_name: if lead.partner_name and lead.contact_name:
partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context) partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context)
partner_id = self._lead_create_contact(cr, uid, lead, lead.contact_name, False, partner_id, context=context) partner_id = self._lead_create_contact(cr, uid, lead, lead.contact_name, False, partner_id, context=context)
@ -748,8 +743,14 @@ class crm_lead(base_stage, format_address, osv.osv):
partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context) partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context)
elif not lead.partner_name and lead.contact_name: elif not lead.partner_name and lead.contact_name:
partner_id = self._lead_create_contact(cr, uid, lead, lead.contact_name, False, context=context) partner_id = self._lead_create_contact(cr, uid, lead, lead.contact_name, False, context=context)
elif lead.email_from and self.pool.get('res.partner')._parse_partner_name(lead.email_from, context=context)[0]:
contact_name = self.pool.get('res.partner')._parse_partner_name(lead.email_from, context=context)[0]
partner_id = self._lead_create_contact(cr, uid, lead, contact_name, False, context=context)
else: else:
partner_id = self._lead_create_contact(cr, uid, lead, lead.name, False, context=context) raise osv.except_osv(
_('Warning!'),
_('No customer name defined. Please fill one of the following fields: Company Name, Contact Name or Email ("Name <email@address>")')
)
return partner_id return partner_id
def _lead_set_partner(self, cr, uid, lead, partner_id, context=None): def _lead_set_partner(self, cr, uid, lead, partner_id, context=None):
@ -763,7 +764,7 @@ class crm_lead(base_stage, format_address, osv.osv):
res = False res = False
res_partner = self.pool.get('res.partner') res_partner = self.pool.get('res.partner')
if partner_id: if partner_id:
res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id.id or False}) res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id and lead.section_id.id or False})
contact_id = res_partner.address_get(cr, uid, [partner_id])['default'] contact_id = res_partner.address_get(cr, uid, [partner_id])['default']
res = lead.write({'partner_id': partner_id}, context=context) res = lead.write({'partner_id': partner_id}, context=context)
message = _("<b>Partner</b> set to <em>%s</em>." % (lead.partner_id.name)) message = _("<b>Partner</b> set to <em>%s</em>." % (lead.partner_id.name))
@ -872,8 +873,8 @@ class crm_lead(base_stage, format_address, osv.osv):
'res_id': int(opportunity_id), 'res_id': int(opportunity_id),
'view_id': False, 'view_id': False,
'views': [(form_view or False, 'form'), 'views': [(form_view or False, 'form'),
(tree_view or False, 'tree'), (tree_view or False, 'tree'),
(False, 'calendar'), (False, 'graph')], (False, 'calendar'), (False, 'graph')],
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
} }
@ -980,8 +981,11 @@ class crm_lead(base_stage, format_address, osv.osv):
'description': desc, 'description': desc,
'email_from': msg.get('from'), 'email_from': msg.get('from'),
'email_cc': msg.get('cc'), 'email_cc': msg.get('cc'),
'partner_id': msg.get('author_id', False),
'user_id': False, 'user_id': False,
} }
if msg.get('author_id'):
defaults.update(self.on_change_partner(cr, uid, None, msg.get('author_id'), context=context)['value'])
if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES): if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
defaults['priority'] = msg.get('priority') defaults['priority'] = msg.get('priority')
defaults.update(custom_values) defaults.update(custom_values)

View File

@ -171,11 +171,13 @@
<record id="mt_lead_stage" model="mail.message.subtype"> <record id="mt_lead_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field> <field name="name">Stage Changed</field>
<field name="res_model">crm.lead</field> <field name="res_model">crm.lead</field>
<field name="default" eval="False"/>
<field name="description">Stage changed</field> <field name="description">Stage changed</field>
</record> </record>
<record id="mt_lead_won" model="mail.message.subtype"> <record id="mt_lead_won" model="mail.message.subtype">
<field name="name">Opportunity Won</field> <field name="name">Opportunity Won</field>
<field name="res_model">crm.lead</field> <field name="res_model">crm.lead</field>
<field name="default" eval="False"/>
<field name="description">Opportunity won</field> <field name="description">Opportunity won</field>
</record> </record>
<record id="mt_lead_lost" model="mail.message.subtype"> <record id="mt_lead_lost" model="mail.message.subtype">

View File

@ -168,7 +168,7 @@
</group> </group>
</group> </group>
<notebook colspan="4"> <notebook colspan="4">
<page string="Notes"> <page string="Internal Notes">
<field name="description"/> <field name="description"/>
</page> </page>
<page string="Extra Info"> <page string="Extra Info">
@ -219,12 +219,14 @@
<field name="date_deadline" invisible="1"/> <field name="date_deadline" invisible="1"/>
<field name="create_date" groups="base.group_no_one"/> <field name="create_date" groups="base.group_no_one"/>
<field name="name"/> <field name="name"/>
<field name="type"/>
<field name="contact_name"/> <field name="contact_name"/>
<field name="country_id" invisible="context.get('invisible_country', True)"/> <field name="country_id" invisible="context.get('invisible_country', True)"/>
<field name="email_from"/> <field name="email_from"/>
<field name="phone"/> <field name="phone"/>
<field name="stage_id"/> <field name="stage_id"/>
<field name="user_id" invisible="1"/> <field name="user_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="section_id" invisible="context.get('invisible_section', True)"/> <field name="section_id" invisible="context.get('invisible_section', True)"/>
<field name="state" invisible="1"/> <field name="state" invisible="1"/>
<field name="type_id" invisible="1"/> <field name="type_id" invisible="1"/>
@ -235,6 +237,7 @@
</field> </field>
</record> </record>
<!-- CRM Lead Calendar View --> <!-- CRM Lead Calendar View -->
<record model="ir.ui.view" id="crm_case_calendar_view_leads"> <record model="ir.ui.view" id="crm_case_calendar_view_leads">
<field name="name">CRM - Leads Calendar</field> <field name="name">CRM - Leads Calendar</field>
@ -322,35 +325,39 @@
<search string="Search Leads"> <search string="Search Leads">
<field name="name" string="Lead / Customer" filter_domain="['|','|',('partner_name','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/> <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="categ_ids" string="Category" filter_domain="[('categ_ids','ilike',self)]"/>
<field name="create_date"/> <field name="section_id" context="{'invisible_section': False, 'default_section_id': self}"/>
<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="In Progress" name="open" domain="[('state','=','open')]"/>
<separator/>
<filter string="Unassigned Leads" icon="terp-personal-" domain="[('user_id','=', False)]" help="Unassigned Leads"/>
<separator/>
<filter string="Leads Assigned to Me or My Team(s)" icon="terp-personal+" context="{'invisible_section': False}"
domain="['|', ('section_id.user_id','=',uid), ('section_id.member_ids', 'in', [uid])]"
help="Leads that are assigned to one of the sale teams I manage, or to me"/>
<field name="user_id"/> <field name="user_id"/>
<field name="section_id" context="{'invisible_section': False}"/> <field name="create_date"/>
<field name="country_id" context="{'invisible_country': False}"/> <field name="country_id" context="{'invisible_country': False}"/>
<separator/>
<filter string="Open" name="open" domain="[('state','!=','cancel')]" help="Open Leads"/>
<filter string="Dead" name="dead" domain="[('state','=','cancel')]"/>
<filter string="Unassigned" domain="[('user_id','=', False)]" help="No salesperson"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]" help="Unread messages"/>
<filter string="Assigned to Me"
domain="[('user_id','=',uid)]" context="{'invisible_section': False}"
help="Leads that are assigned to me"/>
<filter string="Assigned to My Team(s)"
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Leads that are assigned to any sales teams I am member of"/>
<separator/>
<group expand="0" string="Group By..."> <group expand="0" string="Group By...">
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/> <filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}"/> <filter string="Team" domain="[]" context="{'group_by':'section_id'}"/>
<filter string="Referrer" icon="terp-personal" domain="[]" context="{'group_by':'referred'}"/> <filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Campaign" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'type_id'}"/> <filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Channel" icon="terp-call-start" domain="[]" context="{'group_by':'channel_id'}"/> <filter string="Country" domain="[]" context="{'group_by':'country_id'}"/>
<separator orientation="vertical"/> <filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/> <filter string="Referrer" domain="[]" context="{'group_by':'referred'}"/>
<filter string="Creation" help="Create date" icon="terp-go-month" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/> <filter string="Campaign" domain="[]" context="{'group_by':'type_id'}"/>
<filter string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
<filter string="Creation" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/>
</group> </group>
<group string="Display"> <group string="Display">
<filter string="Show Countries" icon="terp-personal+" context="{'invisible_country': False}" help="Show Countries"/> <filter string="Show Countries" context="{'invisible_country': False}" help="Show Countries"/>
<filter string="Show Sales Team" icon="terp-personal+" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/> <filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/>
</group> </group>
</search> </search>
</field> </field>
</record> </record>
@ -517,6 +524,7 @@
<field name="probability" avg="Avg. of Probability"/> <field name="probability" avg="Avg. of Probability"/>
<field name="section_id" invisible="context.get('invisible_section', True)"/> <field name="section_id" invisible="context.get('invisible_section', True)"/>
<field name="user_id"/> <field name="user_id"/>
<field name="referred" invisible="1"/>
<field name="priority" invisible="1"/> <field name="priority" invisible="1"/>
<field name="state" groups="base.group_no_one"/> <field name="state" groups="base.group_no_one"/>
<field name="message_unread" invisible="1"/> <field name="message_unread" invisible="1"/>
@ -531,35 +539,41 @@
<field name="model">crm.lead</field> <field name="model">crm.lead</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Search Opportunities"> <search string="Search Opportunities">
<field name="name" string="Opportunity" <field name="name" string="Opportunity" filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/>
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)]"/> <field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike', self)]"/>
<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="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/>
<filter string="Opportunities Assigned to Me or My Team(s)" icon="terp-personal+"
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="section_id" context="{'invisible_section': False, 'default_section_id': self}"/> <field name="section_id" context="{'invisible_section': False, 'default_section_id': self}"/>
<field name="user_id"/>
<field name="partner_id"/> <field name="partner_id"/>
<separator/>
<filter string="New" name="new" domain="[('state','=','draft')]" help="New Opportunities"/>
<filter string="In Progress" name="open" domain="[('state','=','open')]" help="Open Opportunities"/>
<filter string="Won" name="won" domain="[('state','=','done')]"/>
<filter string="Lost" name="lost" domain="[('state','=','cancel')]"/>
<filter string="Unassigned" domain="[('user_id','=', False)]" help="No salesperson"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]" help="Unread messages"/>
<filter string="Assigned to Me"
domain="[('user_id','=',uid)]" context="{'invisible_section': False}"
help="Opportunities that are assigned to me"/>
<filter string="Assigned to My Team(s)"
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Opportunities that are assigned to any sales teams I am member of"/>
<separator/>
<group expand="0" string="Group By..." colspan="16"> <group expand="0" string="Group By..." colspan="16">
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/> <filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Team" help="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}"/> <filter string="Team" domain="[]" context="{'group_by':'section_id'}"/>
<filter string="Customer" help="Partner" icon="terp-personal+" domain="[]" context="{'group_by':'partner_id'}"/> <filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/> <filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}"/> <filter string="Country" domain="[]" context="{'group_by':'country_id'}"/>
<filter string="Campaign" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'type_id'}"/> <filter string="Priority" domain="[]" context="{'group_by':'priority'}"/>
<filter string="Channel" icon="terp-call-start" domain="[]" context="{'group_by':'channel_id'}"/> <filter string="Expected Closing" domain="[]" context="{'group_by':'date_deadline'}"/>
<filter string="Creation" icon="terp-go-month" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/> <filter string="Referrer" domain="[]" context="{'group_by':'referred'}"/>
<filter string="Exp.Closing" icon="terp-go-month" help="Expected Closing" domain="[]" context="{'group_by':'date_deadline'}"/> <filter string="Campaign" domain="[]" context="{'group_by':'type_id'}"/>
<filter string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
<filter string="Creation" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/>
</group> </group>
<group string="Display"> <group string="Display">
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/> <filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/>
<filter string="Show Countries" context="{'invisible_country': False}" help="Show Countries"/>
</group> </group>
</search> </search>
</field> </field>

View File

@ -68,24 +68,4 @@ class calendar_attendee(osv.osv):
relation="crm.case.categ", multi='categ_id'), relation="crm.case.categ", multi='categ_id'),
} }
class res_users(osv.osv):
_name = 'res.users'
_inherit = 'res.users'
def create(self, cr, uid, data, context=None):
user_id = super(res_users, self).create(cr, uid, data, context=context)
# add shortcut unless 'noshortcut' is True in context
if not(context and context.get('noshortcut', False)):
data_obj = self.pool.get('ir.model.data')
try:
data_id = data_obj._get_id(cr, uid, 'crm', 'ir_ui_view_sc_calendar0')
view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
self.pool.get('ir.ui.view_sc').copy(cr, uid, view_id, default = {
'user_id': user_id}, context=context)
except:
# Tolerate a missing shortcut. See product/product.py for similar code.
_logger.debug('Skipped meetings shortcut for user "%s".', data.get('name','<new'))
return user_id
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -4,31 +4,37 @@
!record {model: crm.lead, id: test_crm_lead_01}: !record {model: crm.lead, id: test_crm_lead_01}:
type: 'lead' type: 'lead'
name: 'Test lead 1' name: 'Test lead 1'
email_from: 'Raoul Grosbedon <raoul@grosbedon.fr>'
stage_id: stage_lead1 stage_id: stage_lead1
- -
!record {model: crm.lead, id: test_crm_lead_02}: !record {model: crm.lead, id: test_crm_lead_02}:
type: 'lead' type: 'lead'
name: 'Test lead 2' name: 'Test lead 2'
email_from: 'Raoul Grosbedon <raoul@grosbedon.fr>'
stage_id: stage_lead1 stage_id: stage_lead1
- -
!record {model: crm.lead, id: test_crm_lead_03}: !record {model: crm.lead, id: test_crm_lead_03}:
type: 'lead' type: 'lead'
name: 'Test lead 3' name: 'Test lead 3'
email_from: 'Raoul Grosbedon <raoul@grosbedon.fr>'
stage_id: stage_lead1 stage_id: stage_lead1
- -
!record {model: crm.lead, id: test_crm_lead_04}: !record {model: crm.lead, id: test_crm_lead_04}:
type: 'lead' type: 'lead'
name: 'Test lead 4' name: 'Test lead 4'
contact_name: 'Fabrice Lepoilu'
stage_id: stage_lead1 stage_id: stage_lead1
- -
!record {model: crm.lead, id: test_crm_lead_05}: !record {model: crm.lead, id: test_crm_lead_05}:
type: 'lead' type: 'lead'
name: 'Test lead 5' name: 'Test lead 5'
contact_name: 'Fabrice Lepoilu'
stage_id: stage_lead1 stage_id: stage_lead1
- -
!record {model: crm.lead, id: test_crm_lead_06}: !record {model: crm.lead, id: test_crm_lead_06}:
type: 'lead' type: 'lead'
name: 'Test lead 6' name: 'Test lead 6'
partner_name: 'Agrolait SuperSeed SA'
stage_id: stage_lead1 stage_id: stage_lead1
- -
!record {model: res.users, id: test_res_user_01}: !record {model: res.users, id: test_res_user_01}:

View File

@ -34,7 +34,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
('convert', 'Convert to opportunity'), ('convert', 'Convert to opportunity'),
('merge', 'Merge with existing opportunities') ('merge', 'Merge with existing opportunities')
], 'Conversion Action', required=True), ], 'Conversion Action', required=True),
'opportunity_ids': fields.many2many('crm.lead', string='Opportunities', domain=[('type', '=', 'opportunity')]), 'opportunity_ids': fields.many2many('crm.lead', string='Opportunities'),
} }
def default_get(self, cr, uid, fields, context=None): def default_get(self, cr, uid, fields, context=None):
@ -46,42 +46,34 @@ class crm_lead2opportunity_partner(osv.osv_memory):
lead_obj = self.pool.get('crm.lead') lead_obj = self.pool.get('crm.lead')
res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context) res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context)
opportunities = res.get('opportunity_ids') or [] if context.get('active_id'):
partner_id = False tomerge = set([int(context['active_id'])])
email = False
for lead in lead_obj.browse(cr, uid, opportunities, context=context): email = False
partner_id = lead.partner_id and lead.partner_id.id or False partner_id = res.get('partner_id')
lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)
#TOFIX: use mail.mail_message.to_mail #TOFIX: use mail.mail_message.to_mail
email = re.findall(r'([^ ,<@]+@[^> ,]+)', lead.email_from or '') email = re.findall(r'([^ ,<@]+@[^> ,]+)', lead.email_from or '')
email = map(lambda x: "'" + x + "'", email)
if not partner_id and res.get('partner_id'): if partner_id:
partner_id = res.get('partner_id') # Search for opportunities that have the same partner and that arent done or cancelled
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id)])
ids = [] for id in ids:
if partner_id: tomerge.add(id)
# Search for opportunities that have the same partner and that arent done or cancelled
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), ('type', '=', 'opportunity'), '!', ('state', 'in', ['done', 'cancel'])])
if ids:
opportunities.append(ids[0])
if not partner_id:
if email: if email:
# Find email of existing opportunity matching the email_from of the lead ids = lead_obj.search(cr, uid, [('email_from', 'ilike', email[0])])
cr.execute("""select id from crm_lead where type='opportunity' and for id in ids:
substring(email_from from '([^ ,<@]+@[^> ,]+)') in (%s)""" % (','.join(email))) tomerge.add(id)
ids = map(lambda x:x[0], cr.fetchall())
if ids:
opportunities.append(ids[0])
if 'action' in fields: if 'action' in fields:
res.update({'action' : partner_id and 'exist' or 'create'}) res.update({'action' : partner_id and 'exist' or 'create'})
if 'partner_id' in fields: if 'partner_id' in fields:
res.update({'partner_id' : partner_id}) res.update({'partner_id' : partner_id})
if 'name' in fields: if 'name' in fields:
res.update({'name' : ids and 'merge' or 'convert'}) res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
if 'opportunity_ids' in fields: if 'opportunity_ids' in fields and len(tomerge) >= 2:
res.update({'opportunity_ids': opportunities}) res.update({'opportunity_ids': list(tomerge)})
return res return res
@ -115,38 +107,44 @@ class crm_lead2opportunity_partner(osv.osv_memory):
lead.allocate_salesman(cr, uid, lead_ids, user_ids, team_id=team_id, context=context) lead.allocate_salesman(cr, uid, lead_ids, user_ids, team_id=team_id, context=context)
return res return res
def _merge_opportunity(self, cr, uid, ids, opportunity_ids, action='merge', context=None):
if context is None:
context = {}
res = False
# Expected: all newly-converted leads (active_ids) will be merged with the opportunity(ies)
# that have been selected in the 'opportunity_ids' m2m, with all these records
# merged into the first opportunity (and the rest deleted)
opportunity_ids = [o.id for o in opportunity_ids]
lead_ids = context.get('active_ids', [])
if action == 'merge' and lead_ids and opportunity_ids:
# Add the leads in the to-merge list, next to other opps
# (the fact that they're passed in context['lead_ids'] means that
# they cannot be selected to contain the result of the merge.
opportunity_ids.extend(lead_ids)
context.update({'lead_ids': lead_ids, "convert" : True})
res = self.pool.get('crm.lead').merge_opportunity(cr, uid, opportunity_ids, context=context)
return res
def action_apply(self, cr, uid, ids, context=None): def action_apply(self, cr, uid, ids, context=None):
""" """
Convert lead to opportunity or merge lead and opportunity and open Convert lead to opportunity or merge lead and opportunity and open
the freshly created opportunity view. the freshly created opportunity view.
""" """
if context is None:
context = {}
w = self.browse(cr, uid, ids, context=context)[0]
opp_ids = [o.id for o in w.opportunity_ids]
if w.name == 'merge':
lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, context=context)
lead_ids = [lead_id]
lead = self.pool.get('crm.lead').read(cr, uid, lead_id, ['type'], context=context)
if lead['type'] == "lead":
context.update({'active_ids': lead_ids})
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
else:
lead_ids = context.get('active_ids', [])
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
def _create_partner(self, cr, uid, ids, context=None):
"""
Create partner based on action.
:return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
"""
#TODO this method in only called by crm_lead2opportunity_partner
#wizard and would probably diserve to be refactored or at least
#moved to a better place
if context is None: if context is None:
context = {} context = {}
lead = self.pool.get('crm.lead') lead = self.pool.get('crm.lead')
lead_ids = context.get('active_ids', []) lead_ids = context.get('active_ids', [])
data = self.browse(cr, uid, ids, context=context)[0] data = self.browse(cr, uid, ids, context=context)[0]
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context) partner_id = data.partner_id and data.partner_id.id or False
self._merge_opportunity(cr, uid, ids, data.opportunity_ids, data.name, context=context) return lead.handle_partner_assignation(cr, uid, lead_ids, data.action, partner_id, context=context)
return lead.redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
class crm_lead2opportunity_mass_convert(osv.osv_memory): class crm_lead2opportunity_mass_convert(osv.osv_memory):
_name = 'crm.lead2opportunity.partner.mass' _name = 'crm.lead2opportunity.partner.mass'

View File

@ -9,11 +9,19 @@
<form string="Convert to Opportunity" version="7.0"> <form string="Convert to Opportunity" version="7.0">
<group name="name"> <group name="name">
<field name="name" class="oe_inline"/> <field name="name" class="oe_inline"/>
<field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}"> </group>
<group string="Opportunities">
<field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}" nolabel="1">
<tree> <tree>
<field name="create_date"/>
<field name="name"/> <field name="name"/>
<field name="partner_id"/> <field name="type"/>
<field name="user_id"/> <field name="contact_name"/>
<field name="country_id" invisible="context.get('invisible_country', True)"/>
<field name="email_from"/>
<field name="phone"/>
<field name="stage_id"/>
<field name="user_id" invisible="1"/>
<field name="section_id"/> <field name="section_id"/>
</tree> </tree>
</field> </field>
@ -51,9 +59,15 @@
<group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}"> <group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
<field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}"> <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
<tree> <tree>
<field name="create_date"/>
<field name="name"/> <field name="name"/>
<field name="partner_id"/> <field name="type"/>
<field name="user_id"/> <field name="contact_name"/>
<field name="country_id" invisible="context.get('invisible_country', True)"/>
<field name="email_from"/>
<field name="phone"/>
<field name="stage_id"/>
<field name="user_id" invisible="1"/>
<field name="section_id"/> <field name="section_id"/>
</tree> </tree>
</field> </field>

View File

@ -11,9 +11,14 @@
<separator string="Select Leads/Opportunities"/> <separator string="Select Leads/Opportunities"/>
<field name="opportunity_ids"> <field name="opportunity_ids">
<tree> <tree>
<field name="create_date"/>
<field name="name"/> <field name="name"/>
<field name="partner_id"/> <field name="type"/>
<field name="user_id"/> <field name="contact_name"/>
<field name="email_from"/>
<field name="phone"/>
<field name="stage_id"/>
<field name="user_id" invisible="1"/>
<field name="section_id"/> <field name="section_id"/>
</tree> </tree>
</field> </field>

View File

@ -96,20 +96,4 @@ class crm_partner_binding(osv.osv_memory):
return res return res
def _create_partner(self, cr, uid, ids, context=None):
"""
Create partner based on action.
:return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
"""
#TODO this method in only called by crm_lead2opportunity_partner
#wizard and would probably diserve to be refactored or at least
#moved to a better place
if context is None:
context = {}
lead = self.pool.get('crm.lead')
lead_ids = context.get('active_ids', [])
data = self.browse(cr, uid, ids, context=context)[0]
partner_id = data.partner_id and data.partner_id.id or False
return lead.handle_partner_assignation(cr, uid, lead_ids, data.action, partner_id, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -195,6 +195,7 @@ class crm_claim(base_stage, osv.osv):
'description': desc, 'description': desc,
'email_from': msg.get('from'), 'email_from': msg.get('from'),
'email_cc': msg.get('cc'), 'email_cc': msg.get('cc'),
'partner_id': msg.get('author_id', False),
} }
if msg.get('priority'): if msg.get('priority'):
defaults['priority'] = msg.get('priority') defaults['priority'] = msg.get('priority')

View File

@ -106,6 +106,7 @@ class crm_helpdesk(base_state, base_stage, osv.osv):
'email_from': msg.get('from'), 'email_from': msg.get('from'),
'email_cc': msg.get('cc'), 'email_cc': msg.get('cc'),
'user_id': False, 'user_id': False,
'partner_id': msg.get('author_id', False),
} }
defaults.update(custom_values) defaults.update(custom_values)
return super(crm_helpdesk,self).message_new(cr, uid, msg, custom_values=defaults, context=context) return super(crm_helpdesk,self).message_new(cr, uid, msg, custom_values=defaults, context=context)

View File

@ -57,6 +57,6 @@ Key Features
'installable': True, 'installable': True,
'application': True, 'application': True,
'auto_install': False, 'auto_install': False,
'images': ['images/1_event_type_list.jpeg','images/2_events.jpeg','images/3_registrations.jpeg'], 'images': ['images/1_event_type_list.jpeg','images/2_events.jpeg','images/3_registrations.jpeg','images/events_kanban.jpeg'],
} }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -54,6 +54,7 @@ Main Features
'fleet_data.xml', 'fleet_data.xml',
'fleet_board_view.xml', 'fleet_board_view.xml',
], ],
'images': ['images/costs_analysis.jpeg','images/indicative_costs_analysis.jpeg','images/vehicles.jpeg','images/vehicles_contracts.jpeg','images/vehicles_fuel.jpeg','images/vehicles_odometer.jpeg','images/vehicles_services.jpeg'],
'update_xml' : ['security/fleet_security.xml','security/ir.model.access.csv'], 'update_xml' : ['security/fleet_security.xml','security/ir.model.access.csv'],
'demo': ['fleet_demo.xml'], 'demo': ['fleet_demo.xml'],

View File

@ -29,13 +29,40 @@ _logger = logging.getLogger(__name__)
try: try:
import gdata.docs.data import gdata.docs.data
import gdata.docs.client import gdata.docs.client
from gdata.client import RequestError
from gdata.docs.service import DOCUMENT_LABEL # API breakage madness in the gdata API - those guys are insane.
import gdata.auth try:
from gdata.docs.data import Resource # gdata 2.0.15+
gdata.docs.client.DocsClient.copy_resource
except AttributeError:
# gdata 2.0.14- : copy_resource() was copy()
gdata.docs.client.DocsClient.copy_resource = gdata.docs.client.DocsClient.copy
try:
# gdata 2.0.16+
gdata.docs.client.DocsClient.get_resource_by_id
except AttributeError:
try:
# gdata 2.0.15+
gdata.docs.client.DocsClient.get_resource_by_self_link
def get_resource_by_id_2_0_16(self, resource_id, **kwargs):
return self.GetResourceBySelfLink(
gdata.docs.client.RESOURCE_FEED_URI + ('/%s' % resource_id), **kwargs)
gdata.docs.client.DocsClient.get_resource_by_id = get_resource_by_id_2_0_16
except AttributeError:
# gdata 2.0.14- : alias get_resource_by_id()
gdata.docs.client.DocsClient.get_resource_by_id = gdata.docs.client.DocsClient.get_doc
try:
import atom.http_interface
_logger.info('GData lib version `%s` detected' % atom.http_interface.USER_AGENT)
except (ImportError, AttributeError):
_logger.debug('GData lib version could not be detected', exc_info=True)
except ImportError: except ImportError:
_logger.warning("Please install latest gdata-python-client from http://code.google.com/p/gdata-python-client/downloads/list") _logger.warning("Please install latest gdata-python-client from http://code.google.com/p/gdata-python-client/downloads/list")
class google_docs_ir_attachment(osv.osv): class google_docs_ir_attachment(osv.osv):
_inherit = 'ir.attachment' _inherit = 'ir.attachment'
@ -49,11 +76,12 @@ class google_docs_ir_attachment(osv.osv):
#get gmail password and login. We use default_get() instead of a create() followed by a read() on the #get gmail password and login. We use default_get() instead of a create() followed by a read() on the
# google.login object, because it is easier. The keys 'user' and 'password' ahve to be passed in the dict # google.login object, because it is easier. The keys 'user' and 'password' ahve to be passed in the dict
# but the values will be replaced by the user gmail password and login. # but the values will be replaced by the user gmail password and login.
user_config = google_pool.default_get( cr, uid, {'user' : '' , 'password' : ''}, context=context) user_config = google_pool.default_get(cr, uid, {'user' : '' , 'password' : ''}, context=context)
#login gmail account #login gmail account
client = google_pool.google_login( user_config['user'], user_config['password'], type='docs_client', context=context) client = google_pool.google_login(user_config['user'], user_config['password'], type='docs_client', context=context)
if not client: if not client:
raise osv.except_osv( _('Google Docs Error!'), _("Check your google configuration in Users/Users/Synchronization tab.")) raise osv.except_osv(_('Google Docs Error!'), _("Check your google configuration in Users/Users/Synchronization tab."))
_logger.info('Logged into google docs as %s', user_config['user'])
return client return client
def create_empty_google_doc(self, cr, uid, res_model, res_id, context=None): def create_empty_google_doc(self, cr, uid, res_model, res_id, context=None):
@ -94,9 +122,9 @@ class google_docs_ir_attachment(osv.osv):
client = self._auth(cr, uid) client = self._auth(cr, uid)
# fetch and copy the original document # fetch and copy the original document
try: try:
doc = client.GetDoc(gdoc_template_id) doc = client.get_resource_by_id(gdoc_template_id)
#copy the document you choose in the configuration #copy the document you choose in the configuration
copy_resource = client.copy(doc, name_gdocs) copy_resource = client.copy_resource(doc, name_gdocs)
except: except:
raise osv.except_osv(_('Google Docs Error!'), _("Your resource id is not correct. You can find the id in the google docs URL.")) raise osv.except_osv(_('Google Docs Error!'), _("Your resource id is not correct. You can find the id in the google docs URL."))
# create an ir.attachment # create an ir.attachment

View File

@ -27,7 +27,7 @@
'sequence': 31, 'sequence': 31,
'website': 'http://www.openerp.com', 'website': 'http://www.openerp.com',
'summary': 'Periodical Evaluations, Appraisals, Surveys', 'summary': 'Periodical Evaluations, Appraisals, Surveys',
'images': ['images/hr_evaluation_analysis.jpeg','images/hr_evaluation.jpeg'], 'images': ['images/hr_evaluation_analysis.jpeg','images/hr_evaluation.jpeg','images/hr_interview_requests.jpeg'],
'depends': ['hr','base_calendar','survey'], 'depends': ['hr','base_calendar','survey'],
'description': """ 'description': """
Periodical Employees evaluation and appraisals Periodical Employees evaluation and appraisals

View File

@ -353,6 +353,7 @@ class hr_applicant(base_stage, osv.Model):
'email_from': msg.get('from'), 'email_from': msg.get('from'),
'email_cc': msg.get('cc'), 'email_cc': msg.get('cc'),
'user_id': False, 'user_id': False,
'partner_id': msg.get('author_id', False),
} }
if msg.get('priority'): if msg.get('priority'):
defaults['priority'] = msg.get('priority') defaults['priority'] = msg.get('priority')

View File

@ -470,11 +470,13 @@
<record id="mt_stage_changed" model="mail.message.subtype"> <record id="mt_stage_changed" model="mail.message.subtype">
<field name="name">Stage Changed</field> <field name="name">Stage Changed</field>
<field name="res_model">hr.applicant</field> <field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Stage changed</field> <field name="description">Stage changed</field>
</record> </record>
<record id="mt_applicant_hired" model="mail.message.subtype"> <record id="mt_applicant_hired" model="mail.message.subtype">
<field name="name">Applicant Hired</field> <field name="name">Applicant Hired</field>
<field name="res_model">hr.applicant</field> <field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Applicant hired</field> <field name="description">Applicant hired</field>
</record> </record>
<record id="mt_applicant_refused" model="mail.message.subtype"> <record id="mt_applicant_refused" model="mail.message.subtype">

View File

@ -45,7 +45,7 @@ The validation can be configured in the company:
""", """,
'author': 'OpenERP SA', 'author': 'OpenERP SA',
'website': 'http://www.openerp.com', 'website': 'http://www.openerp.com',
'images': ['images/hr_my_timesheet.jpeg','images/hr_timesheet_analysis.jpeg','images/hr_timesheet_sheet_analysis.jpeg','images/hr_timesheets.jpeg'], 'images': ['images/hr_my_current_timesheet.jpeg','images/hr_timesheet_analysis.jpeg','images/hr_timesheet_sheet_analysis.jpeg','images/hr_timesheet_activity.jpeg'],
'depends': ['hr_timesheet', 'hr_timesheet_invoice', 'process'], 'depends': ['hr_timesheet', 'hr_timesheet_invoice', 'process'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',

View File

@ -44,6 +44,7 @@ If you want to save your employees' time and avoid them to always have coins in
'report/report_lunch_order_view.xml', 'report/report_lunch_order_view.xml',
'security/ir.model.access.csv',], 'security/ir.model.access.csv',],
'css':['static/src/css/lunch.css'], 'css':['static/src/css/lunch.css'],
'images': ['images/new_order.jpeg','images/lunch_account.jpeg','images/order_by_supplier_analysis.jpeg','images/alert.jpeg'],
'demo': ['lunch_demo.xml',], 'demo': ['lunch_demo.xml',],
'installable': True, 'installable': True,
'application' : True, 'application' : True,

View File

@ -39,7 +39,8 @@
<field name="user_id"/> <field name="user_id"/>
<filter name='is_payment' string="Payment" domain="[('state','=','payment')]"/> <filter name='is_payment' string="Payment" domain="[('state','=','payment')]"/>
<separator/> <separator/>
<filter name='is_mine' string="My Account" domain="[('user_id','=',uid)]"/> <filter name='is_mine_group' string="My Account grouped" domain="[('user_id','=',uid)]" context="{'group_by':'user_id'}"/>
<filter name="group_by_user" string="By User" context="{'group_by':'user_id'}"/>
</search> </search>
</field> </field>
</record> </record>
@ -116,7 +117,7 @@
<field name="res_model">lunch.cashmove</field> <field name="res_model">lunch.cashmove</field>
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
<field name="search_view_id" ref="view_lunch_employee_payment_filter"/> <field name="search_view_id" ref="view_lunch_employee_payment_filter"/>
<field name="context">{"search_default_is_mine":1}</field> <field name="context">{"search_default_is_mine_group":1}</field>
<field name="help" type="html"> <field name="help" type="html">
<p> <p>
Here you can see your cash moves.<br/>A cash moves can be either an expense or a payment. Here you can see your cash moves.<br/>A cash moves can be either an expense or a payment.

View File

@ -71,16 +71,12 @@ Main Features
'installable': True, 'installable': True,
'application': True, 'application': True,
'images': [ 'images': [
'images/customer_history.jpeg', 'images/inbox.jpeg',
'images/messages_form.jpeg', 'images/messages_form.jpeg',
'images/messages_list.jpeg', 'images/messages_list.jpeg',
'static/src/img/email_icong.png', 'images/email.jpeg',
'static/src/img/_al.png', 'images/join_a_group.jpeg',
'static/src/img/_pincky.png', 'images/share_a_message.jpeg',
'static/src/img/groupdefault.png',
'static/src/img/attachment.png',
'static/src/img/checklist.png',
'static/src/img/formatting.png',
], ],
'css': [ 'css': [
'static/src/css/mail.css', 'static/src/css/mail.css',

View File

@ -21,6 +21,8 @@ should inherit from this class.
ClientAction (ir.actions.client) ClientAction (ir.actions.client)
++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++
.. code-block:: xml
<record id="action_mail_inbox_feeds" model="ir.actions.client"> <record id="action_mail_inbox_feeds" model="ir.actions.client">
<field name="name">Inbox</field> <field name="name">Inbox</field>
<field name="tag">mail.wall</field> <field name="tag">mail.wall</field>
@ -36,6 +38,7 @@ ClientAction (ir.actions.client)
'mail_thread' widget for field on standard view. (default value like a thread for 'mail_thread' widget for field on standard view. (default value like a thread for
record, view on flat mode, no reply, no read/unread) record, view on flat mode, no reply, no read/unread)
'mail.widget' it's the root thread, used by 'mail.wall' and 'mail_thread' 'mail.widget' it's the root thread, used by 'mail.wall' and 'mail_thread'
- ``help`` : Text HTML to display if there are no message - ``help`` : Text HTML to display if there are no message
- ``context`` : insert 'default_model' and 'default_res_id' - ``context`` : insert 'default_model' and 'default_res_id'
- ``params`` : options for the widget - ``params`` : options for the widget

View File

@ -85,9 +85,6 @@ class mail_notification(osv.Model):
if notification.read: if notification.read:
continue continue
partner = notification.partner_id partner = notification.partner_id
# Do not send an email to the writer
if partner.user_ids and partner.user_ids[0].id == uid:
continue
# Do not send to partners without email address defined # Do not send to partners without email address defined
if not partner.email: if not partner.email:
continue continue
@ -129,11 +126,20 @@ class mail_notification(osv.Model):
if signature: if signature:
body_html = tools.append_content_to_html(body_html, signature, plaintext=True, container_tag='div') body_html = tools.append_content_to_html(body_html, signature, plaintext=True, container_tag='div')
# email_from: partner-user alias or partner email or mail.message email_from
if msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0].alias_domain and msg.author_id.user_ids[0].alias_name:
email_from = '%s <%s@%s>' % (msg.author_id.name, msg.author_id.user_ids[0].alias_name, msg.author_id.user_ids[0].alias_domain)
elif msg.author_id:
email_from = '%s <%s>' % (msg.author_id.name, msg.author_id.email)
else:
email_from = msg.email_from
mail_values = { mail_values = {
'mail_message_id': msg.id, 'mail_message_id': msg.id,
'email_to': [], 'email_to': [],
'auto_delete': True, 'auto_delete': True,
'body_html': body_html, 'body_html': body_html,
'email_from': email_from,
'state': 'outgoing', 'state': 'outgoing',
} }
mail_values['email_to'] = ', '.join(mail_values['email_to']) mail_values['email_to'] = ', '.join(mail_values['email_to'])

View File

@ -78,6 +78,13 @@ class mail_mail(osv.Model):
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx), 'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
} }
def default_get(self, cr, uid, fields, context=None):
# protection for `default_type` values leaking from menu action context (e.g. for invoices)
# To remove when automatic context propagation is removed in web client
if context and context.get('default_type') and context.get('default_type') not in self._all_columns['type'].column.selection:
context = dict(context, default_type = None)
return super(mail_mail, self).default_get(cr, uid, fields, context=context)
def create(self, cr, uid, values, context=None): def create(self, cr, uid, values, context=None):
if 'notification' not in values and values.get('mail_message_id'): if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True values['notification'] = True

View File

@ -243,7 +243,7 @@ class mail_thread(osv.AbstractModel):
# subscribe uid unless asked not to # subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'): if not context.get('mail_create_nosubscribe'):
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context) self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
self.message_subscribe_from_parent(cr, uid, [thread_id], values.keys(), context=context) self.message_auto_subscribe(cr, uid, [thread_id], values.keys(), context=context)
# automatic logging unless asked not to (mainly for various testing purpose) # automatic logging unless asked not to (mainly for various testing purpose)
if not context.get('mail_create_nolog'): if not context.get('mail_create_nolog'):
@ -261,7 +261,7 @@ class mail_thread(osv.AbstractModel):
# Perform write, update followers # Perform write, update followers
result = super(mail_thread, self).write(cr, uid, ids, values, context=context) result = super(mail_thread, self).write(cr, uid, ids, values, context=context)
self.message_subscribe_from_parent(cr, uid, ids, values.keys(), context=context) self.message_auto_subscribe(cr, uid, ids, values.keys(), context=context)
# Perform the tracking # Perform the tracking
if tracked_fields: if tracked_fields:
@ -1069,7 +1069,24 @@ class mail_thread(osv.AbstractModel):
self.check_access_rights(cr, uid, 'write') self.check_access_rights(cr, uid, 'write')
return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context) return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
def message_subscribe_from_parent(self, cr, uid, ids, updated_fields, context=None): def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=['user_id'], context=None):
""" Returns the list of relational fields linking to res.users that should
trigger an auto subscribe. The default list checks for the fields
- called 'user_id'
- linking to res.users
- with track_visibility set
In OpenERP V7, this is sufficent for all major addon such as opportunity,
project, issue, recruitment, sale.
Override this method if a custom behavior is needed about fields
that automatically subscribe users.
"""
user_field_lst = []
for name, column_info in self._all_columns.items():
if name in auto_follow_fields and name in updated_fields and getattr(column_info.column, 'track_visibility', False) and column_info.column._obj == 'res.users':
user_field_lst.append(name)
return user_field_lst
def message_auto_subscribe(self, cr, uid, ids, updated_fields, context=None):
""" """
1. fetch project subtype related to task (parent_id.res_model = 'project.task') 1. fetch project subtype related to task (parent_id.res_model = 'project.task')
2. for each project subtype: subscribe the follower to the task 2. for each project subtype: subscribe the follower to the task
@ -1077,13 +1094,16 @@ class mail_thread(osv.AbstractModel):
subtype_obj = self.pool.get('mail.message.subtype') subtype_obj = self.pool.get('mail.message.subtype')
follower_obj = self.pool.get('mail.followers') follower_obj = self.pool.get('mail.followers')
# fetch auto_follow_fields
user_field_lst = self._message_get_auto_subscribe_fields(cr, uid, updated_fields, context=context)
# fetch related record subtypes # fetch related record subtypes
related_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('parent_id.res_model', '=', self._name)], context=context) related_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('parent_id.res_model', '=', self._name)], context=context)
subtypes = subtype_obj.browse(cr, uid, related_subtype_ids, context=context) subtypes = subtype_obj.browse(cr, uid, related_subtype_ids, context=context)
default_subtypes = [subtype for subtype in subtypes if subtype.res_model == False] default_subtypes = [subtype for subtype in subtypes if subtype.res_model == False]
related_subtypes = [subtype for subtype in subtypes if subtype.res_model != False] related_subtypes = [subtype for subtype in subtypes if subtype.res_model != False]
relation_fields = set([subtype.relation_field for subtype in subtypes if subtype.relation_field != False]) relation_fields = set([subtype.relation_field for subtype in subtypes if subtype.relation_field != False])
if not related_subtypes or not any(relation in updated_fields for relation in relation_fields): if (not related_subtypes or not any(relation in updated_fields for relation in relation_fields)) and not user_field_lst:
return True return True
for record in self.browse(cr, uid, ids, context=context): for record in self.browse(cr, uid, ids, context=context):
@ -1105,20 +1125,24 @@ class mail_thread(osv.AbstractModel):
for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context): for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
new_followers.setdefault(follower.partner_id.id, set()).add(subtype.parent_id.id) new_followers.setdefault(follower.partner_id.id, set()).add(subtype.parent_id.id)
if not parent_res_id or not parent_model: if parent_res_id and parent_model:
continue for subtype in default_subtypes:
follower_ids = follower_obj.search(cr, SUPERUSER_ID, [
('res_model', '=', parent_model),
('res_id', '=', parent_res_id),
('subtype_ids', 'in', [subtype.id])
], context=context)
for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
new_followers.setdefault(follower.partner_id.id, set()).add(subtype.id)
for subtype in default_subtypes: # add followers coming from res.users relational fields that are tracked
follower_ids = follower_obj.search(cr, SUPERUSER_ID, [ user_ids = [getattr(record, name).id for name in user_field_lst if getattr(record, name)]
('res_model', '=', parent_model), for partner_id in [user.partner_id.id for user in self.pool.get('res.users').browse(cr, SUPERUSER_ID, user_ids, context=context)]:
('res_id', '=', parent_res_id), new_followers.setdefault(partner_id, None)
('subtype_ids', 'in', [subtype.id])
], context=context)
for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
new_followers.setdefault(follower.partner_id.id, set()).add(subtype.id)
for pid, subtypes in new_followers.items(): for pid, subtypes in new_followers.items():
self.message_subscribe(cr, uid, [record.id], [pid], list(subtypes), context=context) subtypes = list(subtypes) if subtypes is not None else None
self.message_subscribe(cr, uid, [record.id], [pid], subtypes, context=context)
return True return True
#------------------------------------------------------ #------------------------------------------------------

View File

@ -105,7 +105,7 @@ openerp.mail = function (session) {
// As it only looks at the extension it is quite approximative. // As it only looks at the extension it is quite approximative.
filetype: function(url){ filetype: function(url){
url = url.filename || url; url = url.filename || url;
var tokens = url.split('.'); var tokens = (url+'').split('.');
if(tokens.length <= 1){ if(tokens.length <= 1){
return 'unknown'; return 'unknown';
} }
@ -218,7 +218,7 @@ openerp.mail = function (session) {
this.author_id = datasets.author_id || false, this.author_id = datasets.author_id || false,
this.attachment_ids = datasets.attachment_ids || [], this.attachment_ids = datasets.attachment_ids || [],
this.partner_ids = datasets.partner_ids || []; this.partner_ids = datasets.partner_ids || [];
this._date = datasets.date; this.date = datasets.date;
this.format_data(); this.format_data();
@ -232,19 +232,20 @@ openerp.mail = function (session) {
else { else {
this.options.show_read = this.to_read; this.options.show_read = this.to_read;
this.options.show_unread = !this.to_read; this.options.show_unread = !this.to_read;
this.options.rerender = true;
this.options.toggle_read = true;
} }
this.options.rerender = true;
this.options.toggle_read = true;
} }
this.parent_thread = parent.messages != undefined ? parent : this.options.root_thread; this.parent_thread = typeof parent.on_message_detroy == 'function' ? parent : this.options.root_thread;
this.thread = false; this.thread = false;
}, },
/* Convert date, timerelative and avatar in displayable data. */ /* Convert date, timerelative and avatar in displayable data. */
format_data: function () { format_data: function () {
//formating and add some fields for render //formating and add some fields for render
if (this._date) { this.date = this.date ? session.web.str_to_datetime(this.date) : false;
this.timerelative = $.timeago(this._date+"Z"); if (this.date && new Date().getTime()-this.date.getTime() < 7*24*60*60*1000) {
this.timerelative = $.timeago(this.date);
} }
if (this.type == 'email' && (!this.author_id || !this.author_id[0])) { if (this.type == 'email' && (!this.author_id || !this.author_id[0])) {
this.avatar = ('/mail/static/src/img/email_icon.png'); this.avatar = ('/mail/static/src/img/email_icon.png');
@ -253,7 +254,7 @@ openerp.mail = function (session) {
} else { } else {
this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid); this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
} }
if (this.author_id) { if (this.author_id && this.author_id[1]) {
var email = this.author_id[1].match(/(.*)<(.*@.*)>/); var email = this.author_id[1].match(/(.*)<(.*@.*)>/);
if (!email) { if (!email) {
this.author_id.push(_.str.escapeHTML(this.author_id[1]), '', this.author_id[1]); this.author_id.push(_.str.escapeHTML(this.author_id[1]), '', this.author_id[1]);
@ -271,7 +272,7 @@ openerp.mail = function (session) {
var attach = this.attachment_ids[l]; var attach = this.attachment_ids[l];
if (!attach.formating) { if (!attach.formating) {
attach.url = mail.ChatterUtils.get_attachment_url(this.session, this.id, attach.id); attach.url = mail.ChatterUtils.get_attachment_url(this.session, this.id, attach.id);
attach.filetype = mail.ChatterUtils.filetype(attach.filename); attach.filetype = mail.ChatterUtils.filetype(attach.filename || attach.name);
attach.name = mail.ChatterUtils.breakword(attach.name || attach.filename); attach.name = mail.ChatterUtils.breakword(attach.name || attach.filename);
attach.formating = true; attach.formating = true;
} }
@ -1605,7 +1606,8 @@ openerp.mail = function (session) {
this.node.params = _.extend({ this.node.params = _.extend({
'display_indented_thread': -1, 'display_indented_thread': -1,
'show_reply_button': false, 'show_reply_button': false,
'show_read_unread_button': false, 'show_read_unread_button': true,
'read_action': 'unread',
'show_record_name': false, 'show_record_name': false,
'show_compact_message': 1, 'show_compact_message': 1,
}, this.node.params); }, this.node.params);

View File

@ -216,9 +216,12 @@ openerp_mail_followers = function(session, mail) {
display_subtypes:function (data) { display_subtypes:function (data) {
var self = this; var self = this;
var subtype_list_ul = this.$('.oe_subtype_list'); var subtype_list_ul = this.$('.oe_subtype_list');
subtype_list_ul.empty(); var records = [];
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
var nb_subtype = 0; var nb_subtype = 0;
subtype_list_ul.empty();
if (this.view.datarecord.id) {
records = data[this.view.datarecord.id].message_subtype_data;
}
_(records).each(function (record) {nb_subtype++;}); _(records).each(function (record) {nb_subtype++;});
if (nb_subtype > 1) { if (nb_subtype > 1) {
this.$('hr').show(); this.$('hr').show();

View File

@ -246,7 +246,7 @@
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[2]"/></a> <a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[2]"/></a>
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[2]"/></span> <span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[2]"/></span>
<span class='oe_subtle'></span> <span class='oe_subtle'></span>
<span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span> <span t-att-title="widget.date"><t t-if="widget.timerelative" t-raw="widget.timerelative"/><t t-if="!widget.timerelative" t-raw="widget.date"/></span>
<span t-if="!widget.options.readonly" class='oe_subtle'></span> <span t-if="!widget.options.readonly" class='oe_subtle'></span>
<t t-if="!widget.options.readonly" t-call="mail.thread.message.vote"/> <t t-if="!widget.options.readonly" t-call="mail.thread.message.vote"/>
</div> </div>

View File

@ -52,9 +52,16 @@ class membership_invoice(osv.osv_memory):
'amount': data.member_price 'amount': data.member_price
} }
invoice_list = partner_obj.create_membership_invoice(cr, uid, context.get('active_ids', []), datas=datas, context=context) invoice_list = partner_obj.create_membership_invoice(cr, uid, context.get('active_ids', []), datas=datas, context=context)
res = mod_obj.get_object_reference(cr, uid, 'account', 'view_account_invoice_filter') try:
search_view_id = mod_obj.get_object_reference(cr, uid, 'account', 'view_account_invoice_filter')[1]
except ValueError:
search_view_id = False
try:
form_view_id = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')[1]
except ValueError:
form_view_id = False
return { return {
'domain': [('id', 'in', invoice_list)], 'domain': [('id', 'in', invoice_list)],
'name': 'Membership Invoices', 'name': 'Membership Invoices',
@ -62,7 +69,8 @@ class membership_invoice(osv.osv_memory):
'view_mode': 'tree,form', 'view_mode': 'tree,form',
'res_model': 'account.invoice', 'res_model': 'account.invoice',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'search_view_id': res and res[1] or False 'views': [(False, 'tree'), (form_view_id, 'form')],
'search_view_id': search_view_id,
} }
membership_invoice() membership_invoice()

View File

@ -28,7 +28,7 @@
'category': 'Manufacturing', 'category': 'Manufacturing',
'sequence': 18, 'sequence': 18,
'summary': 'Manufacturing Orders, Bill of Materials, Routing', 'summary': 'Manufacturing Orders, Bill of Materials, Routing',
'images': ['images/bill_of_materials.jpeg', 'images/manufacturing_order.jpeg', 'images/planning_manufacturing_order.jpeg', 'images/production_analysis.jpeg', 'images/production_dashboard.jpeg','images/routings.jpeg','images/work_centers.jpeg'], 'images': ['images/bill_of_materials.jpeg', 'images/manufacturing_order.jpeg', 'images/planning_manufacturing_order.jpeg', 'images/manufacturing_analysis.jpeg', 'images/production_dashboard.jpeg','images/routings.jpeg','images/work_centers.jpeg'],
'depends': ['product','procurement', 'stock', 'resource', 'purchase','process'], 'depends': ['product','procurement', 'stock', 'resource', 'purchase','process'],
'description': """ 'description': """
Manage the Manufacturing process in OpenERP Manage the Manufacturing process in OpenERP

View File

@ -57,6 +57,11 @@ Notes can be found in the 'Home' menu.
'css': [ 'css': [
'static/src/css/note.css', 'static/src/css/note.css',
], ],
'images': [
'images/note_kanban.jpeg',
'images/note.jpeg',
'images/categories_tree.jpeg'
],
'installable': True, 'installable': True,
'application': True, 'application': True,
'auto_install': False, 'auto_install': False,

View File

@ -29,7 +29,7 @@ class note_stage(osv.osv):
_columns = { _columns = {
'name': fields.char('Stage Name', translate=True, required=True), 'name': fields.char('Stage Name', translate=True, required=True),
'sequence': fields.integer('Sequence', help="Used to order the note stages"), 'sequence': fields.integer('Sequence', help="Used to order the note stages"),
'user_id': fields.many2one('res.users', 'Owner', help="Owner of the note stage.", required=True), 'user_id': fields.many2one('res.users', 'Owner', help="Owner of the note stage.", required=True, ondelete='cascade'),
'fold': fields.boolean('Folded by Default'), 'fold': fields.boolean('Folded by Default'),
} }
_order = 'sequence asc' _order = 'sequence asc'
@ -112,7 +112,7 @@ class note_note(osv.osv):
'date_done': fields.date('Date done'), 'date_done': fields.date('Date done'),
'color': fields.integer('Color Index'), 'color': fields.integer('Color Index'),
'tag_ids' : fields.many2many('note.tag','note_tags_rel','note_id','tag_id','Tags'), 'tag_ids' : fields.many2many('note.tag','note_tags_rel','note_id','tag_id','Tags'),
'current_partner_id' : fields.function(_get_my_current_partner), 'current_partner_id' : fields.function(_get_my_current_partner, type="many2one", relation='res.partner', string="Owner"),
} }
_defaults = { _defaults = {
'open' : 1, 'open' : 1,

View File

@ -24,7 +24,7 @@ class pad_common(osv.osv_memory):
# make sure pad server in the form of http://hostname # make sure pad server in the form of http://hostname
if not pad["server"]: if not pad["server"]:
return '' return pad
if not pad["server"].startswith('http'): if not pad["server"].startswith('http'):
pad["server"] = 'http://' + pad["server"] pad["server"] = 'http://' + pad["server"]
pad["server"] = pad["server"].rstrip('/') pad["server"] = pad["server"].rstrip('/')
@ -96,7 +96,7 @@ class pad_common(osv.osv_memory):
field = v.column field = v.column
if hasattr(field,'pad_content_field'): if hasattr(field,'pad_content_field'):
pad = self.pad_generate_url(cr, uid, context) pad = self.pad_generate_url(cr, uid, context)
default[k] = pad['url'] default[k] = pad.get('url')
return super(pad_common, self).copy(cr, uid, id, default, context) return super(pad_common, self).copy(cr, uid, id, default, context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -48,7 +48,7 @@ Main Features
* Refund previous sales * Refund previous sales
""", """,
'author': 'OpenERP SA', 'author': 'OpenERP SA',
'images': ['images/cash_registers.jpeg', 'images/pos_analysis.jpeg','images/register_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'], 'images': ['images/pos_touch_screen.jpeg', 'images/pos_session.jpeg', 'images/pos_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'],
'depends': ['sale_stock'], 'depends': ['sale_stock'],
'data': [ 'data': [
'security/point_of_sale_security.xml', 'security/point_of_sale_security.xml',

View File

@ -853,8 +853,7 @@ class pos_order(osv.osv):
inv_line['price_unit'] = line.price_unit inv_line['price_unit'] = line.price_unit
inv_line['discount'] = line.discount inv_line['discount'] = line.discount
inv_line['name'] = inv_name inv_line['name'] = inv_name
inv_line['invoice_line_tax_id'] = ('invoice_line_tax_id' in inv_line)\ inv_line['invoice_line_tax_id'] = [(6, 0, [x.id for x in line.product_id.taxes_id] )]
and [(6, 0, inv_line['invoice_line_tax_id'])] or []
inv_line_ref.create(cr, uid, inv_line, context=context) inv_line_ref.create(cr, uid, inv_line, context=context)
inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context) inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context)
wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr) wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr)
@ -1156,7 +1155,6 @@ class pos_order_line(osv.osv):
prod = self.pool.get('product.product').browse(cr, uid, product, context=context) prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
taxes = prod.taxes_id
price = price_unit * (1 - (discount or 0.0) / 100.0) price = price_unit * (1 - (discount or 0.0) / 100.0)
taxes = account_tax_obj.compute_all(cr, uid, prod.taxes_id, price, qty, product=prod, partner=False) taxes = account_tax_obj.compute_all(cr, uid, prod.taxes_id, price, qty, product=prod, partner=False)

View File

@ -233,7 +233,9 @@
font-style: italic; font-style: italic;
cursor:pointer; cursor:pointer;
} }
.point-of-sale .oe_pos_synch-notification.oe_inactive{
cursor: default;
}
.point-of-sale .oe_pos_synch-notification .oe_status_red{ .point-of-sale .oe_pos_synch-notification .oe_status_red{
display:inline-block; display:inline-block;
cursor:pointer; cursor:pointer;
@ -542,7 +544,9 @@
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); /* for some reason the -90deg orientation doesn't match the -webkit-linear-gradient. It should be 180deg here.
* webkit also insists on rendering *both* gradients instead of only the native one. So it doesn't looks right. ugh.
background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); */
/*background:#FFF;*/ /*background:#FFF;*/
padding: 3px; padding: 3px;
padding-top: 15px; padding-top: 15px;
@ -607,7 +611,9 @@
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
/* troublesome in latest webkit
background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
*/
/*background:#FFF;*/ /*background:#FFF;*/
padding: 3px; padding: 3px;
padding-top:15px; padding-top:15px;
@ -991,12 +997,20 @@
margin-bottom:10px; margin-bottom:10px;
} }
.point-of-sale .order .summary .line{ .point-of-sale .order .summary .line{
float: right;
margin-right:15px; margin-right:15px;
margin-left: 15px;
padding-top:5px; padding-top:5px;
border-top: solid 2px; border-top: solid 2px;
border-color:#777; border-color:#777;
} }
.point-of-sale .order .summary .line .subentry{
font-size: 10px;
font-weight: normal;
text-align: center;
}
.point-of-sale .order .summary .line.empty{ .point-of-sale .order .summary .line.empty{
text-align: right;
border-color:#BBB; border-color:#BBB;
color:#999; color:#999;
} }

View File

@ -40,6 +40,10 @@ function openerp_pos_db(instance, module){
//cache the data in memory to avoid roundtrips to the localstorage //cache the data in memory to avoid roundtrips to the localstorage
this.cache = {}; this.cache = {};
this.product_by_id = {};
this.product_by_ean13 = {};
this.product_by_category_id = {};
this.category_by_id = {}; this.category_by_id = {};
this.root_category_id = 0; this.root_category_id = 0;
this.category_products = {}; this.category_products = {};
@ -49,6 +53,7 @@ function openerp_pos_db(instance, module){
this.category_search_string = {}; this.category_search_string = {};
this.packagings_by_id = {}; this.packagings_by_id = {};
this.packagings_by_product_id = {}; this.packagings_by_product_id = {};
this.packagings_by_ean13 = {};
}, },
/* returns the category object from its id. If you pass a list of id as parameters, you get /* returns the category object from its id. If you pass a list of id as parameters, you get
* a list of category objects. * a list of category objects.
@ -137,7 +142,6 @@ function openerp_pos_db(instance, module){
/* saves a record store to the database */ /* saves a record store to the database */
save: function(store,data){ save: function(store,data){
var str_data = JSON.stringify(data); var str_data = JSON.stringify(data);
console.log('Storing '+ Math.round(str_data.length/1024.0)+' KB of data to store: '+store);
localStorage[this.name + '_' + store] = JSON.stringify(data); localStorage[this.name + '_' + store] = JSON.stringify(data);
this.cache[store] = data; this.cache[store] = data;
}, },
@ -153,8 +157,7 @@ function openerp_pos_db(instance, module){
return str + '\n'; return str + '\n';
}, },
add_products: function(products){ add_products: function(products){
var stored_products = this.load('products',{}); var stored_categories = this.product_by_category_id;
var stored_categories = this.load('categories',{});
if(!products instanceof Array){ if(!products instanceof Array){
products = [products]; products = [products];
@ -187,10 +190,11 @@ function openerp_pos_db(instance, module){
} }
this.category_search_string[ancestor] += search_string; this.category_search_string[ancestor] += search_string;
} }
stored_products[product.id] = product; this.product_by_id[product.id] = product;
if(product.ean13){
this.product_by_ean13[product.ean13] = product;
}
} }
this.save('products',stored_products);
this.save('categories',stored_categories);
}, },
add_packagings: function(packagings){ add_packagings: function(packagings){
for(var i = 0, len = packagings.length; i < len; i++){ for(var i = 0, len = packagings.length; i < len; i++){
@ -200,6 +204,9 @@ function openerp_pos_db(instance, module){
this.packagings_by_product_id[pack.product_id[0]] = []; this.packagings_by_product_id[pack.product_id[0]] = [];
} }
this.packagings_by_product_id[pack.product_id[0]].push(pack); this.packagings_by_product_id[pack.product_id[0]].push(pack);
if(pack.ean13){
this.packagings_by_ean13[pack.ean13] = pack;
}
} }
}, },
/* removes all the data from the database. TODO : being able to selectively remove data */ /* removes all the data from the database. TODO : being able to selectively remove data */
@ -219,31 +226,24 @@ function openerp_pos_db(instance, module){
return count; return count;
}, },
get_product_by_id: function(id){ get_product_by_id: function(id){
return this.load('products',{})[id]; return this.product_by_id[id];
}, },
get_product_by_ean13: function(ean13){ get_product_by_ean13: function(ean13){
var products = this.load('products',{}); if(this.product_by_ean13[ean13]){
for(var i in products){ return this.product_by_ean13[ean13];
if( products[i] && products[i].ean13 === ean13){
return products[i];
}
} }
for(var p in this.packagings_by_id){ var pack = this.packagings_by_ean13[ean13];
var pack = this.packagings_by_id[p]; if(pack){
if( pack.ean === ean13){ return this.product_by_id[pack.product_id[0]];
return products[pack.product_id[0]];
}
} }
return undefined; return undefined;
}, },
get_product_by_category: function(category_id){ get_product_by_category: function(category_id){
var stored_categories = this.load('categories',{}); var product_ids = this.product_by_category_id[category_id];
var stored_products = this.load('products',{});
var product_ids = stored_categories[category_id];
var list = []; var list = [];
if (product_ids) { if (product_ids) {
for (var i = 0, len = Math.min(product_ids.length, this.limit); i < len; i++) { for (var i = 0, len = Math.min(product_ids.length, this.limit); i < len; i++) {
list.push(stored_products[product_ids[i]]); list.push(this.product_by_id[product_ids[i]]);
} }
} }
return list; return list;
@ -275,12 +275,9 @@ function openerp_pos_db(instance, module){
}, },
remove_order: function(order_id){ remove_order: function(order_id){
var orders = this.load('orders',[]); var orders = this.load('orders',[]);
console.log('Remove order:',order_id);
console.log('Order count:',orders.length);
orders = _.filter(orders, function(order){ orders = _.filter(orders, function(order){
return order.id !== order_id; return order.id !== order_id;
}); });
console.log('Order count:',orders.length);
this.save('orders',orders); this.save('orders',orders);
}, },
get_orders: function(){ get_orders: function(){

View File

@ -1,6 +1,24 @@
function openerp_pos_models(instance, module){ //module is instance.point_of_sale function openerp_pos_models(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb; var QWeb = instance.web.qweb;
// rounds a value with a fixed number of decimals.
// round(3.141492,2) -> 3.14
function round(value,decimals){
var mult = Math.pow(10,decimals || 0);
return Math.round(value*mult)/mult;
}
window.round = round;
// rounds a value with decimal form precision
// round(3.141592,0.025) ->3.125
function round_pr(value,precision){
if(!precision || precision < 0){
throw new Error('round_pr(): needs a precision greater than zero, got '+precision+' instead');
}
return Math.round(value / precision) * precision;
}
window.round_pr = round_pr;
// The PosModel contains the Point Of Sale's representation of the backend. // The PosModel contains the Point Of Sale's representation of the backend.
// Since the PoS must work in standalone ( Without connection to the server ) // Since the PoS must work in standalone ( Without connection to the server )
@ -24,8 +42,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
this.db = new module.PosLS(); // a database used to store the products and categories this.db = new module.PosLS(); // a database used to store the products and categories
this.db.clear('products','categories'); this.db.clear('products','categories');
this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
// default attributes values. If null, it will be loaded below. // default attributes values. If null, it will be loaded below.
this.set({ this.set({
@ -101,8 +118,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}).then(function(company_partners){ }).then(function(company_partners){
self.get('company').contact_address = company_partners[0].contact_address; self.get('company').contact_address = company_partners[0].contact_address;
return self.fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]); return self.fetch('res.currency',['symbol','position','rounding','accuracy'],[['id','=',self.get('company').currency_id[0]]]);
}).then(function(currencies){ }).then(function(currencies){
console.log('Currency:',currencies[0]);
self.set('currency',currencies[0]); self.set('currency',currencies[0]);
return self.fetch('product.uom', null, null); return self.fetch('product.uom', null, null);
@ -117,7 +135,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return self.fetch('product.packaging', null, null); return self.fetch('product.packaging', null, null);
}).then(function(packagings){ }).then(function(packagings){
self.set('product.packaging',packagings); self.set('product.packaging',packagings);
return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]); return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
}).then(function(users){ }).then(function(users){
self.set('user_list',users); self.set('user_list',users);
@ -211,7 +229,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// logs the usefull posmodel data to the console for debug purposes // logs the usefull posmodel data to the console for debug purposes
log_loaded_data: function(){ log_loaded_data: function(){
console.log('PosModel data has been loaded:'); console.log('PosModel data has been loaded:');
console.log('PosModel: categories:',this.get('categories'));
console.log('PosModel: units:',this.get('units')); console.log('PosModel: units:',this.get('units'));
console.log('PosModel: bank_statements:',this.get('bank_statements')); console.log('PosModel: bank_statements:',this.get('bank_statements'));
console.log('PosModel: journals:',this.get('journals')); console.log('PosModel: journals:',this.get('journals'));
@ -339,19 +356,26 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.product = options.product; this.product = options.product;
this.price = options.product.get('price'); this.price = options.product.get('price');
this.quantity = 1; this.quantity = 1;
this.quantityStr = '1';
this.discount = 0; this.discount = 0;
this.discountStr = '0';
this.type = 'unit'; this.type = 'unit';
this.selected = false; this.selected = false;
}, },
// sets a discount [0,100]% // sets a discount [0,100]%
set_discount: function(discount){ set_discount: function(discount){
this.discount = Math.max(0,Math.min(100,discount)); var disc = Math.min(Math.max(parseFloat(discount) || 0, 0),100);
this.discount = disc;
this.discountStr = '' + disc;
this.trigger('change'); this.trigger('change');
}, },
// returns the discount [0,100]% // returns the discount [0,100]%
get_discount: function(){ get_discount: function(){
return this.discount; return this.discount;
}, },
get_discount_str: function(){
return this.discountStr;
},
get_product_type: function(){ get_product_type: function(){
return this.type; return this.type;
}, },
@ -359,13 +383,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// product's unity of measure properties. Quantities greater than zero will not get // product's unity of measure properties. Quantities greater than zero will not get
// rounded to zero // rounded to zero
set_quantity: function(quantity){ set_quantity: function(quantity){
if(_.isNaN(quantity)){ if(quantity === 'remove'){
this.order.removeOrderline(this); this.order.removeOrderline(this);
}else if(quantity !== undefined){ return;
this.quantity = Math.max(0,quantity); }else{
var quant = Math.max(parseFloat(quantity) || 0, 0);
var unit = this.get_unit(); var unit = this.get_unit();
if(unit && this.quantity > 0 ){ if(unit){
this.quantity = Math.max(unit.rounding, Math.round(quantity / unit.rounding) * unit.rounding); this.quantity = Math.max(unit.rounding, Math.round(quant / unit.rounding) * unit.rounding);
this.quantityStr = this.quantity.toFixed(Math.max(0,Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))));
}else{
this.quantity = quant;
this.quantityStr = '' + this.quantity;
} }
} }
this.trigger('change'); this.trigger('change');
@ -374,6 +403,17 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
get_quantity: function(){ get_quantity: function(){
return this.quantity; return this.quantity;
}, },
get_quantity_str: function(){
return this.quantityStr;
},
get_quantity_str_with_unit: function(){
var unit = this.get_unit();
if(unit && unit.name !== 'Unit(s)'){
return this.quantityStr + ' ' + unit.name;
}else{
return this.quantityStr;
}
},
// return the unit of measure of the product // return the unit of measure of the product
get_unit: function(){ get_unit: function(){
var unit_id = (this.product.get('uos_id') || this.product.get('uom_id')); var unit_id = (this.product.get('uos_id') || this.product.get('uom_id'));
@ -390,15 +430,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
get_product: function(){ get_product: function(){
return this.product; return this.product;
}, },
// return the base price of this product (for this orderline)
get_price: function(){
return this.price;
},
// changes the base price of the product for this orderline
set_price: function(price){
this.price = price;
this.trigger('change');
},
// selects or deselects this orderline // selects or deselects this orderline
set_selected: function(selected){ set_selected: function(selected){
this.selected = selected; this.selected = selected;
@ -429,7 +460,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
export_as_JSON: function() { export_as_JSON: function() {
return { return {
qty: this.get_quantity(), qty: this.get_quantity(),
price_unit: this.get_price(), price_unit: this.get_unit_price(),
discount: this.get_discount(), discount: this.get_discount(),
product_id: this.get_product().get('id'), product_id: this.get_product().get('id'),
}; };
@ -439,9 +470,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return { return {
quantity: this.get_quantity(), quantity: this.get_quantity(),
unit_name: this.get_unit().name, unit_name: this.get_unit().name,
price: this.get_price(), price: this.get_unit_price(),
discount: this.get_discount(), discount: this.get_discount(),
product_name: this.get_product().get('name'), product_name: this.get_product().get('name'),
price_display : this.get_display_price(),
price_with_tax : this.get_price_with_tax(), price_with_tax : this.get_price_with_tax(),
price_without_tax: this.get_price_without_tax(), price_without_tax: this.get_price_without_tax(),
tax: this.get_tax(), tax: this.get_tax(),
@ -449,6 +481,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
product_description_sale: this.get_product().get('description_sale'), product_description_sale: this.get_product().get('description_sale'),
}; };
}, },
// changes the base price of the product for this orderline
set_unit_price: function(price){
this.price = round(parseFloat(price) || 0, 2);
this.trigger('change');
},
get_unit_price: function(){
var rounding = this.pos.get('currency').rounding;
return round_pr(this.price,rounding);
},
get_display_price: function(){
var rounding = this.pos.get('currency').rounding;
return round_pr(round_pr(this.get_unit_price() * this.get_quantity(),rounding) * (1- this.get_discount()/100.0),rounding);
},
get_price_without_tax: function(){ get_price_without_tax: function(){
return this.get_all_prices().priceWithoutTax; return this.get_all_prices().priceWithoutTax;
}, },
@ -458,9 +503,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
get_tax: function(){ get_tax: function(){
return this.get_all_prices().tax; return this.get_all_prices().tax;
}, },
get_all_prices: function() { get_all_prices: function(){
var self = this; var self = this;
var base = this.get_quantity() * this.price * (1 - (this.get_discount() / 100)); var currency_rounding = this.pos.get('currency').rounding;
var base = round_pr(this.get_quantity() * this.get_unit_price() * (1.0 - (this.get_discount() / 100.0)), currency_rounding);
var totalTax = base; var totalTax = base;
var totalNoTax = base; var totalNoTax = base;
@ -474,12 +520,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
if (tax.price_include) { if (tax.price_include) {
var tmp; var tmp;
if (tax.type === "percent") { if (tax.type === "percent") {
tmp = base - (base / (1 + tax.amount)); tmp = base - round_pr(base / (1 + tax.amount),currency_rounding);
} else if (tax.type === "fixed") { } else if (tax.type === "fixed") {
tmp = tax.amount * self.get_quantity(); tmp = round_pr(tax.amount * self.get_quantity(),currency_rounding);
} else { } else {
throw "This type of tax is not supported by the point of sale: " + tax.type; throw "This type of tax is not supported by the point of sale: " + tax.type;
} }
tmp = round_pr(tmp,currency_rounding);
taxtotal += tmp; taxtotal += tmp;
totalNoTax -= tmp; totalNoTax -= tmp;
} else { } else {
@ -491,6 +538,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
} else { } else {
throw "This type of tax is not supported by the point of sale: " + tax.type; throw "This type of tax is not supported by the point of sale: " + tax.type;
} }
tmp = round_pr(tmp,currency_rounding);
taxtotal += tmp; taxtotal += tmp;
totalTax += tmp; totalTax += tmp;
} }
@ -515,7 +563,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}, },
//sets the amount of money on this payment line //sets the amount of money on this payment line
set_amount: function(value){ set_amount: function(value){
this.amount = value; this.amount = parseFloat(value) || 0;
this.trigger('change'); this.trigger('change');
}, },
// returns the amount of money on this paymentline // returns the amount of money on this paymentline
@ -584,7 +632,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
line.set_quantity(options.quantity); line.set_quantity(options.quantity);
} }
if(options.price !== undefined){ if(options.price !== undefined){
line.set_price(options.price); line.set_unit_price(options.price);
} }
var last_orderline = this.getLastOrderline(); var last_orderline = this.getLastOrderline();
@ -613,14 +661,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
getName: function() { getName: function() {
return this.get('name'); return this.get('name');
}, },
getTotal: function() { getSubtotal : function(){
return (this.get('orderLines')).reduce((function(sum, orderLine){
return sum + orderLine.get_display_price();
}), 0);
},
getTotalTaxIncluded: function() {
return (this.get('orderLines')).reduce((function(sum, orderLine) { return (this.get('orderLines')).reduce((function(sum, orderLine) {
return sum + orderLine.get_price_with_tax(); return sum + orderLine.get_price_with_tax();
}), 0); }), 0);
}, },
getDiscountTotal: function() { getDiscountTotal: function() {
return (this.get('orderLines')).reduce((function(sum, orderLine) { return (this.get('orderLines')).reduce((function(sum, orderLine) {
return sum + (orderLine.get_price() * (orderLine.get_discount()/100) * orderLine.get_quantity()); return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity());
}), 0); }), 0);
}, },
getTotalTaxExcluded: function() { getTotalTaxExcluded: function() {
@ -639,10 +692,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}), 0); }), 0);
}, },
getChange: function() { getChange: function() {
return this.getPaidTotal() - this.getTotal(); return this.getPaidTotal() - this.getTotalTaxIncluded();
}, },
getDueLeft: function() { getDueLeft: function() {
return this.getTotal() - this.getPaidTotal(); return this.getTotalTaxIncluded() - this.getPaidTotal();
}, },
// sets the type of receipt 'receipt'(default) or 'invoice' // sets the type of receipt 'receipt'(default) or 'invoice'
set_receipt_type: function(type){ set_receipt_type: function(type){
@ -698,10 +751,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return { return {
orderlines: orderlines, orderlines: orderlines,
paymentlines: paymentlines, paymentlines: paymentlines,
total_with_tax: this.getTotal(), subtotal: this.getSubtotal(),
total_with_tax: this.getTotalTaxIncluded(),
total_without_tax: this.getTotalTaxExcluded(), total_without_tax: this.getTotalTaxExcluded(),
total_tax: this.getTax(), total_tax: this.getTax(),
total_paid: this.getPaidTotal(), total_paid: this.getPaidTotal(),
total_discount: this.getDiscountTotal(),
change: this.getChange(), change: this.getChange(),
name : this.getName(), name : this.getName(),
client: client ? client.name : null , client: client ? client.name : null ,
@ -743,7 +798,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return { return {
name: this.getName(), name: this.getName(),
amount_paid: this.getPaidTotal(), amount_paid: this.getPaidTotal(),
amount_total: this.getTotal(), amount_total: this.getTotalTaxIncluded(),
amount_tax: this.getTax(), amount_tax: this.getTax(),
amount_return: this.getChange(), amount_return: this.getChange(),
lines: orderLines, lines: orderLines,
@ -800,20 +855,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
buffer: (this.get('buffer')) + newChar buffer: (this.get('buffer')) + newChar
}); });
} }
this.updateTarget(); this.trigger('set_value',this.get('buffer'));
}, },
deleteLastChar: function() { deleteLastChar: function() {
var tempNewBuffer = this.get('buffer').slice(0, -1); if(this.get('buffer') === ""){
if(this.get('mode') === 'quantity'){
if(!tempNewBuffer){ this.trigger('set_value','remove');
this.set({ buffer: "0" }); }else{
this.killTarget(); this.trigger('set_value',this.get('buffer'));
}else{
if (isNaN(tempNewBuffer)) {
tempNewBuffer = "0";
} }
this.set({ buffer: tempNewBuffer }); }else{
this.updateTarget(); var newBuffer = this.get('buffer').slice(0,-1) || "";
this.set({ buffer: newBuffer });
this.trigger('set_value',this.get('buffer'));
} }
}, },
switchSign: function() { switchSign: function() {
@ -822,7 +876,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.set({ this.set({
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
}); });
this.updateTarget(); this.trigger('set_value',this.get('buffer'));
}, },
changeMode: function(newMode) { changeMode: function(newMode) {
this.set({ this.set({
@ -836,15 +890,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
mode: "quantity" mode: "quantity"
}); });
}, },
updateTarget: function() { resetValue: function(){
var bufferContent, params; this.set({buffer:'0'});
bufferContent = this.get('buffer');
if (bufferContent && !isNaN(bufferContent)) {
this.trigger('set_value', parseFloat(bufferContent));
}
},
killTarget: function(){
this.trigger('set_value',Number.NaN);
}, },
}); });
} }

View File

@ -368,7 +368,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({ module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
template:'ChooseReceiptPopupWidget', template:'ChooseReceiptPopupWidget',
show: function(){ show: function(){
console.log('show');
this._super(); this._super();
this.renderElement(); this.renderElement();
var self = this; var self = this;
@ -603,7 +602,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// initiates the connection to the payment terminal and starts the update requests // initiates the connection to the payment terminal and starts the update requests
this.start = function(){ this.start = function(){
var def = new $.Deferred(); var def = new $.Deferred();
console.log("START");
self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft()) self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
.done(function(ack){ .done(function(ack){
if(ack === 'ok'){ if(ack === 'ok'){
@ -613,7 +611,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}else{ }else{
console.error('unknown payment request return value:',ack); console.error('unknown payment request return value:',ack);
} }
console.log("START_END");
def.resolve(); def.resolve();
}); });
return def; return def;
@ -621,10 +618,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// gets updated status from the payment terminal and performs the appropriate consequences // gets updated status from the payment terminal and performs the appropriate consequences
this.update = function(){ this.update = function(){
console.log("UPDATE");
var def = new $.Deferred(); var def = new $.Deferred();
if(self.canceled){ if(self.canceled){
console.log("UPDATE_END");
return def.resolve(); return def.resolve();
} }
self.pos.proxy.payment_status() self.pos.proxy.payment_status()
@ -656,7 +651,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}else{ }else{
console.error('unknown status value:',status.status); console.error('unknown status value:',status.status);
} }
console.log("UPDATE_END");
def.resolve(); def.resolve();
}); });
return def; return def;
@ -664,14 +658,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// cancels a payment. // cancels a payment.
this.cancel = function(){ this.cancel = function(){
console.log("CANCEL");
if(!self.paid && !self.canceled){ if(!self.paid && !self.canceled){
self.canceled = true; self.canceled = true;
self.pos.proxy.payment_cancel(); self.pos.proxy.payment_cancel();
self.pos_widget.screen_selector.set_current_screen(self.previous_screen); self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
self.queue.clear(); self.queue.clear();
} }
console.log("CANCEL_END");
return (new $.Deferred()).resolve(); return (new $.Deferred()).resolve();
} }
@ -865,6 +857,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.bindPaymentLineEvents(); this.bindPaymentLineEvents();
this.bind_orderline_events(); this.bind_orderline_events();
this.paymentlinewidgets = []; this.paymentlinewidgets = [];
this.focusedLine = null;
}, },
show: function(){ show: function(){
this._super(); this._super();
@ -894,6 +887,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}); });
this.updatePaymentSummary(); this.updatePaymentSummary();
this.line_refocus();
}, },
close: function(){ close: function(){
this._super(); this._super();
@ -931,17 +925,30 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.bind_orderline_events(); this.bind_orderline_events();
this.renderElement(); this.renderElement();
}, },
line_refocus: function(lineWidget){
if(lineWidget){
if(this.focusedLine !== lineWidget){
this.focusedLine = lineWidget;
}
}
if(this.focusedLine){
this.focusedLine.focus();
}
},
addPaymentLine: function(newPaymentLine) { addPaymentLine: function(newPaymentLine) {
var self = this; var self = this;
var l = new module.PaymentlineWidget(null, { var l = new module.PaymentlineWidget(this, {
payment_line: newPaymentLine payment_line: newPaymentLine,
}); });
l.on('delete_payment_line', self, function(r) { l.on('delete_payment_line', self, function(r) {
self.deleteLine(r); self.deleteLine(r);
}); });
l.appendTo(this.$('#paymentlines')); l.appendTo(this.$('#paymentlines'));
this.paymentlinewidgets.push(l); this.paymentlinewidgets.push(l);
this.$('.paymentline-amount input:last').focus(); if(this.numpadState){
this.numpadState.resetValue();
}
this.line_refocus(l);
}, },
renderElement: function() { renderElement: function() {
this._super(); this._super();
@ -958,25 +965,26 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}, },
deleteLine: function(lineWidget) { deleteLine: function(lineWidget) {
this.currentPaymentLines.remove([lineWidget.payment_line]); this.currentPaymentLines.remove([lineWidget.payment_line]);
lineWidget.destroy();
}, },
updatePaymentSummary: function() { updatePaymentSummary: function() {
var currentOrder = this.pos.get('selectedOrder'); var currentOrder = this.pos.get('selectedOrder');
var paidTotal = currentOrder.getPaidTotal(); var paidTotal = currentOrder.getPaidTotal();
var dueTotal = currentOrder.getTotal(); var dueTotal = currentOrder.getTotalTaxIncluded();
var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0; var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0; var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
this.$('#payment-due-total').html(dueTotal.toFixed(2)); this.$('#payment-due-total').html(this.format_currency(dueTotal));
this.$('#payment-paid-total').html(paidTotal.toFixed(2)); this.$('#payment-paid-total').html(this.format_currency(paidTotal));
this.$('#payment-remaining').html(remaining.toFixed(2)); this.$('#payment-remaining').html(this.format_currency(remaining));
this.$('#payment-change').html(change.toFixed(2)); this.$('#payment-change').html(this.format_currency(change));
if((currentOrder.selected_orderline == undefined)) if(currentOrder.selected_orderline === undefined){
remaining = 1 remaining = 1; // What is this ?
}
if(this.pos_widget.action_bar){ if(this.pos_widget.action_bar){
this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0); this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0);
} }
this.$('.paymentline-amount input:last').focus();
}, },
set_numpad_state: function(numpadState) { set_numpad_state: function(numpadState) {
if (this.numpadState) { if (this.numpadState) {
@ -998,5 +1006,4 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.currentPaymentLines.last().set_amount(val); this.currentPaymentLines.last().set_amount(val);
}, },
}); });
} }

View File

@ -22,14 +22,20 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of
if(this.pos && this.pos.get('currency')){ if(this.pos && this.pos.get('currency')){
this.currency = this.pos.get('currency'); this.currency = this.pos.get('currency');
}else{ }else{
this.currency = {symbol: '$', position: 'after'}; this.currency = {symbol: '$', position: 'after', rounding: 0.01};
} }
var decimals = Math.max(0,Math.ceil(Math.log(1.0 / this.currency.rounding) / Math.log(10)));
this.format_currency = function(amount){ this.format_currency = function(amount){
if(typeof amount === 'number'){
amount = Math.round(amount*100)/100;
amount = amount.toFixed(decimals);
}
if(this.currency.position === 'after'){ if(this.currency.position === 'after'){
return Math.round(amount*100)/100 + ' ' + this.currency.symbol; return amount + ' ' + this.currency.symbol;
}else{ }else{
return this.currency.symbol + ' ' + Math.round(amount*100)/100; return this.currency.symbol + ' ' + amount;
} }
} }

View File

@ -186,7 +186,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}else if( mode === 'discount'){ }else if( mode === 'discount'){
order.getSelectedLine().set_discount(val); order.getSelectedLine().set_discount(val);
}else if( mode === 'price'){ }else if( mode === 'price'){
order.getSelectedLine().set_price(val); order.getSelectedLine().set_unit_price(val);
} }
} else { } else {
this.pos.get('selectedOrder').destroy(); this.pos.get('selectedOrder').destroy();
@ -269,8 +269,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}, },
update_summary: function(){ update_summary: function(){
var order = this.pos.get('selectedOrder'); var order = this.pos.get('selectedOrder');
var total = order ? order.getTotal() : 0; var total = order ? order.getTotalTaxIncluded() : 0;
this.$('.summary .value.total').html(this.format_currency(total)); var taxes = order ? total - order.getTotalTaxExcluded() : 0;
this.$('.summary .total > .value').html(this.format_currency(total));
this.$('.summary .total .subentry .value').html(this.format_currency(taxes));
}, },
set_display_mode: function(mode){ set_display_mode: function(mode){
if(this.display_mode !== mode){ if(this.display_mode !== mode){
@ -311,24 +313,34 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}, },
changeAmount: function(event) { changeAmount: function(event) {
var newAmount = event.currentTarget.value; var newAmount = event.currentTarget.value;
if (newAmount && !isNaN(newAmount)) { var amount = parseFloat(newAmount);
this.amount = parseFloat(newAmount); if(!isNaN(amount)){
this.payment_line.set_amount(this.amount); this.amount = amount;
this.payment_line.set_amount(amount);
} }
}, },
changedAmount: function() { changedAmount: function() {
if (this.amount !== this.payment_line.get_amount()) if (this.amount !== this.payment_line.get_amount()){
this.renderElement(); this.renderElement();
}
}, },
renderElement: function() { renderElement: function() {
var self = this; var self = this;
this.name = this.payment_line.get_cashregister().get('journal_id')[1]; this.name = this.payment_line.get_cashregister().get('journal_id')[1];
this._super(); this._super();
this.$('input').keyup(_.bind(this.changeAmount, this)); this.$('input').keyup(function(event){
self.changeAmount(event);
});
this.$('.delete-payment-line').click(function() { this.$('.delete-payment-line').click(function() {
self.trigger('delete_payment_line', self); self.trigger('delete_payment_line', self);
}); });
}, },
focus: function(){
var val = this.$('input')[0].value;
this.$('input')[0].focus();
this.$('input')[0].value = val;
this.$('input')[0].select();
},
}); });
module.OrderButtonWidget = module.PosBaseWidget.extend({ module.OrderButtonWidget = module.PosBaseWidget.extend({
@ -606,20 +618,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(this.scrollbar){ if(this.scrollbar){
this.scrollbar.destroy(); this.scrollbar.destroy();
} }
var products = this.pos.get('products').models || [];
this.pos.get('products') for(var i = 0, len = products.length; i < len; i++){
.chain() var product = new module.ProductWidget(self, {
.map(function(product) { model: products[i],
var product = new module.ProductWidget(self, { click_product_action: this.click_product_action,
model: product, });
weight: self.weight, this.productwidgets.push(product);
click_product_action: self.click_product_action, product.appendTo(this.$('.product-list'));
}) }
self.productwidgets.push(product);
return product;
})
.invoke('appendTo', this.$('.product-list'));
this.scrollbar = new module.ScrollbarWidget(this,{ this.scrollbar = new module.ScrollbarWidget(this,{
target_widget: this, target_widget: this,
target_selector: '.product-list-scroller', target_selector: '.product-list-scroller',

View File

@ -48,11 +48,17 @@
</t> </t>
<t t-name="SynchNotificationWidget"> <t t-name="SynchNotificationWidget">
<div class="oe_pos_synch-notification"> <t t-if="widget.get_nbr_pending() > 0">
<t t-if="widget.get_nbr_pending() > 0" t-esc="widget.get_nbr_pending()"/> <div class="oe_pos_synch-notification">
<div t-if="widget.get_nbr_pending() > 0" class="oe_status_red"></div> <t t-esc="widget.get_nbr_pending()"/>
<div t-if="widget.get_nbr_pending() === 0" class="oe_status_green"></div> <div class="oe_status_red"></div>
</div> </div>
</t>
<t t-if="widget.get_nbr_pending() === 0">
<div class="oe_pos_synch-notification oe_inactive">
<div class="oe_status_green"></div>
</div>
</t>
</t> </t>
<t t-name="HeaderButtonWidget"> <t t-name="HeaderButtonWidget">
@ -214,11 +220,7 @@
<span class="left-block"> <span class="left-block">
Total: Total:
</span> </span>
<span class="right-block"> <span class='right-block' id="payment-due-total"></span>
<t t-if="widget.currency.position == 'before'" t-esc="widget.currency.symbol"/>
<span id="payment-due-total"></span>
<t t-if="widget.currency.position == 'after'" t-esc="widget.currency.symbol"/>
</span>
</div> </div>
<table id="paymentlines"> <table id="paymentlines">
</table> </table>
@ -227,31 +229,19 @@
<span class='left-block'> <span class='left-block'>
Paid: Paid:
</span> </span>
<span class='right-block'> <span class='right-block' id="payment-paid-total"></span>
<t t-if="widget.currency.position == 'before'" t-esc="widget.currency.symbol"/>
<span id="payment-paid-total"></span>
<t t-if="widget.currency.position == 'after'" t-esc="widget.currency.symbol"/>
</span>
</div> </div>
<div class="infoline"> <div class="infoline">
<span class='left-block'> <span class='left-block'>
Remaining: Remaining:
</span> </span>
<span class='right-block'> <span class='right-block' id="payment-remaining"></span>
<t t-if="widget.currency.position == 'before'" t-esc="widget.currency.symbol"/>
<span id="payment-remaining"></span>
<t t-if="widget.currency.position == 'after'" t-esc="widget.currency.symbol"/>
</span>
</div> </div>
<div class="infoline" > <div class="infoline" >
<span class='left-block'> <span class='left-block'>
Change: Change:
</span> </span>
<span class='right-block'> <span class='right-block' id="payment-change"></span>
<t t-if="widget.currency.position == 'before'" t-esc="widget.currency.symbol"/>
<span id="payment-change"></span>
<t t-if="widget.currency.position == 'after'" t-esc="widget.currency.symbol"/>
</span>
</div> </div>
</div> </div>
</div> </div>
@ -431,9 +421,13 @@
</ul> </ul>
<div class="summary"> <div class="summary">
<span t-attf-class="line #{widget.pos.get('selectedOrder').get('orderLines').length === 0 ? 'empty' : ''}"> <div t-attf-class="line #{widget.pos.get('selectedOrder').get('orderLines').length === 0 ? 'empty' : ''}">
<span class="label total">Total:</span> <span class="value total">0.00 €</span> <div class='entry total'>
</span> <span class="label">Total: </span> <span class="value">0.00 €</span>
<div class='subentry'>Taxes: <span class="value">0.00€</span></div>
</div>
</div>
<div class='clear'></div>
</div> </div>
</div> </div>
</div> </div>
@ -500,26 +494,26 @@
<t t-esc="widget.model.get_product().get('name')"/> <t t-esc="widget.model.get_product().get('name')"/>
</span> </span>
<span class="price"> <span class="price">
<t t-esc="widget.format_currency(widget.model.get_price_with_tax())"/> <t t-esc="widget.format_currency(widget.model.get_display_price())"/>
</span> </span>
<ul class="info-list"> <ul class="info-list">
<t t-if="widget.model.get_quantity() !== 1.0"> <t t-if="widget.model.get_quantity_str() !== '1'">
<li class="info"> <li class="info">
<em> <em>
<t t-esc="widget.model.get_quantity()" /> <t t-esc="widget.model.get_quantity_str()" />
</em> </em>
<t t-esc="widget.model.get_unit().name" /> <t t-esc="widget.model.get_unit().name" />
at at
<t t-esc="widget.format_currency(widget.model.get_price())" /> <t t-esc="widget.format_currency(widget.model.get_unit_price())" />
/ /
<t t-esc="widget.model.get_unit().name" /> <t t-esc="widget.model.get_unit().name" />
</li> </li>
</t> </t>
<t t-if="widget.model.get_discount() > 0"> <t t-if="widget.model.get_discount_str() !== '0'">
<li class="info"> <li class="info">
With a With a
<em> <em>
<t t-esc="widget.model.get_discount()" />% <t t-esc="widget.model.get_discount_str()" />%
</em> </em>
discount discount
</li> </li>
@ -571,33 +565,36 @@
Shop: <t t-esc="widget.shop_obj.name"/><br /> Shop: <t t-esc="widget.shop_obj.name"/><br />
<br /> <br />
<table> <table>
<tr t-foreach="widget.currentOrderLines.toArray()" t-as="order"> <tr t-foreach="widget.currentOrderLines.toArray()" t-as="orderline">
<td> <td>
<t t-esc="order.get_product().get('name')"/> <t t-esc="orderline.get_product().get('name')"/>
<t t-if="order.get_discount() > 0"> <t t-if="orderline.get_discount() > 0">
<div class="pos-disc-font"> <div class="pos-disc-font">
With a <t t-esc="order.get_discount()"/>% discount With a <t t-esc="orderline.get_discount()"/>% discount
</div> </div>
</t> </t>
</td> </td>
<td class="pos-right-align"> <td class="pos-right-align">
<t t-esc="order.get_quantity().toFixed(0)"/> <t t-esc="orderline.get_quantity_str_with_unit()"/>
</td> </td>
<td class="pos-right-align"> <td class="pos-right-align">
<t t-esc="widget.format_currency(order.get_price() * (1 - order.get_discount()/100) * order.get_quantity().toFixed(2))"/> <t t-esc="widget.format_currency(orderline.get_display_price())"/>
</td> </td>
</tr> </tr>
</table> </table>
<br /> <br />
<table> <table>
<tr><td>Subtotal:</td><td class="pos-right-align">
<t t-esc="widget.format_currency(widget.currentOrder.getSubtotal())"/>
</td></tr>
<tr><td>Tax:</td><td class="pos-right-align"> <tr><td>Tax:</td><td class="pos-right-align">
<t t-esc="widget.format_currency(widget.currentOrder.getTax().toFixed(2))"/> <t t-esc="widget.format_currency(widget.currentOrder.getTax())"/>
</td></tr> </td></tr>
<tr><td>Discount:</td><td class="pos-right-align"> <tr><td>Discount:</td><td class="pos-right-align">
<t t-esc="widget.format_currency(widget.currentOrder.getDiscountTotal().toFixed(2))"/> <t t-esc="widget.format_currency(widget.currentOrder.getDiscountTotal())"/>
</td></tr> </td></tr>
<tr class="emph"><td>Total:</td><td class="pos-right-align"> <tr class="emph"><td>Total:</td><td class="pos-right-align">
<t t-esc="widget.format_currency(widget.currentOrder.getTotal().toFixed(2))"/> <t t-esc="widget.format_currency(widget.currentOrder.getTotalTaxIncluded())"/>
</td></tr> </td></tr>
</table> </table>
<br /> <br />
@ -607,14 +604,14 @@
<t t-esc="pline.get_cashregister().get('journal_id')[1]"/> <t t-esc="pline.get_cashregister().get('journal_id')[1]"/>
</td> </td>
<td class="pos-right-align"> <td class="pos-right-align">
<t t-esc="widget.format_currency((pline.get_amount()).toFixed(2))"/> <t t-esc="widget.format_currency(pline.get_amount())"/>
</td> </td>
</tr> </tr>
</table> </table>
<br /> <br />
<table> <table>
<tr><td>Change:</td><td class="pos-right-align"> <tr><td>Change:</td><td class="pos-right-align">
<t t-esc="widget.format_currency((widget.currentOrder.getPaidTotal() - widget.currentOrder.getTotal()).toFixed(2))"/> <t t-esc="widget.format_currency(widget.currentOrder.getPaidTotal() - widget.currentOrder.getTotalTaxIncluded())"/>
</td></tr> </td></tr>
</table> </table>
</div> </div>

View File

@ -103,7 +103,7 @@ class procurement_order(osv.osv):
readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \ readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
" a make to order method."), " a make to order method."),
'note': fields.text('Note'), 'note': fields.text('Note'),
'message': fields.char('Latest error', size=124, help="Exception occurred while computing procurement orders."), 'message': fields.char('Latest error', help="Exception occurred while computing procurement orders."),
'state': fields.selection([ 'state': fields.selection([
('draft','Draft'), ('draft','Draft'),
('cancel','Cancelled'), ('cancel','Cancelled'),
@ -367,10 +367,22 @@ class procurement_order(osv.osv):
if message: if message:
message = _("Procurement '%s' is in exception: ") % (procurement.name) + message message = _("Procurement '%s' is in exception: ") % (procurement.name) + message
cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id)) #temporary context passed in write to prevent an infinite loop
ctx_wkf = dict(context or {})
ctx_wkf['workflow.trg_write.%s' % self._name] = False
self.write(cr, uid, [procurement.id], {'message': message},context=ctx_wkf)
self.message_post(cr, uid, [procurement.id], body=message, context=context) self.message_post(cr, uid, [procurement.id], body=message, context=context)
return ok return ok
def _workflow_trigger(self, cr, uid, ids, trigger, context=None):
""" Don't trigger workflow for the element specified in trigger
"""
wkf_op_key = 'workflow.%s.%s' % (trigger, self._name)
if context and not context.get(wkf_op_key, True):
# make sure we don't have a trigger loop while processing triggers
return
return super(procurement_order,self)._workflow_trigger(cr, uid, ids, trigger, context=context)
def action_produce_assign_service(self, cr, uid, ids, context=None): def action_produce_assign_service(self, cr, uid, ids, context=None):
""" Changes procurement state to Running. """ Changes procurement state to Running.
@return: True @return: True

View File

@ -33,7 +33,10 @@
'images/project_task_tree.jpeg', 'images/project_task_tree.jpeg',
'images/project_task.jpeg', 'images/project_task.jpeg',
'images/project.jpeg', 'images/project.jpeg',
'images/task_analysis.jpeg' 'images/task_analysis.jpeg',
'images/project_kanban.jpeg',
'images/task_kanban.jpeg',
'images/task_stages.jpeg'
], ],
'depends': [ 'depends': [
'base_setup', 'base_setup',

View File

@ -94,16 +94,19 @@
<record id="mt_task_blocked" model="mail.message.subtype"> <record id="mt_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field> <field name="name">Task Blocked</field>
<field name="res_model">project.task</field> <field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task blocked</field> <field name="description">Task blocked</field>
</record> </record>
<record id="mt_task_closed" model="mail.message.subtype"> <record id="mt_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field> <field name="name">Task Done</field>
<field name="res_model">project.task</field> <field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task closed</field> <field name="description">Task closed</field>
</record> </record>
<record id="mt_task_stage" model="mail.message.subtype"> <record id="mt_task_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field> <field name="name">Stage Changed</field>
<field name="res_model">project.task</field> <field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Stage changed</field> <field name="description">Stage changed</field>
</record> </record>
<!-- Project-related subtypes for messaging / Chatter --> <!-- Project-related subtypes for messaging / Chatter -->

View File

@ -501,6 +501,7 @@ class project_issue(base_stage, osv.osv):
'description': desc, 'description': desc,
'email_from': msg.get('from'), 'email_from': msg.get('from'),
'email_cc': msg.get('cc'), 'email_cc': msg.get('cc'),
'partner_id': msg.get('author_id', False),
'user_id': False, 'user_id': False,
} }
if msg.get('priority'): if msg.get('priority'):

View File

@ -59,16 +59,19 @@ Access all issues from the top Project menu, and access the issues of a specific
<record id="mt_issue_blocked" model="mail.message.subtype"> <record id="mt_issue_blocked" model="mail.message.subtype">
<field name="name">Issue Blocked</field> <field name="name">Issue Blocked</field>
<field name="res_model">project.issue</field> <field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue blocked</field> <field name="description">Issue blocked</field>
</record> </record>
<record id="mt_issue_closed" model="mail.message.subtype"> <record id="mt_issue_closed" model="mail.message.subtype">
<field name="name">Issue Closed</field> <field name="name">Issue Closed</field>
<field name="res_model">project.issue</field> <field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue closed</field> <field name="description">Issue closed</field>
</record> </record>
<record id="mt_issue_stage" model="mail.message.subtype"> <record id="mt_issue_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field> <field name="name">Stage Changed</field>
<field name="res_model">project.issue</field> <field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Stage changed</field> <field name="description">Stage changed</field>
</record> </record>
<!-- Project-related subtypes for messaging / Chatter --> <!-- Project-related subtypes for messaging / Chatter -->

View File

@ -52,10 +52,12 @@
<!-- Purchase-related subtypes for messaging / Chatter --> <!-- Purchase-related subtypes for messaging / Chatter -->
<record id="mt_rfq_confirmed" model="mail.message.subtype"> <record id="mt_rfq_confirmed" model="mail.message.subtype">
<field name="name">RFQ Confirmed</field> <field name="name">RFQ Confirmed</field>
<field name="default" eval="False"/>
<field name="res_model">purchase.order</field> <field name="res_model">purchase.order</field>
</record> </record>
<record id="mt_rfq_approved" model="mail.message.subtype"> <record id="mt_rfq_approved" model="mail.message.subtype">
<field name="name">RFQ Approved</field> <field name="name">RFQ Approved</field>
<field name="default" eval="False"/>
<field name="res_model">purchase.order</field> <field name="res_model">purchase.order</field>
</record> </record>

View File

@ -19,10 +19,14 @@
# #
############################################################################## ##############################################################################
import logging
from openerp.osv import fields, osv from openerp.osv import fields, osv
from openerp import pooler from openerp import pooler
from openerp.tools.translate import _ from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
class sale_configuration(osv.osv_memory): class sale_configuration(osv.osv_memory):
_inherit = 'sale.config.settings' _inherit = 'sale.config.settings'
@ -81,8 +85,12 @@ Example: Product: this product is deprecated, do not purchase more than 5.
user = self.pool.get('res.users').browse(cr, uid, uid, context) user = self.pool.get('res.users').browse(cr, uid, uid, context)
res['time_unit'] = user.company_id.project_time_mode_id.id res['time_unit'] = user.company_id.project_time_mode_id.id
else: else:
product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant') try:
res['time_unit'] = product.uom_id.id product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant')
res['time_unit'] = product.uom_id.id
except ValueError:
# keep default value in that case
_logger.warning("Product with xml_id 'product.product_product_consultant' not found")
return res return res
def _get_default_time_unit(self, cr, uid, context=None): def _get_default_time_unit(self, cr, uid, context=None):
@ -98,16 +106,11 @@ Example: Product: this product is deprecated, do not purchase more than 5.
wizard = self.browse(cr, uid, ids)[0] wizard = self.browse(cr, uid, ids)[0]
if wizard.time_unit: if wizard.time_unit:
product = False
try: try:
product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant') product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant')
except:
#product with xml_id product_product_consultant has not been found. Don't do anything except logging the exception
import logging
_logger = logging.getLogger(__name__)
_logger.warning("Warning, product with xml_id 'product_product_consultant' hasn't been found")
if product:
product.write({'uom_id': wizard.time_unit.id, 'uom_po_id': wizard.time_unit.id}) product.write({'uom_id': wizard.time_unit.id, 'uom_po_id': wizard.time_unit.id})
except ValueError:
_logger.warning("Product with xml_id 'product.product_product_consultant' not found, UoMs not updated!")
if wizard.module_project and wizard.time_unit: if wizard.module_project and wizard.time_unit:
user = self.pool.get('res.users').browse(cr, uid, uid, context) user = self.pool.get('res.users').browse(cr, uid, uid, context)

View File

@ -926,7 +926,6 @@ class sale_order_line(osv.osv):
elif uom: # whether uos is set or not elif uom: # whether uos is set or not
default_uom = product_obj.uom_id and product_obj.uom_id.id default_uom = product_obj.uom_id and product_obj.uom_id.id
q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom) q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
result['product_uom'] = default_uom
if product_obj.uos_id: if product_obj.uos_id:
result['product_uos'] = product_obj.uos_id.id result['product_uos'] = product_obj.uos_id.id
result['product_uos_qty'] = qty * product_obj.uos_coeff result['product_uos_qty'] = qty * product_obj.uos_coeff

View File

@ -48,11 +48,13 @@
<record id="mt_order_sent" model="mail.message.subtype"> <record id="mt_order_sent" model="mail.message.subtype">
<field name="name">Quotation send</field> <field name="name">Quotation send</field>
<field name="res_model">sale.order</field> <field name="res_model">sale.order</field>
<field name="default" eval="False"/>
<field name="description">Quotation send</field> <field name="description">Quotation send</field>
</record> </record>
<record id="mt_order_confirmed" model="mail.message.subtype"> <record id="mt_order_confirmed" model="mail.message.subtype">
<field name="name">Sales Order Confirmed</field> <field name="name">Sales Order Confirmed</field>
<field name="res_model">sale.order</field> <field name="res_model">sale.order</field>
<field name="default" eval="False"/>
<field name="description">Quotation confirmed</field> <field name="description">Quotation confirmed</field>
</record> </record>

View File

@ -14,3 +14,23 @@
!assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}: !assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
- order_line[0].name == u'[LCD17] 17\u201d LCD Monitor' - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- order_line[0].price_unit == 1350.0 - order_line[0].price_unit == 1350.0
- order_line[0].product_uom_qty == 8
- order_line[0].product_uom.id == ref('product.product_uom_unit')
-
I create another sale order
-
!record {model: sale.order, id: sale_order_test2}:
partner_id: base.res_partner_2
order_line:
- product_id: product.product_product_7
product_uom_qty: 16
product_uom: product.product_uom_dozen
-
I verify that the onchange was correctly triggered
-
!assert {model: sale.order, id: sale.sale_order_test2, string: The onchange function of product was not correctly triggered}:
- order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- order_line[0].price_unit == 1350.0 * 12
- order_line[0].product_uom.id == ref('product.product_uom_dozen')
- order_line[0].product_uom_qty == 16

View File

@ -91,7 +91,7 @@
<field name="group_uos" class="oe_inline"/> <field name="group_uos" class="oe_inline"/>
<label for="group_uos" /> <label for="group_uos" />
</div> </div>
<div> <div invisible="1">
<field name="group_product_variant" class="oe_inline"/> <field name="group_product_variant" class="oe_inline"/>
<label for="group_product_variant"/> <label for="group_product_variant"/>
</div> </div>

View File

@ -16,7 +16,7 @@ openerp.web_analytics = function(instance) {
var ga = document.createElement('script'); var ga = document.createElement('script');
ga.type = 'text/javascript'; ga.type = 'text/javascript';
ga.async = true; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; ga.src = ('https:' === document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga,s); s.parentNode.insertBefore(ga,s);
})(); })();
@ -25,16 +25,15 @@ openerp.web_analytics = function(instance) {
/* /*
* This method initializes the tracker * This method initializes the tracker
*/ */
init: function() { init: function(webclient) {
/* Comment this lines when going on production, only used for testing on localhost var self = this;
_gaq.push(['_setAccount', 'UA-35793871-1']); self.initialized = $.Deferred();
_gaq.push(['_setDomainName', 'none']);
*/
/* Uncomment this lines when going on production */
_gaq.push(['_setAccount', 'UA-7333765-1']); _gaq.push(['_setAccount', 'UA-7333765-1']);
_gaq.push(['_setDomainName', '.openerp.com']); // Allow multi-domain _gaq.push(['_setDomainName', '.openerp.com']); // Allow multi-domain
/**/ self.initialize_custom(webclient).then(function() {
webclient.on('state_pushed', self, self.on_state_pushed);
self.include_tracker();
});
}, },
/* /*
* This method MUST be overriden by saas_demo and saas_trial in order to * This method MUST be overriden by saas_demo and saas_trial in order to
@ -48,41 +47,53 @@ openerp.web_analytics = function(instance) {
* This method gets the user access level, to be used as CV in GA * This method gets the user access level, to be used as CV in GA
*/ */
_get_user_access_level: function() { _get_user_access_level: function() {
if (!instance.session.session_is_valid()) {
return "Unauthenticated User";
}
if (instance.session.uid === 1) { if (instance.session.uid === 1) {
return 'Admin User'; return 'Admin User';
// Make the difference between portal users and anonymous users
} else if (instance.session.username.indexOf('@') !== -1) {
if (instance.session.username.indexOf('anonymous') === -1) {
return 'Portal User';
} else {
return 'Anonymous User';
}
} else if (instance.session.username.indexOf('anonymous') !== -1) {
return 'Anonymous User';
} else {
return 'Normal User';
} }
// Make the difference between portal users and anonymous users
if (instance.session.username.indexOf('@') !== -1) {
return 'Portal User';
}
if (instance.session.username === 'anonymous') {
return 'Anonymous User';
}
return 'Normal User';
}, },
/* /*
* This method contains the initialization of all user-related custom variables * This method contains the initialization of all user-related custom variables
* stored in GA. Also other modules can override it to add new custom variables * stored in GA. Also other modules can override it to add new custom variables
* Must be followed by a call to _push_*() in order to actually send the data
* to GA.
*/ */
initialize_custom: function() { initialize_custom: function() {
var self = this; var self = this;
return instance.session.rpc("/web/webclient/version_info", {}) instance.session.rpc("/web/webclient/version_info", {})
.done(function(res) { .done(function(res) {
_gaq.push(['_setCustomVar', 5, 'Version', res.server_version, 3]); _gaq.push(['_setCustomVar', 5, 'Version', res.server_version, 3]);
// Track User Access Level, Custom Variable 4 in GA with visitor level scope self._push_customvars();
// Values: 'Admin User', 'Normal User', 'Portal User', 'Anonymous User' self.initialized.resolve(self);
_gaq.push(['_setCustomVar', 4, 'User Access Level', self.user_access_level, 1]);
// Track User Type Conversion, Custom Variable 3 in GA with session level scope
// Values: 'Visitor', 'Demo', 'Online Trial', 'Online Paying', 'Local User'
_gaq.push(['_setCustomVar', 1, 'User Type Conversion', self._get_user_type(), 2]);
_gaq.push(['_trackPageview']);
return;
}); });
return self.initialized;
}, },
/*
* Method called in order to send _setCustomVar to GA
*/
_push_customvars: function() {
var self = this;
// Track User Access Level, Custom Variable 4 in GA with visitor level scope
// Values: 'Admin User', 'Normal User', 'Portal User', 'Anonymous User'
_gaq.push(['_setCustomVar', 4, 'User Access Level', self._get_user_access_level(), 1]);
// Track User Type Conversion, Custom Variable 3 in GA with session level scope
// Values: 'Visitor', 'Demo', 'Online Trial', 'Online Paying', 'Local User'
_gaq.push(['_setCustomVar', 1, 'User Type Conversion', self._get_user_type(), 2]);
},
/* /*
* Method called in order to send _trackPageview to GA * Method called in order to send _trackPageview to GA
*/ */
@ -142,6 +153,7 @@ openerp.web_analytics = function(instance) {
'action': state.view_type, 'action': state.view_type,
'label': url, 'label': url,
}); });
this._push_pageview(url);
} }
}, },
/* /*
@ -157,19 +169,19 @@ openerp.web_analytics = function(instance) {
this._super.apply(this, arguments); this._super.apply(this, arguments);
var self = this; var self = this;
this.on('record_created', self, function(r) { this.on('record_created', self, function(r) {
var url = instance.web_analytics.generateUrl({'model': r.model, 'view_type': 'form'}); var url = instance.web_analytics.generateUrl({'model': self.model, 'view_type': 'form'});
t._push_event({ t._push_event({
'category': r.model, 'category': self.model,
'action': 'form', 'action': 'create',
'label': url, 'label': url,
'noninteraction': true, 'noninteraction': true,
}); });
}); });
this.on('record_saved', self, function(r) { this.on('record_saved', self, function(r) {
var url = instance.web_analytics.generateUrl({'model': r.model, 'view_type': 'form'}); var url = instance.web_analytics.generateUrl({'model': self.model, 'view_type': 'form'});
t._push_event({ t._push_event({
'category': r.model, 'category': self.model,
'action': 'form', 'action': 'save',
'label': url, 'label': url,
'noninteraction': true, 'noninteraction': true,
}); });
@ -181,12 +193,12 @@ openerp.web_analytics = function(instance) {
instance.web.ActionManager.include({ instance.web.ActionManager.include({
ir_actions_client: function (action, options) { ir_actions_client: function (action, options) {
var url = instance.web_analytics.generateUrl({'action': action.tag}); var url = instance.web_analytics.generateUrl({'action': action.tag});
var category = action.res_model || action.type;
t._push_event({ t._push_event({
'category': action.res_model || action.type, 'category': action.type,
'action': action.name || action.tag, 'action': action.tag,
'label': url, 'label': url,
}); });
t._push_pageview(url);
return this._super.apply(this, arguments); return this._super.apply(this, arguments);
}, },
}); });
@ -224,21 +236,12 @@ openerp.web_analytics = function(instance) {
options = {'action': params.action}; options = {'action': params.action};
} }
var url = instance.web_analytics.generateUrl(options); var url = instance.web_analytics.generateUrl(options);
if (error.code) { t._push_event({
t._push_event({ 'category': options.model || "ir.actions.client",
'category': error.message, 'action': "error " + (error.code ? error.message + error.data.message : error.type + error.data.debug),
'action': error.data.message, 'label': url,
'label': url, 'noninteraction': true,
'noninteraction': true, });
});
} else {
t._push_event({
'category': error.type,
'action': error.data.debug,
'label': url,
'noninteraction': true,
});
}
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
}); });
@ -251,43 +254,37 @@ openerp.web_analytics = function(instance) {
instance.web_analytics.generateUrl = function(options) { instance.web_analytics.generateUrl = function(options) {
var url = ''; var url = '';
_.each(options, function(value, key) { var keys = _.keys(options);
url += '/' + key + '=' + value; keys = _.sortBy(keys, function(i) { return i;});
_.each(keys, function(key) {
url += '/' + key + '/' + options[key];
}); });
return url; return url;
}; };
// kept for API compatibility
instance.web_analytics.setupTracker = function(wc) { instance.web_analytics.setupTracker = function(wc) {
var t = wc.tracker; return wc.tracker.initialized;
return $.when(t._get_user_access_level()).then(function(r) {
t.user_access_level = r;
t.initialize_custom().then(function() {
wc.on('state_pushed', t, t.on_state_pushed);
t.include_tracker();
});
});
}; };
// Set correctly the tracker in the current instance instance.web.Client.include({
if (instance.client instanceof instance.web.WebClient) { // not for embedded clients bind_events: function() {
instance.webclient.tracker = new instance.web_analytics.Tracker(); this._super.apply(this, arguments);
instance.web_analytics.setupTracker(instance.webclient); this.tracker = new instance.web_analytics.Tracker(this);
} else if (!instance.client) { },
// client does not already exists, we are in monodb mode });
instance.web.WebClient.include({
start: function() {
var d = this._super.apply(this, arguments);
this.tracker = new instance.web_analytics.Tracker();
return d;
},
show_application: function() {
var self = this;
$.when(this.subscribe_deferred).then(function() {
instance.web_analytics.setupTracker(self);
});
this._super();
},
});
}
instance.web.Session.include({
session_authenticate: function() {
return $.when(this._super.apply(this, arguments)).then(function() {
// the call to bind_events() may have been delayed in some embed
// cases, and when that happens we can skip the push, as the
// proper one will be done when bind_event is eventually called.
if (instance.client && instance.client.tracker) {
instance.client.tracker._push_customvars();
}
});
},
});
}; };