[MERGE] OpenChatter "Last day before World's End" merge.

Message subtype update.
Mail:
- added a tracking mechanism compatible with message subtypes. Using a model-defined structure (see mail_thread), modified fields are tracked at write and generate logging messages that can be attached to one or several subtypes (i.e. opportunity won/lost)
- added a mechanism to automatically subscribe followers of a 'parent' document to a 'child' document (i.e. project -> task, project -> issue, salesteam -> crm). This is done by propagating subscription of subtypes, using a parent_id and relation_field on subtypes.
Addons:
- update to use the tracking mechanism. This allows to remove almost all message_post and send_note methods in the code, as those methods were generally simple logging methods
- update to the the automatic followers subscribe
- updated subtypes
- added some tests
- removed dead code about old needaction, logging or chatter
- some bug fixes

bzr revid: tde@openerp.com-20121220144731-bd2xzz3au7s9994y
This commit is contained in:
Thibault Delavallée 2012-12-20 15:47:31 +01:00
commit 6e1f3c9d5d
65 changed files with 926 additions and 1489 deletions

View File

@ -184,7 +184,14 @@ class account_invoice(osv.osv):
_inherit = ['mail.thread']
_description = 'Invoice'
_order = "id desc"
_track = {
'type': {
},
'state': {
'account.mt_invoice_paid': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'paid' and obj['type'] in ('out_invoice', 'out_refund'),
'account.mt_invoice_validated': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open' and obj['type'] in ('out_invoice', 'out_refund'),
},
}
_columns = {
'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}),
'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}),
@ -194,7 +201,7 @@ class account_invoice(osv.osv):
('in_invoice','Supplier Invoice'),
('out_refund','Customer Refund'),
('in_refund','Supplier Refund'),
],'Type', readonly=True, select=True, change_default=True),
],'Type', readonly=True, select=True, change_default=True, track_visibility='always'),
'number': fields.related('move_id','name', type='char', readonly=True, size=64, relation='account.move', store=True, string='Number'),
'internal_number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
@ -210,7 +217,7 @@ class account_invoice(osv.osv):
('open','Open'),
('paid','Paid'),
('cancel','Cancelled'),
],'Status', select=True, readonly=True,
],'Status', select=True, readonly=True, track_visibility='onchange',
help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Invoice. \
\n* The \'Pro-forma\' when invoice is in Pro-forma status,invoice does not have an invoice number. \
\n* The \'Open\' status is used when user create invoice,a invoice number is generated.Its in open status till user does not pay invoice. \
@ -221,7 +228,7 @@ class account_invoice(osv.osv):
'date_due': fields.date('Due Date', readonly=True, states={'draft':[('readonly',False)]}, select=True,
help="If you use payment terms, the due date will be computed automatically at the generation "\
"of accounting entries. The payment term may compute several due dates, for example 50% now and 50% in one month, but if you want to force a due date, make sure that the payment term is not set on the invoice. If you keep the payment term and the due date empty, it means direct payment."),
'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
help="If you use payment terms, the due date will be computed automatically at the generation "\
"of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
@ -233,7 +240,7 @@ class account_invoice(osv.osv):
'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
'move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, select=1, ondelete='restrict', help="Link to the automatically generated Journal Items."),
'amount_untaxed': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Untaxed',
'amount_untaxed': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Untaxed', track_visibility='always',
store={
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
'account.invoice.tax': (_get_invoice_tax, None, 20),
@ -254,7 +261,7 @@ class account_invoice(osv.osv):
'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
},
multi='all'),
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
@ -278,7 +285,7 @@ class account_invoice(osv.osv):
help="Remaining amount due."),
'payment_ids': fields.function(_compute_lines, relation='account.move.line', type="many2many", string='Payments'),
'move_name': fields.char('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
'user_id': fields.many2one('res.users', 'Salesperson', readonly=True, states={'draft':[('readonly',False)]}),
'user_id': fields.many2one('res.users', 'Salesperson', readonly=True, track_visibility='onchange', states={'draft':[('readonly',False)]}),
'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]})
}
_defaults = {
@ -373,10 +380,7 @@ class account_invoice(osv.osv):
if context is None:
context = {}
try:
res = super(account_invoice, self).create(cr, uid, vals, context)
if res:
self.create_send_note(cr, uid, [res], context=context)
return res
return super(account_invoice, self).create(cr, uid, vals, context)
except Exception, e:
if '"journal_id" viol' in e.args[0]:
raise orm.except_orm(_('Configuration Error!'),
@ -440,7 +444,6 @@ class account_invoice(osv.osv):
if context is None:
context = {}
self.write(cr, uid, ids, {'state':'paid'}, context=context)
self.confirm_paid_send_note(cr, uid, ids, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
@ -1047,13 +1050,12 @@ class account_invoice(osv.osv):
self.write(cr, uid, ids, {})
for obj_inv in self.browse(cr, uid, ids, context=context):
id = obj_inv.id
invtype = obj_inv.type
number = obj_inv.number
move_id = obj_inv.move_id and obj_inv.move_id.id or False
reference = obj_inv.reference or ''
self.write(cr, uid, ids, {'internal_number':number})
self.write(cr, uid, ids, {'internal_number': number})
if invtype in ('in_invoice', 'in_refund'):
if not reference:
@ -1074,13 +1076,6 @@ class account_invoice(osv.osv):
'WHERE account_move_line.move_id = %s ' \
'AND account_analytic_line.move_id = account_move_line.id',
(ref, move_id))
for inv_id, name in self.name_get(cr, uid, [id]):
ctx = context.copy()
if obj_inv.type in ('out_invoice', 'out_refund'):
ctx = self.get_log_context(cr, uid, context=ctx)
message = _("Invoice '%s' is validated.") % name
self.message_post(cr, uid, [inv_id], body=message, context=context)
return True
def action_cancel(self, cr, uid, ids, context=None):
@ -1109,7 +1104,6 @@ class account_invoice(osv.osv):
# will be automatically deleted too
account_move_obj.unlink(cr, uid, move_ids, context=context)
self._log_event(cr, uid, ids, -1.0, 'Cancel Invoice')
self.invoice_cancel_send_note(cr, uid, ids, context=context)
return True
###################
@ -1332,8 +1326,8 @@ class account_invoice(osv.osv):
else:
code = invoice.currency_id.symbol
# TODO: use currency's formatting function
msg = _("Invoice '%s' is paid partially: %s%s of %s%s (%s%s remaining).") % \
(name, pay_amount, code, invoice.amount_total, code, total, code)
msg = _("Invoice partially paid: %s%s of %s%s (%s%s remaining).") % \
(pay_amount, code, invoice.amount_total, code, total, code)
self.message_post(cr, uid, [inv_id], body=msg, context=context)
self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
@ -1341,35 +1335,6 @@ class account_invoice(osv.osv):
self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
return True
# -----------------------------------------
# OpenChatter notifications and need_action
# -----------------------------------------
def _get_document_type(self, type):
type_dict = {
# Translation markers will have no effect at runtime, only used to properly flag export
'out_invoice': _('Customer invoice'),
'in_invoice': _('Supplier invoice'),
'out_refund': _('Customer Refund'),
'in_refund': _('Supplier Refund'),
}
return type_dict.get(type, 'Invoice')
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>created</b>.") % (self._get_document_type(obj.type)),
subtype="account.mt_invoice_new", context=context)
def confirm_paid_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (self._get_document_type(obj.type)),
subtype="account.mt_invoice_paid", context=context)
def invoice_cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (self._get_document_type(obj.type)),
context=context)
class account_invoice_line(osv.osv):

View File

@ -151,14 +151,16 @@
<field name="object">account.invoice</field>
</record>
<!-- mail: subtypes -->
<record id="mt_invoice_new" model="mail.message.subtype">
<field name="name">created</field>
<!-- Account-related subtypes for messaging / Chatter -->
<record id="mt_invoice_validated" model="mail.message.subtype">
<field name="name">Validated</field>
<field name="res_model">account.invoice</field>
<field name="description">Invoice validated</field>
</record>
<record id="mt_invoice_paid" model="mail.message.subtype">
<field name="name">paid</field>
<field name="name">Paid</field>
<field name="res_model">account.invoice</field>
<field name="description">Invoice paid</field>
</record>
</data>
</openerp>

View File

@ -266,6 +266,12 @@ class account_voucher(osv.osv):
_inherit = ['mail.thread']
_order = "date desc, id desc"
# _rec_name = 'number'
_track = {
'state': {
'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
},
}
_columns = {
'active': fields.boolean('Active', help="By default, reconciliation vouchers made on draft bank statements are set as inactive, which allow to hide the customer/supplier payment while the bank statement isn't confirmed."),
'type':fields.selection([
@ -293,7 +299,7 @@ class account_voucher(osv.osv):
('cancel','Cancelled'),
('proforma','Pro-forma'),
('posted','Posted')
], 'Status', readonly=True, size=32,
], 'Status', readonly=True, size=32, track_visibility='onchange',
help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Voucher. \
\n* The \'Pro-forma\' when voucher is in Pro-forma status,voucher does not have an voucher number. \
\n* The \'Posted\' status is used when user create voucher,a voucher number is generated and voucher entries are created in account \
@ -350,11 +356,6 @@ class account_voucher(osv.osv):
'payment_rate_currency_id': _get_payment_rate_currency,
}
def create(self, cr, uid, vals, context=None):
voucher = super(account_voucher, self).create(cr, uid, vals, context=context)
self.create_send_note(cr, uid, [voucher], context=context)
return voucher
def compute_tax(self, cr, uid, ids, context=None):
tax_pool = self.pool.get('account.tax')
partner_pool = self.pool.get('res.partner')
@ -1302,7 +1303,6 @@ class account_voucher(osv.osv):
'state': 'posted',
'number': name,
})
self.post_send_note(cr, uid, [voucher.id], context=context)
if voucher.journal_id.entry_posted:
move_pool.post(cr, uid, [move_id], context={})
# We automatically reconcile the account move lines.
@ -1310,8 +1310,6 @@ class account_voucher(osv.osv):
for rec_ids in rec_list_ids:
if len(rec_ids) >= 2:
reconcile = move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.writeoff_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id)
if reconcile:
self.reconcile_send_note(cr, uid, [voucher.id], context=context)
return True
def copy(self, cr, uid, id, default=None, context=None):
@ -1329,33 +1327,6 @@ class account_voucher(osv.osv):
default['date'] = time.strftime('%Y-%m-%d')
return super(account_voucher, self).copy(cr, uid, id, default, context)
# -----------------------------------------
# OpenChatter notifications and need_action
# -----------------------------------------
_document_type = {
'sale': 'Sales Receipt',
'purchase': 'Purchase Receipt',
'payment': 'Supplier Payment',
'receipt': 'Customer Payment',
False: 'Payment',
}
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s <b>created</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
def post_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
def reconcile_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
account_voucher()
class account_voucher_line(osv.osv):
_name = 'account.voucher.line'

View File

@ -13,10 +13,11 @@
<p>You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability. For advanced accounting features, you should install the "Accounting and Finance" module.</p>]]></field>
</record>
<!-- mail: subtypes -->
<record id="mt_voucher" model="mail.message.subtype">
<!-- Voucher-related subtypes for messaging / Chatter -->
<record id="mt_voucher_state_change" model="mail.message.subtype">
<field name="name">Status Change</field>
<field name="res_model">account.voucher</field>
<field name="description">Status &lt;b&gt;changed&lt;/b&gt;</field>
</record>
</data>

View File

@ -39,7 +39,7 @@ that have no counterpart in the general financial accounts.
'security/ir.model.access.csv',
'analytic_sequence.xml',
'analytic_view.xml',
'analytic_data.xml'
'analytic_data.xml',
],
'demo': [],
'installable': True,

View File

@ -31,6 +31,13 @@ class account_analytic_account(osv.osv):
_name = 'account.analytic.account'
_inherit = ['mail.thread']
_description = 'Analytic Account'
_track = {
'state': {
'analytic.mt_account_pending': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'pending',
'analytic.mt_account_closed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'close',
'analytic.mt_account_opened': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open',
},
}
def _compute_level_tree(self, cr, uid, ids, child_ids, res, field_names, context=None):
currency_obj = self.pool.get('res.currency')
@ -189,7 +196,7 @@ class account_analytic_account(osv.osv):
'date_start': fields.date('Start Date'),
'date': fields.date('Date End', select=True),
'company_id': fields.many2one('res.company', 'Company', required=False), #not required because we want to allow different companies to use the same chart of account, except for leaf accounts.
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','To Renew'),('close','Closed')], 'Status', required=True,),
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','To Renew'),('close','Closed')], 'Status', required=True, track_visibility='onchange'),
'currency_id': fields.function(_currency, fnct_inv=_set_company_currency, #the currency_id field is readonly except if it's a view account and if there is no company
store = {
'res.company': (_get_analytic_account, ['currency_id'], 10),
@ -308,23 +315,6 @@ class account_analytic_account(osv.osv):
account_ids = self.search(cr, uid, args, limit=limit, context=context)
return self.name_get(cr, uid, account_ids, context=context)
def create(self, cr, uid, vals, context=None):
contract = super(account_analytic_account, self).create(cr, uid, vals, context=context)
if contract:
self.create_send_note(cr, uid, [contract], context=context)
return contract
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
message = _("Contract <b>created</b>.")
if obj.partner_id:
message = _("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name,)
self.message_post(cr, uid, [obj.id], body=message,
subtype="analytic.mt_account_status", context=context)
account_analytic_account()
class account_analytic_line(osv.osv):
_name = 'account.analytic.line'
_description = 'Analytic Line'

View File

@ -2,10 +2,21 @@
<openerp>
<data noupdate="1">
<!-- mail: subtypes -->
<record id="mt_account_status" model="mail.message.subtype">
<field name="name">Status Change</field>
<!-- Analytic-account-related subtypes for messaging / Chatter -->
<record id="mt_account_pending" model="mail.message.subtype">
<field name="name">Contract to Renew</field>
<field name="res_model">account.analytic.account</field>
<field name="description">Contract &lt;b&gt;pending&lt;/b&gt;</field>
</record>
<record id="mt_account_closed" model="mail.message.subtype">
<field name="name">Contract Finished</field>
<field name="res_model">account.analytic.account</field>
<field name="description">Contract &lt;b&gt;closed&lt;/b&gt;</field>
</record>
<record id="mt_account_opened" model="mail.message.subtype">
<field name="name">Contract Opened</field>
<field name="res_model">account.analytic.account</field>
<field name="description">Stage &lt;b&gt;opened&lt;/b&gt;</field>
</record>
</data>

View File

@ -19,10 +19,11 @@
#
##############################################################################
from openerp.osv import fields, osv
from openerp import tools
from openerp.tools.translate import _
import time
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from openerp.tools.translate import _
from base_calendar import get_real_ids, base_calendar_id2real_id
from openerp.addons.base_status.base_state import base_state
#
@ -53,7 +54,7 @@ class crm_meeting(base_state, osv.Model):
string='Attendees', states={'done': [('readonly', True)]}),
'state': fields.selection(
[('draft', 'Unconfirmed'), ('open', 'Confirmed')],
string='Status', size=16, readonly=True),
string='Status', size=16, readonly=True, track_visibility='onchange'),
# Meeting fields
'name': fields.char('Meeting Subject', size=128, required=True, states={'done': [('readonly', True)]}),
'categ_ids': fields.many2many('crm.meeting.type', 'meeting_category_rel',
@ -111,21 +112,11 @@ class crm_meeting(base_state, osv.Model):
# ----------------------------------------
# shows events of the day for this user
def needaction_domain_get(self, cr, uid, domain=[], context={}):
return [('date','<=',time.strftime('%Y-%M-%D 23:59:59')), ('date_deadline','>=', time.strftime('%Y-%M-%D 00:00:00')), ('user_id','=',uid)]
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
return _('Meeting')
def case_open_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Meeting <b>confirmed</b>."), context=context)
def case_close_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Meeting <b>completed</b>."), context=context)
def _needaction_domain_get(self, cr, uid, context=None):
return [('date', '<=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('date_deadline', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT + ' 23:59:59')), ('user_id', '=', uid)]
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
cal_event_pool = self.pool.get('calendar.event')
if isinstance(thread_id, str):
thread_id = get_real_ids(thread_id)
return super(crm_meeting, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)

View File

@ -150,7 +150,6 @@ class base_stage(object):
if hasattr(self, 'onchange_stage_id'):
value = self.onchange_stage_id(cr, uid, ids, stage_id, context=context)['value']
value['stage_id'] = stage_id
self.stage_set_send_note(cr, uid, ids, stage_id, context=context)
return self.write(cr, uid, ids, value, context=context)
def stage_change(self, cr, uid, ids, op, order, context=None):
@ -210,7 +209,6 @@ class base_stage(object):
else:
raise osv.except_osv(_('Error!'), _("You are already at the top level of your sales-team category.\nTherefore you cannot escalate furthermore."))
self.write(cr, uid, [case.id], data, context=context)
case.case_escalate_send_note(case.section_id.parent_id, context=context)
return True
def case_open(self, cr, uid, ids, context=None):
@ -221,32 +219,23 @@ class base_stage(object):
if not case.user_id:
data['user_id'] = uid
self.case_set(cr, uid, [case.id], 'open', data, context=context)
self.case_open_send_note(cr, uid, [case.id], context=context)
return True
def case_close(self, cr, uid, ids, context=None):
""" Closes case """
self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
self.case_close_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
def case_cancel(self, cr, uid, ids, context=None):
""" Cancels case """
self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
self.case_cancel_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
def case_pending(self, cr, uid, ids, context=None):
""" Set case as pending """
self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
self.case_pending_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
def case_reset(self, cr, uid, ids, context=None):
""" Resets case as draft """
self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
self.case_reset_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
def case_set(self, cr, uid, ids, new_state_name=None, values_to_update=None, new_stage_id=None, context=None):
""" Generic method for setting case. This methods wraps the update
@ -273,7 +262,7 @@ class base_stage(object):
if values_to_update:
self.write(cr, uid, ids, values_to_update, context=context)
return True
def write(self, cr, uid, ids, vals, context=None):
res = super(base_stage,self).write(cr, uid, ids, vals, context)
if vals.get('stage_id'):
@ -293,69 +282,8 @@ class base_stage(object):
rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])], context=context)
return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
def _check(self, cr, uid, ids=False, context=None):
""" Function called by the scheduler to process cases for date actions.
Must be overriden by inheriting classes.
"""
return True
# ******************************
# Notifications
# ******************************
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
""" Default prefix for notifications. For example: "%s has been
<b>closed</b>.". As several models will inherit from base_stage,
this method returns a void string. Class using base_stage
will have to override this method to define the prefix they
want to display.
"""
return ''
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Send a notification when the stage changes. This method has
to be overriden, because each document will have its particular
behavior and/or stage model (such as project.task.type or
crm.case.stage).
"""
return True
def case_open_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_close_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_cancel_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>cancelled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_pending_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_reset_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
for id in ids:
if new_section:
msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
else:
msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True

View File

@ -106,7 +106,6 @@ class base_state(object):
else:
raise osv.except_osv(_('Error !'), _('You can not escalate, you are already at the top level regarding your sales-team category.'))
self.write(cr, uid, [case.id], data, context=context)
case.case_escalate_send_note(parent_id, context=context)
self._action(cr, uid, cases, 'escalate', context=context)
return True
@ -120,39 +119,30 @@ class base_state(object):
if not case.user_id:
values['user_id'] = uid
self.case_set(cr, uid, [case.id], 'open', values, context=context)
self.case_open_send_note(cr, uid, [case.id], context=context)
return True
def case_close(self, cr, uid, ids, context=None):
""" Closes case """
self.case_set(cr, uid, ids, 'done', {'date_closed': fields.datetime.now()}, context=context)
self.case_close_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'done', {'date_closed': fields.datetime.now()}, context=context)
def case_cancel(self, cr, uid, ids, context=None):
""" Cancels case """
self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
self.case_cancel_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
def case_pending(self, cr, uid, ids, context=None):
""" Sets case as pending """
self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
self.case_pending_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
def case_reset(self, cr, uid, ids, context=None):
""" Resets case as draft """
self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
self.case_reset_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
def case_set(self, cr, uid, ids, state_name, update_values=None, context=None):
""" Generic method for setting case. This methods wraps the update
of the record, as well as call to _action and browse_record
case setting to fill the cache.
:params: state_name: the new value of the state, such as
:params: state_name: the new value of the state, such as
'draft' or 'close'.
:params: update_values: values that will be added with the state
update when writing values to the record.
@ -162,8 +152,9 @@ class base_state(object):
if update_values is None:
update_values = {}
update_values['state'] = state_name
self.write(cr, uid, ids, update_values, context=context)
res = self.write(cr, uid, ids, update_values, context=context)
self._action(cr, uid, cases, state_name, context=context)
return res
def _action(self, cr, uid, cases, state_to, scrit=None, context=None):
if context is None:
@ -174,49 +165,3 @@ class base_state(object):
model_ids = model_obj.search(cr, uid, [('model','=',self._name)])
rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])])
return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context)
# ******************************
# Notifications
# ******************************
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
return ''
def case_open_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
for id in ids:
if new_section:
msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
else:
msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_close_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_cancel_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_pending_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True
def case_reset_send_note(self, cr, uid, ids, context=None):
for id in ids:
msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context)
return True

View File

@ -71,7 +71,18 @@ class crm_lead(base_stage, format_address, osv.osv):
_name = "crm.lead"
_description = "Lead/Opportunity"
_order = "priority,date_action,id desc"
_inherit = ['mail.thread','ir.needaction_mixin']
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'new',
'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
},
'stage_id': {
'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['new', 'cancel', 'done'],
},
}
def _get_default_section_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
@ -214,7 +225,7 @@ class crm_lead(base_stage, format_address, osv.osv):
return [('id', '=', '0')]
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', track_visibility='onchange',
select=True, help="Linked partner (optional). Usually created when converting the lead."),
'id': fields.integer('ID', readonly=True),
@ -223,8 +234,8 @@ class crm_lead(base_stage, format_address, osv.osv):
'date_action_last': fields.datetime('Last Action', readonly=1),
'date_action_next': fields.datetime('Next Action', readonly=1),
'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1),
'section_id': fields.many2one('crm.case.section', 'Sales Team', \
select=True, help='When sending mails, the default email address is taken from the sales team.'),
'section_id': fields.many2one('crm.case.section', 'Sales Team',
select=True, track_visibility='onchange', help='When sending mails, the default email address is taken from the sales team.'),
'create_date': fields.datetime('Creation Date' , readonly=True),
'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
'description': fields.text('Notes'),
@ -240,9 +251,9 @@ class crm_lead(base_stage, format_address, osv.osv):
'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
'date_closed': fields.datetime('Closed', readonly=True),
'stage_id': fields.many2one('crm.case.stage', 'Stage',
'stage_id': fields.many2one('crm.case.stage', 'Stage', track_visibility='onchange',
domain="['&', '&', ('fold', '=', False), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"),
'user_id': fields.many2one('res.users', 'Salesperson', select=True),
'user_id': fields.many2one('res.users', 'Salesperson', select=True, track_visibility='onchange'),
'referred': fields.char('Referred By', size=64),
'date_open': fields.datetime('Opened', readonly=True),
'day_open': fields.function(_compute_day, string='Days to Open', \
@ -255,7 +266,7 @@ class crm_lead(base_stage, format_address, osv.osv):
# Only used for type opportunity
'probability': fields.float('Success Rate (%)',group_operator="avg"),
'planned_revenue': fields.float('Expected Revenue'),
'planned_revenue': fields.float('Expected Revenue', track_visibility='always'),
'ref': fields.reference('Reference', selection=crm._links_get, size=128),
'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
'phone': fields.char("Phone", size=64),
@ -303,15 +314,6 @@ class crm_lead(base_stage, format_address, osv.osv):
('check_probability', 'check(probability >= 0 and probability <= 100)', 'The probability of closing the deal should be between 0% and 100%!')
]
def create(self, cr, uid, vals, context=None):
obj_id = super(crm_lead, self).create(cr, uid, vals, context)
section_id = self.browse(cr, uid, obj_id, context=context).section_id
if section_id:
followers = [follow.id for follow in section_id.message_follower_ids]
self.message_subscribe(cr, uid, [obj_id], followers, context=context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
if not stage_id:
return {'value':{}}
@ -413,7 +415,6 @@ class crm_lead(base_stage, format_address, osv.osv):
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0)], context=context)
if stage_id:
self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0}, new_stage_id=stage_id, context=context)
self.case_mark_lost_send_note(cr, uid, ids, context=context)
return True
def case_mark_won(self, cr, uid, ids, context=None):
@ -422,7 +423,6 @@ class crm_lead(base_stage, format_address, osv.osv):
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0)], context=context)
if stage_id:
self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0}, new_stage_id=stage_id, context=context)
self.case_mark_won_send_note(cr, uid, ids, context=context)
return True
def set_priority(self, cr, uid, ids, priority):
@ -700,7 +700,7 @@ class crm_lead(base_stage, format_address, osv.osv):
continue
vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
self.write(cr, uid, [lead.id], vals, context=context)
self.convert_opportunity_send_note(cr, uid, lead, context=context)
self.message_post(cr, uid, ids, body=_("Lead <b>converted into an Opportunity</b>"), subtype="crm.mt_lead_convert_to_opportunity", context=context)
if user_ids or section_id:
self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context)
@ -759,7 +759,8 @@ class crm_lead(base_stage, format_address, osv.osv):
res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id.id or False})
contact_id = res_partner.address_get(cr, uid, [partner_id])['default']
res = lead.write({'partner_id': partner_id}, context=context)
self._lead_set_partner_send_note(cr, uid, [lead.id], context)
message = _("<b>Partner</b> set to <em>%s</em>." % (lead.partner_id.name))
self.message_post(cr, uid, [lead.id], body=message, context=context)
return res
def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None):
@ -913,12 +914,7 @@ class crm_lead(base_stage, format_address, osv.osv):
stage = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
if stage.on_change:
vals['probability'] = stage.probability
if vals.get('section_id'):
section_id = self.pool.get('crm.case.section').browse(cr, uid, vals.get('section_id'), context=context)
if section_id:
vals.setdefault('message_follower_ids', [])
vals['message_follower_ids'] += [(4, follower.id) for follower in section_id.message_follower_ids]
return super(crm_lead,self).write(cr, uid, ids, vals, context)
return super(crm_lead, self).write(cr, uid, ids, vals, context=context)
def new_mail_send(self, cr, uid, ids, context=None):
'''
@ -1007,30 +1003,6 @@ class crm_lead(base_stage, format_address, osv.osv):
# OpenChatter methods and notifications
# ----------------------------------------
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """
stage_name = self.pool.get('crm.case.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body=_("Stage changed to <b>%s</b>.") % (stage_name), subtype="mt_crm_stage", context=context)
def case_get_note_msg_prefix(self, cr, uid, lead, context=None):
if isinstance(lead, (int, long)):
lead = self.browse(cr, uid, [lead], context=context)[0]
return ('Opportunity' if lead.type == 'opportunity' else 'Lead')
def create_send_note(self, cr, uid, ids, context=None):
for id in ids:
message = _("%s has been <b>created</b>.") % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=message, context=context)
return True
def case_mark_lost_send_note(self, cr, uid, ids, context=None):
message = _("Opportunity has been <b>lost</b>.")
return self.message_post(cr, uid, ids, body=message, subtype="mt_crm_lost", context=context)
def case_mark_won_send_note(self, cr, uid, ids, context=None):
message = _("Opportunity has been <b>won</b>.")
return self.message_post(cr, uid, ids, body=message, subtype="mt_crm_won", context=context)
def schedule_phonecall_send_note(self, cr, uid, ids, phonecall_id, action, context=None):
phonecall = self.pool.get('crm.phonecall').browse(cr, uid, [phonecall_id], context=context)[0]
if action == 'log': prefix = 'Logged'
@ -1038,17 +1010,6 @@ class crm_lead(base_stage, format_address, osv.osv):
message = _("<b>%s a call</b> for the <em>%s</em>.") % (prefix, phonecall.date)
return self.message_post(cr, uid, ids, body=message, context=context)
def _lead_set_partner_send_note(self, cr, uid, ids, context=None):
for lead in self.browse(cr, uid, ids, context=context):
message = _("%s <b>partner</b> is now set to <em>%s</em>." % (self.case_get_note_msg_prefix(cr, uid, lead, context=context), lead.partner_id.name))
lead.message_post(body=message)
return True
def convert_opportunity_send_note(self, cr, uid, lead, context=None):
message = _("Lead has been <b>converted to an opportunity</b>.")
lead.message_post(body=message)
return True
def onchange_state(self, cr, uid, ids, state_id, context=None):
if state_id:
country_id=self.pool.get('res.country.state').browse(cr, uid, state_id, context).country_id.id

View File

@ -154,20 +154,68 @@
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<!-- mail subtype -->
<record id="mail.mt_crm_won" model="mail.message.subtype">
<field name="name">Won</field>
<field name="res_model">crm.lead</field>
</record>
<record id="mail.mt_crm_lost" model="mail.message.subtype">
<field name="name">Lost</field>
<!-- CRM-related subtypes for messaging / Chatter -->
<record id="mt_lead_create" model="mail.message.subtype">
<field name="name">Lead Created</field>
<field name="res_model">crm.lead</field>
<field name="default" eval="False"/>
<field name="description">Opportunity created</field>
</record>
<record id="mail.mt_crm_stage" model="mail.message.subtype">
<record id="mt_lead_convert_to_opportunity" model="mail.message.subtype">
<field name="name">Lead to Opportunity</field>
<field name="res_model">crm.lead</field>
<field name="default" eval="False"/>
<field name="description">Lead converted into an opportunity</field>
</record>
<record id="mt_lead_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">crm.lead</field>
<field name="description">Stage changed</field>
</record>
<record id="mt_lead_won" model="mail.message.subtype">
<field name="name">Opportunity Won</field>
<field name="res_model">crm.lead</field>
<field name="description">Opportunity won</field>
</record>
<record id="mt_lead_lost" model="mail.message.subtype">
<field name="name">Opportunity Lost</field>
<field name="res_model">crm.lead</field>
<field name="default" eval="False"/>
<field name="description">Opportunity lost</field>
</record>
<!-- Salesteam-related subtypes for messaging / Chatter -->
<record id="mt_salesteam_lead" model="mail.message.subtype">
<field name="name">Lead Created</field>
<field name="res_model">crm.case.section</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_lead_create')"/>
<field name="relation_field">section_id</field>
</record>
<record id="mt_salesteam_lead_opportunity" model="mail.message.subtype">
<field name="name">Lead to Opportunity</field>
<field name="default" eval="False"/>
<field name="res_model">crm.case.section</field>
<field name="parent_id" eval="ref('mt_lead_convert_to_opportunity')"/>
<field name="relation_field">section_id</field>
</record>
<record id="mt_salesteam_lead_stage" model="mail.message.subtype">
<field name="name">Opportunity Stage Changed</field>
<field name="res_model">crm.case.section</field>
<field name="parent_id" eval="ref('mt_lead_stage')"/>
<field name="relation_field">section_id</field>
</record>
<record id="mt_salesteam_lead_won" model="mail.message.subtype">
<field name="name">Opportunity Won</field>
<field name="res_model">crm.case.section</field>
<field name="parent_id" eval="ref('mt_lead_won')"/>
<field name="relation_field">section_id</field>
</record>
<record id="mt_salesteam_lead_lost" model="mail.message.subtype">
<field name="name">Opportunity Lost</field>
<field name="res_model">crm.case.section</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_lead_lost')"/>
<field name="relation_field">section_id</field>
</record>
</data>

View File

@ -20,8 +20,6 @@
##############################################################################
from openerp.osv import fields, osv
from openerp import tools
from openerp.tools.translate import _
import logging
_logger = logging.getLogger(__name__)
@ -36,32 +34,6 @@ class crm_meeting(osv.Model):
'opportunity_id': fields.many2one ('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"),
}
def create(self, cr, uid, vals, context=None):
obj_id = super(crm_meeting, self).create(cr, uid, vals, context=context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def create_send_note(self, cr, uid, ids, context=None):
if context is None:
context = {}
# update context: if come from phonecall, default state values can make the message_post crash
context.pop('default_state', False)
for meeting in self.browse(cr, uid, ids, context=context):
# in the message, transpose meeting.date to the timezone of the current user
meeting_date = fields.DT.datetime.strptime(meeting.date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
meeting_date_tz = fields.datetime.context_timestamp(cr, uid, meeting_date, context=context).strftime(tools.DATETIME_FORMATS_MAP['%+'] + " (%Z)")
if meeting.opportunity_id: # meeting can be create from phonecalls or opportunities, therefore checking for the parent
lead = meeting.opportunity_id
message = _("Meeting linked to the opportunity <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (lead.name, meeting_date_tz)
lead.message_post(body=message)
elif meeting.phonecall_id:
phonecall = meeting.phonecall_id
message = _("Meeting linked to the phonecall <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (phonecall.name, meeting_date_tz)
phonecall.message_post(body=message)
else:
message = _("A meeting has been <b>scheduled</b> on <em>%s</em>.") % (meeting_date_tz)
meeting.message_post(body=message)
return True
class calendar_attendee(osv.osv):
""" Calendar Attendee """

View File

@ -23,8 +23,7 @@ from openerp.addons.base_status.base_state import base_state
import crm
from datetime import datetime
from openerp.osv import fields, osv
import time
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
class crm_phonecall(base_state, osv.osv):
@ -49,7 +48,7 @@ class crm_phonecall(base_state, osv.osv):
('pending', 'Not Held'),
('cancel', 'Cancelled'),
('done', 'Held'),],
string='Status', size=16, readonly=True,
string='Status', size=16, readonly=True, track_visibility='onchange',
help='The status is set to \'Todo\', when a case is created.\
If the case is in progress the status is set to \'Open\'.\
When the call is over, the status is set to \'Held\'.\
@ -84,13 +83,6 @@ class crm_phonecall(base_state, osv.osv):
'active': 1
}
def create(self, cr, uid, vals, context=None):
obj_id = super(crm_phonecall, self).create(cr, uid, vals, context)
for phonecall in self.browse(cr, uid, [obj_id], context=context):
if not phonecall.opportunity_id:
self.case_open_send_note(cr, uid, [obj_id], context=context)
return obj_id
def case_close(self, cr, uid, ids, context=None):
""" Overrides close for crm_case for setting duration """
res = True
@ -294,28 +286,6 @@ class crm_phonecall(base_state, osv.osv):
# OpenChatter
# ----------------------------------------
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
return 'Phonecall'
def case_reset_send_note(self, cr, uid, ids, context=None):
message = _('Phonecall has been <b>reset and set as open</b>.')
return self.message_post(cr, uid, ids, body=message, context=context)
def case_open_send_note(self, cr, uid, ids, context=None):
lead_obj = self.pool.get('crm.lead')
for phonecall in self.browse(cr, uid, ids, context=context):
if phonecall.opportunity_id:
lead = phonecall.opportunity_id
# convert datetime field to a datetime, using server format, then
# convert it to the user TZ and re-render it with %Z to add the timezone
phonecall_datetime = fields.DT.datetime.strptime(phonecall.date, DEFAULT_SERVER_DATETIME_FORMAT)
phonecall_date_str = fields.datetime.context_timestamp(cr, uid, phonecall_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
message = _("Phonecall linked to the opportunity <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (lead.name, phonecall_date_str)
else:
message = _("Phonecall has been <b>created and opened</b>.")
phonecall.message_post(body=message)
return True
def _call_set_partner_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Partner has been <b>created</b>."), context=context)

View File

@ -128,7 +128,7 @@
</page>
</notebook>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" help="Followers of this salesteam follow automatically all opportunities related to this salesteam."/>
<field name="message_follower_ids" widget="mail_followers" help="Follow this salesteam to automatically track the events associated to users of this team."/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>

View File

@ -104,8 +104,8 @@ class crm_claim(base_stage, osv.osv):
'email_cc': fields.text('Watchers Emails', size=252, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
'email_from': fields.char('Email', size=128, help="Destination email for email gateway."),
'partner_phone': fields.char('Phone', size=32),
'stage_id': fields.many2one ('crm.claim.stage', 'Stage',
domain="['&',('fold', '=', False),'|', ('section_ids', '=', section_id), ('case_default', '=', True)]"),
'stage_id': fields.many2one ('crm.claim.stage', 'Stage', track_visibility='onchange',
domain="['&',('fold', '=', False),'|', ('section_ids', '=', section_id), ('case_default', '=', True)]"),
'cause': fields.text('Root Cause'),
'state': fields.related('stage_id', 'state', type="selection", store=True,
selection=crm.AVAILABLE_STATES, string="Status", readonly=True,
@ -158,18 +158,13 @@ class crm_claim(base_stage, osv.osv):
return stage_ids[0]
return False
def create(self, cr, uid, vals, context=None):
obj_id = super(crm_claim, self).create(cr, uid, vals, context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def case_refuse(self, cr, uid, ids, context=None):
""" Mark the case as refused: state=done and case_refused=True """
for lead in self.browse(cr, uid, ids):
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, ['&', ('state', '=', 'done'), ('case_refused', '=', True)], context=context)
if stage_id:
self.case_set(cr, uid, [lead.id], values_to_update={}, new_stage_id=stage_id, context=context)
return self.case_refuse_send_note(cr, uid, ids, context=context)
return True
def onchange_partner_id(self, cr, uid, ids, part, email=False):
"""This function returns value of partner address based on partner
@ -231,28 +226,6 @@ class crm_claim(base_stage, osv.osv):
return super(crm_claim,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
# ---------------------------------------------------
# OpenChatter methods and notifications
# ---------------------------------------------------
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
""" Override of default prefix for notifications. """
return 'Claim'
def create_send_note(self, cr, uid, ids, context=None):
msg = _('Claim has been <b>created</b>.')
return self.message_post(cr, uid, ids, body=msg, context=context)
def case_refuse_send_note(self, cr, uid, ids, context=None):
msg = _('Claim has been <b>refused</b>.')
return self.message_post(cr, uid, ids, body=msg, context=context)
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """
stage_name = self.pool.get('crm.claim.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
class res_partner(osv.osv):
_inherit = 'res.partner'
_columns = {

View File

@ -89,11 +89,6 @@ class crm_helpdesk(base_state, base_stage, osv.osv):
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
}
def create(self, cr, uid, vals, context=None):
obj_id = super(crm_helpdesk, self).create(cr, uid, vals, context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
# -------------------------------------------------------
# Mail gateway
# -------------------------------------------------------
@ -140,17 +135,4 @@ class crm_helpdesk(base_state, base_stage, osv.osv):
return super(crm_helpdesk,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
# ---------------------------------------------------
# OpenChatter
# ---------------------------------------------------
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
""" override of default base_state method. """
return 'Case'
def create_send_note(self, cr, uid, ids, context=None):
msg = _('Case has been <b>created</b>.')
self.message_post(cr, uid, ids, body=msg, context=context)
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -48,7 +48,7 @@ class event_event(osv.osv):
_name = 'event.event'
_description = __doc__
_order = 'date_begin'
_inherit = ['mail.thread','ir.needaction_mixin']
_inherit = ['mail.thread', 'ir.needaction_mixin']
def name_get(self, cr, uid, ids, context=None):
if not ids:
@ -67,11 +67,6 @@ class event_event(osv.osv):
res.append((record['id'], display_name))
return res
def create(self, cr, uid, vals, context=None):
obj_id = super(event_event, self).create(cr, uid, vals, context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def copy(self, cr, uid, id, default=None, context=None):
""" Reset the state and the registrations while copying an event
"""
@ -84,7 +79,6 @@ class event_event(osv.osv):
return super(event_event, self).copy(cr, uid, id, default=default, context=context)
def button_draft(self, cr, uid, ids, context=None):
self.button_draft_send_note(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
def button_cancel(self, cr, uid, ids, context=None):
@ -94,11 +88,9 @@ class event_event(osv.osv):
if event_reg.state == 'done':
raise osv.except_osv(_('Error!'),_("You have already set a registration for this event as 'Attended'. Please reset it to draft if you want to cancel this event.") )
registration.write(cr, uid, reg_ids, {'state': 'cancel'}, context=context)
self.button_cancel_send_note(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
def button_done(self, cr, uid, ids, context=None):
self.button_done_send_note(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state': 'done'}, context=context)
def check_registration_limits(self, cr, uid, ids, context=None):
@ -131,7 +123,6 @@ class event_event(osv.osv):
if isinstance(ids, (int, long)):
ids = [ids]
self.check_registration_limits(cr, uid, ids, context=context)
self.button_confirm_send_note(cr, uid, ids, context=context)
return self.confirm_event(cr, uid, ids, context=context)
def _get_register(self, cr, uid, ids, fields, args, context=None):
@ -201,6 +192,7 @@ class event_event(osv.osv):
('confirm', 'Confirmed'),
('done', 'Done')],
'Status', readonly=True, required=True,
track_visibility='onchange',
help='If event is created, the status is \'Draft\'.If event is confirmed for the particular dates the status is set to \'Confirmed\'. If the event is over, the status is set to \'Done\'.If event is cancelled the status is set to \'Cancelled\'.'),
'email_registration_id' : fields.many2one('email.template','Registration Confirmation Email', help='This field contains the template of the mail that will be automatically sent each time a registration for this event is confirmed.'),
'email_confirmation_id' : fields.many2one('email.template','Event Confirmation Email', help="If you set an email template, each participant will receive this email announcing the confirmation of the event."),
@ -292,42 +284,12 @@ class event_event(osv.osv):
res['value'] = {'date_end': date_end.strftime("%Y-%m-%d %H:%M:%S")}
return res
# ----------------------------------------
# OpenChatter methods and notifications
# ----------------------------------------
def create_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>created</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def button_cancel_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>cancelled</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def button_draft_send_note(self, cr, uid, ids, context=None):
message = _("Event has been set to <b>draft</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def button_done_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>done</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def button_confirm_send_note(self, cr, uid, ids, context=None):
message = _("Event has been <b>confirmed</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
event_event()
class event_registration(osv.osv):
"""Event Registration"""
_name= 'event.registration'
_description = __doc__
_inherit = ['ir.needaction_mixin','mail.thread']
_inherit = ['mail.thread', 'ir.needaction_mixin']
_columns = {
'id': fields.integer('ID'),
'origin': fields.char('Source Document', size=124,readonly=True,help="Name of the sale order which create the registration"),
@ -347,6 +309,7 @@ class event_registration(osv.osv):
('cancel', 'Cancelled'),
('open', 'Confirmed'),
('done', 'Attended')], 'Status',
track_visibility='onchange',
size=16, readonly=True),
'email': fields.char('Email', size=64),
'phone': fields.char('Phone', size=64),
@ -359,19 +322,12 @@ class event_registration(osv.osv):
_order = 'name, create_date desc'
def do_draft(self, cr, uid, ids, context=None):
self.do_draft_send_note(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
def confirm_registration(self, cr, uid, ids, context=None):
for reg in self.browse(cr, uid, ids, context=context or {}):
self.pool.get('event.event').message_post(cr, uid, [reg.event_id.id], body=_('New registration confirmed: %s.') % (reg.name or '', ),subtype="event.mt_event_registration", context=context)
self.message_post(cr, uid, ids, body=_('Registration confirmed.'), context=context)
return self.write(cr, uid, ids, {'state': 'open'},context=context)
def create(self, cr, uid, vals, context=None):
obj_id = super(event_registration, self).create(cr, uid, vals, context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def registration_open(self, cr, uid, ids, context=None):
""" Open Registration
@ -395,13 +351,11 @@ class event_registration(osv.osv):
if today >= registration.event_id.date_begin:
values = {'state': 'done', 'date_closed': today}
self.write(cr, uid, ids, values)
self.message_post(cr, uid, ids, body=_('State set to Done'), context=context)
else:
raise osv.except_osv(_('Error!'), _("You must wait for the starting day of the event to do this action."))
return True
def button_reg_cancel(self, cr, uid, ids, context=None, *args):
self.message_post(cr, uid, ids, body=_('State set to Cancel'), context=context)
return self.write(cr, uid, ids, {'state': 'cancel'})
def mail_user(self, cr, uid, ids, context=None):
@ -449,20 +403,4 @@ class event_registration(osv.osv):
data.update(d['value'])
return {'value': data}
# ----------------------------------------
# OpenChatter methods and notifications
# ----------------------------------------
def create_send_note(self, cr, uid, ids, context=None):
message = _("Registration has been <b>created</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def do_draft_send_note(self, cr, uid, ids, context=None):
message = _("Registration has been set as <b>draft</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
event_registration()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -12,9 +12,9 @@
<field name="state">open</field>
</record>
<!-- mail: event subtype -->
<!-- Event-related subtypes for messaging / Chatter -->
<record id="event.mt_event_registration" model="mail.message.subtype">
<field name="name">New Registrations</field>
<field name="name">New Registration</field>
<field name="res_model">event.event</field>
<field name="default" eval="False"/>
</record>

View File

@ -64,6 +64,14 @@ class hr_expense_expense(osv.osv):
_inherit = ['mail.thread']
_description = "Expense"
_order = "id desc"
_track = {
'state': {
'hr_expense.mt_expense_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'accepted',
'hr_expense.mt_expense_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancelled',
'hr_expense.mt_expense_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
},
}
_columns = {
'name': fields.char('Description', size=128, required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'id': fields.integer('Sheet ID', readonly=True),
@ -89,7 +97,8 @@ class hr_expense_expense(osv.osv):
('accepted', 'Approved'),
('done', 'Done'),
],
'Status', readonly=True, help='When the expense request is created the status is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the status is \'Waiting Confirmation\'.\
'Status', readonly=True, track_visibility='onchange',
help='When the expense request is created the status is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the status is \'Waiting Confirmation\'.\
\nIf the admin accepts it, the status is \'Accepted\'.\n If a receipt is made for the expense request, the status is \'Done\'.'),
}
_defaults = {
@ -124,27 +133,17 @@ class hr_expense_expense(osv.osv):
company_id = employee.company_id.id
return {'value': {'department_id': department_id, 'company_id': company_id}}
def expense_confirm(self, cr, uid, ids, *args):
def expense_confirm(self, cr, uid, ids, context=None):
for expense in self.browse(cr, uid, ids):
if expense.employee_id and expense.employee_id.parent_id.user_id:
self.message_subscribe_users(cr, uid, [expense.id], user_ids=[expense.employee_id.parent_id.user_id.id])
self.write(cr, uid, ids, {
'state':'confirm',
'date_confirm': time.strftime('%Y-%m-%d')
})
return True
return self.write(cr, uid, ids, {'state': 'confirm', 'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
def expense_accept(self, cr, uid, ids, *args):
self.write(cr, uid, ids, {
'state':'accepted',
'date_valid':time.strftime('%Y-%m-%d'),
'user_valid': uid,
})
return True
def expense_accept(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'accepted', 'date_valid': time.strftime('%Y-%m-%d'), 'user_valid': uid}, context=context)
def expense_canceled(self, cr, uid, ids, *args):
self.write(cr, uid, ids, {'state':'cancelled'})
return True
def expense_canceled(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
def action_receipt_create(self, cr, uid, ids, context=None):
property_obj = self.pool.get('ir.property')

View File

@ -17,5 +17,23 @@
<field name="parent_id" ref="product.product_category_all"/>
<field name="name">Expenses</field>
</record>
<!-- Expense-related subtypes for messaging / Chatter -->
<record id="mt_expense_approved" model="mail.message.subtype">
<field name="name">Approved</field>
<field name="res_model">hr.expense.expense</field>
<field name="description">Expense approved</field>
</record>
<record id="mt_expense_refused" model="mail.message.subtype">
<field name="name">Refused</field>
<field name="res_model">hr.expense.expense</field>
<field name="description">Expense refused</field>
</record>
<record id="mt_expense_confirmed" model="mail.message.subtype">
<field name="name">To Approve</field>
<field name="res_model">hr.expense.expense</field>
<field name="description">Expense confirmed, waiting confirmation</field>
</record>
</data>
</openerp>

View File

@ -21,7 +21,8 @@
#
##############################################################################
import datetime, time
import datetime
import time
from itertools import groupby
from operator import itemgetter
@ -102,13 +103,19 @@ class hr_holidays_status(osv.osv):
res.append((record.id, name))
return res
hr_holidays_status()
class hr_holidays(osv.osv):
_name = "hr.holidays"
_description = "Leave"
_order = "type desc, date_from asc"
_inherit = [ 'mail.thread','ir.needaction_mixin']
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'validate',
'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'refuse',
'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
},
}
def _employee_get(self, cr, uid, context=None):
ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
@ -135,7 +142,8 @@ class hr_holidays(osv.osv):
_columns = {
'name': fields.char('Description', size=64),
'state': fields.selection([('draft', 'To Submit'), ('cancel', 'Cancelled'),('confirm', 'To Approve'), ('refuse', 'Refused'), ('validate1', 'Second Approval'), ('validate', 'Approved')],
'Status', readonly=True, help='The status is set to \'To Submit\', when a holiday request is created.\
'Status', readonly=True, track_visibility='onchange',
help='The status is set to \'To Submit\', when a holiday request is created.\
\nThe status is \'To Approve\', when holiday request is confirmed by user.\
\nThe status is \'Refused\', when holiday request is refused by manager.\
\nThe status is \'Approved\', when holiday request is approved by manager.'),
@ -360,7 +368,6 @@ class hr_holidays(osv.osv):
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'confirm', cr)
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'validate', cr)
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'second_validate', cr)
self.holidays_validate_notificate(cr, uid, ids, context=context)
return True
def holidays_confirm(self, cr, uid, ids, context=None):
@ -368,8 +375,7 @@ class hr_holidays(osv.osv):
for record in self.browse(cr, uid, ids, context=context):
if record.employee_id and record.employee_id.parent_id and record.employee_id.parent_id.user_id:
self.message_subscribe_users(cr, uid, [record.id], user_ids=[record.employee_id.parent_id.user_id.id], context=context)
self.holidays_confirm_notificate(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state':'confirm'})
return self.write(cr, uid, ids, {'state': 'confirm'})
def holidays_refuse(self, cr, uid, ids, context=None):
obj_emp = self.pool.get('hr.employee')
@ -380,7 +386,6 @@ class hr_holidays(osv.osv):
self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id': manager})
else:
self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id2': manager})
self.holidays_refuse_notificate(cr, uid, ids, context=context)
self.holidays_cancel(cr, uid, ids, context=context)
return True
@ -413,7 +418,7 @@ class hr_holidays(osv.osv):
# OpenChatter and notifications
# -----------------------------
def needaction_domain_get(self, cr, uid, ids, context=None):
def _needaction_domain_get(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
@ -422,37 +427,11 @@ class hr_holidays(osv.osv):
dom = ['|'] + dom + [('state', '=', 'validate1')]
return dom
def create_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id],
_("Request <b>created</b>, waiting confirmation."), context=context)
return True
def holidays_confirm_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
self.message_post(cr, uid, [obj.id],
_("Request <b>submitted</b>, waiting for validation by the manager."), context=context)
def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id],
_("Request <b>approved</b>, waiting second validation."), context=context)
def holidays_validate_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
if obj.double_validation:
self.message_post(cr, uid, [obj.id],
_("Request <b>validated</b>."), context=context)
else:
self.message_post(cr, uid, [obj.id],
_("The request has been <b>approved</b>."), context=context)
def holidays_refuse_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
self.message_post(cr, uid, [obj.id],
_("Request <b>refused</b>"), context=context)
class resource_calendar_leaves(osv.osv):
_inherit = "resource.calendar.leaves"
_description = "Leave Detail"

View File

@ -44,5 +44,24 @@
<field name="limit">True</field>
<field name="color_name">brown</field>
</record>
<!-- Holidays-related subtypes for messaging / Chatter -->
<record id="mt_holidays_confirmed" model="mail.message.subtype">
<field name="name">To Approve</field>
<field name="res_model">hr.holidays</field>
<field name="description">Request confirmed, waiting confirmation</field>
</record>
<record id="mt_holidays_approved" model="mail.message.subtype">
<field name="name">Approved</field>
<field name="res_model">hr.holidays</field>
<field name="description">Request approved</field>
</record>
<record id="mt_holidays_refused" model="mail.message.subtype">
<field name="name">Refused</field>
<field name="res_model">hr.holidays</field>
<field name="default" eval="False"/>
<field name="description">Request refused</field>
</record>
</data>
</openerp>

View File

@ -92,6 +92,15 @@ class hr_applicant(base_stage, osv.Model):
_description = "Applicant"
_order = "id desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'hr_recruitment.mt_applicant_hired': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
'hr_recruitment.mt_applicant_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
},
'stage_id': {
'hr_recruitment.mt_stage_changed': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['done', 'cancel'],
},
}
def _get_default_department_id(self, cr, uid, context=None):
""" Gives default department by checking if present in the context """
@ -186,7 +195,7 @@ class hr_applicant(base_stage, osv.Model):
'partner_id': fields.many2one('res.partner', 'Contact'),
'create_date': fields.datetime('Creation Date', readonly=True, select=True),
'write_date': fields.datetime('Update Date', readonly=True),
'stage_id': fields.many2one ('hr.recruitment.stage', 'Stage',
'stage_id': fields.many2one ('hr.recruitment.stage', 'Stage', track_visibility='onchange',
domain="['&', ('fold', '=', False), '|', ('department_id', '=', department_id), ('department_id', '=', False)]"),
'state': fields.related('stage_id', 'state', type="selection", store=True,
selection=AVAILABLE_STATES, string="Status", readonly=True,
@ -197,7 +206,7 @@ class hr_applicant(base_stage, osv.Model):
set to \'Pending\'.'),
'categ_ids': fields.many2many('hr.applicant_category', string='Tags'),
'company_id': fields.many2one('res.company', 'Company'),
'user_id': fields.many2one('res.users', 'Responsible'),
'user_id': fields.many2one('res.users', 'Responsible', track_visibility='onchange'),
# Applicant Columns
'date_closed': fields.datetime('Closed', readonly=True, select=True),
'date_open': fields.datetime('Opened', readonly=True, select=True),
@ -383,7 +392,9 @@ class hr_applicant(base_stage, osv.Model):
def create(self, cr, uid, vals, context=None):
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
self.create_send_note(cr, uid, [obj_id], context=context)
applicant = self.browse(cr, uid, obj_id, context=context)
if applicant.job_id:
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('Applicant <b>created</b>'), subtype="hr_recruitment.mt_job_new_applicant", context=context)
return obj_id
def case_open(self, cr, uid, ids, context=None):
@ -465,51 +476,6 @@ class hr_applicant(base_stage, osv.Model):
"""
return self.set_priority(cr, uid, ids, '3')
# -------------------------------------------------------
# OpenChatter methods and notifications
# -------------------------------------------------------
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """
if not stage_id: return True
stage_name = self.pool.get('hr.recruitment.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body=_("Stage changed to <b>%s</b>.") % (stage_name), context=context)
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
return 'Applicant'
def case_open_send_note(self, cr, uid, ids, context=None):
message = _("Applicant has been set <b>in progress</b>.")
return self.message_post(cr, uid, ids, body=message, context=context)
def case_close_send_note(self, cr, uid, ids, context=None):
if context is None:
context = {}
for applicant in self.browse(cr, uid, ids, context=context):
if applicant.job_id:
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('New employee joined the company %s.')%(applicant.name,), subtype="hr_recruitment.mt_hired", context=context)
if applicant.emp_id:
message = _("Applicant has been <b>hired</b> and created as an employee.")
self.message_post(cr, uid, [applicant.id], body=message, context=context)
else:
message = _("Applicant has been <b>hired</b>.")
self.message_post(cr, uid, [applicant.id], body=message, context=context)
return True
def case_cancel_send_note(self, cr, uid, ids, context=None):
msg = 'Applicant <b>refused</b>.'
return self.message_post(cr, uid, ids, body=msg, context=context)
def case_reset_send_note(self, cr, uid, ids, context=None):
message =_("Applicant has been set as <b>new</b>.")
return self.message_post(cr, uid, ids, body=message, context=context)
def create_send_note(self, cr, uid, ids, context=None):
message = _("Applicant has been <b>created</b>.")
for applicant in self.browse(cr, uid, ids, context=context):
if applicant.job_id:
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=message, subtype="hr_recruitment.mt_applicant_new", context=context)
return self.message_post(cr, uid, ids, body=message, context=context)
class hr_job(osv.osv):
_inherit = "hr.job"

View File

@ -461,14 +461,28 @@
<field name="alias_user_id" ref="base.user_root"/>
</record>
<!-- mail: subtypes -->
<record id="mt_hired" model="mail.message.subtype">
<field name="name">Employee Hired</field>
<field name="res_model">hr.job</field>
</record>
<record id="mt_applicant_new" model="mail.message.subtype">
<!-- Job-related subtypes for messaging / Chatter -->
<record id="mt_job_new_applicant" model="mail.message.subtype">
<field name="name">New Applicant</field>
<field name="res_model">hr.job</field>
</record>
<!-- Applicant-related subtypes for messaging / Chatter -->
<record id="mt_stage_changed" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">hr.applicant</field>
<field name="description">Stage changed</field>
</record>
<record id="mt_applicant_hired" model="mail.message.subtype">
<field name="name">Applicant Hired</field>
<field name="res_model">hr.applicant</field>
<field name="description">Applicant hired</field>
</record>
<record id="mt_applicant_refused" model="mail.message.subtype">
<field name="name">Applicant Refused</field>
<field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Applicant refused</field>
</record>
</data>
</openerp>

View File

@ -87,30 +87,16 @@ class account_analytic_account(osv.osv):
return res
def set_close(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'close'}, context=context)
message = _("Contract has been <b>closed</b>.")
self.message_post(cr, uid, ids, body=message, subtype="hr_timesheet_invoice.mt_account_closed", context=context)
return True
return self.write(cr, uid, ids, {'state': 'close'}, context=context)
def set_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
message = _("Contract has been <b>canceled</b>.")
self.message_post(cr, uid, ids, body=message, subtype="hr_timesheet_invoice.mt_account_canceled", context=context)
return True
return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
def set_open(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'open'}, context=context)
message = _("Contract has been <b>opened</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def set_pending(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'pending'}, context=context)
message = _("Contract has been set as <b>pending</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
account_analytic_account()
return self.write(cr, uid, ids, {'state': 'pending'}, context=context)
class account_analytic_line(osv.osv):

View File

@ -22,15 +22,5 @@
<field name="factor">20.0</field>
</record>
<!-- mail: subtypes -->
<record id="mt_account_closed" model="mail.message.subtype">
<field name="name">finished</field>
<field name="res_model">account.analytic.account</field>
</record>
<record id="mt_account_canceled" model="mail.message.subtype">
<field name="name">canceled</field>
<field name="res_model">account.analytic.account</field>
</record>
</data>
</openerp>

View File

@ -226,13 +226,15 @@ class hr_timesheet_sheet(osv.osv):
# ------------------------------------------------
# OpenChatter methods and notifications
# ------------------------------------------------
def _needaction_domain_get(self, cr, uid, ids, context=None):
def _needaction_domain_get(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
if not empids:
return False
dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
return dom
hr_timesheet_sheet()
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"

View File

@ -36,11 +36,10 @@ class idea_category(osv.osv):
'name': fields.char('Category Name', size=64, required=True),
}
_sql_constraints = [
('name', 'unique(name)', 'The name of the category must be unique' )
('name', 'unique(name)', 'The name of the category must be unique')
]
_order = 'name asc'
idea_category()
class idea_idea(osv.osv):
""" Idea """
@ -48,18 +47,18 @@ class idea_idea(osv.osv):
_inherit = ['mail.thread']
_columns = {
'create_uid': fields.many2one('res.users', 'Creator', required=True, readonly=True),
'name': fields.char('Idea Summary', size=64, required=True, readonly=True, oldname='title', states={'draft':[('readonly',False)]}),
'description': fields.text('Description', help='Content of the idea', readonly=True, states={'draft':[('readonly',False)]}),
'category_ids': fields.many2many('idea.category', string='Tags', readonly=True, states={'draft':[('readonly',False)]}),
'name': fields.char('Idea Summary', size=64, required=True, readonly=True, oldname='title', states={'draft': [('readonly', False)]}),
'description': fields.text('Description', help='Content of the idea', readonly=True, states={'draft': [('readonly', False)]}),
'category_ids': fields.many2many('idea.category', string='Tags', readonly=True, states={'draft': [('readonly', False)]}),
'state': fields.selection([('draft', 'New'),
('open', 'Accepted'),
('cancel', 'Refused'),
('close', 'Done')],
'Status', readonly=True
'Status', readonly=True, track_visibility='onchange',
)
}
_sql_constraints = [
('name', 'unique(name)', 'The name of the idea must be unique' )
('name', 'unique(name)', 'The name of the idea must be unique')
]
_defaults = {
'state': lambda *a: 'draft',
@ -67,21 +66,13 @@ class idea_idea(osv.osv):
_order = 'name asc'
def idea_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, { 'state': 'cancel' })
self.message_post(cr, uid, ids, body=_('Idea has been refused.'), context=context)
return True
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
def idea_open(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'open'}, context=context)
self.message_post(cr, uid, ids, body=_('Idea has been opened.'), context=context)
return True
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def idea_close(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'close'}, context=context)
self.message_post(cr, uid, ids, body=_('Idea has been accepted.'), context=context)
return True
return self.write(cr, uid, ids, {'state': 'close'}, context=context)
def idea_draft(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'draft'}, context=context)
self.message_post(cr, uid, ids, body=_('Idea has been created.'), context=context)
return True
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)

View File

@ -27,6 +27,7 @@
<field name="priority">1000</field>
</record>
<!-- Discussion subtype for messaging / Chatter -->
<record id="mt_comment" model="mail.message.subtype">
<field name="name">Discussions</field>
</record>

View File

@ -40,3 +40,13 @@ OpenChatter updates the res.users class:
- it adds a preference about sending emails when receiving a notification
- make a new user follow itself automatically
- create a welcome message when creating a new user, to make his arrival in OpenERP more friendly
Misc magic context keys
+++++++++++++++++++++++
- mail_create_nosubscribe: when creating a new record that inherit from mail_thread,
do not subscribe the creator to the document followers
- mail_create_nolog: do not log creation message
- mail_notify_noemail: do not send email notifications; partners to notify are
notified, i.e. a mail_notification is created, but no email is actually send

View File

@ -0,0 +1,18 @@
Tracked fields
---------------
Tracked fields is generic logging system for the changes applied to some fields in open chatter.
- In the definition of a fields. you have to simply add tracked=True.
- When changing one of the fields having tracked=True on saved automatically write a log in openchatter for the changes in fields.
How it works:
+++++++++++++
- You have to add tracked=True in field defination as following.
- ``'stage_id': fields.many2one('project.task.type', 'Stage',tracked=True),``
- For developed this feature we override write method of mail_thread.
- And make one mako template which shows old field and updated field.
Open chatter log:
+++++++++++++++++
- After changing field follower can show log of tracked field in open chatter as followed.
- Updated Fields:
- Stage: Analysis -> Specification

View File

@ -114,8 +114,8 @@ class mail_notification(osv.Model):
""" Send by email the notification depending on the user preferences """
if context is None:
context = {}
# mail_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_noemail'):
# mail_notify_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_notify_noemail'):
return True
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)

View File

@ -28,7 +28,7 @@ class mail_message_subtype(osv.osv):
the follower subscription, allowing only some subtypes to be pushed
on the Wall. """
_name = 'mail.message.subtype'
_description = 'mail_message_subtype'
_description = 'Message subtypes'
_columns = {
'name': fields.char('Message Type', required=True, translate=True,
help='Message subtype gives a more precise type on the message, '\
@ -36,10 +36,20 @@ class mail_message_subtype(osv.osv):
'a notification related to a new record (New), or to a stage '\
'change in a process (Stage change). Message subtypes allow to '\
'precisely tune the notifications the user want to receive on its wall.'),
'description': fields.text('Description', translate=True,
help='Description that will be added in the message posted for this '\
'subtype. If void, the name will be added instead.'),
'parent_id': fields.many2one('mail.message.subtype', string='Parent',
ondelete='set null',
help='Parent subtype, used for automatic subscription.'),
'relation_field': fields.char('Relation field',
help='Field used to link the related model to the subtype model when '\
'using automatic subscription on a related document. The field '\
'is used to compute getattr(related_document.relation_field).'),
'res_model': fields.char('Model',
help="Related model of the subtype. If False, this subtype exists for all models."),
help="Model the subtype applies to. If False, this subtype applies to all models."),
'default': fields.boolean('Default',
help="Checked by default when subscribing."),
help="Activated by default when subscribing."),
}
_defaults = {
'default': True,

View File

@ -25,6 +25,9 @@
<field name="name"/>
<field name="res_model"/>
<field name="default"/>
<field name="description"/>
<field name="parent_id"/>
<field name="relation_field"/>
</group>
</sheet>
</form>

View File

@ -26,12 +26,12 @@ import email
import logging
import pytz
import time
from openerp import tools
import xmlrpclib
from email.message import Message
from mail_message import decode
from openerp import tools
from openerp import SUPERUSER_ID
from openerp.addons.mail.mail_message import decode
from openerp.osv import fields, osv
from openerp.tools.safe_eval import safe_eval as eval
@ -70,6 +70,23 @@ class mail_thread(osv.AbstractModel):
_description = 'Email Thread'
_mail_flat_thread = True
# Automatic logging system if mail installed
# _track = {
# 'field': {
# 'module.subtype_xml': lambda self, cr, uid, obj, context=None: obj[state] == done,
# 'module.subtype_xml2': lambda self, cr, uid, obj, context=None: obj[state] != done,
# },
# 'field2': {
# ...
# },
# }
# where
# :param string field: field name
# :param module.subtype_xml: xml_id of a mail.message.subtype (i.e. mail.mt_comment)
# :param obj: is a browse_record
# :param function lambda: returns whether the tracking should record using this subtype
_track = {}
def _get_message_data(self, cr, uid, ids, name, args, context=None):
""" Computes:
- message_unread: has uid unread message for the document
@ -209,18 +226,47 @@ class mail_thread(osv.AbstractModel):
}
#------------------------------------------------------
# Automatic subscription when creating
# CRUD overrides for automatic subscription and logging
#------------------------------------------------------
def create(self, cr, uid, vals, context=None):
""" Override to subscribe the current user. """
def create(self, cr, uid, values, context=None):
""" Chatter override :
- subscribe uid
- subscribe followers of parent
- log a creation message
"""
if context is None:
context = {}
thread_id = super(mail_thread, self).create(cr, uid, vals, context=context)
if not context.get('mail_nosubscribe'):
thread_id = super(mail_thread, self).create(cr, uid, values, context=context)
# subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'):
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
self.message_subscribe_from_parent(cr, uid, [thread_id], values.keys(), context=context)
# automatic logging unless asked not to (mainly for various testing purpose)
if not context.get('mail_create_nolog'):
self.message_post(cr, uid, thread_id, body='Document created', context=context)
return thread_id
def write(self, cr, uid, ids, values, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
# Track initial values of tracked fields
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=context)
if tracked_fields:
initial = self.read(cr, uid, ids, tracked_fields.keys(), context=context)
initial_values = dict((item['id'], item) for item in initial)
# Perform write, update followers
result = super(mail_thread, self).write(cr, uid, ids, values, context=context)
self.message_subscribe_from_parent(cr, uid, ids, values.keys(), context=context)
# Perform the tracking
if tracked_fields:
self.message_track(cr, uid, ids, tracked_fields, initial_values, context=context)
return result
def unlink(self, cr, uid, ids, context=None):
""" Override unlink to delete messages and followers. This cannot be
cascaded, because link is done through (res_model, res_id). """
@ -242,6 +288,94 @@ class mail_thread(osv.AbstractModel):
default['message_follower_ids'] = []
return super(mail_thread, self).copy(cr, uid, id, default=default, context=context)
#------------------------------------------------------
# Automatically log tracked fields
#------------------------------------------------------
def _get_tracked_fields(self, cr, uid, updated_fields, context=None):
""" Return a structure of tracked fields for the current model.
:param list updated_fields: modified field names
:return list: a list of (field_name, column_info obj), containing
always tracked fields and modified on_change fields
"""
lst = []
for name, column_info in self._all_columns.items():
visibility = getattr(column_info.column, 'track_visibility', False)
if visibility == 'always' or (visibility == 'onchange' and name in updated_fields) or name in self._track:
lst.append(name)
if not lst:
return lst
return self.fields_get(cr, uid, lst, context=context)
def message_track(self, cr, uid, ids, tracked_fields, initial_values, context=None):
def convert_for_display(value, field_obj):
if not value:
return ''
if field_obj['type'] == 'many2one':
return value[1]
if field_obj['type'] == 'selection':
return dict(field_obj['selection'])[value]
return value
def format_message(message_description, tracked_values):
message = ''
if message_description:
message = '<span>%s</span>' % message_description
for name, change in tracked_values.items():
message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % change.get('col_info')
if change.get('old_value'):
message += '%s &rarr; ' % change.get('old_value')
message += '%s</div>' % change.get('new_value')
return message
if not tracked_fields:
return True
for record in self.read(cr, uid, ids, tracked_fields.keys(), context=context):
initial = initial_values[record['id']]
changes = []
tracked_values = {}
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
for col_name, col_info in tracked_fields.items():
if record[col_name] == initial[col_name] and getattr(self._all_columns[col_name].column, 'track_visibility', None) == 'always':
tracked_values[col_name] = dict(col_info=col_info['string'],
new_value=convert_for_display(record[col_name], col_info))
elif record[col_name] != initial[col_name]:
if getattr(self._all_columns[col_name].column, 'track_visibility', None) in ['always', 'onchange']:
tracked_values[col_name] = dict(col_info=col_info['string'],
old_value=convert_for_display(initial[col_name], col_info),
new_value=convert_for_display(record[col_name], col_info))
if col_name in tracked_fields:
changes.append(col_name)
if not changes:
continue
# find subtypes and post messages or log if no subtype found
subtypes = []
for field, track_info in self._track.items():
if field not in changes:
continue
for subtype, method in track_info.items():
if method(self, cr, uid, record, context):
subtypes.append(subtype)
posted = False
for subtype in subtypes:
try:
subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1])
except ValueError, e:
_logger.debug('subtype %s not found, giving error "%s"' % (subtype, e))
continue
message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values)
self.message_post(cr, uid, record['id'], body=message, subtype=subtype, context=context)
posted = True
if not posted:
message = format_message('', tracked_values)
self.message_post(cr, uid, record['id'], body=message, context=context)
return True
#------------------------------------------------------
# mail.message wrappers and tools
#------------------------------------------------------
@ -446,8 +580,8 @@ class mail_thread(osv.AbstractModel):
(msg['message_id'], model)
# disabled subscriptions during message_new/update to avoid having the system user running the
# email gateway become a follower of all inbound messages
nosub_ctx = dict(context, mail_nosubscribe=True)
# email gateway become a follower of all inbound messages
nosub_ctx = dict(context, mail_create_nosubscribe=True)
if thread_id and hasattr(model_pool, 'message_update'):
model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx)
else:
@ -534,9 +668,9 @@ class mail_thread(osv.AbstractModel):
alternative = (message.get_content_type() == 'multipart/alternative')
for part in message.walk():
if part.get_content_maintype() == 'multipart':
continue # skip container
filename = part.get_filename() # None if normal part
encoding = part.get_content_charset() # None if attachment
continue # skip container
filename = part.get_filename() # None if normal part
encoding = part.get_content_charset() # None if attachment
# 1) Explicit Attachments -> attachments
if filename or part.get('content-disposition', '').strip().startswith('attachment'):
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
@ -855,6 +989,58 @@ class mail_thread(osv.AbstractModel):
self.check_access_rights(cr, uid, 'read')
return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
def message_subscribe_from_parent(self, cr, uid, ids, updated_fields, context=None):
"""
1. fetch project subtype related to task (parent_id.res_model = 'project.task')
2. for each project subtype: subscribe the follower to the task
"""
subtype_obj = self.pool.get('mail.message.subtype')
follower_obj = self.pool.get('mail.followers')
# fetch related record subtypes
related_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('parent_id.res_model', '=', self._name)], context=context)
subtypes = subtype_obj.browse(cr, uid, related_subtype_ids, context=context)
default_subtypes = [subtype for subtype in subtypes if subtype.res_model == False]
related_subtypes = [subtype for subtype in subtypes if subtype.res_model != False]
relation_fields = set([subtype.relation_field for subtype in subtypes if subtype.relation_field != False])
if not related_subtypes or not any(relation in updated_fields for relation in relation_fields):
return True
for record in self.browse(cr, uid, ids, context=context):
new_followers = dict()
parent_res_id = False
parent_model = False
for subtype in related_subtypes:
if not subtype.relation_field or not subtype.parent_id:
continue
if not subtype.relation_field in self._columns or not getattr(record, subtype.relation_field, False):
continue
parent_res_id = getattr(record, subtype.relation_field).id
parent_model = subtype.res_model
follower_ids = follower_obj.search(cr, SUPERUSER_ID, [
('res_model', '=', parent_model),
('res_id', '=', parent_res_id),
('subtype_ids', 'in', [subtype.id])
], context=context)
for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
new_followers.setdefault(follower.partner_id.id, set()).add(subtype.parent_id.id)
if not parent_res_id or not parent_model:
continue
for subtype in default_subtypes:
follower_ids = follower_obj.search(cr, SUPERUSER_ID, [
('res_model', '=', parent_model),
('res_id', '=', parent_res_id),
('subtype_ids', 'in', [subtype.id])
], context=context)
for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
new_followers.setdefault(follower.partner_id.id, set()).add(subtype.id)
for pid, subtypes in new_followers.items():
self.message_subscribe(cr, uid, [record.id], [pid], list(subtypes), context=context)
return True
#------------------------------------------------------
# Thread state
#------------------------------------------------------

View File

@ -8,6 +8,7 @@
<field name="model_id" ref="model_mail_group"/>
<!-- This rule has to be improved for employee only groups -->
<field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&amp;', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])]</field>
<field name="perm_create" eval="False"/>
</record>
<record id="mail_followers_read_write_own" model="ir.rule">

View File

@ -12,11 +12,12 @@
<span class="oe_unfollow">Unfollow</span>
<span class="oe_following">Following</span>
</button>
<t t-if="widget.comment">
<h5 class="oe_comment"><t t-raw="widget.comment"/></h5>
</t>
<div class="oe_subtype_list"></div>
</div>
<t t-if="widget.comment">
<h5 class="oe_comment"><t t-raw="widget.comment"/></h5>
</t>
<hr size="2"></hr>
<div class='oe_follower_title_box'>
<h4 class='oe_follower_title'>Followers</h4>
<a href='#' class="oe_invite">Invite others</a>

View File

@ -51,6 +51,7 @@ class TestMailBase(common.TransactionCase):
# Usefull models
self.ir_model = self.registry('ir.model')
self.ir_model_data = self.registry('ir.model.data')
self.ir_attachment = self.registry('ir.attachment')
self.mail_alias = self.registry('mail.alias')
self.mail_thread = self.registry('mail.thread')
@ -81,7 +82,8 @@ class TestMailBase(common.TransactionCase):
# Test 'pigs' group to use through the various tests
self.group_pigs_id = self.mail_group.create(cr, uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'},
{'mail_create_nolog': True})
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
def tearDown(self):

View File

@ -19,8 +19,6 @@
#
##############################################################################
from openerp import tools
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.tools.mail import html_sanitize, append_content_to_html
@ -121,18 +119,18 @@ class test_mail(TestMailBase):
# Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
group_messages = frog_group.message_ids
self.assertTrue(len(group_messages) == 1, 'New group should only have the original message')
self.assertTrue(len(group_messages) == 2, 'New group should only have the original message + creation log')
mail_frog_news = MAIL_TEMPLATE.format(to='Friendly Frogs <group+frogs@example.com>', subject='news', extra='')
self.mail_thread.message_process(cr, uid, None, mail_frog_news)
frog_group.refresh()
self.assertTrue(len(frog_group.message_ids) == 2, 'Group should contain 2 messages now')
self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
# Even with a wrong destination, a reply should end up in the correct thread
mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
self.mail_thread.message_process(cr, uid, None, mail_reply)
frog_group.refresh()
self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
self.assertTrue(len(frog_group.message_ids) == 4, 'Group should contain 4 messages now')
# No model passed and no matching alias must raise
mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
@ -206,7 +204,7 @@ class test_mail(TestMailBase):
# Data: create 'disturbing' values in mail.followers: same res_id, other res_model; same res_model, other res_id
group_dummy_id = self.mail_group.create(cr, uid,
{'name': 'Dummy group'})
{'name': 'Dummy group'}, {'mail_create_nolog': True})
self.mail_followers.create(cr, uid,
{'res_model': 'mail.thread', 'res_id': self.group_pigs_id, 'partner_id': partner_bert_id})
self.mail_followers.create(cr, uid,
@ -445,7 +443,7 @@ class test_mail(TestMailBase):
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
mail_compose = self.registry('mail.compose.message')
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'})
group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'}, {'mail_create_nolog': True})
group_bird = self.mail_group.browse(cr, uid, group_bird_id)
# Mail data
@ -588,3 +586,91 @@ class test_mail(TestMailBase):
na_demo_group = self.mail_message._needaction_count(cr, user_raoul.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_demo, na_demo_base + 0, 'Demo should have 0 new needaction')
self.assertEqual(na_demo_group, 0, 'Demo should have 0 needaction related to Pigs')
def test_40_track_field(self):
""" Testing auto tracking of fields. """
def _strip_string_spaces(body):
return body.replace(' ', '').replace('\n', '')
# Data: subscribe Raoul to Pigs, because he will change the public attribute and may loose access to the record
cr, uid = self.cr, self.uid
self.mail_group.message_subscribe_users(cr, uid, [self.group_pigs_id], [self.user_raoul_id])
# Data: res.users.group, to test group_public_id automatic logging
group_system_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_system')
group_system_id = group_system_ref and group_system_ref[1] or False
# Data: custom subtypes
mt_private_id = self.mail_message_subtype.create(cr, uid, {'name': 'private', 'description': 'Private public'})
self.ir_model_data.create(cr, uid, {'name': 'mt_private', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_private_id})
mt_name_supername_id = self.mail_message_subtype.create(cr, uid, {'name': 'name_supername', 'description': 'Supername name'})
self.ir_model_data.create(cr, uid, {'name': 'mt_name_supername', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_name_supername_id})
mt_group_public_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public', 'description': 'Group changed'})
self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_id})
# Data: alter mail_group model for testing purposes (test on classic, selection and many2one fields)
self.mail_group._track = {
'public': {
'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj['public'] == 'private',
},
'name': {
'mail.mt_name_supername': lambda self, cr, uid, obj, ctx=None: obj['name'] == 'supername',
},
'group_public_id': {
'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
},
}
public_col = self.mail_group._columns.get('public')
name_col = self.mail_group._columns.get('name')
group_public_col = self.mail_group._columns.get('group_public_id')
public_col.track_visibility = 'onchange'
name_col.track_visibility = 'always'
group_public_col.track_visibility = 'onchange'
# Test: change name -> always tracked, not related to a subtype
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public'})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 1, 'tracked: a message should have been produced')
# Test: first produced message: no subtype, name change tracked
last_msg = self.group_pigs.message_ids[-1]
self.assertFalse(last_msg.subtype_id, 'tracked: message should not have been linked to a subtype')
self.assertIn('SelectedGroupOnly&#8594;Public', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
self.assertIn('Pigs', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
# Test: change name as supername, public as private -> 2 subtypes
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'name': 'supername', 'public': 'private'})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 3, 'tracked: two messages should have been produced')
# Test: first produced message: mt_name_supername
last_msg = self.group_pigs.message_ids[-2]
self.assertEqual(last_msg.subtype_id.id, mt_private_id, 'tracked: message should be linked to mt_private subtype')
self.assertIn('Private public', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn('Pigs&#8594;supername', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
# Test: second produced message: mt_name_supername
last_msg = self.group_pigs.message_ids[-3]
self.assertEqual(last_msg.subtype_id.id, mt_name_supername_id, 'tracked: message should be linked to mt_name_supername subtype')
self.assertIn('Supername name', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn('Public&#8594;Private', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
self.assertIn('Pigs&#8594;supername', _strip_string_spaces(last_msg.body), 'tracked feature: message body does not hold always tracked field')
# Test: change public as public, group_public_id -> 1 subtype, name always tracked
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public', 'group_public_id': group_system_id})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: one message should have been produced')
# Test: first produced message: mt_group_public_id, with name always tracked, public tracked on change
last_msg = self.group_pigs.message_ids[-4]
self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should not be linked to any subtype')
self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn('Private&#8594;Public', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
self.assertIn('HumanResources/Employee&#8594;Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
# Test: change not tracked field, no tracking message
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: No message should have been produced')
# Data: removed changes
public_col.track_visibility = None
name_col.track_visibility = None
group_public_col.track_visibility = None
self.mail_group._track = {}

View File

@ -375,19 +375,6 @@ class mrp_bom(osv.osv):
default.update(name=_("%s (copy)") % (bom_data['name']), bom_id=False)
return super(mrp_bom, self).copy_data(cr, uid, id, default, context=context)
def create(self, cr, uid, vals, context=None):
obj_id = super(mrp_bom, self).create(cr, uid, vals, context=context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def create_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('product.product')
for obj in self.browse(cr, uid, ids, context=context):
for prod in prod_obj.browse(cr, uid, [obj.product_id], context=context):
self.message_post(cr, uid, [obj.id], body=_("Bill of Material has been <b>created</b> for <em>%s</em> product.") % (prod.id.name_template), context=context)
return True
mrp_bom()
def rounding(f, r):
import math
@ -474,6 +461,7 @@ class mrp_production(osv.osv):
[('draft', 'New'), ('cancel', 'Cancelled'), ('picking_except', 'Picking Exception'), ('confirmed', 'Awaiting Raw Materials'),
('ready', 'Ready to Produce'), ('in_production', 'Production Started'), ('done', 'Done')],
string='Status', readonly=True,
track_visibility='onchange',
help="When the production order is created the status is set to 'Draft'.\n\
If the order is confirmed the status is set to 'Waiting Goods'.\n\
If any exceptions are there, the status is set to 'Picking Exception'.\n\
@ -511,11 +499,6 @@ class mrp_production(osv.osv):
(_check_qty, 'Order quantity cannot be negative or zero!', ['product_qty']),
]
def create(self, cr, uid, vals, context=None):
obj_id = super(mrp_production, self).create(cr, uid, vals, context=context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def unlink(self, cr, uid, ids, context=None):
for production in self.browse(cr, uid, ids, context=context):
if production.state not in ('draft', 'cancel'):
@ -653,7 +636,6 @@ class mrp_production(osv.osv):
move_obj.action_cancel(cr, uid, [x.id for x in production.move_created_ids])
move_obj.action_cancel(cr, uid, [x.id for x in production.move_lines])
self.write(cr, uid, ids, {'state': 'cancel'})
self.action_cancel_send_note(cr, uid, ids, context)
return True
def action_ready(self, cr, uid, ids, context=None):
@ -668,7 +650,6 @@ class mrp_production(osv.osv):
if production.move_prod_id and production.move_prod_id.location_id.id != production.location_dest_id.id:
move_obj.write(cr, uid, [production.move_prod_id.id],
{'location_id': production.location_dest_id.id})
self.action_ready_send_note(cr, uid, [production_id], context)
return True
def action_production_end(self, cr, uid, ids, context=None):
@ -678,7 +659,6 @@ class mrp_production(osv.osv):
for production in self.browse(cr, uid, ids):
self._costs_generate(cr, uid, production)
write_res = self.write(cr, uid, ids, {'state': 'done', 'date_finished': time.strftime('%Y-%m-%d %H:%M:%S')})
self.action_done_send_note(cr, uid, ids, context)
return write_res
def test_production_done(self, cr, uid, ids):
@ -847,9 +827,7 @@ class mrp_production(osv.osv):
""" Changes state to In Production and writes starting date.
@return: True
"""
self.write(cr, uid, ids, {'state': 'in_production', 'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
self.action_in_production_send_note(cr, uid, ids, context)
return True
return self.write(cr, uid, ids, {'state': 'in_production', 'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
def test_if_product(self, cr, uid, ids):
"""
@ -1019,7 +997,6 @@ class mrp_production(osv.osv):
wf_service.trg_validate(uid, 'stock.picking', shipment_id, 'button_confirm', cr)
production.write({'state':'confirmed'}, context=context)
self.action_confirm_send_note(cr, uid, [production.id], context);
return shipment_id
def force_production(self, cr, uid, ids, *args):
@ -1031,46 +1008,6 @@ class mrp_production(osv.osv):
pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
return True
# ---------------------------------------------------
# OpenChatter methods and notifications
# ---------------------------------------------------
def create_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Manufacturing order has been <b>created</b>."), context=context)
return True
def action_cancel_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order has been <b>canceled</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def action_ready_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order is <b>ready to produce</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def action_in_production_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order is <b>in production</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def action_done_send_note(self, cr, uid, ids, context=None):
message = _("Manufacturing order has been <b>done</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def action_confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
# convert datetime field to a datetime, using server format, then
# convert it to the user TZ and re-render it with %Z to add the timezone
obj_datetime = fields.DT.datetime.strptime(obj.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
obj_date_str = fields.datetime.context_timestamp(cr, uid, obj_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
message = _("Manufacturing order has been <b>confirmed</b> and is <b>scheduled</b> for the <em>%s</em>.") % (obj_date_str)
self.message_post(cr, uid, [obj.id], body=message, context=context)
return True
mrp_production()
class mrp_production_workcenter_line(osv.osv):
_name = 'mrp.production.workcenter.line'
@ -1084,14 +1021,14 @@ class mrp_production_workcenter_line(osv.osv):
'cycle': fields.float('Number of Cycles', digits=(16,2)),
'hour': fields.float('Number of Hours', digits=(16,2)),
'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
'production_id': fields.many2one('mrp.production', 'Manufacturing Order', select=True, ondelete='cascade', required=True),
'production_id': fields.many2one('mrp.production', 'Manufacturing Order',
track_visibility='onchange', select=True, ondelete='cascade', required=True),
}
_defaults = {
'sequence': lambda *a: 1,
'hour': lambda *a: 0,
'cycle': lambda *a: 0,
}
mrp_production_workcenter_line()
class mrp_production_product_line(osv.osv):
_name = 'mrp.production.product.line'
@ -1105,7 +1042,6 @@ class mrp_production_product_line(osv.osv):
'product_uos': fields.many2one('product.uom', 'Product UOS'),
'production_id': fields.many2one('mrp.production', 'Production Order', select=True),
}
mrp_production_product_line()
class product_product(osv.osv):
_inherit = "product.product"

View File

@ -160,17 +160,14 @@ class mrp_production_workcenter_line(osv.osv):
""" Sets state to draft.
@return: True
"""
self.write(cr, uid, ids, {'state':'draft'})
self.action_draft_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
def action_start_working(self, cr, uid, ids, context=None):
""" Sets state to start working and writes starting date.
@return: True
"""
self.modify_production_order_state(cr, uid, ids, 'start')
self.write(cr, uid, ids, {'state':'startworking', 'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
self.action_start_send_note(cr, uid, ids, context=context)
self.write(cr, uid, ids, {'state':'startworking', 'date_start': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
return True
def action_done(self, cr, uid, ids, context=None):
@ -186,8 +183,7 @@ class mrp_production_workcenter_line(osv.osv):
delay += (date_finished-date_start).days * 24
delay += (date_finished-date_start).seconds / float(60*60)
self.write(cr, uid, ids, {'state':'done', 'date_finished': date_now,'delay':delay})
self.action_done_send_note(cr, uid, ids, context=context)
self.write(cr, uid, ids, {'state':'done', 'date_finished': date_now,'delay':delay}, context=context)
self.modify_production_order_state(cr,uid,ids,'done')
return True
@ -195,71 +191,20 @@ class mrp_production_workcenter_line(osv.osv):
""" Sets state to cancel.
@return: True
"""
self.write(cr, uid, ids, {'state':'cancel'})
self.action_cancel_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
def action_pause(self, cr, uid, ids, context=None):
""" Sets state to pause.
@return: True
"""
self.write(cr, uid, ids, {'state':'pause'})
self.action_pending_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'pause'}, context=context)
def action_resume(self, cr, uid, ids, context=None):
""" Sets state to startworking.
@return: True
"""
self.write(cr, uid, ids, {'state':'startworking'})
self.action_start_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'startworking'}, context=context)
# -------------------------------------------------------
# OpenChatter methods and notifications
# -------------------------------------------------------
def action_draft_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('mrp.production')
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>created</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
return True
def action_start_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('mrp.production')
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>started</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
return True
def action_done_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('mrp.production')
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>done</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
return True
def action_pending_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('mrp.production')
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order is <b>pending</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
return True
def action_cancel_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('mrp.production')
for workorder in self.browse(cr, uid, ids):
for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
message = _("Work order has been <b>cancelled</b> for production order <em>%s</em>.") % (prod.id.name)
self.message_post(cr, uid, [workorder.id], body=message, context=context)
return True
mrp_production_workcenter_line()
class mrp_production(osv.osv):
_inherit = 'mrp.production'

View File

@ -130,7 +130,7 @@ class mrp_repair(osv.osv):
('2binvoiced','To be Invoiced'),
('invoice_except','Invoice Exception'),
('done','Repaired')
], 'Status', readonly=True,
], 'Status', readonly=True, track_visibility='onchange',
help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order. \
\n* The \'Confirmed\' status is used when a user confirms the repair order. \
\n* The \'Ready to Repair\' status is used to start to repairing, user can start repairing only after repair order is confirmed. \
@ -335,7 +335,6 @@ class mrp_repair(osv.osv):
if line.product_id.track_production and not line.prodlot_id:
raise osv.except_osv(_('Warning!'), _("Serial number is required for operation line with product '%s'") % (line.product_id.name))
mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
self.set_confirm_send_note(cr, uid, ids)
return True
def action_cancel(self, cr, uid, ids, context=None):
@ -348,14 +347,10 @@ class mrp_repair(osv.osv):
mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context)
else:
raise osv.except_osv(_('Warning!'),_('Repair order is already invoiced.'))
self.write(cr,uid,ids,{'state':'cancel'})
self.set_cancel_send_note(cr, uid, ids, context)
return True
return self.write(cr,uid,ids,{'state':'cancel'})
def wkf_invoice_create(self, cr, uid, ids, *args):
self.action_invoice_create(cr, uid, ids)
self.set_toinvoiced_send_note(cr, uid, ids)
return True
return self.action_invoice_create(cr, uid, ids)
def action_invoice_create(self, cr, uid, ids, group=False, context=None):
""" Creates invoice(s) for repair order.
@ -470,7 +465,6 @@ class mrp_repair(osv.osv):
self.pool.get('mrp.repair.line').write(cr, uid, [l.id for
l in repair.operations], {'state': 'confirmed'}, context=context)
self.write(cr, uid, [repair.id], {'state': 'ready'})
self.set_ready_send_note(cr, uid, ids, context)
return True
def action_repair_start(self, cr, uid, ids, context=None):
@ -482,7 +476,6 @@ class mrp_repair(osv.osv):
repair_line.write(cr, uid, [l.id for
l in repair.operations], {'state': 'confirmed'}, context=context)
repair.write({'state': 'under_repair'})
self.set_start_send_note(cr, uid, ids, context)
return True
def action_repair_end(self, cr, uid, ids, context=None):
@ -560,55 +553,8 @@ class mrp_repair(osv.osv):
res[repair.id] = picking
else:
self.write(cr, uid, [repair.id], {'state': 'done'})
self.set_done_send_note(cr, uid, [repair.id], context)
return res
def create(self, cr, uid, vals, context=None):
repair_id = super(mrp_repair, self).create(cr, uid, vals, context=context)
self.create_send_note(cr, uid, [repair_id], context=context)
return repair_id
def create_send_note(self, cr, uid, ids, context=None):
for repair in self.browse(cr, uid, ids, context):
message = _("Repair Order for <em>%s</em> has been <b>created</b>." % (repair.product_id.name))
self.message_post(cr, uid, [repair.id], body=message, context=context)
return True
def set_start_send_note(self, cr, uid, ids, context=None):
for repair in self.browse(cr, uid, ids, context):
message = _("Repair Order for <em>%s</em> has been <b>started</b>." % (repair.product_id.name))
self.message_post(cr, uid, [repair.id], body=message, context=context)
return True
def set_toinvoiced_send_note(self, cr, uid, ids, context=None):
for repair in self.browse(cr, uid, ids, context):
message = _("Draft Invoice of %s %s <b>waiting for validation</b>.") % (repair.invoice_id.amount_total, repair.invoice_id.currency_id.symbol)
self.message_post(cr, uid, [repair.id], body=message, context=context)
return True
def set_confirm_send_note(self, cr, uid, ids, context=None):
for repair in self.browse(cr, uid, ids, context):
message = _( "Repair Order for <em>%s</em> has been <b>accepted</b>." % (repair.product_id.name))
self.message_post(cr, uid, [repair.id], body=message, context=context)
return True
def set_cancel_send_note(self, cr, uid, ids, context=None):
message = _("Repair has been <b>cancelled</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def set_ready_send_note(self, cr, uid, ids, context=None):
message = _("Repair Order is now <b>ready</b> to repair.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
def set_done_send_note(self, cr, uid, ids, context=None):
message = _("Repair Order is <b>closed</b>.")
self.message_post(cr, uid, ids, body=message, context=context)
return True
mrp_repair()
class ProductChangeMixin(object):
def product_id_change(self, cr, uid, ids, pricelist, product, uom=False,

View File

@ -65,20 +65,12 @@ class note_note(osv.osv):
return res
#unactivate a sticky note and record the date
def onclick_note_is_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, { 'open' : False, 'date_done' : fields.date.today() })
self.message_post(cr, uid, ids[0], body='Note is done.', subject=False,
type='notification', parent_id=False, attachments=None, context=context)
return False
return self.write(cr, uid, ids, {'open': False, 'date_done': fields.date.today()}, context=context)
#activate a note
def onclick_note_not_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'open' : True})
self.message_post(cr, uid, ids[0], body='Note has been activated.', subject=False,
type='notification', parent_id=False, attachments=None, context=context)
return False
return self.write(cr, uid, ids, {'open': True}, context=context)
#used for undisplay the follower if it's the current user
def _get_my_current_partner(self, cr, uid, ids, name, args, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
@ -104,7 +96,6 @@ class note_note(osv.osv):
result[record.id] = stage.id
return result
_columns = {
'name': fields.function(_get_note_first_line,
string='Note Summary',
@ -117,7 +108,7 @@ class note_note(osv.osv):
type='many2one',
relation='note.stage'),
'stage_ids': fields.many2many('note.stage','note_stage_rel','note_id','stage_id','Stages of Users'),
'open': fields.boolean('Active'),
'open': fields.boolean('Active', track_visibility='onchange'),
'date_done': fields.date('Date done'),
'color': fields.integer('Color Index'),
'tag_ids' : fields.many2many('note.tag','note_tags_rel','note_id','tag_id','Tags'),

View File

@ -21,7 +21,7 @@
<record id="action_news" model="ir.actions.client">
<field name="name">News</field>
<field name="tag">mail.wall</field>
<field name="model">mail.group</field>
<field name="res_model">mail.group</field>
<field name="params" eval="{
'domain': [
('model', '=', 'mail.group'),
@ -53,8 +53,7 @@
<record id="action_jobs" model="ir.actions.client">
<field name="name">Jobs</field>
<field name="tag">mail.wall</field>
<field name="res_model">mail.message</field>
<field name="res_id" ref="company_jobs"/>
<field name="res_model">mail.group</field>
<field name="params" eval="{
'domain':[
('model', '=', 'mail.group'),

View File

@ -101,11 +101,11 @@ class crm_contact_us(osv.TransientModel):
"""
Create an empty record in the contact table.
Since the 'name' field is mandatory, give an empty string to avoid an integrity error.
Pass mail_nosubscribe key in context because otherwise the inheritance
Pass mail_create_nosubscribe key in context because otherwise the inheritance
leads to a message_subscribe_user, that triggers access right issues.
"""
empty_values = dict((k, False) if k != 'name' else (k, '') for k, v in values.iteritems())
return super(crm_contact_us, self).create(cr, SUPERUSER_ID, empty_values, {'mail_nosubscribe': True})
return super(crm_contact_us, self).create(cr, SUPERUSER_ID, empty_values, {'mail_create_nosubscribe': True})
def submit(self, cr, uid, ids, context=None):
""" When the form is submitted, redirect the user to a "Thanks" message """

View File

@ -112,7 +112,7 @@ class procurement_order(osv.osv):
('running','Running'),
('ready','Ready'),
('done','Done'),
('waiting','Waiting')], 'Status', required=True,
('waiting','Waiting')], 'Status', required=True, track_visibility='onchange',
help='When a procurement is created the status is set to \'Draft\'.\n If the procurement is confirmed, the status is set to \'Confirmed\'.\
\nAfter confirming the status is set to \'Running\'.\n If any exception arises in the order then the status is set to \'Exception\'.\n Once the exception is removed the status becomes \'Ready\'.\n It is in \'Waiting\'. status when the procurement is waiting for another one to finish.'),
'note': fields.text('Note'),
@ -329,7 +329,6 @@ class procurement_order(osv.osv):
move_obj.action_confirm(cr, uid, [id], context=context)
self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move': 1})
self.write(cr, uid, ids, {'state': 'confirmed', 'message': ''})
self.confirm_send_note(cr, uid, ids, context)
return True
def action_move_assigned(self, cr, uid, ids, context=None):
@ -414,7 +413,6 @@ class procurement_order(osv.osv):
if len(to_assign):
move_obj.write(cr, uid, to_assign, {'state': 'assigned'})
self.write(cr, uid, ids, {'state': 'cancel'})
self.cancel_send_note(cr, uid, ids, context=None)
wf_service = netsvc.LocalService("workflow")
for id in ids:
wf_service.trg_trigger(uid, 'procurement.order', id, cr)
@ -451,38 +449,13 @@ class procurement_order(osv.osv):
if procurement.close_move and (procurement.move_id.state <> 'done'):
move_obj.action_done(cr, uid, [procurement.move_id.id])
res = self.write(cr, uid, ids, {'state': 'done', 'date_close': time.strftime('%Y-%m-%d')})
self.done_send_note(cr, uid, ids, context=None)
wf_service = netsvc.LocalService("workflow")
for id in ids:
wf_service.trg_trigger(uid, 'procurement.order', id, cr)
return res
# ----------------------------------------
# OpenChatter methods and notifications
# ----------------------------------------
def create(self, cr, uid, vals, context=None):
obj_id = super(procurement_order, self).create(cr, uid, vals, context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def create_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement <b>created</b>."), context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement <b>confirmed</b>."), context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement <b>cancelled</b>."), context=context)
def done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement <b>done</b>."), context=context)
procurement_order()
class StockPicking(osv.osv):
_inherit = 'stock.picking'
def test_finished(self, cursor, user, ids):
wf_service = netsvc.LocalService("workflow")
res = super(StockPicking, self).test_finished(cursor, user, ids)
@ -494,8 +467,6 @@ class StockPicking(osv.osv):
procurement.id, 'button_check', cursor)
return res
StockPicking()
class stock_warehouse_orderpoint(osv.osv):
"""
Defines Minimum stock rules.

View File

@ -580,15 +580,6 @@ class product_product(osv.osv):
'seller_qty': fields.function(_calc_seller, type='float', string='Supplier Quantity', multi="seller_info", help="This is minimum quantity to purchase from Main Supplier."),
'seller_id': fields.function(_calc_seller, type='many2one', relation="res.partner", string='Main Supplier', help="Main Supplier who has highest priority in Supplier List.", multi="seller_info"),
}
def create(self, cr, uid, vals, context=None):
obj_id = super(product_product, self).create(cr, uid, vals, context=context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Product has been <b>created</b>."), context=context)
def unlink(self, cr, uid, ids, context=None):
unlink_ids = []
unlink_product_tmpl_ids = []

View File

@ -309,32 +309,22 @@ class project(osv.osv):
task_obj = self.pool.get('project.task')
task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', 'not in', ('cancelled', 'done'))])
task_obj.case_close(cr, uid, task_ids, context=context)
self.write(cr, uid, ids, {'state':'close'}, context=context)
self.set_close_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'close'}, context=context)
def set_cancel(self, cr, uid, ids, context=None):
task_obj = self.pool.get('project.task')
task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', '!=', 'done')])
task_obj.case_cancel(cr, uid, task_ids, context=context)
self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
self.set_cancel_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
def set_pending(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'pending'}, context=context)
self.set_pending_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'pending'}, context=context)
def set_open(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'open'}, context=context)
self.set_open_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'open'}, context=context)
def reset_project(self, cr, uid, ids, context=None):
res = self.setActive(cr, uid, ids, value=True, context=context)
self.set_open_send_note(cr, uid, ids, context=context)
return res
return self.setActive(cr, uid, ids, value=True, context=context)
def map_tasks(self, cr, uid, old_project_id, new_project_id, context=None):
""" copy and map tasks from old to new project """
@ -553,24 +543,8 @@ def Project():
vals['type'] = 'contract'
project_id = super(project, self).create(cr, uid, vals, context)
mail_alias.write(cr, uid, [vals['alias_id']], {'alias_defaults': {'project_id': project_id} }, context)
self.create_send_note(cr, uid, [project_id], context=context)
return project_id
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Project has been <b>created</b>."), context=context)
def set_open_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Project has been <b>opened</b>."), context=context)
def set_pending_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Project is now <b>pending</b>."), context=context)
def set_cancel_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Project has been <b>canceled</b>."), context=context)
def set_close_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Project has been <b>closed</b>."), context=context)
def write(self, cr, uid, ids, vals, context=None):
# if alias_model has been changed, update alias_model_id accordingly
if vals.get('alias_model'):
@ -584,6 +558,20 @@ class task(base_stage, osv.osv):
_date_name = "date_start"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'new',
'project.mt_task_started': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open',
'project.mt_task_closed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
},
'stage_id': {
'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['new', 'done', 'open'],
},
'kanban_state': { # kanban state: tracked, but only block subtype
'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj['kanban_state'] == 'blocked',
},
}
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
return (self._resolve_project_id_from_context(cr, uid, context=context) or False)
@ -743,7 +731,7 @@ class task(base_stage, osv.osv):
'description': fields.text('Description'),
'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Important'), ('0','Very important')], 'Priority', select=True),
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
'stage_id': fields.many2one('project.task.type', 'Stage',
'stage_id': fields.many2one('project.task.type', 'Stage', track_visibility='onchange',
domain="['&', ('fold', '=', False), ('project_ids', '=', project_id)]"),
'state': fields.related('stage_id', 'state', type="selection", store=True,
selection=_TASK_STATE, string="Status", readonly=True,
@ -754,6 +742,7 @@ class task(base_stage, osv.osv):
set to \'Pending\'.'),
'categ_ids': fields.many2many('project.category', string='Tags'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
help="A task's kanban state indicates special situations affecting it:\n"
" * Normal is the default situation\n"
" * Blocked indicates something is preventing the progress of this task\n"
@ -763,7 +752,7 @@ class task(base_stage, osv.osv):
'date_start': fields.datetime('Starting Date',select=True),
'date_end': fields.datetime('Ending Date',select=True),
'date_deadline': fields.date('Deadline',select=True),
'project_id': fields.many2one('project.project', 'Project', ondelete='set null', select="1"),
'project_id': fields.many2one('project.project', 'Project', ondelete='set null', select="1", track_visibility='onchange'),
'parent_ids': fields.many2many('project.task', 'project_task_parent_rel', 'task_id', 'parent_id', 'Parent Tasks'),
'child_ids': fields.many2many('project.task', 'project_task_parent_rel', 'parent_id', 'task_id', 'Delegated Tasks'),
'notes': fields.text('Notes'),
@ -789,7 +778,7 @@ class task(base_stage, osv.osv):
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'user_id': fields.many2one('res.users', 'Assigned to'),
'user_id': fields.many2one('res.users', 'Assigned to', track_visibility='onchange'),
'delegated_user_id': fields.related('child_ids', 'user_id', type='many2one', relation='res.users', string='Delegated To'),
'partner_id': fields.many2one('res.partner', 'Customer'),
'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'),
@ -974,14 +963,12 @@ class task(base_stage, osv.osv):
if not task.date_end:
vals['date_end'] = fields.datetime.now()
self.case_set(cr, uid, [task.id], 'done', vals, context=context)
self.case_close_send_note(cr, uid, [task.id], context=context)
return True
def do_reopen(self, cr, uid, ids, context=None):
for task in self.browse(cr, uid, ids, context=context):
project = task.project_id
self.case_set(cr, uid, [task.id], 'open', {}, context=context)
self.case_open_send_note(cr, uid, [task.id], context)
return True
def do_cancel(self, cr, uid, ids, context=None):
@ -993,7 +980,6 @@ class task(base_stage, osv.osv):
self._check_child_task(cr, uid, ids, context=context)
for task in tasks:
self.case_set(cr, uid, [task.id], 'cancelled', {'remaining_hours': 0.0}, context=context)
self.case_cancel_send_note(cr, uid, [task.id], context=context)
return True
def do_open(self, cr, uid, ids, context=None):
@ -1002,26 +988,21 @@ class task(base_stage, osv.osv):
def case_open(self, cr, uid, ids, context=None):
if not isinstance(ids,list): ids = [ids]
self.case_set(cr, uid, ids, 'open', {'date_start': fields.datetime.now()}, context=context)
self.case_open_send_note(cr, uid, ids, context)
return True
return self.case_set(cr, uid, ids, 'open', {'date_start': fields.datetime.now()}, context=context)
def do_draft(self, cr, uid, ids, context=None):
""" Compatibility when changing to case_draft. """
return self.case_draft(cr, uid, ids, context=context)
def case_draft(self, cr, uid, ids, context=None):
self.case_set(cr, uid, ids, 'draft', {}, context=context)
self.case_draft_send_note(cr, uid, ids, context=context)
return True
return self.case_set(cr, uid, ids, 'draft', {}, context=context)
def do_pending(self, cr, uid, ids, context=None):
""" Compatibility when changing to case_pending. """
return self.case_pending(cr, uid, ids, context=context)
def case_pending(self, cr, uid, ids, context=None):
self.case_set(cr, uid, ids, 'pending', {}, context=context)
return self.case_pending_send_note(cr, uid, ids, context=context)
return self.case_set(cr, uid, ids, 'pending', {}, context=context)
def _delegate_task_attachments(self, cr, uid, task_id, delegated_task_id, context=None):
attachment = self.pool.get('ir.attachment')
@ -1061,7 +1042,6 @@ class task(base_stage, osv.osv):
self.do_pending(cr, uid, [task.id], context=context)
elif delegate_data['state'] == 'done':
self.do_close(cr, uid, [task.id], context=context)
self.do_delegation_send_note(cr, uid, [task.id], context)
delegated_tasks[task.id] = delegated_task_id
return delegated_tasks
@ -1085,12 +1065,10 @@ class task(base_stage, osv.osv):
return self.set_remaining_time(cr, uid, ids, 10.0, context)
def set_kanban_state_blocked(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'kanban_state': 'blocked'}, context=context)
return False
return self.write(cr, uid, ids, {'kanban_state': 'blocked'}, context=context)
def set_kanban_state_normal(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'kanban_state': 'normal'}, context=context)
return False
return self.write(cr, uid, ids, {'kanban_state': 'normal'}, context=context)
def set_kanban_state_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'kanban_state': 'done'}, context=context)
@ -1110,38 +1088,9 @@ class task(base_stage, osv.osv):
}, context=context)
return True
def _subscribe_project_followers_to_task(self, cr, uid, task_id, context=None):
""" TDE note: not the best way to do this, we could override _get_followers
of task, and perform a better mapping of subtypes than a mapping
based on names.
However we will keep this implementation, maybe to be refactored
in 7.1 of future versions. """
# task followers are project followers, with matching subtypes
task_record = self.browse(cr, uid, task_id, context=context)
subtype_obj = self.pool.get('mail.message.subtype')
follower_obj = self.pool.get('mail.followers')
if task_record.project_id:
# create mapping
task_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('res_model', '=', self._name)], context=context)
task_subtypes = subtype_obj.browse(cr, uid, task_subtype_ids, context=context)
# fetch subscriptions
follower_ids = follower_obj.search(cr, uid, [('res_model', '=', 'project.project'), ('res_id', '=', task_record.project_id.id)], context=context)
# copy followers
for follower in follower_obj.browse(cr, uid, follower_ids, context=context):
if not follower.subtype_ids:
continue
project_subtype_names = [project_subtype.name for project_subtype in follower.subtype_ids]
task_subtype_ids = [task_subtype.id for task_subtype in task_subtypes if task_subtype.name in project_subtype_names]
self.message_subscribe(cr, uid, [task_id], [follower.partner_id.id],
subtype_ids=task_subtype_ids, context=context)
def create(self, cr, uid, vals, context=None):
task_id = super(task, self).create(cr, uid, vals, context=context)
# subscribe project followers to the task
self._subscribe_project_followers_to_task(cr, uid, task_id, context=context)
self._store_history(cr, uid, [task_id], context=context)
self.create_send_note(cr, uid, [task_id], context=context)
return task_id
# Overridden to reset the kanban_state to normal whenever
@ -1149,6 +1098,11 @@ class task(base_stage, osv.osv):
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
if vals.get('project_id'):
project_id = self.pool.get('project.project').browse(cr, uid, vals.get('project_id'), context=context)
if project_id:
vals.setdefault('message_follower_ids', [])
vals['message_follower_ids'] += [(6, 0,[follower.id]) for follower in project_id.message_follower_ids]
if vals and not 'kanban_state' in vals and 'stage_id' in vals:
new_stage = vals.get('stage_id')
vals_reset_kstate = dict(vals, kanban_state='normal')
@ -1159,17 +1113,11 @@ class task(base_stage, osv.osv):
#raise osv.except_osv(_('Warning!'), _('Stage is not defined in the project.'))
write_vals = vals_reset_kstate if t.stage_id != new_stage else vals
super(task, self).write(cr, uid, [t.id], write_vals, context=context)
self.stage_set_send_note(cr, uid, [t.id], new_stage, context=context)
result = True
else:
result = super(task, self).write(cr, uid, ids, vals, context=context)
if ('stage_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals):
self._store_history(cr, uid, ids, context=context)
# subscribe new project followers to the task
if vals.get('project_id'):
for id in ids:
self._subscribe_project_followers_to_task(cr, uid, id, context=context)
return result
def unlink(self, cr, uid, ids, context=None):
@ -1244,49 +1192,6 @@ class task(base_stage, osv.osv):
getattr(self,act)(cr, uid, ids, context=context)
return super(task,self).message_update(cr, uid, msg, update_vals=update_vals, context=context)
# ---------------------------------------------------
# OpenChatter methods and notifications
# ---------------------------------------------------
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
""" Override of default prefix for notifications. """
return 'Task'
def get_needaction_user_ids(self, cr, uid, ids, context=None):
""" Returns the user_ids that have to perform an action.
Add to the previous results given by super the document responsible
when in draft mode.
:return: dict { record_id: [user_ids], }
"""
result = super(task, self).get_needaction_user_ids(cr, uid, ids, context=context)
for obj in self.browse(cr, uid, ids, context=context):
if obj.state == 'draft' and obj.user_id:
result[obj.id].append(obj.user_id.id)
return result
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
""" Add 'user_id' and 'manager_id' to the monitored fields """
res = super(task, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
return res + ['user_id', 'manager_id']
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """
stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body=_("Stage changed to <b>%s</b>.") % (stage_name),
context=context)
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Task has been <b>created</b>."), context=context)
def case_draft_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_('Task has been set as <b>draft</b>.'), context=context)
def do_delegation_send_note(self, cr, uid, ids, context=None):
for task in self.browse(cr, uid, ids, context=context):
msg = _('Task has been <b>delegated</b> to <em>%s</em>.') % (task.user_id.name)
self.message_post(cr, uid, [task.id], body=msg, context=context)
return True
def project_task_reevaluate(self, cr, uid, ids, context=None):
if self.pool.get('res.users').has_group(cr, uid, 'project.group_time_work_estimation_tasks'):
return {

View File

@ -78,40 +78,66 @@
<field name="fold" eval="True"/>
</record>
<!-- mail: subtypes -->
<record id="mt_project_new" model="mail.message.subtype">
<field name="name">New</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
</record>
<record id="mt_project_closed" model="mail.message.subtype">
<field name="name">Closed</field>
<field name="res_model">project.project</field>
</record>
<record id="mt_project_canceled" model="mail.message.subtype">
<field name="name">Canceled</field>
<field name="res_model">project.project</field>
</record>
<record id="mt_project_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">project.project</field>
</record>
<!-- Task-related subtypes for messaging / Chatter -->
<record id="mt_task_new" model="mail.message.subtype">
<field name="name">New</field>
<field name="name">Task Created</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task created</field>
</record>
<record id="mt_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task started</field>
</record>
<record id="mt_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
<field name="res_model">project.task</field>
<field name="description">Task blocked</field>
</record>
<record id="mt_task_closed" model="mail.message.subtype">
<field name="name">Closed</field>
<field name="name">Task Done</field>
<field name="res_model">project.task</field>
<field name="description">Task closed</field>
</record>
<record id="mt_task_canceled" model="mail.message.subtype">
<field name="name">Canceled</field>
<field name="res_model">project.task</field>
</record>
<record id="mt_task_change" model="mail.message.subtype">
<record id="mt_task_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">project.task</field>
<field name="description">Stage changed</field>
</record>
<!-- Project-related subtypes for messaging / Chatter -->
<record id="mt_project_task_new" model="mail.message.subtype">
<field name="name">Task Created</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_started')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_closed')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_stage" model="mail.message.subtype">
<field name="name">Task Stage Changed</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_stage')"/>
<field name="relation_field">project_id</field>
</record>
<!-- notify all employees of module installation -->

View File

@ -115,7 +115,7 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" help="Follow this project to automatically follow all related tasks and issues." groups="base.group_user"/>
<field name="message_follower_ids" widget="mail_followers" help="Follow this project to automatically track the events associated to tasks and issues of this project." groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>

View File

@ -41,7 +41,8 @@ class project_issue_version(osv.osv):
}
project_issue_version()
_ISSUE_STATE= [('draft', 'New'), ('open', 'In Progress'), ('cancel', 'Cancelled'), ('done', 'Done'),('pending', 'Pending')]
_ISSUE_STATE = [('draft', 'New'), ('open', 'In Progress'), ('cancel', 'Cancelled'), ('done', 'Done'), ('pending', 'Pending')]
class project_issue(base_stage, osv.osv):
_name = "project.issue"
@ -49,6 +50,20 @@ class project_issue(base_stage, osv.osv):
_order = "priority, create_date desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'new',
'project_issue.mt_issue_closed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
'project_issue.mt_issue_started': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open',
},
'stage_id': {
'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['new', 'done', 'open'],
},
'kanban_state': {
'project_issue.mt_issue_blocked': lambda self, cr, uid, obj, ctx=None: obj['kanban_state'] == 'blocked',
},
}
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default project by checking if present in the context """
return self._resolve_project_id_from_context(cr, uid, context=context)
@ -233,6 +248,13 @@ class project_issue(base_stage, osv.osv):
When the case is over, the status is set to \'Done\'.\
If the case needs to be reviewed then the status is \
set to \'Pending\'.'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
help="A Issue's kanban state indicates special situations affecting it:\n"
" * Normal is the default situation\n"
" * Blocked indicates something is preventing the progress of this issue\n"
" * Ready for next stage indicates the issue is ready to be pulled to the next stage",
readonly=True, required=False),
'email_from': fields.char('Email', size=128, help="These people will receive email.", select=1),
'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
'date_open': fields.datetime('Opened', readonly=True,select=True),
@ -244,15 +266,16 @@ class project_issue(base_stage, osv.osv):
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
'version_id': fields.many2one('project.issue.version', 'Version'),
'stage_id': fields.many2one ('project.task.type', 'Stage',
track_visibility='onchange',
domain="['&', ('fold', '=', False), ('project_ids', '=', project_id)]"),
'project_id':fields.many2one('project.project', 'Project'),
'project_id':fields.many2one('project.project', 'Project', track_visibility='onchange'),
'duration': fields.float('Duration'),
'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
'day_open': fields.function(_compute_day, string='Days to Open', \
multi='compute_day', type="float", store=True),
'day_close': fields.function(_compute_day, string='Days to Close', \
multi='compute_day', type="float", store=True),
'user_id': fields.many2one('res.users', 'Assigned to', required=False, select=1),
'user_id': fields.many2one('res.users', 'Assigned to', required=False, select=1, track_visibility='onchange'),
'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
multi='compute_day', type="float", store=True),
'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
@ -279,6 +302,7 @@ class project_issue(base_stage, osv.osv):
'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
'priority': crm.AVAILABLE_PRIORITIES[2][0],
'kanban_state': 'normal',
}
_group_by_full = {
@ -333,9 +357,9 @@ class project_issue(base_stage, osv.osv):
'task_id': new_task_id,
'stage_id': self.stage_find(cr, uid, [bug], bug.project_id.id, [('state', '=', 'pending')], context=context),
}
self.convert_to_task_send_note(cr, uid, [bug.id], context=context)
message = _("Project issue <b>converted</b> to task.")
self.message_post(cr, uid, [bug.id], body=message, context=context)
case_obj.write(cr, uid, [bug.id], vals, context=context)
self.case_pending_send_note(cr, uid, [bug.id], context=context)
return {
'name': _('Tasks'),
@ -359,42 +383,12 @@ class project_issue(base_stage, osv.osv):
return super(project_issue, self).copy(cr, uid, id, default=default,
context=context)
def _subscribe_project_followers_to_issue(self, cr, uid, task_id, context=None):
""" TDE note: not the best way to do this, we could override _get_followers
of issue, and perform a better mapping of subtypes than a mapping
based on names.
However we will keep this implementation, maybe to be refactored
in 7.1 of future versions. """
# task followers are project followers, with matching subtypes
task_record = self.browse(cr, uid, task_id, context=context)
subtype_obj = self.pool.get('mail.message.subtype')
follower_obj = self.pool.get('mail.followers')
if task_record.project_id:
# create mapping
task_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('res_model', '=', self._name)], context=context)
task_subtypes = subtype_obj.browse(cr, uid, task_subtype_ids, context=context)
# fetch subscriptions
follower_ids = follower_obj.search(cr, uid, [('res_model', '=', 'project.project'), ('res_id', '=', task_record.project_id.id)], context=context)
# copy followers
for follower in follower_obj.browse(cr, uid, follower_ids, context=context):
if not follower.subtype_ids:
continue
project_subtype_names = [project_subtype.name for project_subtype in follower.subtype_ids]
task_subtype_ids = [task_subtype.id for task_subtype in task_subtypes if task_subtype.name in project_subtype_names]
self.message_subscribe(cr, uid, [task_id], [follower.partner_id.id],
subtype_ids=task_subtype_ids, context=context)
def write(self, cr, uid, ids, vals, context=None):
#Update last action date every time the user change the stage, the state or send a new email
logged_fields = ['stage_id', 'state', 'message_ids']
if any([field in vals for field in logged_fields]):
vals['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S')
# subscribe new project followers to the issue
if vals.get('project_id'):
for id in ids:
self._subscribe_project_followers_to_issue(cr, uid, id, context=context)
return super(project_issue, self).write(cr, uid, ids, vals, context)
def onchange_task_id(self, cr, uid, ids, task_id, context=None):
@ -410,19 +404,19 @@ class project_issue(base_stage, osv.osv):
self.write(cr, uid, ids, {'date_open': False, 'date_closed': False})
return res
def create(self, cr, uid, vals, context=None):
obj_id = super(project_issue, self).create(cr, uid, vals, context=context)
# subscribe project follower to the issue
self._subscribe_project_followers_to_issue(cr, uid, obj_id, context=context)
self.create_send_note(cr, uid, [obj_id], context=context)
return obj_id
# -------------------------------------------------------
# Stage management
# -------------------------------------------------------
def set_kanban_state_blocked(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'kanban_state': 'blocked'}, context=context)
def set_kanban_state_normal(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'kanban_state': 'normal'}, context=context)
def set_kanban_state_done(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'kanban_state': 'done'}, context=context)
def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
""" Override of the base.stage method
Parameter of the stage search taken from the issue:
@ -455,7 +449,6 @@ class project_issue(base_stage, osv.osv):
def case_cancel(self, cr, uid, ids, context=None):
""" Cancels case """
self.case_set(cr, uid, ids, 'cancelled', {'active': True}, context=context)
self.case_cancel_send_note(cr, uid, ids, context=context)
return True
def case_escalate(self, cr, uid, ids, context=None):
@ -471,7 +464,6 @@ class project_issue(base_stage, osv.osv):
else:
raise osv.except_osv(_('Warning!'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
self.case_set(cr, uid, ids, 'draft', data, context=context)
self.case_escalate_send_note(cr, uid, [case.id], context=context)
return True
# -------------------------------------------------------
@ -529,38 +521,6 @@ class project_issue(base_stage, osv.osv):
return super(project_issue, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
# -------------------------------------------------------
# OpenChatter methods and notifications
# -------------------------------------------------------
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """
stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), subtype="mt_issue_new", context=context)
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
""" Override of default prefix for notifications. """
return 'Project issue'
def convert_to_task_send_note(self, cr, uid, ids, context=None):
message = _("Project issue <b>converted</b> to task.")
return self.message_post(cr, uid, ids, body=message, context=context)
def create_send_note(self, cr, uid, ids, context=None):
message = _("Project issue <b>created</b>.")
return self.message_post(cr, uid, ids, body=message, subtype="mt_issue_new", context=context)
def case_escalate_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.project_id:
message = _("<b>escalated</b> to <em>'%s'</em>.") % (obj.project_id.name)
obj.message_post(body=message)
else:
message = _("<b>escalated</b>.")
obj.message_post(body=message)
return True
project_issue()
class project(osv.osv):
_inherit = "project.project"

View File

@ -43,23 +43,68 @@ You can record issues, assign them to a responsible person, and keep track of th
Access all issues from the top Project menu, and access the issues of a specific project via the projects gallery view.</p>]]></field>
</record>
<!-- Mail subtypes -->
<record id="mail.mt_issue_new" model="mail.message.subtype">
<field name="name">New</field>
<!-- Issue-related subtypes for messaging / Chatter -->
<record id="mt_issue_new" model="mail.message.subtype">
<field name="name">Issue Created</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue created</field>
</record>
<record id="mail.mt_issue_closed" model="mail.message.subtype">
<field name="name">Closed</field>
<record id="mt_issue_started" model="mail.message.subtype">
<field name="name">Issue Started</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue started</field>
</record>
<record id="mail.mt_issue_canceled" model="mail.message.subtype">
<field name="name">Canceled</field>
<record id="mt_issue_blocked" model="mail.message.subtype">
<field name="name">Issue Blocked</field>
<field name="res_model">project.issue</field>
<field name="description">Issue blocked</field>
</record>
<record id="mail.mt_issue_change" model="mail.message.subtype">
<record id="mt_issue_closed" model="mail.message.subtype">
<field name="name">Issue Closed</field>
<field name="res_model">project.issue</field>
<field name="description">Issue closed</field>
</record>
<record id="mt_issue_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">project.issue</field>
<field name="description">Stage changed</field>
</record>
<!-- Project-related subtypes for messaging / Chatter -->
<record id="mt_project_issue_new" model="mail.message.subtype">
<field name="name">Issue Created</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_issue_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_started" model="mail.message.subtype">
<field name="name">Issue Started</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_issue_started')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_blocked" model="mail.message.subtype">
<field name="name">Issue Blocked</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_issue_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_closed" model="mail.message.subtype">
<field name="name">Issue Closed</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_issue_closed')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_stage" model="mail.message.subtype">
<field name="name">Issue Stage Changed</field>
<field name="res_model">project.project</field>
<field name="description">Stage changed</field>
<field name="parent_id" eval="ref('mt_issue_stage')"/>
<field name="relation_field">project_id</field>
</record>
</data>
</openerp>

