[MERGE] merged v7 branch
bzr revid: qdp-launchpad@openerp.com-20130205155556-b4zw4y3gr9v2eom3
This commit is contained in:
commit
552583b9d8
|
@ -3521,7 +3521,7 @@ class account_bank_accounts_wizard(osv.osv_memory):
|
|||
|
||||
_columns = {
|
||||
'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."),
|
||||
'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class bank(osv.osv):
|
|||
|
||||
def _prepare_name(self, bank):
|
||||
"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):
|
||||
"""Add ability to have %(currency_name)s in the format_layout of res.partner.bank.type"""
|
||||
|
|
|
@ -256,7 +256,7 @@
|
|||
</group>
|
||||
<group>
|
||||
<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"/>
|
||||
</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="user_id" groups="base.group_user"/>
|
||||
<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"
|
||||
string="Accounting Period"
|
||||
placeholder="force period"/>
|
||||
|
|
|
@ -347,6 +347,15 @@
|
|||
res_model="account.move.line"
|
||||
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 -->
|
||||
<record id="view_account_journal_tree" model="ir.ui.view">
|
||||
<field name="name">account.journal.tree</field>
|
||||
|
@ -841,6 +850,16 @@
|
|||
res_model="account.move.line"
|
||||
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 -->
|
||||
<record id="view_tax_tree" model="ir.ui.view">
|
||||
<field name="name">account.tax.tree</field>
|
||||
|
|
|
@ -155,11 +155,13 @@
|
|||
<record id="mt_invoice_validated" model="mail.message.subtype">
|
||||
<field name="name">Validated</field>
|
||||
<field name="res_model">account.invoice</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Invoice validated</field>
|
||||
</record>
|
||||
<record id="mt_invoice_paid" model="mail.message.subtype">
|
||||
<field name="name">Paid</field>
|
||||
<field name="res_model">account.invoice</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Invoice paid</field>
|
||||
</record>
|
||||
</data>
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
<field name="partner_id"/>
|
||||
<field name="code"/>
|
||||
<field name="date"/>
|
||||
<field name="date_start" invisible="1"/>
|
||||
<field name="date_start"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="manager_id" invisible="1"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="type" invisible="1"/>
|
||||
|
@ -30,15 +30,18 @@
|
|||
<search string="Analytic Account">
|
||||
<field name="name" filter_domain="['|', ('name','ilike',self), ('code','ilike',self)]" string="Analytic Account"/>
|
||||
<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="manager_id"/>
|
||||
<field name="parent_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...">
|
||||
<filter string="Associated Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Parent Account" icon="terp-folder-green" domain="[]" context="{'group_by':'parent_id'}"/>
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}" groups="base.group_no_one"/>
|
||||
<filter string="Associated Partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Type" domain="[]" context="{'group_by':'type'}"/>
|
||||
<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>
|
||||
</search>
|
||||
</field>
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<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="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_ok!" string="Refund" domain="['|', ('type','=','out_refund'),('type','=','in_refund')]" help="Customer And Supplier Refunds"/>
|
||||
<field name="partner_id"/>
|
||||
|
|
|
@ -35,10 +35,7 @@ class third_party_ledger(report_sxw.rml_parse, common_report_header):
|
|||
'lines': self.lines,
|
||||
'sum_debit_partner': self._sum_debit_partner,
|
||||
'sum_credit_partner': self._sum_credit_partner,
|
||||
# 'sum_debit': self._sum_debit,
|
||||
# 'sum_credit': self._sum_credit,
|
||||
'get_currency': self._get_currency,
|
||||
'comma_me': self.comma_me,
|
||||
'get_start_period': self.get_start_period,
|
||||
'get_end_period': self.get_end_period,
|
||||
'get_account': self._get_account,
|
||||
|
@ -78,11 +75,6 @@ class third_party_ledger(report_sxw.rml_parse, common_report_header):
|
|||
move_state = ['draft','posted']
|
||||
if self.target_move == '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':
|
||||
self.ACCOUNT_TYPE = ['payable']
|
||||
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' \
|
||||
"AND a.active", (tuple(self.ACCOUNT_TYPE), ))
|
||||
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(
|
||||
"SELECT DISTINCT l.partner_id " \
|
||||
"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 l.account_id IN %s " \
|
||||
" " + PARTNER_REQUEST + " " \
|
||||
"AND account.active ",
|
||||
(tuple(move_state), tuple(self.account_ids),))
|
||||
|
||||
res = self.cr.dictfetchall()
|
||||
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)
|
||||
"AND account.active ", params)
|
||||
self.partner_ids = [res['partner_id'] for res in self.cr.dictfetchall()]
|
||||
objects = obj_partner.browse(self.cr, self.uid, self.partner_ids)
|
||||
return super(third_party_ledger, self).set_context(objects, data, self.partner_ids, report_type)
|
||||
|
||||
def lines(self, partner):
|
||||
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
|
||||
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):
|
||||
if self.result_selection == 'customer':
|
||||
return 'Receivable Accounts'
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</record>
|
||||
|
||||
<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="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
|
@ -33,6 +33,13 @@
|
|||
<field name="target">new</field>
|
||||
</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"
|
||||
name="Partner Ledger"
|
||||
action="action_account_partner_ledger"
|
||||
|
|
|
@ -25,19 +25,20 @@
|
|||
<field name="name">account.analytic.account.search</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<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="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="['|', '&', ('date', '!=', False), ('date', '<=', 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="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="['|', '&', ('date', '!=', False), ('date', '<=', 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...">
|
||||
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Account Manager" domain="[]" context="{'group_by':'manager_id'}"/>
|
||||
|
|
|
@ -42,7 +42,7 @@ This module manages:
|
|||
'category': 'Accounting & Finance',
|
||||
'sequence': 4,
|
||||
'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'],
|
||||
'demo' : [],
|
||||
'data' : [
|
||||
|
|
|
@ -962,7 +962,9 @@ class account_voucher(osv.osv):
|
|||
if not voucher_brw.journal_id.sequence_id.active:
|
||||
raise osv.except_osv(_('Configuration Error !'),
|
||||
_('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:
|
||||
raise osv.except_osv(_('Error!'),
|
||||
_('Please define a sequence on the journal.'))
|
||||
|
@ -977,7 +979,7 @@ class account_voucher(osv.osv):
|
|||
'narration': voucher_brw.narration,
|
||||
'date': voucher_brw.date,
|
||||
'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
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<record id="mt_voucher_state_change" model="mail.message.subtype">
|
||||
<field name="name">Status Change</field>
|
||||
<field name="res_model">account.voucher</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Status changed</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -172,7 +172,7 @@ class account_analytic_account(osv.osv):
|
|||
|
||||
_columns = {
|
||||
'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),
|
||||
'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"\
|
||||
|
|
|
@ -6,16 +6,19 @@
|
|||
<record id="mt_account_pending" model="mail.message.subtype">
|
||||
<field name="name">Contract to Renew</field>
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Contract pending</field>
|
||||
</record>
|
||||
<record id="mt_account_closed" model="mail.message.subtype">
|
||||
<field name="name">Contract Finished</field>
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Contract closed</field>
|
||||
</record>
|
||||
<record id="mt_account_opened" model="mail.message.subtype">
|
||||
<field name="name">Contract Opened</field>
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Contract opened</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -549,7 +549,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory):
|
|||
key = (line['model_id'], line['field_id'])
|
||||
custom_updates = fixes.get(key)
|
||||
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']]
|
||||
elif table_name:
|
||||
queries = [("update %(table)s set %(field)s = %%(value)s where id = %%(id)s" % {
|
||||
|
|
|
@ -33,5 +33,39 @@
|
|||
<p>Note: If you do not expect this, you can safely ignore this email.</p>]]></field>
|
||||
</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>
|
||||
</openerp>
|
||||
|
|
|
@ -246,8 +246,18 @@ class res_users(osv.Model):
|
|||
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)
|
||||
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
# 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')
|
||||
assert template._name == 'email.template'
|
||||
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 = self.browse(cr, uid, user_id, context=context)
|
||||
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
|
||||
|
|
|
@ -164,7 +164,10 @@
|
|||
background: #efc9cb;
|
||||
}
|
||||
|
||||
.oe_import .select2-results {
|
||||
font-size: 12px;
|
||||
/* Field dropdown */
|
||||
.oe_import_selector {
|
||||
font-size: 10px;
|
||||
/* copied from base.sass:~148 */
|
||||
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
|
||||
}
|
||||
|
||||
|
|
|
@ -134,6 +134,7 @@ openerp.base_import = function (instance) {
|
|||
start: function () {
|
||||
var self = this;
|
||||
this.setup_encoding_picker();
|
||||
this.setup_separator_picker();
|
||||
|
||||
return $.when(
|
||||
this._super(),
|
||||
|
@ -164,6 +165,26 @@ openerp.base_import = function (instance) {
|
|||
}
|
||||
}).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 () {
|
||||
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) {
|
||||
return $(el).select2('val') || false;
|
||||
}).get();
|
||||
return this.Import.call(
|
||||
'do', [this.id, fields, this.import_options()], options);
|
||||
return this.Import.call('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 () {
|
||||
return this.call_import({ dryrun: true })
|
||||
|
|
|
@ -36,6 +36,7 @@ You can track your suppliers, customers and other contacts.
|
|||
'data': [
|
||||
'contacts_view.xml',
|
||||
],
|
||||
'images': ['images/contacts.jpeg'],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
|
|
|
@ -54,6 +54,7 @@ Dashboard for CRM will include:
|
|||
'base_status',
|
||||
'process',
|
||||
'mail',
|
||||
'email_template',
|
||||
'base_calendar',
|
||||
'resource',
|
||||
'board',
|
||||
|
@ -116,6 +117,6 @@ Dashboard for CRM will include:
|
|||
'installable': True,
|
||||
'application': True,
|
||||
'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:
|
||||
|
|
|
@ -36,7 +36,6 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name',
|
|||
'company_id',
|
||||
'country_id',
|
||||
'section_id',
|
||||
'stage_id',
|
||||
'state_id',
|
||||
'type_id',
|
||||
'user_id',
|
||||
|
@ -472,6 +471,15 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
|
||||
def _get_first_not_null(attr):
|
||||
if hasattr(oldest, attr):
|
||||
return getattr(oldest, attr)
|
||||
for opp in opportunities:
|
||||
if hasattr(opp, attr):
|
||||
if hasattr(opp, attr) and bool(getattr(opp, attr)):
|
||||
return getattr(opp, attr)
|
||||
return False
|
||||
|
||||
|
@ -520,7 +526,7 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
|
||||
# Define the resulting type ('lead' or 'opportunity')
|
||||
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
|
||||
|
||||
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:
|
||||
context = {}
|
||||
|
||||
if context.get('convert'):
|
||||
ids = list(set(ids) - set(context.get('lead_ids', [])))
|
||||
|
||||
# Search opportunities order by create date
|
||||
opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date', context=context)
|
||||
oldest_opp_id = opportunity_ids[0]
|
||||
|
@ -643,19 +646,11 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
|
||||
if len(ids) <= 1:
|
||||
raise osv.except_osv(_('Warning!'),_('Please select more than one element (lead or opportunity) from the list view.'))
|
||||
|
||||
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))
|
||||
ids.sort()
|
||||
oldest = self._merge_find_oldest(cr, uid, ids, context=context)
|
||||
if ctx_opportunities:
|
||||
first_opportunity = ctx_opportunities[0]
|
||||
tail_opportunities = opportunities_list + ctx_opportunities[1:]
|
||||
else:
|
||||
first_opportunity = opportunities_list[0]
|
||||
tail_opportunities = opportunities_list[1:]
|
||||
opportunities_rest = self.browse(cr, uid, list(set(ids) - set([oldest.id])), context=context)
|
||||
first_opportunity = oldest
|
||||
tail_opportunities = opportunities_rest
|
||||
|
||||
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)
|
||||
|
||||
# Merge notifications about loss of information
|
||||
opportunities = [oldest]
|
||||
opportunities.extend(opportunities_rest)
|
||||
self._merge_notify(cr, uid, first_opportunity, opportunities, context=context)
|
||||
# Write merged data into first opportunity
|
||||
self.write(cr, uid, [first_opportunity.id], merged_data, context=context)
|
||||
# Delete tail opportunities
|
||||
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
|
||||
|
||||
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):
|
||||
partner = self.pool.get('res.partner')
|
||||
vals = { 'name': name,
|
||||
vals = {'name': name,
|
||||
'user_id': lead.user_id.id,
|
||||
'comment': lead.description,
|
||||
'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,
|
||||
'type': 'contact'
|
||||
}
|
||||
partner = partner.create(cr, uid,vals, context)
|
||||
partner = partner.create(cr, uid, vals, context=context)
|
||||
return partner
|
||||
|
||||
def _create_lead_partner(self, cr, uid, lead, context=None):
|
||||
partner_id = False
|
||||
partner_id = False
|
||||
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.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)
|
||||
elif not lead.partner_name and lead.contact_name:
|
||||
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:
|
||||
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
|
||||
|
||||
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_partner = self.pool.get('res.partner')
|
||||
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']
|
||||
res = lead.write({'partner_id': partner_id}, context=context)
|
||||
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),
|
||||
'view_id': False,
|
||||
'views': [(form_view or False, 'form'),
|
||||
(tree_view or False, 'tree'),
|
||||
(False, 'calendar'), (False, 'graph')],
|
||||
(tree_view or False, 'tree'),
|
||||
(False, 'calendar'), (False, 'graph')],
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
|
@ -980,8 +981,11 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
'description': desc,
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'partner_id': msg.get('author_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):
|
||||
defaults['priority'] = msg.get('priority')
|
||||
defaults.update(custom_values)
|
||||
|
|
|
@ -171,11 +171,13 @@
|
|||
<record id="mt_lead_stage" model="mail.message.subtype">
|
||||
<field name="name">Stage Changed</field>
|
||||
<field name="res_model">crm.lead</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<record id="mt_lead_won" model="mail.message.subtype">
|
||||
<field name="name">Opportunity Won</field>
|
||||
<field name="res_model">crm.lead</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Opportunity won</field>
|
||||
</record>
|
||||
<record id="mt_lead_lost" model="mail.message.subtype">
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
</group>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Notes">
|
||||
<page string="Internal Notes">
|
||||
<field name="description"/>
|
||||
</page>
|
||||
<page string="Extra Info">
|
||||
|
@ -219,12 +219,14 @@
|
|||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date" groups="base.group_no_one"/>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<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="partner_id" invisible="1"/>
|
||||
<field name="section_id" invisible="context.get('invisible_section', True)"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="type_id" invisible="1"/>
|
||||
|
@ -235,6 +237,7 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- CRM Lead Calendar View -->
|
||||
<record model="ir.ui.view" id="crm_case_calendar_view_leads">
|
||||
<field name="name">CRM - Leads Calendar</field>
|
||||
|
@ -322,35 +325,39 @@
|
|||
<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="categ_ids" string="Category" filter_domain="[('categ_ids','ilike',self)]"/>
|
||||
<field name="create_date"/>
|
||||
<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="section_id" context="{'invisible_section': False, 'default_section_id': self}"/>
|
||||
<field name="user_id"/>
|
||||
<field name="section_id" context="{'invisible_section': False}"/>
|
||||
<field name="create_date"/>
|
||||
<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...">
|
||||
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}"/>
|
||||
<filter string="Referrer" icon="terp-personal" domain="[]" context="{'group_by':'referred'}"/>
|
||||
<filter string="Campaign" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'type_id'}"/>
|
||||
<filter string="Channel" icon="terp-call-start" domain="[]" context="{'group_by':'channel_id'}"/>
|
||||
<separator orientation="vertical"/>
|
||||
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Creation" help="Create date" icon="terp-go-month" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/>
|
||||
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Team" domain="[]" context="{'group_by':'section_id'}"/>
|
||||
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Country" domain="[]" context="{'group_by':'country_id'}"/>
|
||||
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Referrer" domain="[]" context="{'group_by':'referred'}"/>
|
||||
<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 string="Display">
|
||||
<filter string="Show Countries" icon="terp-personal+" 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 Countries" context="{'invisible_country': False}" help="Show Countries"/>
|
||||
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/>
|
||||
</group>
|
||||
</search>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
@ -517,6 +524,7 @@
|
|||
<field name="probability" avg="Avg. of Probability"/>
|
||||
<field name="section_id" invisible="context.get('invisible_section', True)"/>
|
||||
<field name="user_id"/>
|
||||
<field name="referred" invisible="1"/>
|
||||
<field name="priority" invisible="1"/>
|
||||
<field name="state" groups="base.group_no_one"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
|
@ -531,35 +539,41 @@
|
|||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Opportunities">
|
||||
<field name="name" string="Opportunity"
|
||||
filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/>
|
||||
<field name="name" string="Opportunity" filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/>
|
||||
<field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike', self)]"/>
|
||||
<filter icon="terp-mail-message-new" string="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="user_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">
|
||||
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Team" help="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}"/>
|
||||
<filter string="Customer" help="Partner" icon="terp-personal+" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}"/>
|
||||
<filter string="Campaign" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'type_id'}"/>
|
||||
<filter string="Channel" icon="terp-call-start" domain="[]" context="{'group_by':'channel_id'}"/>
|
||||
<filter string="Creation" icon="terp-go-month" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/>
|
||||
<filter string="Exp.Closing" icon="terp-go-month" help="Expected Closing" domain="[]" context="{'group_by':'date_deadline'}"/>
|
||||
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Team" domain="[]" context="{'group_by':'section_id'}"/>
|
||||
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Country" domain="[]" context="{'group_by':'country_id'}"/>
|
||||
<filter string="Priority" domain="[]" context="{'group_by':'priority'}"/>
|
||||
<filter string="Expected Closing" domain="[]" context="{'group_by':'date_deadline'}"/>
|
||||
<filter string="Referrer" domain="[]" context="{'group_by':'referred'}"/>
|
||||
<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 string="Display">
|
||||
<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>
|
||||
</search>
|
||||
</field>
|
||||
|
|
|
@ -68,24 +68,4 @@ class calendar_attendee(osv.osv):
|
|||
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:
|
||||
|
|
|
@ -4,31 +4,37 @@
|
|||
!record {model: crm.lead, id: test_crm_lead_01}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 1'
|
||||
email_from: 'Raoul Grosbedon <raoul@grosbedon.fr>'
|
||||
stage_id: stage_lead1
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_02}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 2'
|
||||
email_from: 'Raoul Grosbedon <raoul@grosbedon.fr>'
|
||||
stage_id: stage_lead1
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_03}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 3'
|
||||
email_from: 'Raoul Grosbedon <raoul@grosbedon.fr>'
|
||||
stage_id: stage_lead1
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_04}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 4'
|
||||
contact_name: 'Fabrice Lepoilu'
|
||||
stage_id: stage_lead1
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_05}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 5'
|
||||
contact_name: 'Fabrice Lepoilu'
|
||||
stage_id: stage_lead1
|
||||
-
|
||||
!record {model: crm.lead, id: test_crm_lead_06}:
|
||||
type: 'lead'
|
||||
name: 'Test lead 6'
|
||||
partner_name: 'Agrolait SuperSeed SA'
|
||||
stage_id: stage_lead1
|
||||
-
|
||||
!record {model: res.users, id: test_res_user_01}:
|
||||
|
|
|
@ -34,7 +34,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
('convert', 'Convert to opportunity'),
|
||||
('merge', 'Merge with existing opportunities')
|
||||
], '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):
|
||||
|
@ -46,42 +46,34 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
lead_obj = self.pool.get('crm.lead')
|
||||
|
||||
res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context)
|
||||
opportunities = res.get('opportunity_ids') or []
|
||||
partner_id = False
|
||||
email = False
|
||||
for lead in lead_obj.browse(cr, uid, opportunities, context=context):
|
||||
partner_id = lead.partner_id and lead.partner_id.id or False
|
||||
if context.get('active_id'):
|
||||
tomerge = set([int(context['active_id'])])
|
||||
|
||||
email = 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
|
||||
email = re.findall(r'([^ ,<@]+@[^> ,]+)', lead.email_from or '')
|
||||
email = map(lambda x: "'" + x + "'", email)
|
||||
|
||||
if not partner_id and res.get('partner_id'):
|
||||
partner_id = res.get('partner_id')
|
||||
|
||||
ids = []
|
||||
if 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), ('type', '=', 'opportunity'), '!', ('state', 'in', ['done', 'cancel'])])
|
||||
if ids:
|
||||
opportunities.append(ids[0])
|
||||
if not partner_id:
|
||||
if 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)])
|
||||
for id in ids:
|
||||
tomerge.add(id)
|
||||
if email:
|
||||
# Find email of existing opportunity matching the email_from of the lead
|
||||
cr.execute("""select id from crm_lead where type='opportunity' and
|
||||
substring(email_from from '([^ ,<@]+@[^> ,]+)') in (%s)""" % (','.join(email)))
|
||||
ids = map(lambda x:x[0], cr.fetchall())
|
||||
if ids:
|
||||
opportunities.append(ids[0])
|
||||
ids = lead_obj.search(cr, uid, [('email_from', 'ilike', email[0])])
|
||||
for id in ids:
|
||||
tomerge.add(id)
|
||||
|
||||
if 'action' in fields:
|
||||
res.update({'action' : partner_id and 'exist' or 'create'})
|
||||
if 'partner_id' in fields:
|
||||
res.update({'partner_id' : partner_id})
|
||||
if 'name' in fields:
|
||||
res.update({'name' : ids and 'merge' or 'convert'})
|
||||
if 'opportunity_ids' in fields:
|
||||
res.update({'opportunity_ids': opportunities})
|
||||
if 'action' in fields:
|
||||
res.update({'action' : partner_id and 'exist' or 'create'})
|
||||
if 'partner_id' in fields:
|
||||
res.update({'partner_id' : partner_id})
|
||||
if 'name' in fields:
|
||||
res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
|
||||
if 'opportunity_ids' in fields and len(tomerge) >= 2:
|
||||
res.update({'opportunity_ids': list(tomerge)})
|
||||
|
||||
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)
|
||||
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):
|
||||
"""
|
||||
Convert lead to opportunity or merge lead and opportunity and open
|
||||
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:
|
||||
context = {}
|
||||
lead = self.pool.get('crm.lead')
|
||||
lead_ids = context.get('active_ids', [])
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
|
||||
self._merge_opportunity(cr, uid, ids, data.opportunity_ids, data.name, context=context)
|
||||
return lead.redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
|
||||
|
||||
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)
|
||||
|
||||
class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
||||
_name = 'crm.lead2opportunity.partner.mass'
|
||||
|
|
|
@ -9,11 +9,19 @@
|
|||
<form string="Convert to Opportunity" version="7.0">
|
||||
<group name="name">
|
||||
<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>
|
||||
<field name="create_date"/>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="type"/>
|
||||
<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"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
@ -51,9 +59,15 @@
|
|||
<group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
|
||||
<field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
|
||||
<tree>
|
||||
<field name="create_date"/>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="type"/>
|
||||
<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"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
|
|
@ -11,9 +11,14 @@
|
|||
<separator string="Select Leads/Opportunities"/>
|
||||
<field name="opportunity_ids">
|
||||
<tree>
|
||||
<field name="create_date"/>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="type"/>
|
||||
<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"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
|
|
@ -96,20 +96,4 @@ class crm_partner_binding(osv.osv_memory):
|
|||
|
||||
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:
|
||||
|
|
|
@ -195,6 +195,7 @@ class crm_claim(base_stage, osv.osv):
|
|||
'description': desc,
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'partner_id': msg.get('author_id', False),
|
||||
}
|
||||
if msg.get('priority'):
|
||||
defaults['priority'] = msg.get('priority')
|
||||
|
|
|
@ -106,6 +106,7 @@ class crm_helpdesk(base_state, base_stage, osv.osv):
|
|||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
'partner_id': msg.get('author_id', False),
|
||||
}
|
||||
defaults.update(custom_values)
|
||||
return super(crm_helpdesk,self).message_new(cr, uid, msg, custom_values=defaults, context=context)
|
||||
|
|
|
@ -57,6 +57,6 @@ Key Features
|
|||
'installable': True,
|
||||
'application': True,
|
||||
'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:
|
||||
|
|
|
@ -54,6 +54,7 @@ Main Features
|
|||
'fleet_data.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'],
|
||||
|
||||
'demo': ['fleet_demo.xml'],
|
||||
|
|
|
@ -29,13 +29,40 @@ _logger = logging.getLogger(__name__)
|
|||
try:
|
||||
import gdata.docs.data
|
||||
import gdata.docs.client
|
||||
from gdata.client import RequestError
|
||||
from gdata.docs.service import DOCUMENT_LABEL
|
||||
import gdata.auth
|
||||
from gdata.docs.data import Resource
|
||||
|
||||
# API breakage madness in the gdata API - those guys are insane.
|
||||
try:
|
||||
# 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:
|
||||
_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):
|
||||
_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
|
||||
# google.login object, because it is easier. The keys 'user' and 'password' ahve to be passed in the dict
|
||||
# but the values will be replaced by the user gmail password and login.
|
||||
user_config = google_pool.default_get( cr, uid, {'user' : '' , 'password' : ''}, context=context)
|
||||
user_config = google_pool.default_get(cr, uid, {'user' : '' , 'password' : ''}, context=context)
|
||||
#login gmail account
|
||||
client = google_pool.google_login( user_config['user'], user_config['password'], type='docs_client', context=context)
|
||||
client = google_pool.google_login(user_config['user'], user_config['password'], type='docs_client', context=context)
|
||||
if not client:
|
||||
raise osv.except_osv( _('Google Docs Error!'), _("Check your google configuration in Users/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
|
||||
|
||||
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)
|
||||
# fetch and copy the original document
|
||||
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_resource = client.copy(doc, name_gdocs)
|
||||
copy_resource = client.copy_resource(doc, name_gdocs)
|
||||
except:
|
||||
raise osv.except_osv(_('Google Docs Error!'), _("Your resource id is not correct. You can find the id in the google docs URL."))
|
||||
# create an ir.attachment
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
'sequence': 31,
|
||||
'website': 'http://www.openerp.com',
|
||||
'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'],
|
||||
'description': """
|
||||
Periodical Employees evaluation and appraisals
|
||||
|
|
|
@ -353,6 +353,7 @@ class hr_applicant(base_stage, osv.Model):
|
|||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
'partner_id': msg.get('author_id', False),
|
||||
}
|
||||
if msg.get('priority'):
|
||||
defaults['priority'] = msg.get('priority')
|
||||
|
|
|
@ -470,11 +470,13 @@
|
|||
<record id="mt_stage_changed" model="mail.message.subtype">
|
||||
<field name="name">Stage Changed</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<record id="mt_applicant_hired" model="mail.message.subtype">
|
||||
<field name="name">Applicant Hired</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Applicant hired</field>
|
||||
</record>
|
||||
<record id="mt_applicant_refused" model="mail.message.subtype">
|
||||
|
|
|
@ -45,7 +45,7 @@ The validation can be configured in the company:
|
|||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'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'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
|
|
|
@ -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',
|
||||
'security/ir.model.access.csv',],
|
||||
'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',],
|
||||
'installable': True,
|
||||
'application' : True,
|
||||
|
|
|
@ -39,7 +39,8 @@
|
|||
<field name="user_id"/>
|
||||
<filter name='is_payment' string="Payment" domain="[('state','=','payment')]"/>
|
||||
<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>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -116,7 +117,7 @@
|
|||
<field name="res_model">lunch.cashmove</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<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">
|
||||
<p>
|
||||
Here you can see your cash moves.<br/>A cash moves can be either an expense or a payment.
|
||||
|
|
|
@ -71,16 +71,12 @@ Main Features
|
|||
'installable': True,
|
||||
'application': True,
|
||||
'images': [
|
||||
'images/customer_history.jpeg',
|
||||
'images/inbox.jpeg',
|
||||
'images/messages_form.jpeg',
|
||||
'images/messages_list.jpeg',
|
||||
'static/src/img/email_icong.png',
|
||||
'static/src/img/_al.png',
|
||||
'static/src/img/_pincky.png',
|
||||
'static/src/img/groupdefault.png',
|
||||
'static/src/img/attachment.png',
|
||||
'static/src/img/checklist.png',
|
||||
'static/src/img/formatting.png',
|
||||
'images/email.jpeg',
|
||||
'images/join_a_group.jpeg',
|
||||
'images/share_a_message.jpeg',
|
||||
],
|
||||
'css': [
|
||||
'static/src/css/mail.css',
|
||||
|
|
|
@ -21,6 +21,8 @@ should inherit from this class.
|
|||
ClientAction (ir.actions.client)
|
||||
++++++++++++++++++++++++++++++++
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="action_mail_inbox_feeds" model="ir.actions.client">
|
||||
<field name="name">Inbox</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
|
||||
record, view on flat mode, no reply, no read/unread)
|
||||
'mail.widget' it's the root thread, used by 'mail.wall' and 'mail_thread'
|
||||
|
||||
- ``help`` : Text HTML to display if there are no message
|
||||
- ``context`` : insert 'default_model' and 'default_res_id'
|
||||
- ``params`` : options for the widget
|
||||
|
|
|
@ -85,9 +85,6 @@ class mail_notification(osv.Model):
|
|||
if notification.read:
|
||||
continue
|
||||
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
|
||||
if not partner.email:
|
||||
continue
|
||||
|
@ -129,11 +126,20 @@ class mail_notification(osv.Model):
|
|||
if signature:
|
||||
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_message_id': msg.id,
|
||||
'email_to': [],
|
||||
'auto_delete': True,
|
||||
'body_html': body_html,
|
||||
'email_from': email_from,
|
||||
'state': 'outgoing',
|
||||
}
|
||||
mail_values['email_to'] = ', '.join(mail_values['email_to'])
|
||||
|
|
|
@ -78,6 +78,13 @@ class mail_mail(osv.Model):
|
|||
'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):
|
||||
if 'notification' not in values and values.get('mail_message_id'):
|
||||
values['notification'] = True
|
||||
|
|
|
@ -243,7 +243,7 @@ class mail_thread(osv.AbstractModel):
|
|||
# subscribe uid unless asked not to
|
||||
if not context.get('mail_create_nosubscribe'):
|
||||
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)
|
||||
if not context.get('mail_create_nolog'):
|
||||
|
@ -261,7 +261,7 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
# Perform write, update followers
|
||||
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
|
||||
if tracked_fields:
|
||||
|
@ -1069,7 +1069,24 @@ class mail_thread(osv.AbstractModel):
|
|||
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)
|
||||
|
||||
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')
|
||||
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')
|
||||
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
|
||||
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)
|
||||
default_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])
|
||||
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
|
||||
|
||||
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):
|
||||
new_followers.setdefault(follower.partner_id.id, set()).add(subtype.parent_id.id)
|
||||
|
||||
if not parent_res_id or not parent_model:
|
||||
continue
|
||||
if parent_res_id and parent_model:
|
||||
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:
|
||||
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)
|
||||
# add followers coming from res.users relational fields that are tracked
|
||||
user_ids = [getattr(record, name).id for name in user_field_lst if getattr(record, name)]
|
||||
for partner_id in [user.partner_id.id for user in self.pool.get('res.users').browse(cr, SUPERUSER_ID, user_ids, context=context)]:
|
||||
new_followers.setdefault(partner_id, None)
|
||||
|
||||
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
|
||||
|
||||
#------------------------------------------------------
|
||||
|
|
|
@ -105,7 +105,7 @@ openerp.mail = function (session) {
|
|||
// As it only looks at the extension it is quite approximative.
|
||||
filetype: function(url){
|
||||
url = url.filename || url;
|
||||
var tokens = url.split('.');
|
||||
var tokens = (url+'').split('.');
|
||||
if(tokens.length <= 1){
|
||||
return 'unknown';
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ openerp.mail = function (session) {
|
|||
this.author_id = datasets.author_id || false,
|
||||
this.attachment_ids = datasets.attachment_ids || [],
|
||||
this.partner_ids = datasets.partner_ids || [];
|
||||
this._date = datasets.date;
|
||||
this.date = datasets.date;
|
||||
|
||||
this.format_data();
|
||||
|
||||
|
@ -232,19 +232,20 @@ openerp.mail = function (session) {
|
|||
else {
|
||||
this.options.show_read = 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;
|
||||
},
|
||||
|
||||
/* Convert date, timerelative and avatar in displayable data. */
|
||||
format_data: function () {
|
||||
//formating and add some fields for render
|
||||
if (this._date) {
|
||||
this.timerelative = $.timeago(this._date+"Z");
|
||||
this.date = this.date ? session.web.str_to_datetime(this.date) : false;
|
||||
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])) {
|
||||
this.avatar = ('/mail/static/src/img/email_icon.png');
|
||||
|
@ -253,7 +254,7 @@ openerp.mail = function (session) {
|
|||
} else {
|
||||
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(/(.*)<(.*@.*)>/);
|
||||
if (!email) {
|
||||
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];
|
||||
if (!attach.formating) {
|
||||
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.formating = true;
|
||||
}
|
||||
|
@ -1605,7 +1606,8 @@ openerp.mail = function (session) {
|
|||
this.node.params = _.extend({
|
||||
'display_indented_thread': -1,
|
||||
'show_reply_button': false,
|
||||
'show_read_unread_button': false,
|
||||
'show_read_unread_button': true,
|
||||
'read_action': 'unread',
|
||||
'show_record_name': false,
|
||||
'show_compact_message': 1,
|
||||
}, this.node.params);
|
||||
|
|
|
@ -216,9 +216,12 @@ openerp_mail_followers = function(session, mail) {
|
|||
display_subtypes:function (data) {
|
||||
var self = this;
|
||||
var subtype_list_ul = this.$('.oe_subtype_list');
|
||||
subtype_list_ul.empty();
|
||||
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
|
||||
var records = [];
|
||||
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++;});
|
||||
if (nb_subtype > 1) {
|
||||
this.$('hr').show();
|
||||
|
|
|
@ -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&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 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>
|
||||
<t t-if="!widget.options.readonly" t-call="mail.thread.message.vote"/>
|
||||
</div>
|
||||
|
|
|
@ -52,9 +52,16 @@ class membership_invoice(osv.osv_memory):
|
|||
'amount': data.member_price
|
||||
}
|
||||
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 {
|
||||
'domain': [('id', 'in', invoice_list)],
|
||||
'name': 'Membership Invoices',
|
||||
|
@ -62,7 +69,8 @@ class membership_invoice(osv.osv_memory):
|
|||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.invoice',
|
||||
'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()
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
'category': 'Manufacturing',
|
||||
'sequence': 18,
|
||||
'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'],
|
||||
'description': """
|
||||
Manage the Manufacturing process in OpenERP
|
||||
|
|
|
@ -57,6 +57,11 @@ Notes can be found in the 'Home' menu.
|
|||
'css': [
|
||||
'static/src/css/note.css',
|
||||
],
|
||||
'images': [
|
||||
'images/note_kanban.jpeg',
|
||||
'images/note.jpeg',
|
||||
'images/categories_tree.jpeg'
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
|
|
|
@ -29,7 +29,7 @@ class note_stage(osv.osv):
|
|||
_columns = {
|
||||
'name': fields.char('Stage Name', translate=True, required=True),
|
||||
'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'),
|
||||
}
|
||||
_order = 'sequence asc'
|
||||
|
@ -112,7 +112,7 @@ class note_note(osv.osv):
|
|||
'date_done': fields.date('Date done'),
|
||||
'color': fields.integer('Color Index'),
|
||||
'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 = {
|
||||
'open' : 1,
|
||||
|
|
|
@ -24,7 +24,7 @@ class pad_common(osv.osv_memory):
|
|||
|
||||
# make sure pad server in the form of http://hostname
|
||||
if not pad["server"]:
|
||||
return ''
|
||||
return pad
|
||||
if not pad["server"].startswith('http'):
|
||||
pad["server"] = 'http://' + pad["server"]
|
||||
pad["server"] = pad["server"].rstrip('/')
|
||||
|
@ -96,7 +96,7 @@ class pad_common(osv.osv_memory):
|
|||
field = v.column
|
||||
if hasattr(field,'pad_content_field'):
|
||||
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)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -48,7 +48,7 @@ Main Features
|
|||
* Refund previous sales
|
||||
""",
|
||||
'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'],
|
||||
'data': [
|
||||
'security/point_of_sale_security.xml',
|
||||
|
|
|
@ -853,8 +853,7 @@ class pos_order(osv.osv):
|
|||
inv_line['price_unit'] = line.price_unit
|
||||
inv_line['discount'] = line.discount
|
||||
inv_line['name'] = inv_name
|
||||
inv_line['invoice_line_tax_id'] = ('invoice_line_tax_id' in inv_line)\
|
||||
and [(6, 0, inv_line['invoice_line_tax_id'])] or []
|
||||
inv_line['invoice_line_tax_id'] = [(6, 0, [x.id for x in line.product_id.taxes_id] )]
|
||||
inv_line_ref.create(cr, uid, inv_line, context=context)
|
||||
inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context)
|
||||
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)
|
||||
|
||||
taxes = prod.taxes_id
|
||||
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)
|
||||
|
||||
|
|
|
@ -233,7 +233,9 @@
|
|||
font-style: italic;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.point-of-sale .oe_pos_synch-notification.oe_inactive{
|
||||
cursor: default;
|
||||
}
|
||||
.point-of-sale .oe_pos_synch-notification .oe_status_red{
|
||||
display:inline-block;
|
||||
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: -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: 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;*/
|
||||
padding: 3px;
|
||||
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: -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));
|
||||
/* troublesome in latest webkit
|
||||
background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
|
||||
*/
|
||||
/*background:#FFF;*/
|
||||
padding: 3px;
|
||||
padding-top:15px;
|
||||
|
@ -991,12 +997,20 @@
|
|||
margin-bottom:10px;
|
||||
}
|
||||
.point-of-sale .order .summary .line{
|
||||
float: right;
|
||||
margin-right:15px;
|
||||
margin-left: 15px;
|
||||
padding-top:5px;
|
||||
border-top: solid 2px;
|
||||
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{
|
||||
text-align: right;
|
||||
border-color:#BBB;
|
||||
color:#999;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ function openerp_pos_db(instance, module){
|
|||
//cache the data in memory to avoid roundtrips to the localstorage
|
||||
this.cache = {};
|
||||
|
||||
this.product_by_id = {};
|
||||
this.product_by_ean13 = {};
|
||||
this.product_by_category_id = {};
|
||||
|
||||
this.category_by_id = {};
|
||||
this.root_category_id = 0;
|
||||
this.category_products = {};
|
||||
|
@ -49,6 +53,7 @@ function openerp_pos_db(instance, module){
|
|||
this.category_search_string = {};
|
||||
this.packagings_by_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
|
||||
* a list of category objects.
|
||||
|
@ -137,7 +142,6 @@ function openerp_pos_db(instance, module){
|
|||
/* saves a record store to the database */
|
||||
save: function(store,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);
|
||||
this.cache[store] = data;
|
||||
},
|
||||
|
@ -153,8 +157,7 @@ function openerp_pos_db(instance, module){
|
|||
return str + '\n';
|
||||
},
|
||||
add_products: function(products){
|
||||
var stored_products = this.load('products',{});
|
||||
var stored_categories = this.load('categories',{});
|
||||
var stored_categories = this.product_by_category_id;
|
||||
|
||||
if(!products instanceof Array){
|
||||
products = [products];
|
||||
|
@ -187,10 +190,11 @@ function openerp_pos_db(instance, module){
|
|||
}
|
||||
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){
|
||||
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]].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 */
|
||||
|
@ -219,31 +226,24 @@ function openerp_pos_db(instance, module){
|
|||
return count;
|
||||
},
|
||||
get_product_by_id: function(id){
|
||||
return this.load('products',{})[id];
|
||||
return this.product_by_id[id];
|
||||
},
|
||||
get_product_by_ean13: function(ean13){
|
||||
var products = this.load('products',{});
|
||||
for(var i in products){
|
||||
if( products[i] && products[i].ean13 === ean13){
|
||||
return products[i];
|
||||
}
|
||||
if(this.product_by_ean13[ean13]){
|
||||
return this.product_by_ean13[ean13];
|
||||
}
|
||||
for(var p in this.packagings_by_id){
|
||||
var pack = this.packagings_by_id[p];
|
||||
if( pack.ean === ean13){
|
||||
return products[pack.product_id[0]];
|
||||
}
|
||||
var pack = this.packagings_by_ean13[ean13];
|
||||
if(pack){
|
||||
return this.product_by_id[pack.product_id[0]];
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
get_product_by_category: function(category_id){
|
||||
var stored_categories = this.load('categories',{});
|
||||
var stored_products = this.load('products',{});
|
||||
var product_ids = stored_categories[category_id];
|
||||
var product_ids = this.product_by_category_id[category_id];
|
||||
var list = [];
|
||||
if (product_ids) {
|
||||
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;
|
||||
|
@ -275,12 +275,9 @@ function openerp_pos_db(instance, module){
|
|||
},
|
||||
remove_order: function(order_id){
|
||||
var orders = this.load('orders',[]);
|
||||
console.log('Remove order:',order_id);
|
||||
console.log('Order count:',orders.length);
|
||||
orders = _.filter(orders, function(order){
|
||||
return order.id !== order_id;
|
||||
});
|
||||
console.log('Order count:',orders.length);
|
||||
this.save('orders',orders);
|
||||
},
|
||||
get_orders: function(){
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
function openerp_pos_models(instance, module){ //module is instance.point_of_sale
|
||||
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.
|
||||
// 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.db = new module.PosLS(); // a database used to store the products and 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.
|
||||
this.set({
|
||||
|
@ -101,8 +118,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}).then(function(company_partners){
|
||||
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){
|
||||
console.log('Currency:',currencies[0]);
|
||||
self.set('currency',currencies[0]);
|
||||
|
||||
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);
|
||||
}).then(function(packagings){
|
||||
self.set('product.packaging',packagings);
|
||||
|
||||
|
||||
return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
|
||||
}).then(function(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
|
||||
log_loaded_data: function(){
|
||||
console.log('PosModel data has been loaded:');
|
||||
console.log('PosModel: categories:',this.get('categories'));
|
||||
console.log('PosModel: units:',this.get('units'));
|
||||
console.log('PosModel: bank_statements:',this.get('bank_statements'));
|
||||
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.price = options.product.get('price');
|
||||
this.quantity = 1;
|
||||
this.quantityStr = '1';
|
||||
this.discount = 0;
|
||||
this.discountStr = '0';
|
||||
this.type = 'unit';
|
||||
this.selected = false;
|
||||
},
|
||||
// sets a discount [0,100]%
|
||||
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');
|
||||
},
|
||||
// returns the discount [0,100]%
|
||||
get_discount: function(){
|
||||
return this.discount;
|
||||
},
|
||||
get_discount_str: function(){
|
||||
return this.discountStr;
|
||||
},
|
||||
get_product_type: function(){
|
||||
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
|
||||
// rounded to zero
|
||||
set_quantity: function(quantity){
|
||||
if(_.isNaN(quantity)){
|
||||
if(quantity === 'remove'){
|
||||
this.order.removeOrderline(this);
|
||||
}else if(quantity !== undefined){
|
||||
this.quantity = Math.max(0,quantity);
|
||||
return;
|
||||
}else{
|
||||
var quant = Math.max(parseFloat(quantity) || 0, 0);
|
||||
var unit = this.get_unit();
|
||||
if(unit && this.quantity > 0 ){
|
||||
this.quantity = Math.max(unit.rounding, Math.round(quantity / unit.rounding) * unit.rounding);
|
||||
if(unit){
|
||||
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');
|
||||
|
@ -374,6 +403,17 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
get_quantity: function(){
|
||||
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
|
||||
get_unit: function(){
|
||||
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(){
|
||||
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
|
||||
set_selected: function(selected){
|
||||
this.selected = selected;
|
||||
|
@ -429,7 +460,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
export_as_JSON: function() {
|
||||
return {
|
||||
qty: this.get_quantity(),
|
||||
price_unit: this.get_price(),
|
||||
price_unit: this.get_unit_price(),
|
||||
discount: this.get_discount(),
|
||||
product_id: this.get_product().get('id'),
|
||||
};
|
||||
|
@ -439,9 +470,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
return {
|
||||
quantity: this.get_quantity(),
|
||||
unit_name: this.get_unit().name,
|
||||
price: this.get_price(),
|
||||
price: this.get_unit_price(),
|
||||
discount: this.get_discount(),
|
||||
product_name: this.get_product().get('name'),
|
||||
price_display : this.get_display_price(),
|
||||
price_with_tax : this.get_price_with_tax(),
|
||||
price_without_tax: this.get_price_without_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'),
|
||||
};
|
||||
},
|
||||
// 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(){
|
||||
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(){
|
||||
return this.get_all_prices().tax;
|
||||
},
|
||||
get_all_prices: function() {
|
||||
get_all_prices: function(){
|
||||
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 totalNoTax = base;
|
||||
|
||||
|
@ -474,12 +520,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
if (tax.price_include) {
|
||||
var tmp;
|
||||
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") {
|
||||
tmp = tax.amount * self.get_quantity();
|
||||
tmp = round_pr(tax.amount * self.get_quantity(),currency_rounding);
|
||||
} else {
|
||||
throw "This type of tax is not supported by the point of sale: " + tax.type;
|
||||
}
|
||||
tmp = round_pr(tmp,currency_rounding);
|
||||
taxtotal += tmp;
|
||||
totalNoTax -= tmp;
|
||||
} else {
|
||||
|
@ -491,6 +538,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
} else {
|
||||
throw "This type of tax is not supported by the point of sale: " + tax.type;
|
||||
}
|
||||
tmp = round_pr(tmp,currency_rounding);
|
||||
taxtotal += 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
|
||||
set_amount: function(value){
|
||||
this.amount = value;
|
||||
this.amount = parseFloat(value) || 0;
|
||||
this.trigger('change');
|
||||
},
|
||||
// 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);
|
||||
}
|
||||
if(options.price !== undefined){
|
||||
line.set_price(options.price);
|
||||
line.set_unit_price(options.price);
|
||||
}
|
||||
|
||||
var last_orderline = this.getLastOrderline();
|
||||
|
@ -613,14 +661,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
getName: function() {
|
||||
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 sum + orderLine.get_price_with_tax();
|
||||
}), 0);
|
||||
},
|
||||
getDiscountTotal: function() {
|
||||
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);
|
||||
},
|
||||
getTotalTaxExcluded: function() {
|
||||
|
@ -639,10 +692,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}), 0);
|
||||
},
|
||||
getChange: function() {
|
||||
return this.getPaidTotal() - this.getTotal();
|
||||
return this.getPaidTotal() - this.getTotalTaxIncluded();
|
||||
},
|
||||
getDueLeft: function() {
|
||||
return this.getTotal() - this.getPaidTotal();
|
||||
return this.getTotalTaxIncluded() - this.getPaidTotal();
|
||||
},
|
||||
// sets the type of receipt 'receipt'(default) or 'invoice'
|
||||
set_receipt_type: function(type){
|
||||
|
@ -698,10 +751,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
return {
|
||||
orderlines: orderlines,
|
||||
paymentlines: paymentlines,
|
||||
total_with_tax: this.getTotal(),
|
||||
subtotal: this.getSubtotal(),
|
||||
total_with_tax: this.getTotalTaxIncluded(),
|
||||
total_without_tax: this.getTotalTaxExcluded(),
|
||||
total_tax: this.getTax(),
|
||||
total_paid: this.getPaidTotal(),
|
||||
total_discount: this.getDiscountTotal(),
|
||||
change: this.getChange(),
|
||||
name : this.getName(),
|
||||
client: client ? client.name : null ,
|
||||
|
@ -743,7 +798,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
return {
|
||||
name: this.getName(),
|
||||
amount_paid: this.getPaidTotal(),
|
||||
amount_total: this.getTotal(),
|
||||
amount_total: this.getTotalTaxIncluded(),
|
||||
amount_tax: this.getTax(),
|
||||
amount_return: this.getChange(),
|
||||
lines: orderLines,
|
||||
|
@ -800,20 +855,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
buffer: (this.get('buffer')) + newChar
|
||||
});
|
||||
}
|
||||
this.updateTarget();
|
||||
this.trigger('set_value',this.get('buffer'));
|
||||
},
|
||||
deleteLastChar: function() {
|
||||
var tempNewBuffer = this.get('buffer').slice(0, -1);
|
||||
|
||||
if(!tempNewBuffer){
|
||||
this.set({ buffer: "0" });
|
||||
this.killTarget();
|
||||
}else{
|
||||
if (isNaN(tempNewBuffer)) {
|
||||
tempNewBuffer = "0";
|
||||
if(this.get('buffer') === ""){
|
||||
if(this.get('mode') === 'quantity'){
|
||||
this.trigger('set_value','remove');
|
||||
}else{
|
||||
this.trigger('set_value',this.get('buffer'));
|
||||
}
|
||||
this.set({ buffer: tempNewBuffer });
|
||||
this.updateTarget();
|
||||
}else{
|
||||
var newBuffer = this.get('buffer').slice(0,-1) || "";
|
||||
this.set({ buffer: newBuffer });
|
||||
this.trigger('set_value',this.get('buffer'));
|
||||
}
|
||||
},
|
||||
switchSign: function() {
|
||||
|
@ -822,7 +876,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
this.set({
|
||||
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
|
||||
});
|
||||
this.updateTarget();
|
||||
this.trigger('set_value',this.get('buffer'));
|
||||
},
|
||||
changeMode: function(newMode) {
|
||||
this.set({
|
||||
|
@ -836,15 +890,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
mode: "quantity"
|
||||
});
|
||||
},
|
||||
updateTarget: function() {
|
||||
var bufferContent, params;
|
||||
bufferContent = this.get('buffer');
|
||||
if (bufferContent && !isNaN(bufferContent)) {
|
||||
this.trigger('set_value', parseFloat(bufferContent));
|
||||
}
|
||||
},
|
||||
killTarget: function(){
|
||||
this.trigger('set_value',Number.NaN);
|
||||
resetValue: function(){
|
||||
this.set({buffer:'0'});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -368,7 +368,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
|
||||
template:'ChooseReceiptPopupWidget',
|
||||
show: function(){
|
||||
console.log('show');
|
||||
this._super();
|
||||
this.renderElement();
|
||||
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
|
||||
this.start = function(){
|
||||
var def = new $.Deferred();
|
||||
console.log("START");
|
||||
self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
|
||||
.done(function(ack){
|
||||
if(ack === 'ok'){
|
||||
|
@ -613,7 +611,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
}else{
|
||||
console.error('unknown payment request return value:',ack);
|
||||
}
|
||||
console.log("START_END");
|
||||
def.resolve();
|
||||
});
|
||||
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
|
||||
this.update = function(){
|
||||
console.log("UPDATE");
|
||||
var def = new $.Deferred();
|
||||
if(self.canceled){
|
||||
console.log("UPDATE_END");
|
||||
return def.resolve();
|
||||
}
|
||||
self.pos.proxy.payment_status()
|
||||
|
@ -656,7 +651,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
}else{
|
||||
console.error('unknown status value:',status.status);
|
||||
}
|
||||
console.log("UPDATE_END");
|
||||
def.resolve();
|
||||
});
|
||||
return def;
|
||||
|
@ -664,14 +658,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
|
||||
// cancels a payment.
|
||||
this.cancel = function(){
|
||||
console.log("CANCEL");
|
||||
if(!self.paid && !self.canceled){
|
||||
self.canceled = true;
|
||||
self.pos.proxy.payment_cancel();
|
||||
self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
|
||||
self.queue.clear();
|
||||
}
|
||||
console.log("CANCEL_END");
|
||||
return (new $.Deferred()).resolve();
|
||||
}
|
||||
|
||||
|
@ -865,6 +857,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
this.bindPaymentLineEvents();
|
||||
this.bind_orderline_events();
|
||||
this.paymentlinewidgets = [];
|
||||
this.focusedLine = null;
|
||||
},
|
||||
show: function(){
|
||||
this._super();
|
||||
|
@ -894,6 +887,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
});
|
||||
|
||||
this.updatePaymentSummary();
|
||||
this.line_refocus();
|
||||
},
|
||||
close: function(){
|
||||
this._super();
|
||||
|
@ -931,17 +925,30 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
this.bind_orderline_events();
|
||||
this.renderElement();
|
||||
},
|
||||
line_refocus: function(lineWidget){
|
||||
if(lineWidget){
|
||||
if(this.focusedLine !== lineWidget){
|
||||
this.focusedLine = lineWidget;
|
||||
}
|
||||
}
|
||||
if(this.focusedLine){
|
||||
this.focusedLine.focus();
|
||||
}
|
||||
},
|
||||
addPaymentLine: function(newPaymentLine) {
|
||||
var self = this;
|
||||
var l = new module.PaymentlineWidget(null, {
|
||||
payment_line: newPaymentLine
|
||||
var l = new module.PaymentlineWidget(this, {
|
||||
payment_line: newPaymentLine,
|
||||
});
|
||||
l.on('delete_payment_line', self, function(r) {
|
||||
self.deleteLine(r);
|
||||
});
|
||||
l.appendTo(this.$('#paymentlines'));
|
||||
this.paymentlinewidgets.push(l);
|
||||
this.$('.paymentline-amount input:last').focus();
|
||||
if(this.numpadState){
|
||||
this.numpadState.resetValue();
|
||||
}
|
||||
this.line_refocus(l);
|
||||
},
|
||||
renderElement: function() {
|
||||
this._super();
|
||||
|
@ -958,25 +965,26 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
deleteLine: function(lineWidget) {
|
||||
this.currentPaymentLines.remove([lineWidget.payment_line]);
|
||||
lineWidget.destroy();
|
||||
},
|
||||
updatePaymentSummary: function() {
|
||||
var currentOrder = this.pos.get('selectedOrder');
|
||||
var paidTotal = currentOrder.getPaidTotal();
|
||||
var dueTotal = currentOrder.getTotal();
|
||||
var dueTotal = currentOrder.getTotalTaxIncluded();
|
||||
var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
|
||||
var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
|
||||
|
||||
this.$('#payment-due-total').html(dueTotal.toFixed(2));
|
||||
this.$('#payment-paid-total').html(paidTotal.toFixed(2));
|
||||
this.$('#payment-remaining').html(remaining.toFixed(2));
|
||||
this.$('#payment-change').html(change.toFixed(2));
|
||||
if((currentOrder.selected_orderline == undefined))
|
||||
remaining = 1
|
||||
this.$('#payment-due-total').html(this.format_currency(dueTotal));
|
||||
this.$('#payment-paid-total').html(this.format_currency(paidTotal));
|
||||
this.$('#payment-remaining').html(this.format_currency(remaining));
|
||||
this.$('#payment-change').html(this.format_currency(change));
|
||||
if(currentOrder.selected_orderline === undefined){
|
||||
remaining = 1; // What is this ?
|
||||
}
|
||||
|
||||
if(this.pos_widget.action_bar){
|
||||
this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0);
|
||||
}
|
||||
this.$('.paymentline-amount input:last').focus();
|
||||
},
|
||||
set_numpad_state: function(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);
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -22,14 +22,20 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of
|
|||
if(this.pos && this.pos.get('currency')){
|
||||
this.currency = this.pos.get('currency');
|
||||
}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){
|
||||
if(typeof amount === 'number'){
|
||||
amount = Math.round(amount*100)/100;
|
||||
amount = amount.toFixed(decimals);
|
||||
}
|
||||
if(this.currency.position === 'after'){
|
||||
return Math.round(amount*100)/100 + ' ' + this.currency.symbol;
|
||||
return amount + ' ' + this.currency.symbol;
|
||||
}else{
|
||||
return this.currency.symbol + ' ' + Math.round(amount*100)/100;
|
||||
return this.currency.symbol + ' ' + amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
}else if( mode === 'discount'){
|
||||
order.getSelectedLine().set_discount(val);
|
||||
}else if( mode === 'price'){
|
||||
order.getSelectedLine().set_price(val);
|
||||
order.getSelectedLine().set_unit_price(val);
|
||||
}
|
||||
} else {
|
||||
this.pos.get('selectedOrder').destroy();
|
||||
|
@ -269,8 +269,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
update_summary: function(){
|
||||
var order = this.pos.get('selectedOrder');
|
||||
var total = order ? order.getTotal() : 0;
|
||||
this.$('.summary .value.total').html(this.format_currency(total));
|
||||
var total = order ? order.getTotalTaxIncluded() : 0;
|
||||
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){
|
||||
if(this.display_mode !== mode){
|
||||
|
@ -311,24 +313,34 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
changeAmount: function(event) {
|
||||
var newAmount = event.currentTarget.value;
|
||||
if (newAmount && !isNaN(newAmount)) {
|
||||
this.amount = parseFloat(newAmount);
|
||||
this.payment_line.set_amount(this.amount);
|
||||
var amount = parseFloat(newAmount);
|
||||
if(!isNaN(amount)){
|
||||
this.amount = amount;
|
||||
this.payment_line.set_amount(amount);
|
||||
}
|
||||
},
|
||||
changedAmount: function() {
|
||||
if (this.amount !== this.payment_line.get_amount())
|
||||
if (this.amount !== this.payment_line.get_amount()){
|
||||
this.renderElement();
|
||||
}
|
||||
},
|
||||
renderElement: function() {
|
||||
var self = this;
|
||||
this.name = this.payment_line.get_cashregister().get('journal_id')[1];
|
||||
this._super();
|
||||
this.$('input').keyup(_.bind(this.changeAmount, this));
|
||||
this.$('input').keyup(function(event){
|
||||
self.changeAmount(event);
|
||||
});
|
||||
this.$('.delete-payment-line').click(function() {
|
||||
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({
|
||||
|
@ -606,20 +618,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
if(this.scrollbar){
|
||||
this.scrollbar.destroy();
|
||||
}
|
||||
|
||||
this.pos.get('products')
|
||||
.chain()
|
||||
.map(function(product) {
|
||||
var product = new module.ProductWidget(self, {
|
||||
model: product,
|
||||
weight: self.weight,
|
||||
click_product_action: self.click_product_action,
|
||||
})
|
||||
self.productwidgets.push(product);
|
||||
return product;
|
||||
})
|
||||
.invoke('appendTo', this.$('.product-list'));
|
||||
|
||||
var products = this.pos.get('products').models || [];
|
||||
for(var i = 0, len = products.length; i < len; i++){
|
||||
var product = new module.ProductWidget(self, {
|
||||
model: products[i],
|
||||
click_product_action: this.click_product_action,
|
||||
});
|
||||
this.productwidgets.push(product);
|
||||
product.appendTo(this.$('.product-list'));
|
||||
}
|
||||
this.scrollbar = new module.ScrollbarWidget(this,{
|
||||
target_widget: this,
|
||||
target_selector: '.product-list-scroller',
|
||||
|
|
|
@ -48,11 +48,17 @@
|
|||
</t>
|
||||
|
||||
<t t-name="SynchNotificationWidget">
|
||||
<div class="oe_pos_synch-notification">
|
||||
<t t-if="widget.get_nbr_pending() > 0" t-esc="widget.get_nbr_pending()"/>
|
||||
<div t-if="widget.get_nbr_pending() > 0" class="oe_status_red"></div>
|
||||
<div t-if="widget.get_nbr_pending() === 0" class="oe_status_green"></div>
|
||||
</div>
|
||||
<t t-if="widget.get_nbr_pending() > 0">
|
||||
<div class="oe_pos_synch-notification">
|
||||
<t t-esc="widget.get_nbr_pending()"/>
|
||||
<div class="oe_status_red"></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-name="HeaderButtonWidget">
|
||||
|
@ -214,11 +220,7 @@
|
|||
<span class="left-block">
|
||||
Total:
|
||||
</span>
|
||||
<span class="right-block">
|
||||
<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>
|
||||
<span class='right-block' id="payment-due-total"></span>
|
||||
</div>
|
||||
<table id="paymentlines">
|
||||
</table>
|
||||
|
@ -227,31 +229,19 @@
|
|||
<span class='left-block'>
|
||||
Paid:
|
||||
</span>
|
||||
<span class='right-block'>
|
||||
<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>
|
||||
<span class='right-block' id="payment-paid-total"></span>
|
||||
</div>
|
||||
<div class="infoline">
|
||||
<span class='left-block'>
|
||||
Remaining:
|
||||
</span>
|
||||
<span class='right-block'>
|
||||
<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>
|
||||
<span class='right-block' id="payment-remaining"></span>
|
||||
</div>
|
||||
<div class="infoline" >
|
||||
<span class='left-block'>
|
||||
Change:
|
||||
</span>
|
||||
<span class='right-block'>
|
||||
<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>
|
||||
<span class='right-block' id="payment-change"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -431,9 +421,13 @@
|
|||
|
||||
</ul>
|
||||
<div class="summary">
|
||||
<span 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>
|
||||
</span>
|
||||
<div t-attf-class="line #{widget.pos.get('selectedOrder').get('orderLines').length === 0 ? 'empty' : ''}">
|
||||
<div class='entry total'>
|
||||
<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>
|
||||
|
@ -500,26 +494,26 @@
|
|||
<t t-esc="widget.model.get_product().get('name')"/>
|
||||
</span>
|
||||
<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>
|
||||
<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">
|
||||
<em>
|
||||
<t t-esc="widget.model.get_quantity()" />
|
||||
<t t-esc="widget.model.get_quantity_str()" />
|
||||
</em>
|
||||
<t t-esc="widget.model.get_unit().name" />
|
||||
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" />
|
||||
</li>
|
||||
</t>
|
||||
<t t-if="widget.model.get_discount() > 0">
|
||||
<t t-if="widget.model.get_discount_str() !== '0'">
|
||||
<li class="info">
|
||||
With a
|
||||
<em>
|
||||
<t t-esc="widget.model.get_discount()" />%
|
||||
<t t-esc="widget.model.get_discount_str()" />%
|
||||
</em>
|
||||
discount
|
||||
</li>
|
||||
|
@ -571,33 +565,36 @@
|
|||
Shop: <t t-esc="widget.shop_obj.name"/><br />
|
||||
<br />
|
||||
<table>
|
||||
<tr t-foreach="widget.currentOrderLines.toArray()" t-as="order">
|
||||
<tr t-foreach="widget.currentOrderLines.toArray()" t-as="orderline">
|
||||
<td>
|
||||
<t t-esc="order.get_product().get('name')"/>
|
||||
<t t-if="order.get_discount() > 0">
|
||||
<t t-esc="orderline.get_product().get('name')"/>
|
||||
<t t-if="orderline.get_discount() > 0">
|
||||
<div class="pos-disc-font">
|
||||
With a <t t-esc="order.get_discount()"/>% discount
|
||||
With a <t t-esc="orderline.get_discount()"/>% discount
|
||||
</div>
|
||||
</t>
|
||||
</td>
|
||||
<td class="pos-right-align">
|
||||
<t t-esc="order.get_quantity().toFixed(0)"/>
|
||||
<t t-esc="orderline.get_quantity_str_with_unit()"/>
|
||||
</td>
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<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">
|
||||
<t t-esc="widget.format_currency(widget.currentOrder.getTax().toFixed(2))"/>
|
||||
<t t-esc="widget.format_currency(widget.currentOrder.getTax())"/>
|
||||
</td></tr>
|
||||
<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>
|
||||
<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>
|
||||
</table>
|
||||
<br />
|
||||
|
@ -607,14 +604,14 @@
|
|||
<t t-esc="pline.get_cashregister().get('journal_id')[1]"/>
|
||||
</td>
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table>
|
||||
<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>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -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" \
|
||||
" a make to order method."),
|
||||
'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([
|
||||
('draft','Draft'),
|
||||
('cancel','Cancelled'),
|
||||
|
@ -367,10 +367,22 @@ class procurement_order(osv.osv):
|
|||
|
||||
if 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)
|
||||
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):
|
||||
""" Changes procurement state to Running.
|
||||
@return: True
|
||||
|
|
|
@ -33,7 +33,10 @@
|
|||
'images/project_task_tree.jpeg',
|
||||
'images/project_task.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': [
|
||||
'base_setup',
|
||||
|
|
|
@ -94,16 +94,19 @@
|
|||
<record id="mt_task_blocked" model="mail.message.subtype">
|
||||
<field name="name">Task Blocked</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Task blocked</field>
|
||||
</record>
|
||||
<record id="mt_task_closed" model="mail.message.subtype">
|
||||
<field name="name">Task Done</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Task closed</field>
|
||||
</record>
|
||||
<record id="mt_task_stage" model="mail.message.subtype">
|
||||
<field name="name">Stage Changed</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<!-- Project-related subtypes for messaging / Chatter -->
|
||||
|
|
|
@ -501,6 +501,7 @@ class project_issue(base_stage, osv.osv):
|
|||
'description': desc,
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'partner_id': msg.get('author_id', False),
|
||||
'user_id': False,
|
||||
}
|
||||
if msg.get('priority'):
|
||||
|
|
|
@ -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">
|
||||
<field name="name">Issue Blocked</field>
|
||||
<field name="res_model">project.issue</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Issue blocked</field>
|
||||
</record>
|
||||
<record id="mt_issue_closed" model="mail.message.subtype">
|
||||
<field name="name">Issue Closed</field>
|
||||
<field name="res_model">project.issue</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Issue closed</field>
|
||||
</record>
|
||||
<record id="mt_issue_stage" model="mail.message.subtype">
|
||||
<field name="name">Stage Changed</field>
|
||||
<field name="res_model">project.issue</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Stage changed</field>
|
||||
</record>
|
||||
<!-- Project-related subtypes for messaging / Chatter -->
|
||||
|
|
|
@ -52,10 +52,12 @@
|
|||
<!-- Purchase-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_rfq_confirmed" model="mail.message.subtype">
|
||||
<field name="name">RFQ Confirmed</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="res_model">purchase.order</field>
|
||||
</record>
|
||||
<record id="mt_rfq_approved" model="mail.message.subtype">
|
||||
<field name="name">RFQ Approved</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="res_model">purchase.order</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -19,10 +19,14 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp import pooler
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class sale_configuration(osv.osv_memory):
|
||||
_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)
|
||||
res['time_unit'] = user.company_id.project_time_mode_id.id
|
||||
else:
|
||||
product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant')
|
||||
res['time_unit'] = product.uom_id.id
|
||||
try:
|
||||
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
|
||||
|
||||
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]
|
||||
|
||||
if wizard.time_unit:
|
||||
product = False
|
||||
try:
|
||||
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})
|
||||
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:
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context)
|
||||
|
|
|
@ -926,7 +926,6 @@ class sale_order_line(osv.osv):
|
|||
elif uom: # whether uos is set or not
|
||||
default_uom = product_obj.uom_id and product_obj.uom_id.id
|
||||
q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
|
||||
result['product_uom'] = default_uom
|
||||
if product_obj.uos_id:
|
||||
result['product_uos'] = product_obj.uos_id.id
|
||||
result['product_uos_qty'] = qty * product_obj.uos_coeff
|
||||
|
|
|
@ -48,11 +48,13 @@
|
|||
<record id="mt_order_sent" model="mail.message.subtype">
|
||||
<field name="name">Quotation send</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Quotation send</field>
|
||||
</record>
|
||||
<record id="mt_order_confirmed" model="mail.message.subtype">
|
||||
<field name="name">Sales Order Confirmed</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
<field name="default" eval="False"/>
|
||||
<field name="description">Quotation confirmed</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -14,3 +14,23 @@
|
|||
!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].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
|
|
@ -91,7 +91,7 @@
|
|||
<field name="group_uos" class="oe_inline"/>
|
||||
<label for="group_uos" />
|
||||
</div>
|
||||
<div>
|
||||
<div invisible="1">
|
||||
<field name="group_product_variant" class="oe_inline"/>
|
||||
<label for="group_product_variant"/>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@ openerp.web_analytics = function(instance) {
|
|||
var ga = document.createElement('script');
|
||||
ga.type = 'text/javascript';
|
||||
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];
|
||||
s.parentNode.insertBefore(ga,s);
|
||||
})();
|
||||
|
@ -25,16 +25,15 @@ openerp.web_analytics = function(instance) {
|
|||
/*
|
||||
* This method initializes the tracker
|
||||
*/
|
||||
init: function() {
|
||||
/* Comment this lines when going on production, only used for testing on localhost
|
||||
_gaq.push(['_setAccount', 'UA-35793871-1']);
|
||||
_gaq.push(['_setDomainName', 'none']);
|
||||
*/
|
||||
|
||||
/* Uncomment this lines when going on production */
|
||||
init: function(webclient) {
|
||||
var self = this;
|
||||
self.initialized = $.Deferred();
|
||||
_gaq.push(['_setAccount', 'UA-7333765-1']);
|
||||
_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
|
||||
|
@ -48,41 +47,53 @@ openerp.web_analytics = function(instance) {
|
|||
* This method gets the user access level, to be used as CV in GA
|
||||
*/
|
||||
_get_user_access_level: function() {
|
||||
if (!instance.session.session_is_valid()) {
|
||||
return "Unauthenticated User";
|
||||
}
|
||||
if (instance.session.uid === 1) {
|
||||
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
|
||||
* 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() {
|
||||
var self = this;
|
||||
return instance.session.rpc("/web/webclient/version_info", {})
|
||||
instance.session.rpc("/web/webclient/version_info", {})
|
||||
.done(function(res) {
|
||||
_gaq.push(['_setCustomVar', 5, 'Version', res.server_version, 3]);
|
||||
// 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.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;
|
||||
self._push_customvars();
|
||||
self.initialized.resolve(self);
|
||||
});
|
||||
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
|
||||
*/
|
||||
|
@ -142,6 +153,7 @@ openerp.web_analytics = function(instance) {
|
|||
'action': state.view_type,
|
||||
'label': url,
|
||||
});
|
||||
this._push_pageview(url);
|
||||
}
|
||||
},
|
||||
/*
|
||||
|
@ -157,19 +169,19 @@ openerp.web_analytics = function(instance) {
|
|||
this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
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({
|
||||
'category': r.model,
|
||||
'action': 'form',
|
||||
'category': self.model,
|
||||
'action': 'create',
|
||||
'label': url,
|
||||
'noninteraction': true,
|
||||
});
|
||||
});
|
||||
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({
|
||||
'category': r.model,
|
||||
'action': 'form',
|
||||
'category': self.model,
|
||||
'action': 'save',
|
||||
'label': url,
|
||||
'noninteraction': true,
|
||||
});
|
||||
|
@ -181,12 +193,12 @@ openerp.web_analytics = function(instance) {
|
|||
instance.web.ActionManager.include({
|
||||
ir_actions_client: function (action, options) {
|
||||
var url = instance.web_analytics.generateUrl({'action': action.tag});
|
||||
var category = action.res_model || action.type;
|
||||
t._push_event({
|
||||
'category': action.res_model || action.type,
|
||||
'action': action.name || action.tag,
|
||||
'category': action.type,
|
||||
'action': action.tag,
|
||||
'label': url,
|
||||
});
|
||||
t._push_pageview(url);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
@ -224,21 +236,12 @@ openerp.web_analytics = function(instance) {
|
|||
options = {'action': params.action};
|
||||
}
|
||||
var url = instance.web_analytics.generateUrl(options);
|
||||
if (error.code) {
|
||||
t._push_event({
|
||||
'category': error.message,
|
||||
'action': error.data.message,
|
||||
'label': url,
|
||||
'noninteraction': true,
|
||||
});
|
||||
} else {
|
||||
t._push_event({
|
||||
'category': error.type,
|
||||
'action': error.data.debug,
|
||||
'label': url,
|
||||
'noninteraction': true,
|
||||
});
|
||||
}
|
||||
t._push_event({
|
||||
'category': options.model || "ir.actions.client",
|
||||
'action': "error " + (error.code ? error.message + error.data.message : error.type + error.data.debug),
|
||||
'label': url,
|
||||
'noninteraction': true,
|
||||
});
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
@ -251,43 +254,37 @@ openerp.web_analytics = function(instance) {
|
|||
|
||||
instance.web_analytics.generateUrl = function(options) {
|
||||
var url = '';
|
||||
_.each(options, function(value, key) {
|
||||
url += '/' + key + '=' + value;
|
||||
var keys = _.keys(options);
|
||||
keys = _.sortBy(keys, function(i) { return i;});
|
||||
_.each(keys, function(key) {
|
||||
url += '/' + key + '/' + options[key];
|
||||
});
|
||||
return url;
|
||||
};
|
||||
|
||||
// kept for API compatibility
|
||||
instance.web_analytics.setupTracker = function(wc) {
|
||||
var t = wc.tracker;
|
||||
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();
|
||||
});
|
||||
});
|
||||
return wc.tracker.initialized;
|
||||
};
|
||||
|
||||
// Set correctly the tracker in the current instance
|
||||
if (instance.client instanceof instance.web.WebClient) { // not for embedded clients
|
||||
instance.webclient.tracker = new instance.web_analytics.Tracker();
|
||||
instance.web_analytics.setupTracker(instance.webclient);
|
||||
} 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.Client.include({
|
||||
bind_events: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.tracker = new instance.web_analytics.Tracker(this);
|
||||
},
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue