[MERGE]: Merge with lp:openobject-addons
bzr revid: mma@tinyerp.com-20120914050233-abnyaijtxlw9zsxy
This commit is contained in:
commit
54a3b527f2
|
@ -2338,6 +2338,16 @@ class account_model(osv.osv):
|
|||
|
||||
return move_ids
|
||||
|
||||
def onchange_journal_id(self, cr, uid, ids, journal_id, context=None):
|
||||
company_id = False
|
||||
|
||||
if journal_id:
|
||||
journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
|
||||
if journal.company_id.id:
|
||||
company_id = journal.company_id.id
|
||||
|
||||
return {'value': {'company_id': company_id}}
|
||||
|
||||
account_model()
|
||||
|
||||
class account_model_line(osv.osv):
|
||||
|
@ -3013,9 +3023,9 @@ class wizard_multi_charts_accounts(osv.osv_memory):
|
|||
'purchase_tax_rate': fields.float('Purchase Tax(%)'),
|
||||
'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sales and purchase rates or use the usual m2o fields. This last choice assumes that the set of tax defined for the chosen template is complete'),
|
||||
}
|
||||
|
||||
|
||||
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
|
||||
currency_id = False
|
||||
currency_id = False
|
||||
if company_id:
|
||||
currency_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.id
|
||||
return {'value': {'currency_id': currency_id}}
|
||||
|
|
|
@ -43,11 +43,15 @@ class bank(osv.osv):
|
|||
"Return the name to use when creating a bank journal"
|
||||
return (bank.bank_name or '') + ' ' + bank.acc_number
|
||||
|
||||
def _prepare_name_get(self, cr, uid, bank_type_obj, bank_obj, context=None):
|
||||
"""Add ability to have %(currency_name)s in the format_layout of
|
||||
res.partner.bank.type"""
|
||||
bank_obj._data[bank_obj.id]['currency_name'] = bank_obj.currency_id and bank_obj.currency_id.name or ''
|
||||
return super(bank, self)._prepare_name_get(cr, uid, bank_type_obj, bank_obj, context=context)
|
||||
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"""
|
||||
currency_ids = list(set(data['currency_id'][0] for data in bank_dicts if data['currency_id']))
|
||||
currencies = self.pool.get('res.currency').browse(cr, uid, currency_ids, context=context)
|
||||
currency_name = dict((currency.id, currency.name) for currency in currencies)
|
||||
|
||||
for data in bank_dicts:
|
||||
data['currency_name'] = data['currency_id'] and currency_name[data['currency_id'][0]] or ''
|
||||
return super(bank, self)._prepare_name_get(cr, uid, bank_dicts, context=context)
|
||||
|
||||
def post_write(self, cr, uid, ids, context={}):
|
||||
if isinstance(ids, (int, long)):
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
<field name="fiscalyear_id" widget="selection"/>
|
||||
<label for="date_start" string="Duration"/>
|
||||
<div>
|
||||
<field name="date_start" class="oe_inline" nolabel="1"/> -
|
||||
<field name="date_start" class="oe_inline" nolabel="1"/> -
|
||||
<field name="date_stop" nolabel="1" class="oe_inline"/>
|
||||
</div>
|
||||
</group>
|
||||
|
@ -181,7 +181,7 @@
|
|||
<form string="Account" version="7.0">
|
||||
<label for="code" class="oe_edit_only" string="Account Code and Name"/>
|
||||
<h1>
|
||||
<field name="code" class="oe_inline" placeholder="Account code" style="width: 6em"/> -
|
||||
<field name="code" class="oe_inline" placeholder="Account code" style="width: 6em"/> -
|
||||
<field name="name" class="oe_inline" placeholder="Account name"/>
|
||||
</h1>
|
||||
<group>
|
||||
|
@ -1082,7 +1082,7 @@
|
|||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Journal Item" version="7.0">
|
||||
<sheet>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
|
@ -1349,7 +1349,7 @@
|
|||
<field name="date"/>
|
||||
<field name="to_check"/>
|
||||
<field name="amount" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Journal Items">
|
||||
|
@ -1651,8 +1651,8 @@
|
|||
<form string="Journal Entry Model" version="7.0">
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="company_id" widget='selection' groups="base.group_multi_company"/>
|
||||
<field name="journal_id" on_change="onchange_journal_id(journal_id)"/>
|
||||
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
|
||||
<field name="lines_id" widget="one2many_list"/>
|
||||
|
|
|
@ -223,6 +223,16 @@ class account_config_settings(osv.osv_memory):
|
|||
return {'value': {'date_stop': end_date.strftime('%Y-%m-%d')}}
|
||||
return {}
|
||||
|
||||
def open_company_form(self, cr, uid, ids, context=None):
|
||||
config = self.browse(cr, uid, ids[0], context)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Configure your Company',
|
||||
'res_model': 'res.company',
|
||||
'res_id': config.company_id.id,
|
||||
'view_mode': 'form',
|
||||
}
|
||||
|
||||
def set_default_taxes(self, cr, uid, ids, context=None):
|
||||
""" set default sale and purchase taxes for products """
|
||||
ir_values = self.pool.get('ir.values')
|
||||
|
|
|
@ -224,10 +224,8 @@
|
|||
<div>
|
||||
<div>
|
||||
<label for="company_footer"/>
|
||||
<button name="%(action_bank_tree)d"
|
||||
string="Configure your bank accounts"
|
||||
icon="gtk-go-forward"
|
||||
type="action"
|
||||
<button name="open_company_form" type="object"
|
||||
string="Configure your company bank accounts" icon="gtk-go-forward"
|
||||
class="oe_inline oe_link"/>
|
||||
<field name="company_footer"/>
|
||||
</div>
|
||||
|
|
|
@ -27,13 +27,15 @@ import time
|
|||
import tools
|
||||
from tools.translate import _
|
||||
|
||||
from base.res.res_partner import format_address
|
||||
|
||||
CRM_LEAD_PENDING_STATES = (
|
||||
crm.AVAILABLE_STATES[2][0], # Cancelled
|
||||
crm.AVAILABLE_STATES[3][0], # Done
|
||||
crm.AVAILABLE_STATES[4][0], # Pending
|
||||
)
|
||||
|
||||
class crm_lead(base_stage, osv.osv):
|
||||
class crm_lead(base_stage, format_address, osv.osv):
|
||||
""" CRM Lead Case """
|
||||
_name = "crm.lead"
|
||||
_description = "Lead/Opportunity"
|
||||
|
@ -105,6 +107,12 @@ class crm_lead(base_stage, osv.osv):
|
|||
|
||||
return result, fold
|
||||
|
||||
def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(crm_lead,self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
|
||||
if view_type == 'form':
|
||||
res['arch'] = self.fields_view_get_address(cr, user, res['arch'], context=context)
|
||||
return res
|
||||
|
||||
_group_by_full = {
|
||||
'stage_id': _read_group_stage_ids
|
||||
}
|
||||
|
|
|
@ -20,17 +20,10 @@
|
|||
##############################################################################
|
||||
|
||||
import base64
|
||||
from openerp.tests import common
|
||||
from openerp.addons.mail.tests import test_mail
|
||||
|
||||
class test_message_compose(common.TransactionCase):
|
||||
|
||||
def _mock_smtp_gateway(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def _mock_build_email(self, *args, **kwargs):
|
||||
self._build_email_args = args
|
||||
self._build_email_kwargs = kwargs
|
||||
return self.build_email_real(*args, **kwargs)
|
||||
class test_message_compose(test_mail.TestMailMockups):
|
||||
|
||||
def setUp(self):
|
||||
super(test_message_compose, self).setUp()
|
||||
|
@ -40,11 +33,6 @@ class test_message_compose(common.TransactionCase):
|
|||
self.res_users = self.registry('res.users')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
|
||||
# Install mock SMTP gateway
|
||||
self.build_email_real = self.registry('ir.mail_server').build_email
|
||||
self.registry('ir.mail_server').build_email = self._mock_build_email
|
||||
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
|
||||
|
||||
# create a 'pigs' and 'bird' groups that will be used through the various tests
|
||||
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
|
||||
|
|
|
@ -61,6 +61,7 @@ The main features of the module are:
|
|||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base', 'base_tools', 'base_setup'],
|
||||
'data': [
|
||||
'wizard/invite_view.xml',
|
||||
'wizard/mail_compose_message_view.xml',
|
||||
'res_config_view.xml',
|
||||
'mail_message_view.xml',
|
||||
|
|
|
@ -23,6 +23,7 @@ from osv import osv
|
|||
from osv import fields
|
||||
import tools
|
||||
|
||||
|
||||
class mail_followers(osv.Model):
|
||||
""" mail_followers holds the data related to the follow mechanism inside
|
||||
OpenERP. Partners can choose to follow documents (records) of any kind
|
||||
|
@ -84,16 +85,44 @@ class mail_notification(osv.Model):
|
|||
notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context)
|
||||
return self.write(cr, uid, notif_ids, {'read': True}, context=context)
|
||||
|
||||
def get_partners_to_notify(self, cr, uid, partner_ids, message, context=None):
|
||||
""" Return the list of partners to notify, based on their preferences.
|
||||
|
||||
:param browse_record message: mail.message to notify
|
||||
"""
|
||||
notify_pids = []
|
||||
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
|
||||
# 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
|
||||
# Partner does not want to receive any emails
|
||||
if partner.notification_email_send == 'none':
|
||||
continue
|
||||
# Partner wants to receive only emails and comments
|
||||
if partner.notification_email_send == 'comment' and message.type not in ('email', 'comment'):
|
||||
continue
|
||||
# Partner wants to receive only emails
|
||||
if partner.notification_email_send == 'email' and message.type != 'email':
|
||||
continue
|
||||
notify_pids.append(partner.id)
|
||||
return notify_pids
|
||||
|
||||
def notify(self, cr, uid, partner_ids, msg_id, context=None):
|
||||
""" Send by email the notification depending on the user preferences """
|
||||
context = context or {}
|
||||
# mail_noemail (do not send email) or no partner_ids: do not send, return
|
||||
if context.get('mail_noemail') or not partner_ids:
|
||||
return True
|
||||
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
|
||||
|
||||
notify_partner_ids = self.get_partners_to_notify(cr, uid, partner_ids, msg, context=context)
|
||||
if not notify_partner_ids:
|
||||
return True
|
||||
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
# add signature
|
||||
body_html = msg.body
|
||||
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
|
||||
|
@ -107,27 +136,6 @@ class mail_notification(osv.Model):
|
|||
'body_html': body_html,
|
||||
'state': 'outgoing',
|
||||
}
|
||||
|
||||
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
|
||||
# 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
|
||||
# Partner does not want to receive any emails
|
||||
if partner.notification_email_send == 'none':
|
||||
continue
|
||||
# Partner wants to receive only emails and comments
|
||||
if partner.notification_email_send == 'comment' and msg.type not in ('email', 'comment'):
|
||||
continue
|
||||
# Partner wants to receive only emails
|
||||
if partner.notification_email_send == 'email' and msg.type != 'email':
|
||||
continue
|
||||
if partner.email not in mail_values['email_to']:
|
||||
mail_values['email_to'].append(partner.email)
|
||||
if mail_values['email_to']:
|
||||
mail_values['email_to'] = ', '.join(mail_values['email_to'])
|
||||
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
|
||||
mail_mail.send(cr, uid, [email_notif_id], context=context)
|
||||
return True
|
||||
mail_values['email_to'] = ', '.join(mail_values['email_to'])
|
||||
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
|
||||
return mail_mail.send(cr, uid, [email_notif_id], recipient_ids=notify_partner_ids, context=context)
|
||||
|
|
|
@ -19,13 +19,11 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import datetime as DT
|
||||
import openerp
|
||||
import openerp.tools as tools
|
||||
from operator import itemgetter
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
from tools.translate import _
|
||||
|
||||
|
||||
class mail_group(osv.Model):
|
||||
""" A mail_group is a collection of users sharing messages in a discussion
|
||||
|
@ -47,7 +45,7 @@ class mail_group(osv.Model):
|
|||
_columns = {
|
||||
'description': fields.text('Description'),
|
||||
'menu_id': fields.many2one('ir.ui.menu', string='Related Menu', required=True, ondelete="cascade"),
|
||||
'public': fields.selection([('public','Public'),('private','Private'),('groups','Selected Group Only')], 'Privacy', required=True,
|
||||
'public': fields.selection([('public', 'Public'), ('private', 'Private'), ('groups', 'Selected Group Only')], 'Privacy', required=True,
|
||||
help='This group is visible by non members. \
|
||||
Invisible groups can add members through the invite button.'),
|
||||
'group_public_id': fields.many2one('res.groups', string='Authorized Group'),
|
||||
|
@ -126,14 +124,14 @@ class mail_group(osv.Model):
|
|||
search_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'view_message_search')
|
||||
params = {
|
||||
'search_view_id': search_ref and search_ref[1] or False,
|
||||
'domain': [('model','=','mail.group'), ('res_id','=',mail_group_id)],
|
||||
'domain': [('model', '=', 'mail.group'), ('res_id', '=', mail_group_id)],
|
||||
'context': {'default_model': 'mail.group', 'default_res_id': mail_group_id},
|
||||
'res_model': 'mail.message',
|
||||
'thread_level': 1,
|
||||
}
|
||||
cobj = self.pool.get('ir.actions.client')
|
||||
newref = cobj.copy(cr, uid, ref[1], default={'params': str(params), 'name': vals['name']}, context=context)
|
||||
self.write(cr, uid, [mail_group_id], {'action': 'ir.actions.client,'+str(newref), 'mail_group_id': mail_group_id}, context=context)
|
||||
self.write(cr, uid, [mail_group_id], {'action': 'ir.actions.client,' + str(newref), 'mail_group_id': mail_group_id}, context=context)
|
||||
|
||||
mail_alias.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context)
|
||||
|
||||
|
@ -142,9 +140,13 @@ class mail_group(osv.Model):
|
|||
return mail_group_id
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
groups = self.browse(cr, uid, ids, context=context)
|
||||
# Cascade-delete mail aliases as well, as they should not exist without the mail group.
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
alias_ids = [group.alias_id.id for group in self.browse(cr, uid, ids, context=context) if group.alias_id]
|
||||
alias_ids = [group.alias_id.id for group in groups if group.alias_id]
|
||||
# Cascade-delete menu entries as well
|
||||
self.pool.get('ir.ui.menu').unlink(cr, uid, [group.menu_id.id for group in groups if group.menu_id], context=context)
|
||||
# Delete mail_group
|
||||
res = super(mail_group, self).unlink(cr, uid, ids, context=context)
|
||||
mail_alias.unlink(cr, uid, alias_ids, context=context)
|
||||
return res
|
||||
|
|
|
@ -30,6 +30,7 @@ from tools.translate import _
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class mail_mail(osv.Model):
|
||||
""" Model holding RFC2822 email messages to send. This model also provides
|
||||
facilities to queue and send new email messages. """
|
||||
|
@ -58,8 +59,8 @@ class mail_mail(osv.Model):
|
|||
'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
|
||||
|
||||
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
|
||||
# and during unlink() we will cascade delete the parent and its attachments
|
||||
'notification': fields.boolean('Is Notification')
|
||||
# and during unlink() we will not cascade delete the parent and its attachments
|
||||
'notification': fields.boolean('Is Notification')
|
||||
}
|
||||
|
||||
def _get_default_from(self, cr, uid, context=None):
|
||||
|
@ -76,21 +77,21 @@ class mail_mail(osv.Model):
|
|||
def create(self, cr, uid, values, context=None):
|
||||
if 'notification' not in values and values.get('mail_message_id'):
|
||||
values['notification'] = True
|
||||
return super(mail_mail,self).create(cr, uid, values, context=context)
|
||||
return super(mail_mail, self).create(cr, uid, values, context=context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# cascade-delete the parent message for all mails that are not created for a notification
|
||||
ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
|
||||
ids_to_cascade = self.search(cr, uid, [('notification', '=', False), ('id', 'in', ids)])
|
||||
parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
|
||||
res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
|
||||
res = super(mail_mail, self).unlink(cr, uid, ids, context=context)
|
||||
self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
|
||||
return res
|
||||
|
||||
def mark_outgoing(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
|
||||
return self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
|
||||
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
|
||||
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
|
||||
|
||||
def process_email_queue(self, cr, uid, ids=None, context=None):
|
||||
"""Send immediately queued messages, committing after each
|
||||
|
@ -127,7 +128,7 @@ class mail_mail(osv.Model):
|
|||
"""Perform any post-processing necessary after sending ``mail``
|
||||
successfully, including deleting it completely along with its
|
||||
attachment if the ``auto_delete`` flag of the mail was set.
|
||||
Overridden by subclasses for extra post-processing behaviors.
|
||||
Overridden by subclasses for extra post-processing behaviors.
|
||||
|
||||
:param browse_record mail: the mail that was just sent
|
||||
:return: True
|
||||
|
@ -136,14 +137,46 @@ class mail_mail(osv.Model):
|
|||
mail.unlink()
|
||||
return True
|
||||
|
||||
def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
|
||||
""" if void and related document: '<Author> posted on <Resource>'
|
||||
:param mail: mail.mail browse_record """
|
||||
def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
|
||||
""" If subject is void and record_name defined: '<Author> posted on <Resource>'
|
||||
|
||||
:param boolean force: force the subject replacement
|
||||
:param browse_record mail: mail.mail browse_record
|
||||
:param browse_record partner: specific recipient partner
|
||||
"""
|
||||
if force or (not mail.subject and mail.model and mail.res_id):
|
||||
return '%s posted on %s' % (mail.author_id.name, mail.record_name)
|
||||
return mail.subject
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, context=None):
|
||||
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Return a specific ir_email body. The main purpose of this method
|
||||
is to be inherited by Portal, to add a link for signing in, in
|
||||
each notification email a partner receives.
|
||||
|
||||
:param browse_record mail: mail.mail browse_record
|
||||
:param browse_record partner: specific recipient partner
|
||||
"""
|
||||
return mail.body_html
|
||||
|
||||
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Return a dictionary for specific email values, depending on a
|
||||
partner, or generic to the whole recipients given by mail.email_to.
|
||||
|
||||
:param browse_record mail: mail.mail browse_record
|
||||
:param browse_record partner: specific recipient partner
|
||||
"""
|
||||
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||
subject = self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context)
|
||||
body_alternative = tools.html2plaintext(body)
|
||||
email_to = [partner.email] if partner else tools.email_split(mail.email_to)
|
||||
return {
|
||||
'body': body,
|
||||
'body_alternative': body_alternative,
|
||||
'subject': subject,
|
||||
'email_to': email_to,
|
||||
}
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, recipient_ids=None, context=None):
|
||||
""" Sends the selected emails immediately, ignoring their current
|
||||
state (mails that have already been sent should not be passed
|
||||
unless they should actually be re-sent).
|
||||
|
@ -154,50 +187,55 @@ class mail_mail(osv.Model):
|
|||
:param bool auto_commit: whether to force a commit of the mail status
|
||||
after sending each mail (meant only for scheduler processing);
|
||||
should never be True during normal transactions (default: False)
|
||||
:param list recipient_ids: specific list of res.partner recipients.
|
||||
If set, one email is sent to each partner. Its is possible to
|
||||
tune the sent email through ``send_get_mail_body`` and ``send_get_mail_subject``.
|
||||
If not specified, one email is sent to mail_mail.email_to.
|
||||
:return: True
|
||||
"""
|
||||
ir_mail_server = self.pool.get('ir.mail_server')
|
||||
for mail in self.browse(cr, uid, ids, context=context):
|
||||
try:
|
||||
body = mail.body_html
|
||||
subject = self._send_get_mail_subject(cr, uid, mail, context=context)
|
||||
|
||||
# handle attachments
|
||||
attachments = []
|
||||
for attach in mail.attachment_ids:
|
||||
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
|
||||
|
||||
# use only sanitized html and set its plaintexted version as alternative
|
||||
body_alternative = tools.html2plaintext(body)
|
||||
content_subtype_alternative = 'plain'
|
||||
# specific behavior to customize the send email for notified partners
|
||||
email_list = []
|
||||
if recipient_ids:
|
||||
for partner in self.pool.get('res.partner').browse(cr, uid, recipient_ids, context=context):
|
||||
email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
|
||||
else:
|
||||
email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it without queuing
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from = mail.email_from,
|
||||
email_to = tools.email_split(mail.email_to),
|
||||
subject = subject,
|
||||
body = body,
|
||||
body_alternative = body_alternative,
|
||||
email_cc = tools.email_split(mail.email_cc),
|
||||
reply_to = mail.reply_to,
|
||||
attachments = attachments,
|
||||
message_id = mail.message_id,
|
||||
references = mail.references,
|
||||
object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
|
||||
subtype = 'html',
|
||||
subtype_alternative = content_subtype_alternative)
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=mail.mail_server_id.id, context=context)
|
||||
for email in email_list:
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from = mail.email_from,
|
||||
email_to = email.get('email_to'),
|
||||
subject = email.get('subject'),
|
||||
body = email.get('body'),
|
||||
body_alternative = email.get('body_alternative'),
|
||||
email_cc = tools.email_split(mail.email_cc),
|
||||
reply_to = mail.reply_to,
|
||||
attachments = attachments,
|
||||
message_id = mail.message_id,
|
||||
references = mail.references,
|
||||
object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
|
||||
subtype = 'html',
|
||||
subtype_alternative = 'plain')
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=mail.mail_server_id.id, context=context)
|
||||
if res:
|
||||
mail.write({'state':'sent', 'message_id': res})
|
||||
mail.write({'state': 'sent', 'message_id': res})
|
||||
else:
|
||||
mail.write({'state':'exception'})
|
||||
mail.write({'state': 'exception'})
|
||||
mail.refresh()
|
||||
if mail.state == 'sent':
|
||||
self._postprocess_sent_message(cr, uid, mail, context=context)
|
||||
except Exception:
|
||||
_logger.exception('failed sending mail.mail %s', mail.id)
|
||||
mail.write({'state':'exception'})
|
||||
mail.write({'state': 'exception'})
|
||||
|
||||
if auto_commit == True:
|
||||
cr.commit()
|
||||
|
|
|
@ -20,11 +20,13 @@
|
|||
##############################################################################
|
||||
|
||||
import logging
|
||||
import openerp
|
||||
import tools
|
||||
|
||||
from email.header import decode_header
|
||||
from operator import itemgetter
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -35,6 +37,7 @@ def decode(text):
|
|||
text = decode_header(text.replace('\r', ''))
|
||||
return ''.join([tools.ustr(x[0], x[1]) for x in text])
|
||||
|
||||
|
||||
class mail_message(osv.Model):
|
||||
""" Messages model: system notification (replacing res.log notifications),
|
||||
comments (OpenChatter discussion) and incoming emails. """
|
||||
|
@ -57,7 +60,10 @@ class mail_message(osv.Model):
|
|||
for message in self.browse(cr, uid, ids, context=context):
|
||||
if not message.model or not message.res_id:
|
||||
continue
|
||||
result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1])
|
||||
try:
|
||||
result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1])
|
||||
except openerp.exceptions.AccessDenied, e:
|
||||
pass
|
||||
return result
|
||||
|
||||
def _get_unread(self, cr, uid, ids, name, arg, context=None):
|
||||
|
@ -341,3 +347,25 @@ class mail_message(osv.Model):
|
|||
default = {}
|
||||
default.update(message_id=False, headers=False)
|
||||
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Tools
|
||||
#------------------------------------------------------
|
||||
|
||||
def check_partners_email(self, cr, uid, partner_ids, context=None):
|
||||
""" Verify that selected partner_ids have an email_address defined.
|
||||
Otherwise throw a warning. """
|
||||
partner_wo_email_lst = []
|
||||
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
|
||||
if not partner.email:
|
||||
partner_wo_email_lst.append(partner)
|
||||
if not partner_wo_email_lst:
|
||||
return {}
|
||||
warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
|
||||
for partner in partner_wo_email_lst:
|
||||
warning_msg += '\n- %s' % (partner.name)
|
||||
return {'warning': {
|
||||
'title': _('Partners email addresses not found'),
|
||||
'message': warning_msg,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -623,14 +623,8 @@ class mail_thread(osv.AbstractModel):
|
|||
return self.message_subscribe(cr, uid, ids, partner_ids, context=context)
|
||||
|
||||
def message_subscribe(self, cr, uid, ids, partner_ids, context=None):
|
||||
""" Add partners to the records followers.
|
||||
:param partner_ids: a list of partner_ids to subscribe
|
||||
:param return: new value of followers if read_back key in context
|
||||
"""
|
||||
self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
|
||||
if context and context.get('read_back'):
|
||||
return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
|
||||
return []
|
||||
""" Add partners to the records followers. """
|
||||
return self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
|
||||
|
||||
def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
|
||||
""" Wrapper on message_subscribe, using users. If user_ids is not
|
||||
|
@ -640,14 +634,8 @@ class mail_thread(osv.AbstractModel):
|
|||
return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
|
||||
|
||||
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
|
||||
""" Remove partners from the records followers.
|
||||
:param partner_ids: a list of partner_ids to unsubscribe
|
||||
:param return: new value of followers if read_back key in context
|
||||
"""
|
||||
self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
|
||||
if context and context.get('read_back'):
|
||||
return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
|
||||
return []
|
||||
""" Remove partners from the records followers. """
|
||||
return self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Thread state
|
||||
|
|
|
@ -72,6 +72,9 @@ class res_users(osv.Model):
|
|||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
# create default alias same as the login
|
||||
if not data.get('login', False):
|
||||
raise osv.except_osv(_('Invalid Action!'), _('You may not create a user.'))
|
||||
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
alias_id = mail_alias.create_unique_alias(cr, uid, {'alias_name': data['login']}, model_name=self._name, context=context)
|
||||
data['alias_id'] = alias_id
|
||||
|
|
|
@ -96,10 +96,6 @@
|
|||
width: 120px;
|
||||
}
|
||||
|
||||
.openerp button.oe_mail_button_followers {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.openerp button.oe_mail_button_mouseout {
|
||||
color: white;
|
||||
background-color: #8a89ba;
|
||||
|
|
|
@ -31,33 +31,52 @@ openerp_mail_followers = function(session, mail) {
|
|||
},
|
||||
|
||||
start: function() {
|
||||
var self = this;
|
||||
// NB: all the widget should be modified to check the actual_mode property on view, not use
|
||||
// any other method to know if the view is in create mode anymore
|
||||
// use actual_mode property on view to know if the view is in create mode anymore
|
||||
this.view.on("change:actual_mode", this, this._check_visibility);
|
||||
this._check_visibility();
|
||||
this.$el.find('button.oe_mail_button_follow').click(function () { self.do_follow(); })
|
||||
.mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
|
||||
.mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
|
||||
this.$el.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); })
|
||||
.mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
|
||||
.mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
|
||||
this.reinit();
|
||||
this.bind_events();
|
||||
},
|
||||
|
||||
_check_visibility: function() {
|
||||
this.$el.toggle(this.view.get("actual_mode") !== "create");
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
reinit: function() {
|
||||
this.$el.find('button.oe_mail_button_follow').hide();
|
||||
this.$el.find('button.oe_mail_button_unfollow').hide();
|
||||
},
|
||||
|
||||
bind_events: function() {
|
||||
var self = this;
|
||||
this.$('button.oe_mail_button_unfollow').on('click', function () { self.do_unfollow(); })
|
||||
.mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
|
||||
.mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
|
||||
this.$el.on('click', 'button.oe_mail_button_follow', function () { self.do_follow(); });
|
||||
this.$el.on('click', 'button.oe_mail_button_invite', function(event) {
|
||||
action = {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'mail.wizard.invite',
|
||||
view_mode: 'form',
|
||||
view_type: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {
|
||||
'default_res_model': self.view.dataset.model,
|
||||
'default_res_id': self.view.datarecord.id
|
||||
},
|
||||
}
|
||||
self.do_action(action, function() { self.read_value(); });
|
||||
});
|
||||
},
|
||||
|
||||
read_value: function() {
|
||||
var self = this;
|
||||
return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).pipe(function (results) {
|
||||
return results[0].message_follower_ids;
|
||||
}).pipe(this.proxy('set_value'));
|
||||
},
|
||||
|
||||
set_value: function(value_) {
|
||||
this.reinit();
|
||||
if (! this.view.datarecord.id ||
|
||||
|
@ -65,11 +84,11 @@ openerp_mail_followers = function(session, mail) {
|
|||
this.$el.find('div.oe_mail_recthread_aside').hide();
|
||||
return;
|
||||
}
|
||||
return this.fetch_followers(value_);
|
||||
return this.fetch_followers(value_ || this.get_value());
|
||||
},
|
||||
|
||||
fetch_followers: function (value_) {
|
||||
return this.ds_follow.call('read', [value_ || this.get_value(), ['name', 'user_ids']]).then(this.proxy('display_followers'));
|
||||
return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers'));
|
||||
},
|
||||
|
||||
/** Display the followers, evaluate is_follower directly */
|
||||
|
@ -91,13 +110,13 @@ openerp_mail_followers = function(session, mail) {
|
|||
},
|
||||
|
||||
do_follow: function () {
|
||||
var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
|
||||
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
|
||||
var context = new session.web.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('read_value'));
|
||||
},
|
||||
|
||||
do_unfollow: function () {
|
||||
var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
|
||||
return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
|
||||
var context = new session.web.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('read_value'));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
<!-- dropdown menu with message options and actions -->
|
||||
<span class="oe_dropdown_toggle oe_dropdown_arrow">
|
||||
<ul class="oe_dropdown_menu">
|
||||
<li t-if="record.is_author & options.show_dd_delete"><a class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
|
||||
<li t-if="record.is_author and options.show_dd_delete"><a class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
|
||||
<li t-if="options.show_dd_hide"><a class="oe_mail_msg_hide" t-attf-data-id='{record.id}'>Remove notification</a></li>
|
||||
<!-- Uncomment when adding subtype hiding
|
||||
<li t-if="display['show_hide']">
|
||||
|
@ -130,14 +130,14 @@
|
|||
<t t-raw="record.subject"/>
|
||||
</h1>
|
||||
<div class="oe_mail_msg_body">
|
||||
<t t-if="options.show_record_name & (!record.subject) & (options.thread_level > 0)">
|
||||
<t t-if="options.show_record_name and record.record_name and (!record.subject) and (options.thread_level > 0)">
|
||||
<a t-attf-href="#model=#{record.model}&id=#{record.res_id}"><t t-raw="record.record_name"/></a>
|
||||
</t>
|
||||
<t t-raw="record.body"/>
|
||||
</div>
|
||||
<div class="oe_clear"/>
|
||||
<ul class="oe_mail_msg_footer">
|
||||
<li t-if="options.show_record_name & record.subject & options.thread_level > 0">
|
||||
<li t-if="options.show_record_name and record.record_name and record.subject and options.thread_level > 0">
|
||||
<a t-attf-href="#model=#{record.model}&id=#{record.res_id}"><t t-raw="record.record_name"/></a>
|
||||
</li>
|
||||
<li><a t-attf-href="#model=res.partner&id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li>
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
-->
|
||||
<div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
|
||||
<div class="oe_mail_recthread_actions">
|
||||
<button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button>
|
||||
<button type="button" class="oe_mail_button_invite">Invite</button>
|
||||
<button type="button" class="oe_mail_button_follow">Follow</button>
|
||||
<button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>
|
||||
</div>
|
||||
<div class="oe_mail_recthread_followers">
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import tools
|
||||
|
||||
from openerp.tests import common
|
||||
from openerp.tools.html_sanitize import html_sanitize
|
||||
|
||||
|
@ -82,15 +84,42 @@ Sylvie
|
|||
"""
|
||||
|
||||
|
||||
class test_mail(common.TransactionCase):
|
||||
class TestMailMockups(common.TransactionCase):
|
||||
|
||||
def _mock_smtp_gateway(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def _init_mock_build_email(self):
|
||||
self._build_email_args_list = []
|
||||
self._build_email_kwargs_list = []
|
||||
|
||||
def _mock_build_email(self, *args, **kwargs):
|
||||
self._build_email_args = args
|
||||
self._build_email_kwargs = kwargs
|
||||
return self.build_email_real(*args, **kwargs)
|
||||
self._build_email_args_list.append(args)
|
||||
self._build_email_kwargs_list.append(kwargs)
|
||||
return self._build_email(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(TestMailMockups, self).setUp()
|
||||
# Install mock SMTP gateway
|
||||
self._init_mock_build_email()
|
||||
self._build_email = self.registry('ir.mail_server').build_email
|
||||
self.registry('ir.mail_server').build_email = self._mock_build_email
|
||||
self._send_email = self.registry('ir.mail_server').send_email
|
||||
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
|
||||
|
||||
def tearDown(self):
|
||||
# Remove mocks
|
||||
self.registry('ir.mail_server').build_email = self._build_email
|
||||
self.registry('ir.mail_server').send_email = self._send_email
|
||||
super(TestMailMockups, self).tearDown()
|
||||
|
||||
|
||||
class test_mail(TestMailMockups):
|
||||
|
||||
def _mock_send_get_mail_body(self, *args, **kwargs):
|
||||
# def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None)
|
||||
body = tools.append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner')
|
||||
return body
|
||||
|
||||
def setUp(self):
|
||||
super(test_mail, self).setUp()
|
||||
|
@ -105,10 +134,9 @@ class test_mail(common.TransactionCase):
|
|||
self.res_users = self.registry('res.users')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
|
||||
# Install mock SMTP gateway
|
||||
self.build_email_real = self.registry('ir.mail_server').build_email
|
||||
self.registry('ir.mail_server').build_email = self._mock_build_email
|
||||
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
|
||||
# Mock send_get_mail_body to test its functionality without other addons override
|
||||
self._send_get_mail_body = self.registry('mail.mail').send_get_mail_body
|
||||
self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
|
||||
|
||||
# groups@.. will cause the creation of new mail groups
|
||||
self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
|
||||
|
@ -118,6 +146,11 @@ class test_mail(common.TransactionCase):
|
|||
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
|
||||
|
||||
def tearDown(self):
|
||||
# Remove mocks
|
||||
self.registry('mail.mail').send_get_mail_body = self._send_get_mail_body
|
||||
super(test_mail, self).tearDown()
|
||||
|
||||
def test_00_message_process(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
# Incoming mail creates a new mail_group "frogs"
|
||||
|
@ -274,18 +307,20 @@ class test_mail(common.TransactionCase):
|
|||
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
|
||||
|
||||
# CASE1: post comment, body and subject specified
|
||||
self._init_mock_build_email()
|
||||
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment')
|
||||
message = self.mail_message.browse(cr, uid, msg_id)
|
||||
sent_email = self._build_email_kwargs
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
# Test: notifications have been deleted
|
||||
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id)]), 'mail.mail notifications should have been auto-deleted!')
|
||||
# Test: mail_message: subject is _subject, body is _body1 (no formatting done)
|
||||
self.assertEqual(message.subject, _subject, 'mail.message subject incorrect')
|
||||
self.assertEqual(message.body, _body1, 'mail.message body incorrect')
|
||||
# Test: sent_email: email send by server: correct subject, body; body_alternative
|
||||
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
|
||||
self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
|
||||
self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1, 'sent_email body_alternative is incorrect')
|
||||
# Test: sent_email: email send by server: correct subject, body, body_alternative
|
||||
for sent_email in sent_emails:
|
||||
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
|
||||
self.assertEqual(sent_email['body'], _mail_body1 + '\n<pre>Bert Tartopoils</pre>\n', 'sent_email body incorrect')
|
||||
self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils', 'sent_email body_alternative is incorrect')
|
||||
# Test: mail_message: partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message.partner_ids])
|
||||
test_pids = set([p_a_id, p_b_id, p_c_id])
|
||||
|
@ -295,14 +330,16 @@ class test_mail(common.TransactionCase):
|
|||
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
|
||||
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
|
||||
# Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
|
||||
self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
|
||||
for sent_email in sent_emails:
|
||||
self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
|
||||
|
||||
# CASE2: post an email with attachments, parent_id, partner_ids
|
||||
# TESTS: automatic subject, signature in body_html, attachments propagation
|
||||
self._init_mock_build_email()
|
||||
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email',
|
||||
partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
|
||||
message = self.mail_message.browse(cr, uid, msg_id2)
|
||||
sent_email = self._build_email_kwargs
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id2)]), 'mail.mail notifications should have been auto-deleted!')
|
||||
|
||||
# Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
|
||||
|
@ -310,9 +347,11 @@ class test_mail(common.TransactionCase):
|
|||
self.assertEqual(message.body, html_sanitize(_body2), 'mail.message body incorrect')
|
||||
self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
|
||||
# Test: sent_email: email send by server: correct subject, body, body_alternative
|
||||
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
|
||||
self.assertEqual(sent_email['body'], _mail_body2, 'sent_email body incorrect')
|
||||
self.assertEqual(sent_email['body_alternative'], _mail_bodyalt2, 'sent_email body_alternative incorrect')
|
||||
self.assertEqual(len(sent_emails), 2, 'sent_email number of sent emails incorrect')
|
||||
for sent_email in sent_emails:
|
||||
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
|
||||
self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
|
||||
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
|
||||
# Test: mail_message: partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message.partner_ids])
|
||||
test_pids = set([p_a_id, p_b_id, p_c_id, p_d_id])
|
||||
|
@ -322,7 +361,8 @@ class test_mail(common.TransactionCase):
|
|||
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
|
||||
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
|
||||
# Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
|
||||
self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect')
|
||||
for sent_email in sent_emails:
|
||||
self.assertTrue(set(sent_email['email_to']).issubset(set(['b@b', 'c@c'])), 'sent_email email_to incorrect')
|
||||
# Test: attachments
|
||||
for attach in message.attachment_ids:
|
||||
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import invite
|
||||
import mail_compose_message
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
from tools.translate import _
|
||||
|
||||
|
||||
class invite_wizard(osv.osv_memory):
|
||||
""" Wizard to invite partners and make them followers. """
|
||||
_name = 'mail.wizard.invite'
|
||||
_description = 'Invite wizard'
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
result = super(invite_wizard, self).default_get(cr, uid, fields, context=context)
|
||||
if 'message' in fields and result.get('res_model') and result.get('res_id'):
|
||||
document_name = self.pool.get(result.get('res_model')).name_get(cr, uid, [result.get('res_id')], context=context)[0][1]
|
||||
message = _('<div>You have been invited to follow %s.</div>' % document_name)
|
||||
result['message'] = message
|
||||
elif 'message' in fields:
|
||||
result['message'] = _('<div>You have been invited to follow a new document.</div>')
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'res_model': fields.char('Related Document Model', size=128,
|
||||
required=True, select=1,
|
||||
help='Model of the followed resource'),
|
||||
'res_id': fields.integer('Related Document ID', select=1,
|
||||
help='Id of the followed resource'),
|
||||
'partner_ids': fields.many2many('res.partner', string='Partners'),
|
||||
'message': fields.html('Message'),
|
||||
}
|
||||
|
||||
def onchange_partner_ids(self, cr, uid, ids, value, context=None):
|
||||
""" onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
|
||||
basic purpose of this method is to check that destination partners
|
||||
effectively have email addresses. Otherwise a warning is thrown.
|
||||
"""
|
||||
res = {'value': {}}
|
||||
if not value or not value[0] or not value[0][0] == 6:
|
||||
return
|
||||
res.update(self.pool.get('mail.message').check_partners_email(cr, uid, value[0][2], context=context))
|
||||
return res
|
||||
|
||||
def add_followers(self, cr, uid, ids, context=None):
|
||||
for wizard in self.browse(cr, uid, ids, context=context):
|
||||
model_obj = self.pool.get(wizard.res_model)
|
||||
document = model_obj.browse(cr, uid, wizard.res_id, context=context)
|
||||
|
||||
# filter partner_ids to get the new followers, to avoid sending email to already following partners
|
||||
new_follower_ids = [p.id for p in wizard.partner_ids if p.id not in document.message_follower_ids]
|
||||
model_obj.message_subscribe(cr, uid, [wizard.res_id], new_follower_ids, context=context)
|
||||
|
||||
# send an email
|
||||
if wizard.message:
|
||||
for follower_id in new_follower_ids:
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
mail_id = mail_mail.create(cr, uid, {
|
||||
'subject': 'Invitation to follow %s' % document.name_get()[0][1],
|
||||
'body_html': '%s' % wizard.message,
|
||||
'auto_delete': True,
|
||||
}, context=context)
|
||||
mail_mail.send(cr, uid, [mail_id], recipient_ids=[follower_id], context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- wizard view -->
|
||||
<record model="ir.ui.view" id="mail_wizard_invite_form">
|
||||
<field name="name">Add Followers</field>
|
||||
<field name="model">mail.wizard.invite</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Add Followers" version="7.0">
|
||||
<group>
|
||||
<field name="res_model" invisible="1"/>
|
||||
<field name="res_id" invisible="1"/>
|
||||
<field name="partner_ids" widget="many2many_tags"
|
||||
on_change="onchange_partner_ids(partner_ids)" />
|
||||
<field name="message"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Add Followers"
|
||||
name="add_followers" type="object" class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -193,24 +193,6 @@ class mail_compose_message(osv.TransientModel):
|
|||
"""
|
||||
return {'value': {'content_subtype': value}}
|
||||
|
||||
def _verify_partner_email(self, cr, uid, partner_ids, context=None):
|
||||
""" Verify that selected partner_ids have an email_address defined.
|
||||
Otherwise throw a warning. """
|
||||
partner_wo_email_lst = []
|
||||
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
|
||||
if not partner.email:
|
||||
partner_wo_email_lst.append(partner)
|
||||
if not partner_wo_email_lst:
|
||||
return {}
|
||||
warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
|
||||
for partner in partner_wo_email_lst:
|
||||
warning_msg += '\n- %s' % (partner.name)
|
||||
return {'warning': {
|
||||
'title': _('Partners email addresses not found'),
|
||||
'message': warning_msg,
|
||||
}
|
||||
}
|
||||
|
||||
def onchange_partner_ids(self, cr, uid, ids, value, context=None):
|
||||
""" The basic purpose of this method is to check that destination partners
|
||||
effectively have email addresses. Otherwise a warning is thrown.
|
||||
|
@ -219,7 +201,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
res = {'value': {}}
|
||||
if not value or not value[0] or not value[0][0] == 6:
|
||||
return
|
||||
res.update(self._verify_partner_email(cr, uid, value[0][2], context=context))
|
||||
res.update(self.check_partners_email(cr, uid, value[0][2], context=context))
|
||||
return res
|
||||
|
||||
def dummy(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -72,19 +72,19 @@ class PointOfSaleController(openerpweb.Controller):
|
|||
return getattr(self, method)(request, **kwargs)
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def scan_item_success(self, request):
|
||||
def scan_item_success(self, request, ean):
|
||||
"""
|
||||
A product has been scanned with success
|
||||
"""
|
||||
print 'scan_item_success'
|
||||
print 'scan_item_success: ' + str(ean)
|
||||
return
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def scan_item_error_unrecognized(self, request):
|
||||
def scan_item_error_unrecognized(self, request, ean):
|
||||
"""
|
||||
A product has been scanned without success
|
||||
"""
|
||||
print 'scan_item_error_unrecognized'
|
||||
print 'scan_item_error_unrecognized: ' + str(ean)
|
||||
return
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
|
@ -166,4 +166,9 @@ class PointOfSaleController(openerpweb.Controller):
|
|||
print 'print_receipt' + str(receipt)
|
||||
return
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def print_pdf_invoice(self, request, pdfinvoice):
|
||||
print 'print_pdf_invoice' + str(pdfinvoice)
|
||||
return
|
||||
|
||||
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
</record>
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="groups_id" eval="[(4,ref('group_pos_user'))]"/>
|
||||
<field name="ean13">0410200000005</field>
|
||||
<field name="ean13">0420100000005</field>
|
||||
</record>
|
||||
<record id="account.cash_journal" model="account.journal">
|
||||
<field eval="True" name="journal_user"/>
|
||||
</record>
|
||||
<record id="base.user_root" model="res.users">
|
||||
<field name="ean13">0410300000004</field>
|
||||
<field name="ean13">0410100000006</field>
|
||||
<field name="groups_id" eval="[(4,ref('group_pos_manager'))]"/>
|
||||
</record>
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="ean13">0410400000003</field>
|
||||
<field name="ean13">0420100000005</field>
|
||||
<field name="groups_id" eval="[(4,ref('group_pos_manager'))]"/>
|
||||
</record>
|
||||
|
||||
|
@ -189,6 +189,7 @@
|
|||
<record id="citron" model="product.product">
|
||||
<field name="list_price">1.98</field>
|
||||
<field name="name">Lemon</field>
|
||||
<field name="ean13">2301000000006</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="pos_categ_id" ref="autres_agrumes"/>
|
||||
<field name="uom_id" ref="product.product_uom_kgm" />
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
clear: left;
|
||||
}
|
||||
.point-of-sale .keyboard .lastitem {
|
||||
margin-right: 0;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
/* ---- full sized keyboard ---- */
|
||||
|
@ -79,13 +79,13 @@
|
|||
.point-of-sale .full_keyboard {
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
width: 680px;
|
||||
width: 685px;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
.point-of-sale .full_keyboard li{
|
||||
margin: 0 5px 5px 0;
|
||||
margin: 0 5px 5px 0 !important;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
|
@ -115,22 +115,22 @@
|
|||
.point-of-sale .simple_keyboard {
|
||||
list-style: none;
|
||||
font-size: 16px;
|
||||
width: 545px;
|
||||
width: 555px;
|
||||
height: 220px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
.point-of-sale .simple_keyboard li{
|
||||
margin: 0 5px 5px 0;
|
||||
margin: 0 5px 5px 0 !important;
|
||||
width: 49px;
|
||||
height: 49px;
|
||||
line-height: 49px;
|
||||
}
|
||||
.point-of-sale .simple_keyboard .firstitem.row_asdf{
|
||||
margin-left:25px;
|
||||
margin-left:25px !important;
|
||||
}
|
||||
.point-of-sale .simple_keyboard .firstitem.row_zxcv{
|
||||
margin-left:55px;
|
||||
margin-left:55px !important;
|
||||
}
|
||||
.point-of-sale .simple_keyboard .delete{
|
||||
width: 103px;
|
||||
|
@ -139,7 +139,7 @@
|
|||
width: 103px;
|
||||
}
|
||||
.point-of-sale .simple_keyboard .space{
|
||||
width:268px;
|
||||
width:273px;
|
||||
}
|
||||
.point-of-sale .simple_keyboard .numlock{
|
||||
width:103px;
|
||||
|
|
|
@ -373,7 +373,7 @@
|
|||
/* ********* The product list ********* */
|
||||
|
||||
.point-of-sale .product-list {
|
||||
padding:10px;
|
||||
padding:10px !important;
|
||||
}
|
||||
|
||||
.point-of-sale .product-list-scroller{
|
||||
|
@ -470,7 +470,7 @@
|
|||
}
|
||||
|
||||
.point-of-sale .category-list{
|
||||
padding:10px;
|
||||
padding:10px !important;
|
||||
}
|
||||
/* d) the category button */
|
||||
|
||||
|
@ -479,7 +479,7 @@
|
|||
vertical-align: top;
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
margin: 5px;
|
||||
margin: 5px !important;
|
||||
width: 120px;
|
||||
height:120px;
|
||||
background:#fff;
|
||||
|
@ -566,7 +566,7 @@
|
|||
display: inline-block;
|
||||
line-height: 100px;
|
||||
font-size: 11px;
|
||||
margin: 5px;
|
||||
margin: 5px !important;
|
||||
width: 120px;
|
||||
height:120px;
|
||||
background:#fff;
|
||||
|
@ -809,6 +809,8 @@
|
|||
}
|
||||
|
||||
.point-of-sale .scale-screen .product-picture img{
|
||||
max-width: 178px;
|
||||
max-height:178px;
|
||||
vertical-align: middle;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
@ -860,7 +862,7 @@
|
|||
.point-of-sale .goodbye-message{
|
||||
position: absolute;
|
||||
left:50%;
|
||||
top:30%;
|
||||
top:40%;
|
||||
width:500px;
|
||||
height:400px;
|
||||
margin-left: -250px;
|
||||
|
@ -1100,6 +1102,79 @@
|
|||
.point-of-sale .pos-actionbar .button.rightalign{
|
||||
float:right;
|
||||
}
|
||||
/* ********* The Debug Widget ********* */
|
||||
|
||||
.point-of-sale .debug-widget{
|
||||
z-index:100000;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
width: 200px;
|
||||
font-size: 10px;
|
||||
|
||||
background: rgba(0,0,0,0.82);
|
||||
color: white;
|
||||
text-shadow: none;
|
||||
padding-bottom: 10px;
|
||||
box-shadow: 0px 3px 20px rgba(0,0,0,0.3);
|
||||
cursor:move;
|
||||
}
|
||||
.point-of-sale .debug-widget .toggle{
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
cursor:pointer;
|
||||
top:0px;
|
||||
right:0px;
|
||||
padding:10px;
|
||||
padding-right:15px;
|
||||
}
|
||||
.point-of-sale .debug-widget .content{
|
||||
overflow: hidden;
|
||||
}
|
||||
.point-of-sale .debug-widget h1{
|
||||
background:black;
|
||||
padding-top: 10px;
|
||||
padding-left: 10px;
|
||||
margin-top:0;
|
||||
margin-bottom:0;
|
||||
}
|
||||
.point-of-sale .debug-widget .category{
|
||||
background: black;
|
||||
padding-left: 10px;
|
||||
margin: 0px;
|
||||
font-weight: bold;
|
||||
padding-top:3px;
|
||||
padding-bottom:3px;
|
||||
}
|
||||
.point-of-sale .debug-widget .button{
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
display: block;
|
||||
cursor:pointer;
|
||||
}
|
||||
.point-of-sale .debug-widget .button:hover{
|
||||
background: rgba(96,21,177,0.45);
|
||||
}
|
||||
.point-of-sale .debug-widget input{
|
||||
margin-left:10px;
|
||||
margin-top:7px;
|
||||
}
|
||||
.point-of-sale .debug-widget .status{
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
display: block;
|
||||
cursor:default;
|
||||
}
|
||||
.point-of-sale .debug-widget .status.on{
|
||||
background-color: #6cd11d;
|
||||
}
|
||||
.point-of-sale .debug-widget .event{
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
display: block;
|
||||
cursor:default;
|
||||
background-color: #1E1E1E;
|
||||
}
|
||||
|
||||
/* ********* The PopupWidgets ********* */
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ function openerp_pos_db(instance, module){
|
|||
this.category_childs = {};
|
||||
this.category_parent = {};
|
||||
this.category_search_string = {};
|
||||
this.packagings_by_id = {};
|
||||
this.packagings_by_product_id = {};
|
||||
},
|
||||
/* returns the category object from its id. If you pass a list of id as parameters, you get
|
||||
* a list of category objects.
|
||||
|
@ -116,6 +118,10 @@ function openerp_pos_db(instance, module){
|
|||
if(product.ean13){
|
||||
str += '|' + product.ean13;
|
||||
}
|
||||
var packagings = this.packagings_by_product_id[product.id] || [];
|
||||
for(var i = 0; i < packagings.length; i++){
|
||||
str += '|' + packagings[i].ean;
|
||||
}
|
||||
return str + '\n';
|
||||
},
|
||||
add_products: function(products){
|
||||
|
@ -158,6 +164,16 @@ function openerp_pos_db(instance, module){
|
|||
this.save('products',stored_products);
|
||||
this.save('categories',stored_categories);
|
||||
},
|
||||
add_packagings: function(packagings){
|
||||
for(var i = 0, len = packagings.length; i < len; i++){
|
||||
var pack = packagings[i];
|
||||
this.packagings_by_id[pack.id] = pack;
|
||||
if(!this.packagings_by_product_id[pack.product_id[0]]){
|
||||
this.packagings_by_product_id[pack.product_id[0]] = [];
|
||||
}
|
||||
this.packagings_by_product_id[pack.product_id[0]].push(pack);
|
||||
}
|
||||
},
|
||||
/* removes all the data from the database. TODO : being able to selectively remove data */
|
||||
clear: function(stores){
|
||||
for(var i = 0, len = arguments.length; i < len; i++){
|
||||
|
@ -184,6 +200,12 @@ function openerp_pos_db(instance, module){
|
|||
return products[i];
|
||||
}
|
||||
}
|
||||
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]];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
get_product_by_category: function(category_id){
|
||||
|
@ -223,9 +245,12 @@ 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,26 +1,6 @@
|
|||
|
||||
function openerp_pos_devices(instance,module){ //module is instance.point_of_sale
|
||||
|
||||
var debug_devices = new (instance.web.Class.extend({
|
||||
active: false,
|
||||
payment_status: 'waiting_for_payment',
|
||||
weight: 0,
|
||||
activate: function(){
|
||||
this.active = true;
|
||||
},
|
||||
deactivate: function(){
|
||||
this.active = false;
|
||||
},
|
||||
set_weight: function(weight){ this.activate(); this.weight = weight; },
|
||||
accept_payment: function(){ this.activate(); this.payment_status = 'payment_accepted'; },
|
||||
reject_payment: function(){ this.activate(); this.payment_status = 'payment_rejected'; },
|
||||
delay_payment: function(){ this.activate(); this.payment_status = 'waiting_for_payment'; },
|
||||
}))();
|
||||
|
||||
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
|
||||
window.debug_devices = debug_devices;
|
||||
}
|
||||
|
||||
// this object interfaces with the local proxy to communicate to the various hardware devices
|
||||
// connected to the Point of Sale. As the communication only goes from the POS to the proxy,
|
||||
// methods are used both to signal an event, and to fetch information.
|
||||
|
@ -38,32 +18,42 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
|
||||
this.connection = new instance.web.JsonRPC();
|
||||
this.connection.setup(url);
|
||||
|
||||
this.bypass_proxy = false;
|
||||
this.notifications = {};
|
||||
|
||||
},
|
||||
message : function(name,params,success_callback, error_callback){
|
||||
success_callback = success_callback || function(){};
|
||||
error_callback = error_callback || function(){};
|
||||
|
||||
|
||||
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
|
||||
console.log('PROXY:',name,params);
|
||||
var callbacks = this.notifications[name] || [];
|
||||
for(var i = 0; i < callbacks.length; i++){
|
||||
callbacks[i](params);
|
||||
}
|
||||
|
||||
if(!(debug_devices && debug_devices.active)){
|
||||
this.connection.rpc('/pos/'+name, params || {}, success_callback, error_callback);
|
||||
this.connection.rpc('/pos/'+name, params || {}, success_callback, error_callback);
|
||||
},
|
||||
|
||||
// this allows the client to be notified when a proxy call is made. The notification
|
||||
// callback will be executed with the same arguments as the proxy call
|
||||
add_notification: function(name, callback){
|
||||
if(!this.notifications[name]){
|
||||
this.notifications[name] = [];
|
||||
}
|
||||
this.notifications[name].push(callback);
|
||||
},
|
||||
|
||||
//a product has been scanned and recognized with success
|
||||
// ean is a parsed ean object
|
||||
scan_item_success: function(ean){
|
||||
this.message('scan_item_success',ean);
|
||||
this.message('scan_item_success',{ean: ean});
|
||||
},
|
||||
|
||||
// a product has been scanned but not recognized
|
||||
// ean is a parsed ean object
|
||||
scan_item_error_unrecognized: function(ean){
|
||||
this.message('scan_item_error_unrecognized',ean);
|
||||
this.message('scan_item_error_unrecognized',{ean: ean});
|
||||
},
|
||||
|
||||
//the client is asking for help
|
||||
|
@ -78,12 +68,12 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
|
||||
//the client is starting to weight
|
||||
weighting_start: function(){
|
||||
this.weight = 0;
|
||||
if(debug_devices){
|
||||
debug_devices.weigth = 0;
|
||||
if(!this.weighting){
|
||||
this.weight = 0;
|
||||
this.weighting = true;
|
||||
this.bypass_proxy = false;
|
||||
this.message('weighting_start');
|
||||
}
|
||||
this.weighting = true;
|
||||
this.message('weighting_start');
|
||||
},
|
||||
|
||||
//returns the weight on the scale.
|
||||
|
@ -91,22 +81,29 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
// and a weighting_end()
|
||||
weighting_read_kg: function(){
|
||||
var self = this;
|
||||
if(debug_devices && debug_devices.active){
|
||||
return debug_devices.weight;
|
||||
if(this.bypass_proxy){
|
||||
return this.weight;
|
||||
}else{
|
||||
this.message('weighting_read_kg',{},function(weight){
|
||||
if(self.weighting){
|
||||
if(self.weighting && !self.bypass_proxy){
|
||||
self.weight = weight;
|
||||
}
|
||||
});
|
||||
return self.weight;
|
||||
return this.weight;
|
||||
}
|
||||
},
|
||||
|
||||
// sets a custom weight, ignoring the proxy returned value until the next weighting_end
|
||||
debug_set_weight: function(kg){
|
||||
this.bypass_proxy = true;
|
||||
this.weight = kg;
|
||||
},
|
||||
|
||||
// the client has finished weighting products
|
||||
weighting_end: function(){
|
||||
this.weight = 0;
|
||||
this.weighting = false;
|
||||
this.bypass_proxy = false;
|
||||
this.message('weighting_end');
|
||||
},
|
||||
|
||||
|
@ -116,9 +113,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
payment_request: function(price, method, info){
|
||||
this.paying = true;
|
||||
this.payment_status = 'waiting_for_payment';
|
||||
if(debug_devices){
|
||||
debug_devices.payment_status = 'waiting_for_payment';
|
||||
}
|
||||
this.message('payment_request',{'price':price,'method':method,'info':info});
|
||||
},
|
||||
|
||||
|
@ -127,18 +121,30 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
// returns 'waiting_for_payment' | 'payment_accepted' | 'payment_rejected'
|
||||
is_payment_accepted: function(){
|
||||
var self = this;
|
||||
if(debug_devices.active){
|
||||
return debug_devices.payment_status;
|
||||
if(this.bypass_proxy){
|
||||
this.bypass_proxy = false;
|
||||
return this.payment_status;
|
||||
}else{
|
||||
this.message('is_payment_accepted', {}, function(payment_status){
|
||||
if(self.paying){
|
||||
self.payment_status = payment_status;
|
||||
}
|
||||
});
|
||||
return self.payment_status;
|
||||
return this.payment_status;
|
||||
}
|
||||
},
|
||||
|
||||
// override what the proxy says and accept the payment
|
||||
debug_accept_payment: function(){
|
||||
this.bypass_proxy = true;
|
||||
this.payment_status = 'payment_accepted';
|
||||
},
|
||||
|
||||
// override what the proxy says and reject the payment
|
||||
debug_reject_payment: function(){
|
||||
this.bypass_proxy = true;
|
||||
this.payment_status = 'payment_rejected';
|
||||
},
|
||||
// the client cancels his payment
|
||||
payment_canceled: function(){
|
||||
this.paying = false;
|
||||
|
@ -212,6 +218,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
print_receipt: function(receipt){
|
||||
this.message('print_receipt',{receipt: receipt});
|
||||
},
|
||||
|
||||
// asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server )
|
||||
print_pdf_invoice: function(pdfinvoice){
|
||||
this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice});
|
||||
},
|
||||
});
|
||||
|
||||
// this module interfaces with the barcode reader. It assumes the barcode reader
|
||||
|
@ -237,6 +248,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''};
|
||||
this.client_prefix_set = attributes.client_prefix_set || {'042':''};
|
||||
},
|
||||
|
||||
save_callbacks: function(){
|
||||
var callbacks = {};
|
||||
for(name in this.action_callback){
|
||||
|
@ -244,6 +256,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
}
|
||||
this.action_callback_stack.push(callbacks);
|
||||
},
|
||||
|
||||
restore_callbacks: function(){
|
||||
if(this.action_callback_stack.length){
|
||||
var callbacks = this.action_callback_stack.pop();
|
||||
|
@ -337,7 +350,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
value: 0,
|
||||
unit: 'none',
|
||||
};
|
||||
console.log('ean',ean);
|
||||
|
||||
function match_prefix(prefix_set, type){
|
||||
for(prefix in prefix_set){
|
||||
|
@ -380,6 +392,23 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
return parse_result;
|
||||
},
|
||||
|
||||
on_ean: function(ean){
|
||||
var parse_result = this.parse_ean(ean);
|
||||
|
||||
if (parse_result.type === 'error') { //most likely a checksum error, raise warning
|
||||
console.warn('WARNING: barcode checksum error:',parse_result);
|
||||
}else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
|
||||
if(this.action_callback['product']){
|
||||
this.action_callback['product'](parse_result);
|
||||
}
|
||||
//this.trigger("codebar",parse_result );
|
||||
}else{
|
||||
if(this.action_callback[parse_result.type]){
|
||||
this.action_callback[parse_result.type](parse_result);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// starts catching keyboard events and tries to interpret codebar
|
||||
// calling the callbacks when needed.
|
||||
connect: function(){
|
||||
|
@ -410,21 +439,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
|||
lastTimeStamp = new Date().getTime();
|
||||
if (codeNumbers.length == 13) {
|
||||
//We have found what seems to be a valid codebar
|
||||
var parse_result = self.parse_ean(codeNumbers.join(''));
|
||||
|
||||
if (parse_result.type === 'error') { //most likely a checksum error, raise warning
|
||||
console.warn('WARNING: barcode checksum error:',parse_result);
|
||||
}else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
|
||||
if(self.action_callback['product']){
|
||||
self.action_callback['product'](parse_result);
|
||||
}
|
||||
//this.trigger("codebar",parse_result );
|
||||
}else{
|
||||
if(self.action_callback[parse_result.type]){
|
||||
self.action_callback[parse_result.type](parse_result);
|
||||
}
|
||||
}
|
||||
|
||||
self.on_ean(codeNumbers.join(''));
|
||||
codeNumbers = [];
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
function openerp_pos_models(instance, module){ //module is instance.point_of_sale
|
||||
var QWeb = instance.web.qweb;
|
||||
|
||||
var fetch = function(model, fields, domain, ctx){
|
||||
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
|
||||
};
|
||||
|
||||
// The PosModel contains the Point Of Sale's representation of the backend.
|
||||
// Since the PoS must work in standalone ( Without connection to the server )
|
||||
|
@ -27,6 +24,8 @@ 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
|
||||
|
||||
|
||||
// default attributes values. If null, it will be loaded below.
|
||||
this.set({
|
||||
|
@ -51,7 +50,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
'units': null,
|
||||
'units_by_id': null,
|
||||
|
||||
'selectedOrder': undefined,
|
||||
'selectedOrder': null,
|
||||
});
|
||||
|
||||
this.get('orders').bind('remove', function(){ self.on_removed_order(); });
|
||||
|
@ -70,15 +69,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
});
|
||||
},
|
||||
|
||||
// helper function to load data from the server
|
||||
fetch: function(model, fields, domain, ctx){
|
||||
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
|
||||
},
|
||||
// loads all the needed data on the sever. returns a deferred indicating when all the data has loaded.
|
||||
load_server_data: function(){
|
||||
var self = this;
|
||||
|
||||
var loaded = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
|
||||
var loaded = self.fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
|
||||
.pipe(function(users){
|
||||
self.set('user',users[0]);
|
||||
|
||||
return fetch('res.company',
|
||||
return self.fetch('res.company',
|
||||
[
|
||||
'currency_id',
|
||||
'email',
|
||||
|
@ -93,15 +96,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}).pipe(function(companies){
|
||||
self.set('company',companies[0]);
|
||||
|
||||
return fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]);
|
||||
return self.fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]);
|
||||
}).pipe(function(company_partners){
|
||||
self.get('company').contact_address = company_partners[0].contact_address;
|
||||
|
||||
return fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]);
|
||||
return self.fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]);
|
||||
}).pipe(function(currencies){
|
||||
self.set('currency',currencies[0]);
|
||||
|
||||
return fetch('product.uom', null, null);
|
||||
return self.fetch('product.uom', null, null);
|
||||
}).pipe(function(units){
|
||||
self.set('units',units);
|
||||
var units_by_id = {};
|
||||
|
@ -110,19 +113,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}
|
||||
self.set('units_by_id',units_by_id);
|
||||
|
||||
return fetch('product.packaging', null, null);
|
||||
return self.fetch('product.packaging', null, null);
|
||||
}).pipe(function(packagings){
|
||||
self.set('product.packaging',packagings);
|
||||
|
||||
return fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
|
||||
return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
|
||||
}).pipe(function(users){
|
||||
self.set('user_list',users);
|
||||
|
||||
return fetch('account.tax', ['amount', 'price_include', 'type']);
|
||||
return self.fetch('account.tax', ['amount', 'price_include', 'type']);
|
||||
}).pipe(function(taxes){
|
||||
self.set('taxes', taxes);
|
||||
|
||||
return fetch(
|
||||
return self.fetch(
|
||||
'pos.session',
|
||||
['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
|
||||
[['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
|
||||
|
@ -130,7 +133,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}).pipe(function(sessions){
|
||||
self.set('pos_session', sessions[0]);
|
||||
|
||||
return fetch(
|
||||
return self.fetch(
|
||||
'pos.config',
|
||||
['name','journal_ids','shop_id','journal_id',
|
||||
'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
|
||||
|
@ -147,15 +150,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
self.iface_self_checkout = !!pos_config.iface_self_checkout;
|
||||
self.iface_cashdrawer = !!pos_config.iface_cashdrawer;
|
||||
|
||||
return fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]);
|
||||
return self.fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]);
|
||||
}).pipe(function(shops){
|
||||
self.set('shop',shops[0]);
|
||||
|
||||
return fetch('pos.category', ['id','name','parent_id','child_id','image'])
|
||||
return self.fetch('product.packaging',['ean','product_id']);
|
||||
}).pipe(function(packagings){
|
||||
self.db.add_packagings(packagings);
|
||||
|
||||
return self.fetch('pos.category', ['id','name','parent_id','child_id','image'])
|
||||
}).pipe(function(categories){
|
||||
self.db.add_categories(categories);
|
||||
|
||||
return fetch(
|
||||
return self.fetch(
|
||||
'product.product',
|
||||
['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13',
|
||||
'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description'],
|
||||
|
@ -165,7 +172,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}).pipe(function(products){
|
||||
self.db.add_products(products);
|
||||
|
||||
return fetch(
|
||||
return self.fetch(
|
||||
'account.bank.statement',
|
||||
['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
|
||||
[['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]]
|
||||
|
@ -173,7 +180,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
}).pipe(function(bank_statements){
|
||||
self.set('bank_statements', bank_statements);
|
||||
|
||||
return fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]);
|
||||
return self.fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]);
|
||||
}).pipe(function(journals){
|
||||
self.set('journals',journals);
|
||||
|
||||
|
@ -226,6 +233,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
|
||||
// saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
|
||||
push_order: function(record) {
|
||||
console.log('PUSHING NEW ORDER:',record);
|
||||
this.db.add_order(record);
|
||||
this.flush();
|
||||
},
|
||||
|
@ -241,10 +249,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
// and remove the successfully sent ones from the db once
|
||||
// it has been confirmed that they have been sent correctly.
|
||||
flush: function() {
|
||||
//TODO make the mutex work
|
||||
console.log('FLUSH');
|
||||
//this makes sure only one _int_flush is called at the same time
|
||||
/*
|
||||
return this.flush_mutex.exec(_.bind(function() {
|
||||
return this._flush(0);
|
||||
}, this));
|
||||
*/
|
||||
this._flush(0);
|
||||
},
|
||||
// attempts to send an order of index 'index' in the list of order to send. The index
|
||||
// is used to skip orders that failed. do not call this method outside the mutex provided
|
||||
|
@ -253,6 +266,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
var self = this;
|
||||
var orders = this.db.get_orders();
|
||||
self.set('nbr_pending_operations',orders.length);
|
||||
console.log('TRYING TO FLUSH ORDER:',index,'Of',orders.length);
|
||||
|
||||
var order = orders[index];
|
||||
if(!order){
|
||||
|
@ -268,6 +282,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
})
|
||||
.done(function(){
|
||||
//remove from db if success
|
||||
console.log('Order successfully sent');
|
||||
self.db.remove_order(order.id);
|
||||
self._flush(index);
|
||||
});
|
||||
|
@ -301,6 +316,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
});
|
||||
|
||||
module.Product = Backbone.Model.extend({
|
||||
get_image_url: function(){
|
||||
return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+this.get('id');
|
||||
},
|
||||
});
|
||||
|
||||
module.ProductCollection = Backbone.Collection.extend({
|
||||
|
@ -330,7 +348,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
get_discount: function(){
|
||||
return this.discount;
|
||||
},
|
||||
// FIXME
|
||||
get_product_type: function(){
|
||||
return this.type;
|
||||
},
|
||||
|
@ -546,6 +563,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
this.pos = attributes.pos;
|
||||
this.selected_orderline = undefined;
|
||||
this.screen_data = {}; // see ScreenSelector
|
||||
this.receipt_type = 'receipt'; // 'receipt' || 'invoice'
|
||||
return this;
|
||||
},
|
||||
generateUniqueId: function() {
|
||||
|
@ -617,6 +635,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
getDueLeft: function() {
|
||||
return this.getTotal() - this.getPaidTotal();
|
||||
},
|
||||
// sets the type of receipt 'receipt'(default) or 'invoice'
|
||||
set_receipt_type: function(type){
|
||||
this.receipt_type = type;
|
||||
},
|
||||
get_receipt_type: function(){
|
||||
return this.receipt_type;
|
||||
},
|
||||
// the client related to the current order.
|
||||
set_client: function(client){
|
||||
this.set('client',client);
|
||||
|
|
|
@ -65,6 +65,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
close_popup: function(){
|
||||
if(this.current_popup){
|
||||
this.current_popup.close();
|
||||
this.current_popup.hide();
|
||||
this.current_popup = null;
|
||||
}
|
||||
|
@ -188,7 +189,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
return true;
|
||||
}
|
||||
}
|
||||
this.pos.proxy.scan_item_unrecognized(ean);
|
||||
this.pos.proxy.scan_item_error_unrecognized(ean);
|
||||
return false;
|
||||
},
|
||||
|
||||
|
@ -206,7 +207,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
return true;
|
||||
}
|
||||
}
|
||||
this.pos.proxy.scan_item_unrecognized(ean);
|
||||
this.pos.proxy.scan_item_error_unrecognized(ean);
|
||||
return false;
|
||||
//TODO start the transaction
|
||||
},
|
||||
|
@ -336,6 +337,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
this.$el.show();
|
||||
}
|
||||
},
|
||||
/* called before hide, when a popup is closed */
|
||||
close: function(){
|
||||
},
|
||||
/* hides the popup. keep in mind that this is called in the initialization pass of the
|
||||
* pos instantiation, so you don't want to do anything fancy in here */
|
||||
hide: function(){
|
||||
if(this.$el){
|
||||
this.$el.hide();
|
||||
|
@ -352,9 +358,40 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
|
||||
this.$el.find('.button').off('click').click(function(){
|
||||
self.pos_widget.screen_selector.close_popup();
|
||||
self.pos.proxy.help_canceled();
|
||||
});
|
||||
},
|
||||
close:function(){
|
||||
this.pos.proxy.help_canceled();
|
||||
},
|
||||
});
|
||||
|
||||
module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
|
||||
template:'ChooseReceiptPopupWidget',
|
||||
show: function(){
|
||||
console.log('show');
|
||||
this._super();
|
||||
this.renderElement();
|
||||
var self = this;
|
||||
var currentOrder = self.pos.get('selectedOrder');
|
||||
|
||||
this.$('.button.receipt').off('click').click(function(){
|
||||
currentOrder.set_receipt_type('receipt');
|
||||
self.pos_widget.screen_selector.set_current_screen('products');
|
||||
});
|
||||
|
||||
this.$('.button.invoice').off('click').click(function(){
|
||||
currentOrder.set_receipt_type('invoice');
|
||||
self.pos_widget.screen_selector.set_current_screen('products');
|
||||
});
|
||||
},
|
||||
get_client_name: function(){
|
||||
var client = this.pos.get('selectedOrder').get_client();
|
||||
if( client ){
|
||||
return client.name;
|
||||
}else{
|
||||
return '';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
module.ErrorPopupWidget = module.PopUpWidget.extend({
|
||||
|
@ -492,10 +529,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
var product = this.get_product();
|
||||
return (product ? product.get('list_price') : 0) || 0;
|
||||
},
|
||||
get_product_image: function(){
|
||||
var product = this.get_product();
|
||||
return product ? product.get('image') : undefined;
|
||||
},
|
||||
get_product_weight: function(){
|
||||
return this.weight || 0;
|
||||
},
|
||||
|
@ -527,23 +560,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
|
||||
//we get the first cashregister marked as self-checkout
|
||||
var selfCheckoutRegisters = [];
|
||||
for(var i = 0; i < this.pos.get('cashRegisters').models.length; i++){
|
||||
var cashregister = this.pos.get('cashRegisters').models[i];
|
||||
for(var i = 0; i < self.pos.get('cashRegisters').models.length; i++){
|
||||
var cashregister = self.pos.get('cashRegisters').models[i];
|
||||
if(cashregister.self_checkout_payment_method){
|
||||
selfCheckoutRegisters.push(cashregister);
|
||||
}
|
||||
}
|
||||
|
||||
var cashregister = selfCheckoutRegisters[0] || this.pos.get('cashRegisters').models[0];
|
||||
var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0];
|
||||
currentOrder.addPaymentLine(cashregister);
|
||||
|
||||
self.pos.push_order(currentOrder.exportAsJSON()).then(function() {
|
||||
currentOrder.destroy();
|
||||
self.pos.proxy.transaction_end();
|
||||
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
|
||||
});
|
||||
self.pos.push_order(currentOrder.exportAsJSON())
|
||||
currentOrder.destroy();
|
||||
self.pos.proxy.transaction_end();
|
||||
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
|
||||
}else if(payment === 'payment_rejected'){
|
||||
clearInterval(this.intervalID);
|
||||
clearInterval(self.intervalID);
|
||||
//TODO show a tryagain thingie ?
|
||||
}
|
||||
},500);
|
||||
|
@ -571,19 +602,35 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
|
||||
show_numpad: false,
|
||||
show_leftpane: false,
|
||||
barcode_product_action: function(ean){
|
||||
this.pos.proxy.transaction_start();
|
||||
this._super(ean);
|
||||
},
|
||||
|
||||
barcode_client_action: function(ean){
|
||||
this.pos.proxy.transaction_start();
|
||||
this._super(ean);
|
||||
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
|
||||
$('.goodbye-message').hide();
|
||||
this.pos_widget.screen_selector.show_popup('choose-receipt');
|
||||
},
|
||||
|
||||
show: function(){
|
||||
this._super();
|
||||
var self = this;
|
||||
|
||||
this.add_action_button({
|
||||
label: 'help',
|
||||
icon: '/point_of_sale/static/src/img/icons/png48/help.png',
|
||||
click: function(){
|
||||
$('.goodbye-message').css({opacity:1}).hide();
|
||||
self.help_button_action();
|
||||
},
|
||||
});
|
||||
|
||||
$('.goodbye-message').css({opacity:1}).show();
|
||||
setTimeout(function(){
|
||||
$('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').hide();});
|
||||
},3000);
|
||||
},5000);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -743,6 +790,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
});
|
||||
|
||||
this.updatePaymentSummary();
|
||||
this.$('.paymentline-amout input').last().focus();
|
||||
},
|
||||
close: function(){
|
||||
this._super();
|
||||
|
@ -830,7 +878,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
this.numpadState.set({mode: 'payment'});
|
||||
},
|
||||
set_value: function(val) {
|
||||
this.currentPaymentLines.last().set({amount: val});
|
||||
this.currentPaymentLines.last().set_amount(val);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -284,12 +284,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
this.click_product_action = options.click_product_action;
|
||||
},
|
||||
// returns the url of the product thumbnail
|
||||
get_image_url: function() {
|
||||
return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+this.model.get('id');
|
||||
},
|
||||
renderElement: function() {
|
||||
this._super();
|
||||
this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.get_image_url()));
|
||||
this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.model.get_image_url()));
|
||||
var self = this;
|
||||
$("a", this.$el).click(function(e){
|
||||
if(self.click_product_action){
|
||||
|
@ -667,9 +664,95 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
show: function(){ this.$el.show(); },
|
||||
hide: function(){ this.$el.hide(); },
|
||||
|
||||
});
|
||||
|
||||
// The debug widget lets the user control and monitor the hardware and software status
|
||||
// without the use of the proxy
|
||||
module.DebugWidget = module.PosBaseWidget.extend({
|
||||
template: "DebugWidget",
|
||||
eans:{
|
||||
admin_badge: '0410100000006',
|
||||
client_badge: '0420100000005',
|
||||
invalid_ean: '1232456',
|
||||
soda_33cl: '5449000000996',
|
||||
oranges_kg: '2100002031410',
|
||||
lemon_price: '2301000001560',
|
||||
unknown_product: '9900000000004',
|
||||
},
|
||||
events:[
|
||||
'scan_item_success',
|
||||
'scan_item_error_unrecognized',
|
||||
'payment_request',
|
||||
'open_cashbox',
|
||||
'print_receipt',
|
||||
'print_pdf_invoice',
|
||||
'weighting_read_kg',
|
||||
'is_payment_accepted',
|
||||
],
|
||||
minimized: false,
|
||||
start: function(){
|
||||
var self = this;
|
||||
|
||||
this.$el.draggable();
|
||||
this.$('.toggle').click(function(){
|
||||
var content = self.$('.content');
|
||||
var bg = self.$el;
|
||||
if(!self.minimized){
|
||||
content.animate({'height':'0'},200);
|
||||
}else{
|
||||
content.css({'height':'auto'});
|
||||
}
|
||||
self.minimized = !self.minimized;
|
||||
});
|
||||
this.$('.button.accept_payment').click(function(){
|
||||
self.pos.proxy.debug_accept_payment();
|
||||
});
|
||||
this.$('.button.reject_payment').click(function(){
|
||||
self.pos.proxy.debug_reject_payment();
|
||||
});
|
||||
this.$('.button.set_weight').click(function(){
|
||||
var kg = Number(self.$('input.weight').val());
|
||||
if(!Number.isNaN(kg)){
|
||||
self.pos.proxy.debug_set_weight(kg);
|
||||
}
|
||||
});
|
||||
this.$('.button.custom_ean').click(function(){
|
||||
var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0');
|
||||
self.$('input.ean').val(ean);
|
||||
self.pos.barcode_reader.on_ean(ean);
|
||||
});
|
||||
_.each(this.eans, function(ean, name){
|
||||
self.$('.button.'+name).click(function(){
|
||||
self.$('input.ean').val(ean);
|
||||
self.pos.barcode_reader.on_ean(ean);
|
||||
});
|
||||
});
|
||||
_.each(this.events, function(name){
|
||||
self.pos.proxy.add_notification(name,function(){
|
||||
self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'});
|
||||
self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000);
|
||||
});
|
||||
});
|
||||
self.pos.proxy.add_notification('help_needed',function(){
|
||||
self.$('.status.help_needed').addClass('on');
|
||||
});
|
||||
self.pos.proxy.add_notification('help_canceled',function(){
|
||||
self.$('.status.help_needed').removeClass('on');
|
||||
});
|
||||
self.pos.proxy.add_notification('transaction_start',function(){
|
||||
self.$('.status.transaction').addClass('on');
|
||||
});
|
||||
self.pos.proxy.add_notification('transaction_end',function(){
|
||||
self.$('.status.transaction').removeClass('on');
|
||||
});
|
||||
self.pos.proxy.add_notification('weighting_start',function(){
|
||||
self.$('.status.weighting').addClass('on');
|
||||
});
|
||||
self.pos.proxy.add_notification('weighting_end',function(){
|
||||
self.$('.status.weighting').removeClass('on');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ---------- Main Point of Sale Widget ----------
|
||||
|
||||
|
@ -698,6 +781,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
});
|
||||
|
||||
|
||||
// The PosWidget is the main widget that contains all other widgets in the PointOfSale.
|
||||
// It is mainly composed of :
|
||||
// - a header, containing the list of orders
|
||||
|
@ -722,14 +806,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
this.leftpane_width = '440px';
|
||||
this.cashier_controls_visible = true;
|
||||
this.image_cache = new module.ImageCache(); // for faster products image display
|
||||
|
||||
/*
|
||||
//Epileptic mode
|
||||
setInterval(function(){
|
||||
$('body').css({'-webkit-filter':'hue-rotate('+Math.random()*360+'deg)' });
|
||||
},100);
|
||||
*/
|
||||
|
||||
},
|
||||
|
||||
start: function() {
|
||||
|
@ -758,6 +834,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
|
||||
self.screen_selector.set_default_screen();
|
||||
|
||||
window.screen_selector = self.screen_selector;
|
||||
|
||||
self.pos.barcode_reader.connect();
|
||||
|
||||
instance.webclient.set_content_full_screen(true);
|
||||
|
@ -771,11 +849,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
|
||||
self.$('.loader img').hide();
|
||||
|
||||
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
|
||||
window.pos = self.pos;
|
||||
window.pos_widget = self.pos_widget;
|
||||
}
|
||||
|
||||
},function(){ // error when loading models data from the backend
|
||||
self.$('.loader img').hide();
|
||||
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
|
||||
|
@ -831,6 +904,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
this.error_session_popup = new module.ErrorNoSessionPopupWidget(this, {});
|
||||
this.error_session_popup.appendTo($('.point-of-sale'));
|
||||
|
||||
this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {});
|
||||
this.choose_receipt_popup.appendTo($('.point-of-sale'));
|
||||
|
||||
// -------- Misc ---------
|
||||
|
||||
this.notification = new module.SynchNotificationWidget(this,{});
|
||||
|
@ -890,12 +966,17 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
'error': this.error_popup,
|
||||
'error-product': this.error_product_popup,
|
||||
'error-session': this.error_session_popup,
|
||||
'choose-receipt': this.choose_receipt_popup,
|
||||
},
|
||||
default_client_screen: 'welcome',
|
||||
default_cashier_screen: 'products',
|
||||
default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier',
|
||||
});
|
||||
|
||||
if(this.pos.debug){
|
||||
this.debug_widget = new module.DebugWidget(this);
|
||||
this.debug_widget.appendTo(this.$('#content'));
|
||||
}
|
||||
},
|
||||
|
||||
changed_pending_operations: function () {
|
||||
|
@ -965,9 +1046,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
|||
},
|
||||
try_close: function() {
|
||||
var self = this;
|
||||
self.pos.flush().then(function() {
|
||||
self.close();
|
||||
});
|
||||
//TODO : do the close after the flush...
|
||||
self.pos.flush()
|
||||
self.close();
|
||||
},
|
||||
close: function() {
|
||||
var self = this;
|
||||
|
|
|
@ -185,8 +185,8 @@
|
|||
<span class="product-price">
|
||||
<t t-esc="widget.format_currency(widget.get_product_price()) + '/Kg'" />
|
||||
</span>
|
||||
<t t-if="widget.get_product_image()">
|
||||
<img t-att-src="'data:image/png;base64,'+ widget.get_product_image()" />
|
||||
<t t-if="widget.get_product()">
|
||||
<img t-att-src="widget.get_product().get_image_url()" />
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -313,18 +313,16 @@
|
|||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ReceiptPopupWidget">
|
||||
<t t-name="ChooseReceiptPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-help">
|
||||
<p class="message">Welcome Mr. John Smith <br /> Choose your type of receipt:</p>
|
||||
<p class="message">Welcome <t t-esc="widget.get_client_name()" /><br /> Choose your type of receipt:</p>
|
||||
<div class = "button big-left receipt">
|
||||
Ticket
|
||||
</div>
|
||||
<div class = "button big-right invoice">
|
||||
Invoice
|
||||
</div>
|
||||
<div class="footer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
@ -348,7 +346,8 @@
|
|||
<t t-name="ErrorPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-help">
|
||||
<p class="message"><t t-esc=" widget.message || 'Error.' " /></p>
|
||||
<p class="message"><t t-esc=" widget.message || 'Error' " /></p>
|
||||
<p class="comment"><t t-esc=" widget.comment || '' "/></p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
@ -415,6 +414,57 @@
|
|||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="DebugWidget">
|
||||
<div class="debug-widget">
|
||||
<h1>Debug Window</h1>
|
||||
<div class="toggle">▾</div>
|
||||
<div class="content">
|
||||
<p class="category">Payment Terminal</p>
|
||||
|
||||
<ul>
|
||||
<li class="button accept_payment">Accept Payment</li>
|
||||
<li class="button reject_payment">Reject Payment</li>
|
||||
</ul>
|
||||
<p class="category">Electronic Scale</p>
|
||||
<ul>
|
||||
<li><input type="text" class="weight"></input></li>
|
||||
<li class="button set_weight">Set Weight</li>
|
||||
</ul>
|
||||
|
||||
<p class="category">Barcode Scanner</p>
|
||||
<ul>
|
||||
<li><input type="text" class="ean"></input></li>
|
||||
<li class="button custom_ean">Custom Ean13</li>
|
||||
<li class="button admin_badge">Admin Badge</li>
|
||||
<li class="button client_badge">Client Badge</li>
|
||||
<li class="button soda_33cl">Soda 33cl</li>
|
||||
<li class="button oranges_kg">3.141Kg Oranges</li>
|
||||
<li class="button lemon_price">1.54€ Lemon</li>
|
||||
<li class="button unknown_product">Unknown Product</li>
|
||||
<li class="button invalid_ean">Invalid Ean</li>
|
||||
</ul>
|
||||
|
||||
<p class="category">Hardware Status</p>
|
||||
<ul>
|
||||
<li class="status help_needed">Help needed</li>
|
||||
<li class="status weighting">Weighting</li>
|
||||
<li class="status transaction">In Transaction</li>
|
||||
</ul>
|
||||
<p class="category">Hardware Events</p>
|
||||
<ul>
|
||||
<li class="event scan_item_success">Scan Item Success</li>
|
||||
<li class="event scan_item_error_unrecognized">Scan Item Unrecognized</li>
|
||||
<li class="event payment_request">Payment Request</li>
|
||||
<li class="event open_cashbox">Open Cashbox</li>
|
||||
<li class="event print_receipt">Print Receipt</li>
|
||||
<li class="event print_pdf_invoice">Print Invoice</li>
|
||||
<li class="event weighting_read_kg">Read Weighting Scale</li>
|
||||
<li class="event is_payment_accepted">Check Payment</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="OrderlineWidget">
|
||||
<li class="orderline">
|
||||
<span class="product-name">
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
##############################################################################
|
||||
|
||||
import portal
|
||||
import mail_mail
|
||||
import wizard
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2011 OpenERP S.A (<http://www.openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import tools
|
||||
from osv import osv
|
||||
|
||||
|
||||
class mail_mail_portal(osv.Model):
|
||||
""" Update of mail_mail class, to add the signin URL to notifications.
|
||||
"""
|
||||
_name = 'mail.mail'
|
||||
_inherit = ['mail.mail']
|
||||
|
||||
def _generate_signin_url(self, cr, uid, partner_id, portal_group_id, key, context=None):
|
||||
""" Generate the signin url """
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='', context=context)
|
||||
return base_url + '/login?action=signin&partner_id=%s&group=%s&key=%s' % (partner_id, portal_group_id, key)
|
||||
|
||||
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Return a specific ir_email body. The main purpose of this method
|
||||
is to be inherited by Portal, to add a link for signing in, in
|
||||
each notification email a partner receives.
|
||||
|
||||
:param mail: mail.mail browse_record
|
||||
:param partner: browse_record of the specific recipient partner
|
||||
"""
|
||||
if partner:
|
||||
portal_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal', 'portal')
|
||||
portal_id = portal_ref and portal_ref[1] or False
|
||||
url = self._generate_signin_url(cr, uid, partner.id, portal_id, 1234, context=context)
|
||||
body = tools.append_content_to_html(mail.body_html, url)
|
||||
return body
|
||||
else:
|
||||
return super(mail_mail_portal, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import test_portal
|
||||
|
||||
checks = [
|
||||
test_portal,
|
||||
]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests import test_mail
|
||||
from openerp.tools import append_content_to_html
|
||||
|
||||
|
||||
class test_portal(test_mail.TestMailMockups):
|
||||
|
||||
def setUp(self):
|
||||
super(test_portal, self).setUp()
|
||||
self.ir_model = self.registry('ir.model')
|
||||
self.mail_group = self.registry('mail.group')
|
||||
self.mail_mail = self.registry('mail.mail')
|
||||
self.res_users = self.registry('res.users')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
|
||||
# create a 'pigs' group that will be used through the various tests
|
||||
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
|
||||
|
||||
def test_00_mail_invite(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
user_admin = self.res_users.browse(cr, uid, uid)
|
||||
self.mail_invite = self.registry('mail.wizard.invite')
|
||||
base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='')
|
||||
portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'portal')
|
||||
portal_id = portal_ref and portal_ref[1] or False
|
||||
|
||||
# 0 - Admin
|
||||
p_a_id = user_admin.partner_id.id
|
||||
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE1: generated URL
|
||||
# ----------------------------------------
|
||||
|
||||
url = self.mail_mail._generate_signin_url(cr, uid, p_b_id, portal_id, 1234)
|
||||
self.assertEqual(url, base_url + '/login?action=signin&partner_id=%s&group=%s&key=%s' % (p_b_id, portal_id, 1234),
|
||||
'generated signin URL incorrect')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: invite Bert
|
||||
# ----------------------------------------
|
||||
|
||||
_sent_email_subject = 'Invitation to follow Pigs'
|
||||
_sent_email_body = append_content_to_html('<div>You have been invited to follow Pigs.</div>', url)
|
||||
|
||||
# Do: create a mail_wizard_invite, validate it
|
||||
self._init_mock_build_email()
|
||||
mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, p_b_id)]}, {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id})
|
||||
self.mail_invite.add_followers(cr, uid, [mail_invite_id])
|
||||
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
|
||||
# Test: Pigs followers should contain Admin and Bert
|
||||
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
|
||||
self.assertEqual(set(follower_ids), set([p_a_id, p_b_id]), 'Pigs followers after invite is incorrect')
|
||||
# Test: sent email subject, body
|
||||
self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert')
|
||||
for sent_email in self._build_email_kwargs_list:
|
||||
self.assertEqual(sent_email.get('subject'), _sent_email_subject, 'sent email subject incorrect')
|
||||
self.assertEqual(sent_email.get('body'), _sent_email_body, 'sent email body incorrect')
|
|
@ -30,7 +30,7 @@ class sale_advance_payment_inv(osv.osv_memory):
|
|||
'advance_payment_method':fields.selection(
|
||||
[('all', 'Invoice the whole sale order'), ('percentage','Percentage'), ('fixed','Fixed price (deposit)'),
|
||||
('lines', 'Some order lines')],
|
||||
'Invoice Method', required=True,
|
||||
'What do you want to invoice?', required=True,
|
||||
help="""Use All to create the final invoice.
|
||||
Use Percentage to invoice a percentage of the total amount.
|
||||
Use Fixed Price to invoice a specific amound in advance.
|
||||
|
|
|
@ -117,6 +117,8 @@ openerp.web_linkedin = function(instance) {
|
|||
"count": 25,
|
||||
}).result(function(result) {
|
||||
children_def.resolve(result);
|
||||
}).error(function() {
|
||||
children_def.reject();
|
||||
});
|
||||
defs.push(children_def.pipe(function(result) {
|
||||
result = _.reject(result.people.values || [], function(el) {
|
||||
|
@ -130,6 +132,8 @@ openerp.web_linkedin = function(instance) {
|
|||
var p_to_change = _.toArray(arguments);
|
||||
to_change.child_ids = p_to_change;
|
||||
});
|
||||
}, function() {
|
||||
return $.when();
|
||||
}));
|
||||
/* TODO
|
||||
to_change.linkedinUrl = _.str.sprintf("http://www.linkedin.com/company/%d", entity.id);
|
||||
|
|
Loading…
Reference in New Issue