View File

@ -200,6 +200,7 @@
<field name="user_email"/>
<field name="user_id"/>
<field name="date_deadline"/>
<field name="kanban_state"/>
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@ -227,6 +228,9 @@
<field name="categ_ids"/>
<div class="oe_right">
<span class="oe_kanban_highlight" groups="base.group_user">
<a t-if="record.kanban_state.raw_value === 'normal'" type="object" string="In Progress" name="set_kanban_state_done" class="oe_kanban_status"> </a>
<a t-if="record.kanban_state.raw_value === 'done'" type="object" string="Ready for next stage" name="set_kanban_state_blocked" class="oe_kanban_status oe_kanban_status_green"> </a>
<a t-if="record.kanban_state.raw_value === 'blocked'" type="object" string="Blocked" name="set_kanban_state_normal" class="oe_kanban_status oe_kanban_status_red"> </a>
<t t-set="priority" t-value="record.priority.raw_value || 5"/>
<a type="object" name="set_priority" args="['3']" t-if="priority gt 3" title="Normal Priority">
<img src="/web/static/src/img/icons/star-off.png" width="16" height="16"/>

View File

@ -159,7 +159,12 @@ class purchase_order(osv.osv):
('done', 'Done'),
('cancel', 'Cancelled')
]
_track = {
'state': {
'purchase.mt_rfq_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirmed',
'purchase.mt_rfq_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'approved',
},
}
_columns = {
'name': fields.char('Order Reference', size=64, required=True, select=True, help="Unique number of the purchase order, computed automatically when the purchase order is created."),
'origin': fields.char('Source Document', size=64,
@ -169,7 +174,8 @@ class purchase_order(osv.osv):
help="Reference of the sale order or quotation sent by your supplier. It's mainly used to do the matching when you receive the products as this reference is usually written on the delivery order sent by your supplier."),
'date_order':fields.date('Order Date', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, select=True, help="Date on which this document has been created."),
'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
change_default=True, track_visibility='always'),
'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
help="Put an address if you want to deliver directly from the supplier to the customer. " \
@ -203,7 +209,7 @@ class purchase_order(osv.osv):
'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Untaxed Amount',
store={
'purchase.order.line': (_get_order, None, 10),
}, multi="sums", help="The amount without tax"),
}, multi="sums", help="The amount without tax", track_visibility='always'),
'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Taxes',
store={
'purchase.order.line': (_get_order, None, 10),
@ -242,8 +248,6 @@ class purchase_order(osv.osv):
if vals.get('name','/')=='/':
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/'
order = super(purchase_order, self).create(cr, uid, vals, context=context)
if order:
self.create_send_note(cr, uid, [order], context=context)
return order
def unlink(self, cr, uid, ids, context=None):
@ -457,7 +461,6 @@ class purchase_order(osv.osv):
self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
for id in ids:
self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
self.confirm_send_note(cr, uid, ids, context)
return True
def _prepare_inv_line(self, cr, uid, account_id, order_line, context=None):
@ -488,7 +491,6 @@ class purchase_order(osv.osv):
# Deleting the existing instance of workflow for PO
wf_service.trg_delete(uid, 'purchase.order', p_id, cr)
wf_service.trg_create(uid, 'purchase.order', p_id, cr)
self.draft_send_note(cr, uid, ids, context=context)
return True
def action_invoice_create(self, cr, uid, ids, context=None):
@ -555,13 +557,10 @@ class purchase_order(osv.osv):
# Link this new invoice to related purchase order
order.write({'invoice_ids': [(4, inv_id)]}, context=context)
res = inv_id
if res:
self.invoice_send_note(cr, uid, ids, res, context)
return res
def invoice_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'approved'}, context=context)
self.invoice_done_send_note(cr, uid, ids, context=context)
return True
def has_stockable_product(self, cr, uid, ids, *args):
@ -592,7 +591,6 @@ class purchase_order(osv.osv):
for (id, name) in self.name_get(cr, uid, ids):
wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
self.cancel_send_note(cr, uid, ids, context)
return True
def _prepare_order_picking(self, cr, uid, order, context=None):
@ -677,13 +675,10 @@ class purchase_order(osv.osv):
# In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
# one that should trigger the advancement of the purchase workflow.
# By default we will consider the first one as most important, but this behavior can be overridden.
if picking_ids:
self.shipment_send_note(cr, uid, ids, picking_ids[0], context=context)
return picking_ids[0] if picking_ids else False
def picking_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
self.shipment_done_send_note(cr, uid, ids, context=context)
return True
def copy(self, cr, uid, id, default=None, context=None):
@ -810,48 +805,6 @@ class purchase_order(osv.osv):
wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
return orders_info
# --------------------------------------
# OpenChatter methods and notifications
# --------------------------------------
def needaction_domain_get(self, cr, uid, ids, context=None):
return [('state', '=', 'draft')]
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
def shipment_send_note(self, cr, uid, ids, picking_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
for picking in (pck for pck in order.picking_ids if pck.id == picking_id):
# convert datetime field to a datetime, using server format, then
# convert it to the user TZ and re-render it with %Z to add the timezone
picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
self.message_post(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
def shipment_done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
def invoice_done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
def draft_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
purchase_order()
class purchase_order_line(osv.osv):
def _amount_line(self, cr, uid, ids, prop, arg, context=None):
@ -1200,21 +1153,15 @@ class procurement_order(osv.osv):
}
res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=new_context)
self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
self.purchase_order_create_note(cr, uid, ids, context=context)
self.message_post(cr, uid, ids, body=_("Draft Purchase Order created"), context=context)
return res
def _product_virtual_get(self, cr, uid, order_point):
procurement = order_point.procurement_id
if procurement and procurement.state != 'exception' and procurement.purchase_id and procurement.purchase_id.state in ('draft', 'confirmed'):
return None
return super(procurement_order, self)._product_virtual_get(cr, uid, order_point)
def purchase_order_create_note(self, cr, uid, ids, context=None):
for procurement in self.browse(cr, uid, ids, context=context):
body = _("Draft Purchase Order created")
self.message_post(cr, uid, [procurement.id], body=body, context=context)
procurement_order()
class mail_mail(osv.osv):
_name = 'mail.mail'

