commit
840a0c9590
|
@ -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"/>
|
||||
|
|
|
@ -51,10 +51,10 @@ class account_config_settings(osv.osv_memory):
|
|||
'code_digits': fields.integer('# of Digits', help="No. of digits to use for account code"),
|
||||
'tax_calculation_rounding_method': fields.related('company_id',
|
||||
'tax_calculation_rounding_method', type='selection', selection=[
|
||||
('round_per_line', 'Round per Line'),
|
||||
('round_globally', 'Round Globally'),
|
||||
('round_per_line', 'Round per line'),
|
||||
('round_globally', 'Round globally'),
|
||||
], string='Tax calculation rounding method',
|
||||
help="If you select 'Round per Line' : for each tax, the tax amount will first be computed and rounded for each PO/SO/invoice line and then these rounded amounts will be summed, leading to the total amount for that tax. If you select 'Round Globally': for each tax, the tax amount will be computed for each PO/SO/invoice line, then these amounts will be summed and eventually this total tax amount will be rounded. If you sell with tax included, you should choose 'Round per line' because you certainly want the sum of your tax-included line subtotals to be equal to the total amount with taxes."),
|
||||
help="If you select 'Round per line' : for each tax, the tax amount will first be computed and rounded for each PO/SO/invoice line and then these rounded amounts will be summed, leading to the total amount for that tax. If you select 'Round globally': for each tax, the tax amount will be computed for each PO/SO/invoice line, then these amounts will be summed and eventually this total tax amount will be rounded. If you sell with tax included, you should choose 'Round per line' because you certainly want the sum of your tax-included line subtotals to be equal to the total amount with taxes."),
|
||||
'sale_tax': fields.many2one("account.tax.template", "Default sale tax"),
|
||||
'purchase_tax': fields.many2one("account.tax.template", "Default purchase tax"),
|
||||
'sale_tax_rate': fields.float('Sales tax (%)'),
|
||||
|
@ -72,40 +72,40 @@ class account_config_settings(osv.osv_memory):
|
|||
'sale_refund_journal_id': fields.many2one('account.journal', 'Sale refund journal'),
|
||||
'sale_refund_sequence_prefix': fields.related('sale_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Credit note sequence'),
|
||||
'sale_refund_sequence_next': fields.related('sale_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next credit note number'),
|
||||
'purchase_journal_id': fields.many2one('account.journal', 'Purchase Journal'),
|
||||
'purchase_journal_id': fields.many2one('account.journal', 'Purchase journal'),
|
||||
'purchase_sequence_prefix': fields.related('purchase_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier invoice sequence'),
|
||||
'purchase_sequence_next': fields.related('purchase_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier invoice number'),
|
||||
'purchase_refund_journal_id': fields.many2one('account.journal', 'Purchase refund journal'),
|
||||
'purchase_refund_sequence_prefix': fields.related('purchase_refund_journal_id', 'sequence_id', 'prefix', type='char', string='Supplier credit note sequence'),
|
||||
'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier credit note number'),
|
||||
|
||||
'module_account_check_writing': fields.boolean('pay your suppliers by check',
|
||||
'module_account_check_writing': fields.boolean('Pay your suppliers by check',
|
||||
help="""This allows you to check writing and printing.
|
||||
This installs the module account_check_writing."""),
|
||||
'module_account_accountant': fields.boolean('full accounting features: journals, legal statements, chart of accounts, etc.',
|
||||
'module_account_accountant': fields.boolean('Full accounting features: journals, legal statements, chart of accounts, etc.',
|
||||
help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of Accounts, ...)"""),
|
||||
'module_account_asset': fields.boolean('assets management',
|
||||
'module_account_asset': fields.boolean('Assets management',
|
||||
help="""This allows you to manage the assets owned by a company or a person.
|
||||
It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.
|
||||
This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments,
|
||||
but not accounting (Journal Items, Chart of Accounts, ...)"""),
|
||||
'module_account_budget': fields.boolean('budget management',
|
||||
'module_account_budget': fields.boolean('Budget management',
|
||||
help="""This allows accountants to manage analytic and crossovered budgets.
|
||||
Once the master budgets and the budgets are defined,
|
||||
the project managers can set the planned amount on each analytic account.
|
||||
This installs the module account_budget."""),
|
||||
'module_account_payment': fields.boolean('manage payment orders',
|
||||
'module_account_payment': fields.boolean('Manage payment orders',
|
||||
help="""This allows you to create and manage your payment orders, with purposes to
|
||||
* serve as base for an easy plug-in of various automated payment mechanisms, and
|
||||
* provide a more efficient way to manage invoice payments.
|
||||
This installs the module account_payment."""),
|
||||
'module_account_voucher': fields.boolean('manage customer payments',
|
||||
'module_account_voucher': fields.boolean('Manage customer payments',
|
||||
help="""This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.
|
||||
This installs the module account_voucher."""),
|
||||
'module_account_followup': fields.boolean('manage customer payment follow-ups',
|
||||
'module_account_followup': fields.boolean('Manage customer payment follow-ups',
|
||||
help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
|
||||
This installs the module account_followup."""),
|
||||
'group_proforma_invoices': fields.boolean('allow pro-forma invoices',
|
||||
'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
|
||||
implied_group='account.group_proforma_invoices',
|
||||
help="Allows you to put invoices in pro-forma state."),
|
||||
'default_sale_tax': fields.many2one('account.tax', 'Default sale tax',
|
||||
|
@ -114,7 +114,7 @@ class account_config_settings(osv.osv_memory):
|
|||
help="This purchase tax will be assigned by default on new products."),
|
||||
'decimal_precision': fields.integer('Decimal precision on journal entries',
|
||||
help="""As an example, a decimal precision of 2 will allow journal entries like: 9.99 EUR, whereas a decimal precision of 4 will allow journal entries like: 0.0231 EUR."""),
|
||||
'group_multi_currency': fields.boolean('allow multi currencies',
|
||||
'group_multi_currency': fields.boolean('Allow multi currencies',
|
||||
implied_group='base.group_multi_currency',
|
||||
help="Allows you multi currency environment"),
|
||||
}
|
||||
|
@ -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>
|
||||
|
|
|
@ -25,7 +25,7 @@ class base_config_settings(osv.TransientModel):
|
|||
_inherit = 'base.config.settings'
|
||||
|
||||
_columns = {
|
||||
'auth_signup_uninvited': fields.boolean('allow public users to sign up', help="If unchecked only invited users may sign up"),
|
||||
'auth_signup_uninvited': fields.boolean('Allow public users to sign up', help="If unchecked only invited users may sign up"),
|
||||
'auth_signup_template_user_id': fields.many2one('res.users', 'Template user for new users created through signup'),
|
||||
}
|
||||
|
||||
|
|
|
@ -25,16 +25,16 @@ class base_config_settings(osv.osv_memory):
|
|||
_name = 'base.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
_columns = {
|
||||
'module_multi_company': fields.boolean('manage multiple companies',
|
||||
'module_multi_company': fields.boolean('Manage multiple companies',
|
||||
help="""Work in multi-company environments, with appropriate security access between companies.
|
||||
This installs the module multi_company."""),
|
||||
'module_share': fields.boolean('allow documents sharing',
|
||||
'module_share': fields.boolean('Allow documents sharing',
|
||||
help="""Share or embbed any screen of openerp."""),
|
||||
'module_portal': fields.boolean('activate the customer/supplier portal',
|
||||
'module_portal': fields.boolean('Activate the customer/supplier portal',
|
||||
help="""Give access your customers and suppliers to their documents."""),
|
||||
'module_auth_anonymous': fields.boolean('activate the public portal',
|
||||
'module_auth_anonymous': fields.boolean('Activate the public portal',
|
||||
help="""Enable the public part of openerp, openerp becomes a public website."""),
|
||||
'module_auth_oauth': fields.boolean('use external authentication providers, sign in with google, facebook, ...'),
|
||||
'module_auth_oauth': fields.boolean('Use external authentication providers, sign in with google, facebook, ...'),
|
||||
'module_base_import': fields.boolean("Allow users to import data from CSV files"),
|
||||
}
|
||||
|
||||
|
@ -57,17 +57,17 @@ class sale_config_settings(osv.osv_memory):
|
|||
_name = 'sale.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
_columns = {
|
||||
'module_web_linkedin': fields.boolean('get contacts automatically from LinkedIn',
|
||||
'module_web_linkedin': fields.boolean('Get contacts automatically from linkedIn',
|
||||
help="""When you create a new contact (person or company), you will be able to load all the data from LinkedIn (photos, address, etc)."""),
|
||||
'module_crm': fields.boolean('CRM'),
|
||||
'module_plugin_thunderbird': fields.boolean('enable Thunderbird plugin',
|
||||
'module_plugin_thunderbird': fields.boolean('Enable Thunderbird plugin',
|
||||
help="""The plugin allows you archive email and its attachments to the selected
|
||||
OpenERP objects. You can select a partner, or a lead and
|
||||
attach the selected mail as a .eml file in
|
||||
the attachment of a selected record. You can create documents for CRM Lead,
|
||||
Partner from the selected emails.
|
||||
This installs the module plugin_thunderbird."""),
|
||||
'module_plugin_outlook': fields.boolean('enable Outlook plugin',
|
||||
'module_plugin_outlook': fields.boolean('Enable Outlook plugin',
|
||||
help="""The Outlook plugin allows you to select an object that you would like to add
|
||||
to your email and its attachments from MS Outlook. You can select a partner,
|
||||
or a lead object and archive a selected
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<div>
|
||||
<p>
|
||||
<label string="You will find more options in your company details: address for the header and footer, overdue payments texts, etc."/>
|
||||
<button type="object" name="open_company" string="Configure Your Company Data" icon="gtk-execute" class="oe_inline oe_link"/>
|
||||
<button type="object" name="open_company" string="Configure your company data" icon="gtk-execute" class="oe_inline oe_link"/>
|
||||
</p>
|
||||
</div>
|
||||
<group>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -26,19 +26,19 @@ class crm_configuration(osv.osv_memory):
|
|||
_inherit = ['sale.config.settings', 'fetchmail.config.settings']
|
||||
|
||||
_columns = {
|
||||
'fetchmail_lead': fields.boolean("create leads from incoming mails",
|
||||
'fetchmail_lead': fields.boolean("Create leads from incoming mails",
|
||||
fetchmail_model='crm.lead', fetchmail_name='Incoming Leads',
|
||||
help="""Allows you to configure your incoming mail server, and create leads from incoming emails."""),
|
||||
'module_crm_caldav': fields.boolean("applications with Caldav protocol",
|
||||
'module_crm_caldav': fields.boolean("Applications with Caldav protocol",
|
||||
help="""Use protocol caldav to synchronize meetings with other calendar applications (like Sunbird).
|
||||
This installs the module crm_caldav."""),
|
||||
'module_import_sugarcrm': fields.boolean("SugarCRM",
|
||||
help="""Import SugarCRM leads, opportunities, users, accounts, contacts, employees, meetings, phonecalls, emails, project and project tasks data.
|
||||
This installs the module import_sugarcrm."""),
|
||||
'module_import_google': fields.boolean("Google (Contacts and Calendar)",
|
||||
'module_import_google': fields.boolean("Google (contacts and calendar)",
|
||||
help="""Import google contact in partner address and add google calendar events details in Meeting.
|
||||
This installs the module import_google."""),
|
||||
'module_google_map': fields.boolean("add google maps on customers",
|
||||
'module_google_map': fields.boolean("Add google maps on customers",
|
||||
help="""Locate customers on Google Map.
|
||||
This installs the module google_map."""),
|
||||
'group_fund_raising': fields.boolean("Manage Fund Raising",
|
||||
|
|
|
@ -26,7 +26,7 @@ class crm_claim_settings(osv.osv_memory):
|
|||
_inherit = ['sale.config.settings', 'fetchmail.config.settings']
|
||||
|
||||
_columns = {
|
||||
'fetchmail_claim': fields.boolean("create claims from incoming mails",
|
||||
'fetchmail_claim': fields.boolean("Create claims from incoming mails",
|
||||
fetchmail_model='crm.claim', fetchmail_name='Incoming Claims',
|
||||
help="""Allows you to configure your incoming mail server, and create claims from incoming emails."""),
|
||||
}
|
||||
|
|
|
@ -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 !'})
|
||||
|
|
|
@ -26,23 +26,23 @@ class hr_config_settings(osv.osv_memory):
|
|||
_inherit = 'res.config.settings'
|
||||
|
||||
_columns = {
|
||||
'module_hr_timesheet_sheet': fields.boolean('allow timesheets validation by managers',
|
||||
'module_hr_timesheet_sheet': fields.boolean('Allow timesheets validation by managers',
|
||||
help ="""This installs the module hr_timesheet_sheet."""),
|
||||
'module_hr_attendance': fields.boolean('track attendances',
|
||||
'module_hr_attendance': fields.boolean('Track attendances',
|
||||
help ="""This installs the module hr_attendance."""),
|
||||
'module_hr_timesheet': fields.boolean('manage timesheets',
|
||||
'module_hr_timesheet': fields.boolean('Manage timesheets',
|
||||
help ="""This installs the module hr_timesheet."""),
|
||||
'module_hr_holidays': fields.boolean('manage holidays, leaves and allocation requests',
|
||||
'module_hr_holidays': fields.boolean('Manage holidays, leaves and allocation requests',
|
||||
help ="""This installs the module hr_holidays."""),
|
||||
'module_hr_expense': fields.boolean('manage employees expenses',
|
||||
'module_hr_expense': fields.boolean('Manage employees expenses',
|
||||
help ="""This installs the module hr_expense."""),
|
||||
'module_hr_recruitment': fields.boolean('manage the recruitment process',
|
||||
'module_hr_recruitment': fields.boolean('Manage the recruitment process',
|
||||
help ="""This installs the module hr_recruitment."""),
|
||||
'module_hr_contract': fields.boolean('record contracts per employee',
|
||||
'module_hr_contract': fields.boolean('Record contracts per employee',
|
||||
help ="""This installs the module hr_contract."""),
|
||||
'module_hr_evaluation': fields.boolean('organize employees periodic evaluation',
|
||||
'module_hr_evaluation': fields.boolean('Organize employees periodic evaluation',
|
||||
help ="""This installs the module hr_evaluation."""),
|
||||
'module_hr_payroll': fields.boolean('manage payroll',
|
||||
'module_hr_payroll': fields.boolean('Manage payroll',
|
||||
help ="""This installs the module hr_payroll."""),
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,6 @@ from osv import osv, fields
|
|||
class human_resources_configuration(osv.osv_memory):
|
||||
_inherit = 'hr.config.settings'
|
||||
_columns = {
|
||||
'module_hr_payroll_account': fields.boolean('link your payroll to accounting system',
|
||||
help ="""Create Journal Entries from Payslips"""),
|
||||
'module_hr_payroll_account': fields.boolean('Link your payroll to accounting system',
|
||||
help ="""Create journal entries from payslips"""),
|
||||
}
|
||||
|
|
|
@ -26,10 +26,10 @@ class hr_applicant_settings(osv.osv_memory):
|
|||
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
|
||||
|
||||
_columns = {
|
||||
'module_document_ftp': fields.boolean('allow the automatic indexation of resumes',
|
||||
'module_document_ftp': fields.boolean('Allow the automatic indexation of resumes',
|
||||
help="""Manage your CV's and motivation letter related to all applicants.
|
||||
This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)"""),
|
||||
'fetchmail_applicants': fields.boolean('create applicants from an incoming email account',
|
||||
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
|
||||
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
|
||||
help ="""Allow applicants to send their job application to an email address (jobs@mycompany.com),
|
||||
and create automatically application documents in the system."""),
|
||||
|
|
|
@ -25,16 +25,16 @@ class knowledge_config_settings(osv.osv_memory):
|
|||
_name = 'knowledge.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
_columns = {
|
||||
'module_document_page': fields.boolean('create static web pages',
|
||||
'module_document_page': fields.boolean('Create static web pages',
|
||||
help="""This installs the module document_page."""),
|
||||
'module_document': fields.boolean('manage documents',
|
||||
'module_document': fields.boolean('Manage documents',
|
||||
help="""This is a complete document management system, with: user authentication,
|
||||
full document search (but pptx and docx are not supported), and a document dashboard.
|
||||
This installs the module document."""),
|
||||
'module_document_ftp': fields.boolean('share repositories (FTP)',
|
||||
'module_document_ftp': fields.boolean('Share repositories (FTP)',
|
||||
help="""Access your documents in OpenERP through an FTP interface.
|
||||
This installs the module document_ftp."""),
|
||||
'module_document_webdav': fields.boolean('share repositories (WebDAV)',
|
||||
'module_document_webdav': fields.boolean('Share repositories (WebDAV)',
|
||||
help="""Access your documents in OpenERP through WebDAV.
|
||||
This installs the module document_webdav."""),
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -86,7 +86,7 @@ class mail_alias(osv.Model):
|
|||
help="Optional ID of a thread (record) to which all incoming "
|
||||
"messages will be attached, even if they did not reply to it. "
|
||||
"If set, this will disable the creation of new records completely."),
|
||||
'alias_domain': fields.function(_get_alias_domain, string="Alias Domain", type='char', size=None),
|
||||
'alias_domain': fields.function(_get_alias_domain, string="Alias domain", type='char', size=None),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -25,14 +25,14 @@ class marketing_config_settings(osv.osv_memory):
|
|||
_name = 'marketing.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
_columns = {
|
||||
'module_marketing_campaign': fields.boolean('Marketing Campaigns',
|
||||
'module_marketing_campaign': fields.boolean('Marketing campaigns',
|
||||
help="""Provides leads automation through marketing campaigns.
|
||||
Campaigns can in fact be defined on any resource, not just CRM leads.
|
||||
This installs the module marketing_campaign."""),
|
||||
'module_marketing_campaign_crm_demo': fields.boolean('Demo Data for Marketing Campaigns',
|
||||
'module_marketing_campaign_crm_demo': fields.boolean('Demo data for marketing campaigns',
|
||||
help="""Installs demo data like leads, campaigns and segments for Marketing Campaigns.
|
||||
This installs the module marketing_campaign_crm_demo."""),
|
||||
'module_crm_profiling': fields.boolean('Track Customer Profile to Focus your Campaigns',
|
||||
'module_crm_profiling': fields.boolean('Track customer profile to focus your campaigns',
|
||||
help="""Allows users to perform segmentation within partners.
|
||||
This installs the module crm_profiling."""),
|
||||
}
|
||||
|
|
|
@ -28,11 +28,11 @@ class mrp_config_settings(osv.osv_memory):
|
|||
_inherit = 'res.config.settings'
|
||||
|
||||
_columns = {
|
||||
'module_stock_planning': fields.boolean('manage master production shedule',
|
||||
'module_stock_planning': fields.boolean('Manage master production shedule',
|
||||
help ="""This allows to create a manual procurement plan apart of the normal MRP scheduling,
|
||||
which works automatically based on minimum stock rules.
|
||||
This installs the module stock_planning."""),
|
||||
'module_mrp_repair': fields.boolean("manage repairs of products ",
|
||||
'module_mrp_repair': fields.boolean("Manage repairs of products ",
|
||||
help="""Allows to manage all product repairs.
|
||||
* Add/remove products in the reparation
|
||||
* Impact for stocks
|
||||
|
@ -41,34 +41,34 @@ class mrp_config_settings(osv.osv_memory):
|
|||
* Repair quotation report
|
||||
* Notes for the technician and for the final customer.
|
||||
This installs the module mrp_repair."""),
|
||||
'module_mrp_operations': fields.boolean("allow detailed planning of work order",
|
||||
'module_mrp_operations': fields.boolean("Allow detailed planning of work order",
|
||||
help="""This allows to add state, date_start,date_stop in production order operation lines (in the "Work Centers" tab).
|
||||
This installs the module mrp_operations."""),
|
||||
'module_mrp_subproduct': fields.boolean("produce several products from one manufacturing order",
|
||||
'module_mrp_subproduct': fields.boolean("Produce several products from one manufacturing order",
|
||||
help="""You can configure sub-products in the bill of material.
|
||||
Without this module: A + B + C -> D.
|
||||
With this module: A + B + C -> D + E.
|
||||
This installs the module mrp_subproduct."""),
|
||||
'module_mrp_jit': fields.boolean("generate procurement in real time",
|
||||
'module_mrp_jit': fields.boolean("Generate procurement in real time",
|
||||
help="""This allows Just In Time computation of procurement orders.
|
||||
All procurement orders will be processed immediately, which could in some
|
||||
cases entail a small performance impact.
|
||||
This installs the module mrp_jit."""),
|
||||
'module_stock_no_autopicking': fields.boolean("manage manual picking to fulfill manufacturing orders ",
|
||||
'module_stock_no_autopicking': fields.boolean("Manage manual picking to fulfill manufacturing orders ",
|
||||
help="""This module allows an intermediate picking process to provide raw materials to production orders.
|
||||
For example to manage production made by your suppliers (sub-contracting).
|
||||
To achieve this, set the assembled product which is sub-contracted to "No Auto-Picking"
|
||||
and put the location of the supplier in the routing of the assembly operation.
|
||||
This installs the module stock_no_autopicking."""),
|
||||
'group_mrp_routings': fields.boolean("manage routings and work orders ",
|
||||
'group_mrp_routings': fields.boolean("Manage routings and work orders ",
|
||||
implied_group='mrp.group_mrp_routings',
|
||||
help="""Routings allow you to create and manage the manufacturing operations that should be followed
|
||||
within your work centers in order to produce a product. They are attached to bills of materials
|
||||
that will define the required raw materials."""),
|
||||
'group_mrp_properties': fields.boolean("allow several bill of materials per products using properties",
|
||||
'group_mrp_properties': fields.boolean("Allow several bill of materials per products using properties",
|
||||
implied_group='product.group_mrp_properties',
|
||||
help="""The selection of the right Bill of Material to use will depend on the properties specified on the sale order and the Bill of Material."""),
|
||||
'module_product_manufacturer': fields.boolean("define manufacturers on products ",
|
||||
'module_product_manufacturer': fields.boolean("Define manufacturers on products ",
|
||||
help="""This allows you to define the following for a product:
|
||||
* Manufacturer
|
||||
* Manufacturer Product Name
|
||||
|
|
|
@ -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')
|
|
@ -27,38 +27,38 @@ class project_configuration(osv.osv_memory):
|
|||
_inherit = 'res.config.settings'
|
||||
|
||||
_columns = {
|
||||
'module_project_mrp': fields.boolean('generate tasks from sale orders',
|
||||
'module_project_mrp': fields.boolean('Generate tasks from sale orders',
|
||||
help ="""This feature automatically creates project tasks from service products in sale orders.
|
||||
More precisely, tasks are created for procurement lines with product of type 'Service',
|
||||
procurement method 'Make to Order', and supply method 'Produce'.
|
||||
This installs the module project_mrp."""),
|
||||
'module_pad': fields.boolean("use integrated collaborative note pads on task",
|
||||
'module_pad': fields.boolean("Use integrated collaborative note pads on task",
|
||||
help="""Lets the company customize which Pad installation should be used to link to new pads
|
||||
(by default, http://ietherpad.com/).
|
||||
This installs the module pad."""),
|
||||
'module_project_timesheet': fields.boolean("record timesheet lines per tasks",
|
||||
'module_project_timesheet': fields.boolean("Record timesheet lines per tasks",
|
||||
help="""This allows you to transfer the entries under tasks defined for Project Management to
|
||||
the timesheet line entries for particular date and user, with the effect of creating,
|
||||
editing and deleting either ways.
|
||||
This installs the module project_timesheet."""),
|
||||
'module_project_long_term': fields.boolean("manage resources planning on gantt view",
|
||||
'module_project_long_term': fields.boolean("Manage resources planning on gantt view",
|
||||
help="""A long term project management module that tracks planning, scheduling, and resource allocation.
|
||||
This installs the module project_long_term."""),
|
||||
'module_project_issue': fields.boolean("track issues and bugs",
|
||||
'module_project_issue': fields.boolean("Track issues and bugs",
|
||||
help="""Provides management of issues/bugs in projects.
|
||||
This installs the module project_issue."""),
|
||||
'time_unit': fields.many2one('product.uom', 'Working time unit', required=True,
|
||||
help="""This will set the unit of measure used in projects and tasks."""),
|
||||
'module_project_issue_sheet': fields.boolean("invoice working time on issues",
|
||||
'module_project_issue_sheet': fields.boolean("Invoice working time on issues",
|
||||
help="""Provides timesheet support for the issues/bugs management in project.
|
||||
This installs the module project_issue_sheet."""),
|
||||
'group_tasks_work_on_tasks': fields.boolean("Log work activities on tasks",
|
||||
implied_group='project.group_tasks_work_on_tasks',
|
||||
help="Allows you to compute work on tasks."),
|
||||
'group_time_work_estimation_tasks': fields.boolean("manage time estimation on tasks",
|
||||
'group_time_work_estimation_tasks': fields.boolean("Manage time estimation on tasks",
|
||||
implied_group='project.group_time_work_estimation_tasks',
|
||||
help="Allows you to compute Time Estimation on tasks."),
|
||||
'group_manage_delegation_task': fields.boolean("allow task delegation",
|
||||
'group_manage_delegation_task': fields.boolean("Allow task delegation",
|
||||
implied_group='project.group_delegate_task',
|
||||
help="Allows you to delegate tasks to other users."),
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class project_issue_settings(osv.osv_memory):
|
|||
_inherit = ['project.config.settings', 'fetchmail.config.settings']
|
||||
|
||||
_columns = {
|
||||
'fetchmail_issue': fields.boolean("create issues from an incoming email account ",
|
||||
'fetchmail_issue': fields.boolean("Create issues from an incoming email account ",
|
||||
fetchmail_model='project.issue', fetchmail_name='Incoming Issues',
|
||||
help="""Allows you to configure your incoming mail server, and create issues from incoming emails."""),
|
||||
}
|
||||
|
|
|
@ -29,36 +29,36 @@ class purchase_config_settings(osv.osv_memory):
|
|||
|
||||
_columns = {
|
||||
'default_invoice_method': fields.selection(
|
||||
[('manual', 'Based on Purchase Order Lines'),
|
||||
('picking', 'Based on Receptions'),
|
||||
('order', 'Pre-Generate Draft Invoices based on Purchase Orders'),
|
||||
], 'default invoicing control method', required=True, default_model='purchase.order'),
|
||||
'group_purchase_pricelist':fields.boolean("manage pricelist per supplier",
|
||||
[('manual', 'Based on purchase order lines'),
|
||||
('picking', 'Based on receptions'),
|
||||
('order', 'Pre-generate draft invoices based on purchase orders'),
|
||||
], 'Default invoicing control method', required=True, default_model='purchase.order'),
|
||||
'group_purchase_pricelist':fields.boolean("Manage pricelist per supplier",
|
||||
implied_group='product.group_purchase_pricelist',
|
||||
help="""Allows to manage different prices based on rules per category of Supplier.
|
||||
Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
|
||||
'group_uom':fields.boolean("manage different units of measure for products",
|
||||
'group_uom':fields.boolean("Manage different units of measure for products",
|
||||
implied_group='product.group_uom',
|
||||
help="""Allows you to select and maintain different units of measure for products."""),
|
||||
'group_costing_method':fields.boolean("compute product cost price based on average cost",
|
||||
'group_costing_method':fields.boolean("Compute product cost price based on average cost",
|
||||
implied_group='product.group_costing_method',
|
||||
help="""Allows you to compute product cost price based on average cost."""),
|
||||
'group_purchase_delivery_address': fields.boolean("allow a different address for incoming products and invoicings",
|
||||
'group_purchase_delivery_address': fields.boolean("Allow a different address for incoming products and invoicings",
|
||||
implied_group='purchase.group_delivery_invoice_address',
|
||||
help="Allows you to specify different delivery and invoice addresses on a purchase order."),
|
||||
'module_purchase_analytic_plans': fields.boolean('allow using multiple analytic accounts on the same order',
|
||||
'module_purchase_analytic_plans': fields.boolean('Allow using multiple analytic accounts on the same order',
|
||||
help ="""Allows the user to maintain several analysis plans. These let you split
|
||||
lines on a purchase order between several accounts and analytic plans.
|
||||
This installs the module purchase_analytic_plans."""),
|
||||
'module_warning': fields.boolean("alerts by products or supllier",
|
||||
'module_warning': fields.boolean("Alerts by products or supplier",
|
||||
help="""Allow to configure warnings on products and trigger them when a user wants to purchase a given product or a given supplier.
|
||||
Example: Product: this product is deprecated, do not purchase more than 5.
|
||||
Supplier: don't forget to ask for an express delivery."""),
|
||||
|
||||
'module_purchase_double_validation': fields.boolean("force two levels of approvals",
|
||||
'module_purchase_double_validation': fields.boolean("Force two levels of approvals",
|
||||
help="""Provide a double validation mechanism for purchases exceeding minimum amount.
|
||||
This installs the module purchase_double_validation."""),
|
||||
'module_purchase_requisition': fields.boolean("manage purchase requisitions",
|
||||
'module_purchase_requisition': fields.boolean("Manage purchase requisitions",
|
||||
help="""Purchase Requisitions are used when you want to request quotations from several suppliers for a given set of products.
|
||||
You can configure per product if you directly do a Request for Quotation
|
||||
to one supplier or if you want a purchase requisition to negotiate with several suppliers."""),
|
||||
|
@ -74,9 +74,9 @@ class purchase_config_settings(osv.osv_memory):
|
|||
class account_config_settings(osv.osv_memory):
|
||||
_inherit = 'account.config.settings'
|
||||
_columns = {
|
||||
'module_purchase_analytic_plans': fields.boolean('use multiple analytic accounts on orders',
|
||||
'module_purchase_analytic_plans': fields.boolean('Use multiple analytic accounts on orders',
|
||||
help="""This allows install module purchase_analytic_plans."""),
|
||||
'group_analytic_account_for_purchases': fields.boolean('analytic accounting for purchases',
|
||||
'group_analytic_account_for_purchases': fields.boolean('Analytic accounting for purchases',
|
||||
implied_group='purchase.group_analytic_accounting',
|
||||
help="Allows you to specify an analytic account on purchase orders."),
|
||||
}
|
||||
|
|
|
@ -27,70 +27,70 @@ class sale_configuration(osv.osv_memory):
|
|||
_inherit = 'sale.config.settings'
|
||||
|
||||
_columns = {
|
||||
'group_invoice_so_lines': fields.boolean('generate invoices based on the sale order',
|
||||
'group_invoice_so_lines': fields.boolean('Generate invoices based on the sale order',
|
||||
implied_group='sale.group_invoice_so_lines',
|
||||
help="To allow your salesman to make invoices for sale order lines using the menu 'Lines to Invoice'."),
|
||||
'group_invoice_deli_orders': fields.boolean('generate invoices after and based on delivery orders',
|
||||
'group_invoice_deli_orders': fields.boolean('Generate invoices after and based on delivery orders',
|
||||
implied_group='sale.group_invoice_deli_orders',
|
||||
help="To allow your salesman to make invoices for Delivery Orders using the menu 'Deliveries to Invoice'."),
|
||||
'task_work': fields.boolean("prepare invoices based on task's activities",
|
||||
'task_work': fields.boolean("Prepare invoices based on task's activities",
|
||||
help="""Lets you transfer the entries under tasks defined for Project Management to
|
||||
the Timesheet line entries for particular date and particular user with the effect of creating, editing and deleting either ways
|
||||
and to automatically creates project tasks from procurement lines.
|
||||
This installs the modules project_timesheet and project_mrp."""),
|
||||
'timesheet': fields.boolean('prepare invoices based on timesheets',
|
||||
'timesheet': fields.boolean('Prepare invoices based on timesheets',
|
||||
help = """For modifying account analytic view to show important data to project manager of services companies.
|
||||
You can also view the report of account analytic summary user-wise as well as month wise.
|
||||
This installs the module account_analytic_analysis."""),
|
||||
'module_account_analytic_analysis': fields.boolean('use contracts management',
|
||||
'module_account_analytic_analysis': fields.boolean('Use contracts management',
|
||||
help = """Allows to define your customer contracts conditions: invoicing
|
||||
method (fixed price, on timesheet, advance invoice), the exact pricing
|
||||
(650€/day for a developer), the duration (one year support contract).
|
||||
You will be able to follow the progress of the contract and invoice automatically.
|
||||
It installs the account_analytic_analysis module."""),
|
||||
'default_order_policy': fields.selection(
|
||||
[('manual', 'Invoice Based on Sales Orders'), ('picking', 'Invoice Based on Deliveries')],
|
||||
[('manual', 'Invoice based on sales orders'), ('picking', 'Invoice based on deliveries')],
|
||||
'The default invoicing method is', default_model='sale.order',
|
||||
help="You can generate invoices based on sales orders or based on shippings."),
|
||||
'module_delivery': fields.boolean('allow adding shipping costs',
|
||||
'module_delivery': fields.boolean('Allow adding shipping costs',
|
||||
help ="""Allows you to add delivery methods in sale orders and delivery orders.
|
||||
You can define your own carrier and delivery grids for prices.
|
||||
This installs the module delivery."""),
|
||||
'time_unit': fields.many2one('product.uom', 'the default working time unit for services is'),
|
||||
'default_picking_policy' : fields.boolean("deliver all at once when all products are available.",
|
||||
'time_unit': fields.many2one('product.uom', 'The default working time unit for services is'),
|
||||
'default_picking_policy' : fields.boolean("Deliver all at once when all products are available.",
|
||||
help = "Sales order by default will be configured to deliver all products at once instead of delivering each product when it is available. This may have an impact on the shipping price."),
|
||||
'group_sale_pricelist':fields.boolean("use pricelists to adapt your price per customers",
|
||||
'group_sale_pricelist':fields.boolean("Use pricelists to adapt your price per customers",
|
||||
implied_group='product.group_sale_pricelist',
|
||||
help="""Allows to manage different prices based on rules per category of customers.
|
||||
Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
|
||||
'group_uom':fields.boolean("allow using different units of measures",
|
||||
'group_uom':fields.boolean("Allow using different units of measures",
|
||||
implied_group='product.group_uom',
|
||||
help="""Allows you to select and maintain different units of measure for products."""),
|
||||
'group_sale_delivery_address': fields.boolean("allow a different address for delivery and invoicing ",
|
||||
'group_sale_delivery_address': fields.boolean("Allow a different address for delivery and invoicing ",
|
||||
implied_group='sale.group_delivery_invoice_address',
|
||||
help="Allows you to specify different delivery and invoice addresses on a sale order."),
|
||||
'group_mrp_properties': fields.boolean('product properties on order lines',
|
||||
'group_mrp_properties': fields.boolean('Product properties on order lines',
|
||||
implied_group='sale.group_mrp_properties',
|
||||
help="Allows you to tag sale order lines with properties."),
|
||||
'group_discount_per_so_line': fields.boolean("allow setting a discount on the sale order lines",
|
||||
'group_discount_per_so_line': fields.boolean("Allow setting a discount on the sale order lines",
|
||||
implied_group='sale.group_discount_per_so_line',
|
||||
help="Allows you to apply some discount per sale order line."),
|
||||
'group_multiple_shops': fields.boolean("manage multiple shops",
|
||||
'group_multiple_shops': fields.boolean("Manage multiple shops",
|
||||
implied_group='stock.group_locations',
|
||||
help="This allows to configure and use multiple shops."),
|
||||
'module_warning': fields.boolean("allow configuring alerts by customer or products",
|
||||
'module_warning': fields.boolean("Allow configuring alerts by customer or products",
|
||||
help="""Allow to configure warnings on products and trigger them when a user wants to sale a given product or a given customer.
|
||||
Example: Product: this product is deprecated, do not purchase more than 5.
|
||||
Supplier: don't forget to ask for an express delivery."""),
|
||||
'module_sale_margin': fields.boolean("display margins on sales orders",
|
||||
'module_sale_margin': fields.boolean("Display margins on sales orders",
|
||||
help="""This adds the 'Margin' on sales order.
|
||||
This gives the profitability by calculating the difference between the Unit Price and Cost Price.
|
||||
This installs the module sale_margin."""),
|
||||
'module_sale_journal': fields.boolean("allow batch invoicing of delivery orders through journals",
|
||||
'module_sale_journal': fields.boolean("Allow batch invoicing of delivery orders through journals",
|
||||
help="""Allows you to categorize your sales and deliveries (picking lists) between different journals,
|
||||
and perform batch operations on journals.
|
||||
This installs the module sale_journal."""),
|
||||
'module_analytic_user_function': fields.boolean("one employee can have different roles per contract",
|
||||
'module_analytic_user_function': fields.boolean("One employee can have different roles per contract",
|
||||
help="""Allows you to define what is the default function of a specific user on a given account.
|
||||
This is mostly used when a user encodes his timesheet. The values are retrieved and the fields are auto-filled.
|
||||
But the possibility to change these values is still available.
|
||||
|
@ -172,9 +172,9 @@ class sale_configuration(osv.osv_memory):
|
|||
class account_config_settings(osv.osv_memory):
|
||||
_inherit = 'account.config.settings'
|
||||
_columns = {
|
||||
'module_sale_analytic_plans': fields.boolean('several analytic accounts on sales',
|
||||
'module_sale_analytic_plans': fields.boolean('Several analytic accounts on sales',
|
||||
help="""This allows install module sale_analytic_plans."""),
|
||||
'group_analytic_account_for_sales': fields.boolean('analytic accounting for sales',
|
||||
'group_analytic_account_for_sales': fields.boolean('Analytic accounting for sales',
|
||||
implied_group='sale.group_analytic_accounting',
|
||||
help="Allows you to specify an analytic account on sale orders."),
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -26,14 +26,14 @@ class stock_config_settings(osv.osv_memory):
|
|||
_inherit = 'res.config.settings'
|
||||
|
||||
_columns = {
|
||||
'module_claim_from_delivery': fields.boolean("allow claim on deliveries",
|
||||
'module_claim_from_delivery': fields.boolean("Allow claim on deliveries",
|
||||
help="""Adds a Claim link to the delivery order.
|
||||
This installs the module claim_from_delivery."""),
|
||||
'module_stock_invoice_directly': fields.boolean("create and open the invoice when the user finish a delivery order",
|
||||
'module_stock_invoice_directly': fields.boolean("Create and open the invoice when the user finish a delivery order",
|
||||
help="""This allows to automatically launch the invoicing wizard if the delivery is
|
||||
to be invoiced when you send or deliver goods.
|
||||
This installs the module stock_invoice_directly."""),
|
||||
'module_product_expiry': fields.boolean("expiry date on lots",
|
||||
'module_product_expiry': fields.boolean("Expiry date on lots",
|
||||
help="""Track different dates on products and serial numbers.
|
||||
The following dates can be tracked:
|
||||
- end of life
|
||||
|
@ -41,36 +41,36 @@ class stock_config_settings(osv.osv_memory):
|
|||
- removal date
|
||||
- alert date.
|
||||
This installs the module product_expiry."""),
|
||||
'module_stock_location': fields.boolean("create push/pull logistic rules",
|
||||
'module_stock_location': fields.boolean("Create push/pull logistic rules",
|
||||
help="""Provide push and pull inventory flows. Typical uses of this feature are:
|
||||
manage product manufacturing chains, manage default locations per product,
|
||||
define routes within your warehouse according to business needs, etc.
|
||||
This installs the module stock_location."""),
|
||||
'group_uom': fields.boolean("manage units of measure on products",
|
||||
'group_uom': fields.boolean("Manage units of measure on products",
|
||||
implied_group='product.group_uom',
|
||||
help="""Allows you to select and maintain different units of measure for products."""),
|
||||
'group_uos': fields.boolean("invoice products in a different unit of measure than the sale order",
|
||||
'group_uos': fields.boolean("Invoice products in a different unit of measure than the sale order",
|
||||
implied_group='product.group_uos',
|
||||
help="""Allows you to sell units of a product, but invoice based on a different unit of measure.
|
||||
For instance, you can sell pieces of meat that you invoice based on their weight."""),
|
||||
'group_stock_packaging': fields.boolean("allow to define several packaging methods on products",
|
||||
'group_stock_packaging': fields.boolean("Allow to define several packaging methods on products",
|
||||
implied_group='product.group_stock_packaging',
|
||||
help="""Allows you to create and manage your packaging dimensions and types you want to be maintained in your system."""),
|
||||
'group_stock_production_lot': fields.boolean("track serial number on products",
|
||||
'group_stock_production_lot': fields.boolean("Track serial number on products",
|
||||
implied_group='stock.group_production_lot',
|
||||
help="""This allows you to manage products by using serial numbers.
|
||||
When you select a lot, you can get the upstream or downstream traceability of the products contained in lot."""),
|
||||
'group_stock_tracking_lot': fields.boolean("track serial number on logistic units (pallets)",
|
||||
'group_stock_tracking_lot': fields.boolean("Track serial number on logistic units (pallets)",
|
||||
implied_group='stock.group_tracking_lot',
|
||||
help="""Allows you to get the upstream or downstream traceability of the products contained in lot."""),
|
||||
'group_stock_inventory_valuation': fields.boolean("generate accounting entries per stock movement",
|
||||
'group_stock_inventory_valuation': fields.boolean("Generate accounting entries per stock movement",
|
||||
implied_group='stock.group_inventory_valuation',
|
||||
help="""Allows to configure inventory valuations on products and product categories."""),
|
||||
'group_stock_multiple_locations': fields.boolean("manage multiple locations and warehouses",
|
||||
'group_stock_multiple_locations': fields.boolean("Manage multiple locations and warehouses",
|
||||
implied_group='stock.group_locations',
|
||||
help="""This allows to configure and use multiple stock locations and warehouses,
|
||||
instead of having a single default one."""),
|
||||
'group_product_variant': fields.boolean("support multiple variants per products ",
|
||||
'group_product_variant': fields.boolean("Support multiple variants per products ",
|
||||
implied_group='product.group_product_variant',
|
||||
help="""Allow to manage several variants per product. As an example, if you sell T-Shirts, for the same "Linux T-Shirt", you may have variants on sizes or colors; S, M, L, XL, XXL."""),
|
||||
'decimal_precision': fields.integer('Decimal precision on weight', help="As an example, a decimal precision of 2 will allow weights like: 9.99 kg, whereas a decimal precision of 4 will allow weights like: 0.0231 kg."),
|
||||
|
|
|
@ -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