View File

@ -48,6 +48,16 @@
id="purchase_default_set"
model="ir.values"
name="set"/>
<!-- Purchase-related subtypes for messaging / Chatter -->
<record id="mt_rfq_confirmed" model="mail.message.subtype">
<field name="name">RFQ Confirmed</field>
<field name="res_model">purchase.order</field>
</record>
<record id="mt_rfq_approved" model="mail.message.subtype">
<field name="name">RFQ Approved</field>
<field name="res_model">purchase.order</field>
</record>
</data>
</openerp>

View File

@ -44,7 +44,8 @@ class purchase_requisition(osv.osv):
'purchase_ids' : fields.one2many('purchase.order','requisition_id','Purchase Orders',states={'done': [('readonly', True)]}),
'line_ids' : fields.one2many('purchase.requisition.line','requisition_id','Products to Purchase',states={'done': [('readonly', True)]}),
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
'state': fields.selection([('draft','New'),('in_progress','Sent to Suppliers'),('cancel','Cancelled'),('done','Purchase Done')], 'Status', required=True)
'state': fields.selection([('draft','New'),('in_progress','Sent to Suppliers'),('cancel','Cancelled'),('done','Purchase Done')],
'Status', track_visibility='onchange', required=True)
}
_defaults = {
'date_start': lambda *args: time.strftime('%Y-%m-%d %H:%M:%S'),
@ -71,36 +72,16 @@ class purchase_requisition(osv.osv):
for purchase_id in purchase.purchase_ids:
if str(purchase_id.state) in('draft'):
purchase_order_obj.action_cancel(cr,uid,[purchase_id.id])
self.write(cr, uid, ids, {'state': 'cancel'})
self.cancel_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state': 'cancel'})
def tender_in_progress(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'in_progress'} ,context=context)
self.in_progress_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state':'in_progress'} ,context=context)
def tender_reset(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'draft'})
self.reset_send_note(cr, uid, ids, context=context)
return True
return self.write(cr, uid, ids, {'state': 'draft'})
def tender_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'done', 'date_end':time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
self.done_to_send_note(cr, uid, ids, context=context)
return True
def in_progress_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Draft Requisition has been <b>sent to suppliers</b>."), context=context)
def reset_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), context=context)
def done_to_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), context=context)
return self.write(cr, uid, ids, {'state':'done', 'date_end':time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
def _planned_date(self, requisition, delay=0.0):
company = requisition.company_id
@ -181,17 +162,7 @@ class purchase_requisition(osv.osv):
}, context=context)
return res
def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>created</b>."), context=context)
def create(self, cr, uid, vals, context=None):
requisition = super(purchase_requisition, self).create(cr, uid, vals, context=context)
if requisition:
self.create_send_note(cr, uid, [requisition], context=context)
return requisition
purchase_requisition()
class purchase_requisition_line(osv.osv):

View File

@ -49,6 +49,12 @@ class sale_order(osv.osv):
_name = "sale.order"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_description = "Sales Order"
_track = {
'state': {
'sale.mt_order_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] in ['manual', 'progress'],
'sale.mt_order_sent': lambda self, cr, uid, obj, ctx=None: obj['state'] in ['sent']
},
}
def onchange_shop_id(self, cr, uid, ids, shop_id, context=None):
v = {}
@ -187,12 +193,13 @@ class sale_order(osv.osv):
('manual', 'Sale to Invoice'),
('invoice_except', 'Invoice Exception'),
('done', 'Done'),
], 'Status', readonly=True, help="Gives the status of the quotation or sales order. \nThe exception status is automatically set when a cancel operation occurs in the processing of a document linked to the sale order. \nThe 'Waiting Schedule' status is set when the invoice is confirmed but waiting for the scheduler to run on the order date.", select=True),
], 'Status', readonly=True, track_visibility='onchange',
help="Gives the status of the quotation or sales order. \nThe exception status is automatically set when a cancel operation occurs in the processing of a document linked to the sale order. \nThe 'Waiting Schedule' status is set when the invoice is confirmed but waiting for the scheduler to run on the order date.", select=True),
'date_order': fields.date('Date', required=True, readonly=True, select=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}),
'create_date': fields.datetime('Creation Date', readonly=True, select=True, help="Date on which sales order is created."),
'date_confirm': fields.date('Confirmation Date', readonly=True, select=True, help="Date on which sales order is confirmed."),
'user_id': fields.many2one('res.users', 'Salesperson', states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, select=True),
'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, select=True),
'user_id': fields.many2one('res.users', 'Salesperson', states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, select=True, track_visibility='onchange'),
'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, select=True, track_visibility='always'),
'partner_invoice_id': fields.many2one('res.partner', 'Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Invoice address for current sales order."),
'partner_shipping_id': fields.many2one('res.partner', 'Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Delivery address for current sales order."),
'order_policy': fields.selection([
@ -213,20 +220,20 @@ class sale_order(osv.osv):
fnct_search=_invoiced_search, type='boolean', help="It indicates that sale order has at least one invoice."),
'note': fields.text('Terms and conditions'),
'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Untaxed Amount',
store = {
'amount_untaxed': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Untaxed Amount',
store={
'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
},
multi='sums', help="The amount without tax."),
'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Taxes',
store = {
multi='sums', help="The amount without tax.", track_visibility='always'),
'amount_tax': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Taxes',
store={
'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
},
multi='sums', help="The tax amount."),
'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Total',
store = {
'amount_total': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Total',
store={
'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
},
@ -323,10 +330,7 @@ class sale_order(osv.osv):
def create(self, cr, uid, vals, context=None):
if vals.get('name','/')=='/':
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'sale.order') or '/'
order = super(sale_order, self).create(cr, uid, vals, context=context)
if order:
self.create_send_note(cr, uid, [order], context=context)
return order
return super(sale_order, self).create(cr, uid, vals, context=context)
def button_dummy(self, cr, uid, ids, context=None):
return True
@ -527,8 +531,6 @@ class sale_order(osv.osv):
invoice_ids.append(res)
self.write(cr, uid, [order.id], {'state': 'progress'})
cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
if res:
self.invoice_send_note(cr, uid, ids, res, context)
return res
def action_invoice_cancel(self, cr, uid, ids, context=None):
@ -542,7 +544,6 @@ class sale_order(osv.osv):
line.write({'state': 'confirmed'})
if this.state == 'invoice_except':
this.write({'state': 'progress'})
this.invoice_paid_send_note()
return True
def action_cancel(self, cr, uid, ids, context=None):
@ -561,7 +562,6 @@ class sale_order(osv.osv):
wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
{'state': 'cancel'})
self.cancel_send_note(cr, uid, [sale.id], context=None)
self.write(cr, uid, ids, {'state': 'cancel'})
return True
@ -596,7 +596,6 @@ class sale_order(osv.osv):
else:
self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': fields.date.context_today(self, cr, uid, context=context)})
self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
self.confirm_send_note(cr, uid, ids, context)
return True
def action_quotation_send(self, cr, uid, ids, context=None):
@ -634,41 +633,9 @@ class sale_order(osv.osv):
}
def action_done(self, cr, uid, ids, context=None):
self.done_send_note(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state': 'done'}, context=context)
# ------------------------------------------------
# OpenChatter methods and notifications
# ------------------------------------------------
def needaction_domain_get(self, cr, uid, ids, context=None):
return [('state', '=', 'draft'), ('user_id','=',uid)]
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>created</b>.") % (obj.partner_id.name), context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to Sale Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
def cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Sale Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
def done_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Sale Order for <em>%s</em> set to <b>Done</b>") % (obj.partner_id.name), context=context)
def invoice_paid_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
sale_order()
# TODO add a field price_unit_uos
# - update it on change product and unit price

View File

@ -43,5 +43,18 @@
<field name="body"><![CDATA[<p>This application lets you create and send quotations and process your sales orders; from delivery to invoicing.</p>
<p>If you need to manage your sales pipeline (leads, opportunities, phonecalls), the <i>CRM</i> application may be useful. Use the Settings menu to install it.</p>]]></field>
</record>
<!-- Sale-related subtypes for messaging / Chatter -->
<record id="mt_order_sent" model="mail.message.subtype">
<field name="name">Quotation send</field>
<field name="res_model">sale.order</field>
<field name="description">Quotation send</field>
</record>
<record id="mt_order_confirmed" model="mail.message.subtype">
<field name="name">Sale Order Confirmed</field>
<field name="res_model">sale.order</field>
<field name="description">Quotation confirmed</field>
</record>
</data>
</openerp>

View File

@ -41,6 +41,7 @@ modules.
'data': [
'wizard/crm_make_sale_view.xml',
'sale_crm_view.xml',
'sale_crm_data.xml',
'process/sale_crm_process.xml',
'security/sale_crm_security.xml',
'security/ir.model.access.csv',

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp.osv import osv,fields
from openerp.osv import osv, fields
class sale_order(osv.osv):
_inherit = 'sale.order'
@ -29,22 +29,6 @@ class sale_order(osv.osv):
domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]")
}
def create(self, cr, uid, vals, context=None):
order = super(sale_order, self).create(cr, uid, vals, context=context)
section_id = self.browse(cr, uid, order, context=context).section_id
if section_id:
followers = [follow.id for follow in section_id.message_follower_ids]
self.message_subscribe(cr, uid, [order], followers, context=context)
return order
def write(self, cr, uid, ids, vals, context=None):
if vals.get('section_id'):
section_id = self.pool.get('crm.case.section').browse(cr, uid, vals.get('section_id'), context=context)
if section_id:
vals['message_follower_ids'] = [(4, follower.id) for follower in section_id.message_follower_ids]
return super(sale_order, self).write(cr, uid, ids, vals, context=context)
sale_order()
class res_users(osv.Model):
_inherit = 'res.users'

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<!-- Salesteam-related subtypes for messaging / Chatter -->
<record id="mt_salesteam_order_sent" model="mail.message.subtype">
<field name="name">Quotation Send</field>
<field name="res_model">crm.case.section</field>
<field name="parent_id" eval="ref('sale.mt_order_sent')"/>
<field name="relation_field">section_id</field>
</record>
<record id="mt_salesteam_order_confirmed" model="mail.message.subtype">
<field name="name">Sale Order Confirmed</field>
<field name="res_model">crm.case.section</field>
<field name="parent_id" eval="ref('sale.mt_order_confirmed')"/>
<field name="relation_field">section_id</field>
</record>
</data>
</openerp>

View File

@ -399,9 +399,6 @@ class sale_order(osv.osv):
wf_service = netsvc.LocalService("workflow")
if picking_id:
wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
self.delivery_send_note(cr, uid, [order.id], picking_id, context)
for proc_id in proc_ids:
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
@ -440,8 +437,6 @@ class sale_order(osv.osv):
if towrite:
self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
res = self.write(cr, uid, [order.id], val)
if res:
self.delivery_end_send_note(cr, uid, [order.id], context=context)
return True
def has_stockable_products(self, cr, uid, ids, *args):
@ -451,25 +446,6 @@ class sale_order(osv.osv):
return True
return False
# ------------------------------------------------
# OpenChatter methods and notifications
# ------------------------------------------------
def get_needaction_user_ids(self, cr, uid, ids, context=None):
result = super(sale_order, self).get_needaction_user_ids(cr, uid, ids, context=context)
return result
def delivery_send_note(self, cr, uid, ids, picking_id, context=None):
for order in self.browse(cr, uid, ids, context=context):
for picking in (pck for pck in order.picking_ids if pck.id == picking_id):
# convert datetime field to a datetime, using server format, then
# convert it to the user TZ and re-render it with %Z to add the timezone
picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
self.message_post(cr, uid, [order.id], body=_("Delivery Order <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
def delivery_end_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Order <b>delivered</b>."), context=context)
class sale_order_line(osv.osv):

View File

@ -621,8 +621,6 @@ class stock_picking(osv.osv):
seq_obj_name = self._name
vals['name'] = self.pool.get('ir.sequence').get(cr, user, seq_obj_name)
new_id = super(stock_picking, self).create(cr, user, vals, context)
if new_id:
self.create_send_note(cr, user, [new_id], context=context)
return new_id
_columns = {
@ -644,7 +642,7 @@ class stock_picking(osv.osv):
('confirmed', 'Waiting Availability'),
('assigned', 'Ready to Transfer'),
('done', 'Transferred'),
], 'Status', readonly=True, select=True, help="""
], 'Status', readonly=True, select=True, track_visibility='onchange', help="""
* Draft: not confirmed yet and will not be scheduled until confirmed\n
* Waiting Another Operation: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows)\n
* Waiting Availability: still waiting for the availability of products\n
@ -666,7 +664,7 @@ class stock_picking(osv.osv):
("invoiced", "Invoiced"),
("2binvoiced", "To Be Invoiced"),
("none", "Not Applicable")], "Invoice Control",
select=True, required=True, readonly=True, states={'draft': [('readonly', False)]}),
select=True, required=True, readonly=True, track_visibility='onchange', states={'draft': [('readonly', False)]}),
'company_id': fields.many2one('res.company', 'Company', required=True, select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
}
_defaults = {
@ -742,9 +740,6 @@ class stock_picking(osv.osv):
@return: True
"""
pickings = self.browse(cr, uid, ids, context=context)
for picking in pickings:
if picking.state <> 'confirmed':
self.confirm_send_note(cr, uid, [picking.id], context=context)
self.write(cr, uid, ids, {'state': 'confirmed'})
todo = []
for picking in pickings:
@ -869,7 +864,6 @@ class stock_picking(osv.osv):
ids2 = [move.id for move in pick.move_lines]
self.pool.get('stock.move').action_cancel(cr, uid, ids2, context)
self.write(cr, uid, ids, {'state': 'cancel', 'invoice_state': 'none'})
self.ship_cancel_send_note(cr, uid, ids, context)
return True
#
@ -1359,12 +1353,11 @@ class stock_picking(osv.osv):
wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
delivered_pack_id = new_picking
back_order_name = self.browse(cr, uid, delivered_pack_id, context=context).name
self.back_order_send_note(cr, uid, ids, back_order_name, context)
self.message_post(cr, uid, ids, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_order_name), context=context)
else:
self.action_move(cr, uid, [pick.id], context=context)
wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
delivered_pack_id = pick.id
self.ship_done_send_note(cr, uid, ids, context)
delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
res[pick.id] = {'delivered_picking': delivered_pack.id or False}
@ -1387,88 +1380,6 @@ class stock_picking(osv.osv):
'stock', self._VIEW_LIST.get(type, 'view_picking_form'))
return res and res[1] or False
def log_picking(self, cr, uid, ids, context=None):
""" This function will create log messages for picking.
@param cr: the database cursor
@param uid: the current user's ID for security checks,
@param ids: List of Picking Ids
@param context: A standard dictionary for contextual values
"""
if context is None:
context = {}
lang_obj = self.pool.get('res.lang')
user_lang = self.pool.get('res.users').browse(cr, uid, uid, context=context).context_lang
lang_ids = lang_obj.search(cr, uid, [('code','like',user_lang)])
if lang_ids:
date_format = lang_obj.browse(cr, uid, lang_ids[0], context=context).date_format
else:
date_format = '%m/%d/%Y'
for pick in self.browse(cr, uid, ids, context=context):
msg=''
if pick.auto_picking:
continue
type_list = {
'out':_("Delivery Order"),
'in':_('Reception'),
'internal': _('Internal picking'),
}
message = type_list.get(pick.type, _('Document')) + " '" + (pick.name or '?') + "' "
if pick.min_date:
msg= _(' for the ')+ datetime.strptime(pick.min_date, '%Y-%m-%d %H:%M:%S').strftime(date_format)
state_list = {
'confirmed': _('is scheduled %s.') % msg,
'assigned': _('is ready to process.'),
'cancel': _('is cancelled.'),
'done': _('is done.'),
'auto': _('is waiting.'),
'draft': _('is in draft state.'),
}
context['view_id'] = self._get_view_id(cr, uid, pick.type)
message += state_list[pick.state]
return True
# -----------------------------------------
# OpenChatter methods and notifications
# -----------------------------------------
def _get_document_type(self, cr, uid, obj, context=None):
type_dict = {
'out': _('Delivery order'),
'in': _('Shipment'),
'internal': _('Internal picking'),
}
return type_dict.get(obj.type, _('Picking'))
def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s has been <b>created</b>.") % (self._get_document_type(cr, uid, obj, context=context)), context=context)
def confirm_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s has been <b>confirmed</b>.") % (self._get_document_type(cr, uid, obj, context=context)), context=context)
def scrap_send_note(self, cr, uid, ids, quantity, uom, name, context=None):
return self.message_post(cr, uid, ids, body= _("%s %s %s has been <b>moved to</b> scrap.") % (quantity, uom, name), context=context)
def back_order_send_note(self, cr, uid, ids, back_name, context=None):
return self.message_post(cr, uid, ids, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_name), context=context)
def ship_done_send_note(self, cr, uid, ids, context=None):
type_dict = {
'out': _("Products have been <b>delivered</b>."),
'in': _("Products have been <b>received</b>."),
'internal': _("Products have been <b>moved</b>."),
}
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Products have been <b>%s</b>.") % (type_dict.get(obj.type, 'move done')), context=context)
def ship_cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s has been <b>cancelled</b>.") % (self._get_document_type(cr, uid, obj, context=context)), context=context)
stock_picking()
class stock_production_lot(osv.osv):
@ -2582,7 +2493,8 @@ class stock_move(osv.osv):
for product in product_obj.browse(cr, uid, [move.product_id.id], context=context):
if move.picking_id:
uom = product.uom_id.name if product.uom_id else ''
move.picking_id.scrap_send_note(quantity, uom, product.name, context=context)
message = _("%s %s %s has been <b>moved to</b> scrap.") % (quantity, uom, product.name)
move.picking_id.message_post(body=message)
self.action_done(cr, uid, res, context=context)
return res

View File

@ -26,7 +26,6 @@ from openerp import tools
class stock_change_product_qty(osv.osv_memory):
_name = "stock.change.product.qty"
_inherit = ['mail.thread']
_description = "Change Product Quantity"
_columns = {
'product_id' : fields.many2one('product.product', 'Product'),
@ -103,18 +102,6 @@ class stock_change_product_qty(osv.osv_memory):
inventry_obj.action_confirm(cr, uid, [inventory_id], context=context)
inventry_obj.action_done(cr, uid, [inventory_id], context=context)
self.change_product_qty_send_note(cr, uid, [data.id], context)
return {}
def change_product_qty_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('product.product')
location_obj = self.pool.get('stock.location')
for data in self.browse(cr, uid, ids, context=context):
location_name = location_obj.browse(cr, uid, data.location_id.id, context=context).name
message = _("<b>Quantity has been changed</b> to <em>%s %s </em> for <em>%s</em> location.") % (data.new_quantity, data.product_id.uom_id.name, location_name)
prod_obj.message_post(cr, uid, [data.product_id.id], body=message, context=context)
stock_change_product_qty()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: