[FORWARD] Forward port of addons 7.0 until revision 9008
bzr revid: tde@openerp.com-20130411124446-73gzyw3eo925w3dv
This commit is contained in:
commit
4da68cbc22
|
@ -1384,6 +1384,7 @@ class account_move(osv.osv):
|
||||||
'ref':False,
|
'ref':False,
|
||||||
'balance':False,
|
'balance':False,
|
||||||
'account_tax_id':False,
|
'account_tax_id':False,
|
||||||
|
'statement_id': False,
|
||||||
})
|
})
|
||||||
|
|
||||||
if 'journal_id' in vals and vals.get('journal_id', False):
|
if 'journal_id' in vals and vals.get('journal_id', False):
|
||||||
|
@ -1420,6 +1421,7 @@ class account_move(osv.osv):
|
||||||
context = {} if context is None else context.copy()
|
context = {} if context is None else context.copy()
|
||||||
default.update({
|
default.update({
|
||||||
'state':'draft',
|
'state':'draft',
|
||||||
|
'ref': False,
|
||||||
'name':'/',
|
'name':'/',
|
||||||
})
|
})
|
||||||
context.update({
|
context.update({
|
||||||
|
|
|
@ -367,18 +367,6 @@ class account_invoice(osv.osv):
|
||||||
context['view_id'] = view_id
|
context['view_id'] = view_id
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def create(self, cr, uid, vals, context=None):
|
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
try:
|
|
||||||
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!'),
|
|
||||||
_('There is no Sale/Purchase Journal(s) defined.'))
|
|
||||||
else:
|
|
||||||
raise orm.except_orm(_('Unknown Error!'), str(e))
|
|
||||||
|
|
||||||
def invoice_print(self, cr, uid, ids, context=None):
|
def invoice_print(self, cr, uid, ids, context=None):
|
||||||
'''
|
'''
|
||||||
This function prints the invoice and mark it as sent, so that we can see more easily the next step of the workflow
|
This function prints the invoice and mark it as sent, so that we can see more easily the next step of the workflow
|
||||||
|
|
|
@ -64,30 +64,6 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Replace analytic_id with analytics_id in account.invoice.line -->
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_invoice_line_form_inherit">
|
|
||||||
<field name="name">account.invoice.line.form.inherit</field>
|
|
||||||
<field name="model">account.invoice.line</field>
|
|
||||||
<field name="inherit_id" ref="account.view_invoice_line_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<field name="account_analytic_id" position="replace">
|
|
||||||
<field name="analytics_id" context="{'journal_id':parent.journal_id}" domain="[('plan_id','<>',False)]" groups="analytic.group_analytic_accounting"/>
|
|
||||||
</field>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="invoice_supplier_form_inherit">
|
|
||||||
<field name="name">account.invoice.supplier.form.inherit</field>
|
|
||||||
<field name="model">account.invoice</field>
|
|
||||||
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
|
||||||
<field name="priority">2</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<field name="account_analytic_id" position="replace">
|
|
||||||
<field name="analytics_id" domain="[('plan_id','<>',False)]" context="{'journal_id':parent.journal_id}" groups="analytic.group_analytic_accounting"/>
|
|
||||||
</field>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- views for account.analytic.plan.instance -->
|
<!-- views for account.analytic.plan.instance -->
|
||||||
<record model="ir.ui.view" id="account_analytic_plan_instance_form">
|
<record model="ir.ui.view" id="account_analytic_plan_instance_form">
|
||||||
|
|
|
@ -80,7 +80,7 @@ class account_asset_asset(osv.osv):
|
||||||
for asset in self.browse(cr, uid, ids, context=context):
|
for asset in self.browse(cr, uid, ids, context=context):
|
||||||
if asset.account_move_line_ids:
|
if asset.account_move_line_ids:
|
||||||
raise osv.except_osv(_('Error!'), _('You cannot delete an asset that contains posted depreciation lines.'))
|
raise osv.except_osv(_('Error!'), _('You cannot delete an asset that contains posted depreciation lines.'))
|
||||||
return super(account_account, self).unlink(cr, uid, ids, context=context)
|
return super(account_asset_asset, self).unlink(cr, uid, ids, context=context)
|
||||||
|
|
||||||
def _get_period(self, cr, uid, context=None):
|
def _get_period(self, cr, uid, context=None):
|
||||||
periods = self.pool.get('account.period').find(cr, uid)
|
periods = self.pool.get('account.period').find(cr, uid)
|
||||||
|
|
|
@ -256,7 +256,7 @@
|
||||||
<para style="terp_default_Centre_9">[[not line.date and '-' or formatLang(line.date,date=True) ]]</para>
|
<para style="terp_default_Centre_9">[[not line.date and '-' or formatLang(line.date,date=True) ]]</para>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<para style="terp_default_Right_9">[[ formatLang(line.amount or '-', currency_obj=line.company_currency) ]] </para>
|
<para style="terp_default_Right_9">[[ formatLang(line.amount or 0.0, currency_obj=line.company_currency) ]] </para>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<para style="terp_default_Right_9">[[ formatLang(line.amount_currency, currency_obj=line.currency) ]] </para>
|
<para style="terp_default_Right_9">[[ formatLang(line.amount_currency, currency_obj=line.currency) ]] </para>
|
||||||
|
|
|
@ -171,9 +171,9 @@ class account_analytic_account(osv.osv):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'name': fields.char('Account/Contract Name', size=128, required=True),
|
'name': fields.char('Account/Contract Name', size=128, required=True, track_visibility='onchange'),
|
||||||
'complete_name': fields.function(_get_full_name, type='char', string='Full Name'),
|
'complete_name': fields.function(_get_full_name, type='char', string='Full Name'),
|
||||||
'code': fields.char('Reference', select=True),
|
'code': fields.char('Reference', select=True, track_visibility='onchange'),
|
||||||
'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Contract')], 'Type of Account', required=True,
|
'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Contract')], 'Type of Account', required=True,
|
||||||
help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\
|
help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\
|
||||||
"The type 'Analytic account' stands for usual accounts that you only want to use in accounting.\n"\
|
"The type 'Analytic account' stands for usual accounts that you only want to use in accounting.\n"\
|
||||||
|
@ -191,10 +191,10 @@ class account_analytic_account(osv.osv):
|
||||||
'quantity': fields.function(_debit_credit_bal_qtty, type='float', string='Quantity', multi='debit_credit_bal_qtty'),
|
'quantity': fields.function(_debit_credit_bal_qtty, type='float', string='Quantity', multi='debit_credit_bal_qtty'),
|
||||||
'quantity_max': fields.float('Prepaid Service Units', help='Sets the higher limit of time to work on the contract, based on the timesheet. (for instance, number of hours in a limited support contract.)'),
|
'quantity_max': fields.float('Prepaid Service Units', help='Sets the higher limit of time to work on the contract, based on the timesheet. (for instance, number of hours in a limited support contract.)'),
|
||||||
'partner_id': fields.many2one('res.partner', 'Customer'),
|
'partner_id': fields.many2one('res.partner', 'Customer'),
|
||||||
'user_id': fields.many2one('res.users', 'Project Manager'),
|
'user_id': fields.many2one('res.users', 'Project Manager', track_visibility='onchange'),
|
||||||
'manager_id': fields.many2one('res.users', 'Account Manager'),
|
'manager_id': fields.many2one('res.users', 'Account Manager', track_visibility='onchange'),
|
||||||
'date_start': fields.date('Start Date'),
|
'date_start': fields.date('Start Date'),
|
||||||
'date': fields.date('End Date', select=True),
|
'date': fields.date('End Date', select=True, track_visibility='onchange'),
|
||||||
'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.
|
'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'),('pending','To Renew'),('close','Closed'),('cancelled', 'Cancelled')], 'Status', required=True, track_visibility='onchange'),
|
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'),('pending','To Renew'),('close','Closed'),('cancelled', 'Cancelled')], '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
|
'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
|
||||||
|
|
|
@ -859,7 +859,9 @@ class calendar_alarm(osv.osv):
|
||||||
|
|
||||||
if hasattr(res_obj, 'rrule') and res_obj.rrule:
|
if hasattr(res_obj, 'rrule') and res_obj.rrule:
|
||||||
event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
|
event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
|
||||||
recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.exdate, event_date, res_obj.exrule)
|
#exdate is a string and we need a list
|
||||||
|
exdate = res_obj.exdate and res_obj.exdate.split(',') or []
|
||||||
|
recurrent_dates = get_recurrent_dates(res_obj.rrule, exdate, event_date, res_obj.exrule)
|
||||||
|
|
||||||
trigger_interval = alarm.trigger_interval
|
trigger_interval = alarm.trigger_interval
|
||||||
if trigger_interval == 'days':
|
if trigger_interval == 'days':
|
||||||
|
|
|
@ -126,7 +126,7 @@
|
||||||
<field name="name">Run Event Reminder</field>
|
<field name="name">Run Event Reminder</field>
|
||||||
<field eval="True" name="active" />
|
<field eval="True" name="active" />
|
||||||
<field name="user_id" ref="base.user_root" />
|
<field name="user_id" ref="base.user_root" />
|
||||||
<field name="interval_number">1</field>
|
<field name="interval_number">5</field>
|
||||||
<field name="interval_type">minutes</field>
|
<field name="interval_type">minutes</field>
|
||||||
<field name="numbercall">-1</field>
|
<field name="numbercall">-1</field>
|
||||||
<field eval="False" name="doall" />
|
<field eval="False" name="doall" />
|
||||||
|
|
|
@ -414,7 +414,7 @@
|
||||||
<xsl:value-of select="$size" />
|
<xsl:value-of select="$size" />
|
||||||
</xsl:attribute>
|
</xsl:attribute>
|
||||||
</xsl:if>
|
</xsl:if>
|
||||||
<seq id="{../../@text:style-name}"/>.</bullet>
|
<seq id="{../../@text:style-name}"/></bullet>
|
||||||
</xsl:otherwise>
|
</xsl:otherwise>
|
||||||
</xsl:choose>
|
</xsl:choose>
|
||||||
</xsl:if>
|
</xsl:if>
|
||||||
|
|
|
@ -414,7 +414,7 @@
|
||||||
<xsl:value-of select="$size" />
|
<xsl:value-of select="$size" />
|
||||||
</xsl:attribute>
|
</xsl:attribute>
|
||||||
</xsl:if>
|
</xsl:if>
|
||||||
<seq id="{../../@text:style-name}"/>.</bullet>
|
<seq id="{../../@text:style-name}"/></bullet>
|
||||||
|
|
||||||
</xsl:otherwise>
|
</xsl:otherwise>
|
||||||
</xsl:choose>
|
</xsl:choose>
|
||||||
|
|
|
@ -92,15 +92,6 @@ class crm_lead(base_stage, format_address, osv.osv):
|
||||||
context['empty_list_help_document_name'] = _("leads")
|
context['empty_list_help_document_name'] = _("leads")
|
||||||
return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context)
|
return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context)
|
||||||
|
|
||||||
def onchange_user_id(self, cr, uid, ids, section_id, user_id, context=None):
|
|
||||||
""" When changing the user, also set a section_id or restrict section id
|
|
||||||
to the ones user_id is member of. """
|
|
||||||
if user_id:
|
|
||||||
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
|
|
||||||
if len(section_ids) > 0 and section_id not in section_ids:
|
|
||||||
section_id = section_ids[0]
|
|
||||||
return {'value': {'section_id': section_id}}
|
|
||||||
|
|
||||||
def create(self, cr, uid, vals, context=None):
|
def create(self, cr, uid, vals, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
@ -372,6 +363,16 @@ class crm_lead(base_stage, format_address, osv.osv):
|
||||||
}
|
}
|
||||||
return {'value' : values}
|
return {'value' : values}
|
||||||
|
|
||||||
|
def on_change_user(self, cr, uid, ids, user_id, context=None):
|
||||||
|
""" When changing the user, also set a section_id or restrict section id
|
||||||
|
to the ones user_id is member of. """
|
||||||
|
section_id = False
|
||||||
|
if user_id:
|
||||||
|
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
|
||||||
|
if section_ids:
|
||||||
|
section_id = section_ids[0]
|
||||||
|
return {'value': {'section_id': section_id}}
|
||||||
|
|
||||||
def _check(self, cr, uid, ids=False, context=None):
|
def _check(self, cr, uid, ids=False, context=None):
|
||||||
""" Override of the base.stage method.
|
""" Override of the base.stage method.
|
||||||
Function called by the scheduler to process cases for date actions
|
Function called by the scheduler to process cases for date actions
|
||||||
|
|
|
@ -152,7 +152,7 @@
|
||||||
-->
|
-->
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="user_id" on_change="onchange_user_id(section_id, user_id)"
|
<field name="user_id" on_change="on_change_user(user_id, context)"
|
||||||
context="{'default_groups_ref': ['base.group_user', 'base.group_sale_salesman_all_leads'] }"/>
|
context="{'default_groups_ref': ['base.group_user', 'base.group_sale_salesman_all_leads'] }"/>
|
||||||
<label for="section_id" groups="base.group_multi_salesteams"/>
|
<label for="section_id" groups="base.group_multi_salesteams"/>
|
||||||
<div groups="base.group_multi_salesteams">
|
<div groups="base.group_multi_salesteams">
|
||||||
|
@ -429,7 +429,7 @@
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group>
|
<group>
|
||||||
<field name="user_id" on_change="onchange_user_id(section_id, user_id)" context="{'default_groups_ref': ['base.group_user', 'base.group_sale_salesman_all_leads']}"/>
|
<field name="user_id" on_change="on_change_user(user_id, context)" context="{'default_groups_ref': ['base.group_user', 'base.group_sale_salesman_all_leads']}"/>
|
||||||
<label for="section_id" groups="base.group_multi_salesteams"/>
|
<label for="section_id" groups="base.group_multi_salesteams"/>
|
||||||
<div groups="base.group_multi_salesteams">
|
<div groups="base.group_multi_salesteams">
|
||||||
<field name="section_id" widget="selection"/>
|
<field name="section_id" widget="selection"/>
|
||||||
|
|
|
@ -297,8 +297,7 @@ class email_template(osv.osv):
|
||||||
'copyvalue': self.build_expression(field_value.name, False, null_value or False),
|
'copyvalue': self.build_expression(field_value.name, False, null_value or False),
|
||||||
'null_value': null_value or False
|
'null_value': null_value or False
|
||||||
})
|
})
|
||||||
return {'value':result}
|
return {'value': result}
|
||||||
|
|
||||||
|
|
||||||
def generate_email(self, cr, uid, template_id, res_id, context=None):
|
def generate_email(self, cr, uid, template_id, res_id, context=None):
|
||||||
"""Generates an email from the template for given (model, res_id) pair.
|
"""Generates an email from the template for given (model, res_id) pair.
|
||||||
|
@ -350,11 +349,13 @@ class email_template(osv.osv):
|
||||||
report_name += ext
|
report_name += ext
|
||||||
attachments.append((report_name, result))
|
attachments.append((report_name, result))
|
||||||
|
|
||||||
|
attachment_ids = []
|
||||||
# Add template attachments
|
# Add template attachments
|
||||||
for attach in template.attachment_ids:
|
for attach in template.attachment_ids:
|
||||||
attachments.append((attach.datas_fname, attach.datas))
|
attachment_ids.append(attach.id)
|
||||||
|
|
||||||
values['attachments'] = attachments
|
values['attachments'] = attachments
|
||||||
|
values['attachment_ids'] = attachment_ids
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def send_mail(self, cr, uid, template_id, res_id, force_send=False, context=None):
|
def send_mail(self, cr, uid, template_id, res_id, force_send=False, context=None):
|
||||||
|
@ -369,28 +370,34 @@ class email_template(osv.osv):
|
||||||
was executed for this message only.
|
was executed for this message only.
|
||||||
:returns: id of the mail.message that was created
|
:returns: id of the mail.message that was created
|
||||||
"""
|
"""
|
||||||
if context is None: context = {}
|
if context is None:
|
||||||
|
context = {}
|
||||||
mail_mail = self.pool.get('mail.mail')
|
mail_mail = self.pool.get('mail.mail')
|
||||||
ir_attachment = self.pool.get('ir.attachment')
|
ir_attachment = self.pool.get('ir.attachment')
|
||||||
|
|
||||||
|
# create a mail_mail based on values, without attachments
|
||||||
values = self.generate_email(cr, uid, template_id, res_id, context=context)
|
values = self.generate_email(cr, uid, template_id, res_id, context=context)
|
||||||
assert 'email_from' in values, 'email_from is missing or empty after template rendering, send_mail() cannot proceed'
|
assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed'
|
||||||
attachments = values.pop('attachments') or {}
|
del values['partner_to'] # TODO Properly use them.
|
||||||
del values['partner_to'] # TODO Properly use them.
|
attachment_ids = values.pop('attachment_ids', [])
|
||||||
|
attachments = values.pop('attachments', [])
|
||||||
msg_id = mail_mail.create(cr, uid, values, context=context)
|
msg_id = mail_mail.create(cr, uid, values, context=context)
|
||||||
# link attachments
|
|
||||||
attachment_ids = []
|
# manage attachments
|
||||||
for fname, fcontent in attachments.iteritems():
|
for attachment in attachments:
|
||||||
attachment_data = {
|
attachment_data = {
|
||||||
'name': fname,
|
'name': attachment[0],
|
||||||
'datas_fname': fname,
|
'datas_fname': attachment[0],
|
||||||
'datas': fcontent,
|
'datas': attachment[1],
|
||||||
'res_model': mail_mail._name,
|
'res_model': 'mail.message',
|
||||||
'res_id': msg_id,
|
'res_id': msg_id,
|
||||||
}
|
}
|
||||||
context.pop('default_type', None)
|
context.pop('default_type', None)
|
||||||
attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))
|
attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))
|
||||||
if attachment_ids:
|
if attachment_ids:
|
||||||
|
values['attachment_ids'] = [(6, 0, attachment_ids)]
|
||||||
mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
|
mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
|
||||||
|
|
||||||
if force_send:
|
if force_send:
|
||||||
mail_mail.send(cr, uid, [msg_id], context=context)
|
mail_mail.send(cr, uid, [msg_id], context=context)
|
||||||
return msg_id
|
return msg_id
|
||||||
|
|
|
@ -48,8 +48,8 @@ class test_message_compose(TestMailBase):
|
||||||
_body_html1 = 'Fans of Pigs, unite !'
|
_body_html1 = 'Fans of Pigs, unite !'
|
||||||
_body_html2 = 'I am angry !'
|
_body_html2 = 'I am angry !'
|
||||||
_attachments = [
|
_attachments = [
|
||||||
{'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment')},
|
{'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment'), 'res_model': 'res.partner', 'res_id': self.partner_admin_id},
|
||||||
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment')}
|
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment'), 'res_model': 'res.partner', 'res_id': self.partner_admin_id},
|
||||||
]
|
]
|
||||||
_attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
|
_attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
|
||||||
|
|
||||||
|
@ -115,12 +115,20 @@ class test_message_compose(TestMailBase):
|
||||||
self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
|
self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
|
||||||
self.assertIn(_body_html1, compose.body, 'mail.compose.message body incorrect')
|
self.assertIn(_body_html1, compose.body, 'mail.compose.message body incorrect')
|
||||||
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
||||||
# Test: mail.compose.message: attachments
|
# Test: mail.compose.message: attachments (owner has not been modified)
|
||||||
# Test: mail.message: attachments
|
|
||||||
for attach in compose.attachment_ids:
|
for attach in compose.attachment_ids:
|
||||||
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
|
self.assertEqual(attach.res_model, 'res.partner', 'mail.compose.message attachment res_model through templat was overriden')
|
||||||
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
|
self.assertEqual(attach.res_id, self.partner_admin_id, 'mail.compose.message attachment res_id incorrect')
|
||||||
self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test,
|
self.assertIn((attach.datas_fname, base64.b64decode(attach.datas)), _attachments_test,
|
||||||
|
'mail.message attachment name / data incorrect')
|
||||||
|
# Test: mail.message: attachments
|
||||||
|
mail_compose.send_mail(cr, uid, [compose_id])
|
||||||
|
group_pigs.refresh()
|
||||||
|
message_pigs = group_pigs.message_ids[0]
|
||||||
|
for attach in message_pigs.attachment_ids:
|
||||||
|
self.assertEqual(attach.res_model, 'mail.group', 'mail.compose.message attachment res_model through templat was overriden')
|
||||||
|
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.compose.message attachment res_id incorrect')
|
||||||
|
self.assertIn((attach.datas_fname, base64.b64decode(attach.datas)), _attachments_test,
|
||||||
'mail.message attachment name / data incorrect')
|
'mail.message attachment name / data incorrect')
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
|
@ -49,26 +49,44 @@ class mail_compose_message(osv.TransientModel):
|
||||||
help="Carbon copy recipients (placeholders may be used here)"),
|
help="Carbon copy recipients (placeholders may be used here)"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def send_mail(self, cr, uid, ids, context=None):
|
||||||
|
""" Override of send_mail to duplicate attachments linked to the email.template.
|
||||||
|
Indeed, basic mail.compose.message wizard duplicates attachments in mass
|
||||||
|
mailing mode. But in 'single post' mode, attachments of an email template
|
||||||
|
also have to be duplicated to avoid changing their ownership. """
|
||||||
|
for wizard in self.browse(cr, uid, ids, context=context):
|
||||||
|
if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id:
|
||||||
|
continue
|
||||||
|
template = self.pool.get('email.template').browse(cr, uid, wizard.template_id, context=context)
|
||||||
|
new_attachment_ids = []
|
||||||
|
for attachment in wizard.attachment_ids:
|
||||||
|
if attachment in template.attachment_ids:
|
||||||
|
new_attachment_ids.append(self.pool.get('ir.attachment').copy(cr, uid, attachment.id, {'res_model': 'mail.compose.message', 'res_id': wizard.id}, context=context))
|
||||||
|
else:
|
||||||
|
new_attachment_ids.append(attachment.id)
|
||||||
|
self.write(cr, uid, wizard.id, {'attachment_ids': [(6, 0, new_attachment_ids)]}, context=context)
|
||||||
|
return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
|
||||||
|
|
||||||
def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None):
|
def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None):
|
||||||
""" - mass_mailing: we cannot render, so return the template values
|
""" - mass_mailing: we cannot render, so return the template values
|
||||||
- normal mode: return rendered values """
|
- normal mode: return rendered values """
|
||||||
if template_id and composition_mode == 'mass_mail':
|
if template_id and composition_mode == 'mass_mail':
|
||||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']
|
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
|
||||||
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
|
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
|
||||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||||
elif template_id:
|
elif template_id:
|
||||||
# FIXME odo: change the mail generation to avoid attachment duplication
|
|
||||||
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
|
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
|
||||||
# transform attachments into attachment_ids
|
# transform attachments into attachment_ids; not attached to the document because this will
|
||||||
values['attachment_ids'] = []
|
# be done further in the posting process, allowing to clean database if email not send
|
||||||
|
values['attachment_ids'] = values.pop('attachment_ids', [])
|
||||||
ir_attach_obj = self.pool.get('ir.attachment')
|
ir_attach_obj = self.pool.get('ir.attachment')
|
||||||
for attach_fname, attach_datas in values.pop('attachments', []):
|
for attach_fname, attach_datas in values.pop('attachments', []):
|
||||||
data_attach = {
|
data_attach = {
|
||||||
'name': attach_fname,
|
'name': attach_fname,
|
||||||
'datas': attach_datas,
|
'datas': attach_datas,
|
||||||
'datas_fname': attach_fname,
|
'datas_fname': attach_fname,
|
||||||
'res_model': model,
|
'res_model': 'mail.compose.message',
|
||||||
'res_id': res_id,
|
'res_id': 0,
|
||||||
'type': 'binary', # override default_type from context, possibly meant for another model!
|
'type': 'binary', # override default_type from context, possibly meant for another model!
|
||||||
}
|
}
|
||||||
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
|
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
|
||||||
|
@ -128,7 +146,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
mail.compose.message, transform email_cc and email_to into partner_ids """
|
mail.compose.message, transform email_cc and email_to into partner_ids """
|
||||||
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
||||||
# filter template values
|
# filter template values
|
||||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachments']
|
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments']
|
||||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||||
values['body'] = values.pop('body_html', '')
|
values['body'] = values.pop('body_html', '')
|
||||||
|
|
||||||
|
@ -148,6 +166,8 @@ class mail_compose_message(osv.TransientModel):
|
||||||
values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
|
values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
|
||||||
else:
|
else:
|
||||||
values = {}
|
values = {}
|
||||||
|
# remove attachments as they should not be rendered
|
||||||
|
values.pop('attachment_ids', None)
|
||||||
# get values to return
|
# get values to return
|
||||||
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
|
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
|
||||||
# those values are not managed; they are readonly
|
# those values are not managed; they are readonly
|
||||||
|
|
|
@ -89,7 +89,6 @@ class fetchmail_server(osv.osv):
|
||||||
'script': '/mail/static/scripts/openerp_mailgate.py',
|
'script': '/mail/static/scripts/openerp_mailgate.py',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False, object_id=False):
|
def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False, object_id=False):
|
||||||
port = 0
|
port = 0
|
||||||
values = {}
|
values = {}
|
||||||
|
@ -175,13 +174,14 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
|
||||||
|
|
||||||
def _fetch_mails(self, cr, uid, ids=False, context=None):
|
def _fetch_mails(self, cr, uid, ids=False, context=None):
|
||||||
if not ids:
|
if not ids:
|
||||||
ids = self.search(cr, uid, [('state','=','done')])
|
ids = self.search(cr, uid, [('state','=','done'),('type','in',['pop','imap'])])
|
||||||
return self.fetch_mail(cr, uid, ids, context=context)
|
return self.fetch_mail(cr, uid, ids, context=context)
|
||||||
|
|
||||||
def fetch_mail(self, cr, uid, ids, context=None):
|
def fetch_mail(self, cr, uid, ids, context=None):
|
||||||
"""WARNING: meant for cron usage only - will commit() after each email!"""
|
"""WARNING: meant for cron usage only - will commit() after each email!"""
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
context['fetchmail_cron_running'] = True
|
||||||
mail_thread = self.pool.get('mail.thread')
|
mail_thread = self.pool.get('mail.thread')
|
||||||
action_pool = self.pool.get('ir.actions.server')
|
action_pool = self.pool.get('ir.actions.server')
|
||||||
for server in self.browse(cr, uid, ids, context=context):
|
for server in self.browse(cr, uid, ids, context=context):
|
||||||
|
@ -240,6 +240,29 @@ openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p
|
||||||
server.write({'date': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)})
|
server.write({'date': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def cron_update(self, cr, uid, context=None):
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
if not context.get('fetchmail_cron_running'):
|
||||||
|
# Enabled/Disable cron based on the number of 'done' server of type pop or imap
|
||||||
|
ids = self.search(cr, uid, [('state','=','done'),('type','in',['pop','imap'])])
|
||||||
|
try:
|
||||||
|
cron_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fetchmail', 'ir_cron_mail_gateway_action')[1]
|
||||||
|
self.pool.get('ir.cron').write(cr, 1, [cron_id], {'active': bool(ids)})
|
||||||
|
except ValueError:
|
||||||
|
# Nevermind if default cron cannot be found
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create(self, cr, uid, values, context=None):
|
||||||
|
res = super(fetchmail_server, self).create(cr, uid, values, context=context)
|
||||||
|
self.cron_update(cr, uid, context=context)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, cr, uid, ids, values, context=None):
|
||||||
|
res = super(fetchmail_server, self).write(cr, uid, ids, values, context=context)
|
||||||
|
self.cron_update(cr, uid, context=context)
|
||||||
|
return res
|
||||||
|
|
||||||
class mail_mail(osv.osv):
|
class mail_mail(osv.osv):
|
||||||
_inherit = "mail.mail"
|
_inherit = "mail.mail"
|
||||||
_columns = {
|
_columns = {
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
<field name="model">fetchmail.server</field>
|
<field name="model">fetchmail.server</field>
|
||||||
<field name="function">_fetch_mails</field>
|
<field name="function">_fetch_mails</field>
|
||||||
<field name="args">()</field>
|
<field name="args">()</field>
|
||||||
|
<!-- Active flag is set on fetchmail_server.create/write -->
|
||||||
|
<field name="active" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -321,7 +321,7 @@ class hr_expense_expense(osv.osv):
|
||||||
'price_unit': tax['price_unit'],
|
'price_unit': tax['price_unit'],
|
||||||
'quantity': 1,
|
'quantity': 1,
|
||||||
'price': tax['amount'] * tax['base_sign'] or 0.0,
|
'price': tax['amount'] * tax['base_sign'] or 0.0,
|
||||||
'account_id': tax['account_collected_id'],
|
'account_id': tax['account_collected_id'] or mres['account_id'],
|
||||||
'tax_code_id': tax['tax_code_id'],
|
'tax_code_id': tax['tax_code_id'],
|
||||||
'tax_amount': tax['amount'] * tax['base_sign'],
|
'tax_amount': tax['amount'] * tax['base_sign'],
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,19 @@
|
||||||
<field name="priority">1000</field>
|
<field name="priority">1000</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="ir_cron_mail_garbage_collect_attachments" model="ir.cron">
|
||||||
|
<field name="name">Garbage Collect Mail Attachments</field>
|
||||||
|
<field eval="True" name="active" />
|
||||||
|
<field name="user_id" ref="base.user_root" />
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="interval_type">weeks</field>
|
||||||
|
<field name="numbercall">-1</field>
|
||||||
|
<field eval="False" name="doall" />
|
||||||
|
<field name="model">mail.thread</field>
|
||||||
|
<field name="function">_garbage_collect_attachments</field>
|
||||||
|
<field name="args">()</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- Discussion subtype for messaging / Chatter -->
|
<!-- Discussion subtype for messaging / Chatter -->
|
||||||
<record id="mt_comment" model="mail.message.subtype">
|
<record id="mt_comment" model="mail.message.subtype">
|
||||||
<field name="name">Discussions</field>
|
<field name="name">Discussions</field>
|
||||||
|
|
|
@ -369,6 +369,7 @@ class mail_message(osv.Model):
|
||||||
|
|
||||||
return {'id': message.id,
|
return {'id': message.id,
|
||||||
'type': message.type,
|
'type': message.type,
|
||||||
|
'subtype': message.subtype_id.name if message.subtype_id else False,
|
||||||
'body': body_html,
|
'body': body_html,
|
||||||
'model': message.model,
|
'model': message.model,
|
||||||
'res_id': message.res_id,
|
'res_id': message.res_id,
|
||||||
|
@ -773,7 +774,7 @@ class mail_message(osv.Model):
|
||||||
attachments_to_delete = []
|
attachments_to_delete = []
|
||||||
for message in self.browse(cr, uid, ids, context=context):
|
for message in self.browse(cr, uid, ids, context=context):
|
||||||
for attach in message.attachment_ids:
|
for attach in message.attachment_ids:
|
||||||
if attach.res_model == self._name and attach.res_id == message.id:
|
if attach.res_model == self._name and (attach.res_id == message.id or attach.res_id == 0):
|
||||||
attachments_to_delete.append(attach.id)
|
attachments_to_delete.append(attach.id)
|
||||||
if attachments_to_delete:
|
if attachments_to_delete:
|
||||||
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
|
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
|
||||||
|
|
|
@ -408,7 +408,7 @@ class mail_thread(osv.AbstractModel):
|
||||||
posted = False
|
posted = False
|
||||||
for subtype in subtypes:
|
for subtype in subtypes:
|
||||||
try:
|
try:
|
||||||
subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1])
|
subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1], context=context)
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
_logger.debug('subtype %s not found, giving error "%s"' % (subtype, e))
|
_logger.debug('subtype %s not found, giving error "%s"' % (subtype, e))
|
||||||
continue
|
continue
|
||||||
|
@ -429,6 +429,26 @@ class mail_thread(osv.AbstractModel):
|
||||||
return [('message_unread', '=', True)]
|
return [('message_unread', '=', True)]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def _garbage_collect_attachments(self, cr, uid, context=None):
|
||||||
|
""" Garbage collect lost mail attachments. Those are attachments
|
||||||
|
- linked to res_model 'mail.compose.message', the composer wizard
|
||||||
|
- with res_id 0, because they were created outside of an existing
|
||||||
|
wizard (typically user input through Chatter or reports
|
||||||
|
created on-the-fly by the templates)
|
||||||
|
- unused since at least one day (create_date and write_date)
|
||||||
|
"""
|
||||||
|
limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=1)
|
||||||
|
limit_date_str = datetime.datetime.strftime(limit_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
ir_attachment_obj = self.pool.get('ir.attachment')
|
||||||
|
attach_ids = ir_attachment_obj.search(cr, uid, [
|
||||||
|
('res_model', '=', 'mail.compose.message'),
|
||||||
|
('res_id', '=', 0),
|
||||||
|
('create_date', '<', limit_date_str),
|
||||||
|
('write_date', '<', limit_date_str),
|
||||||
|
], context=context)
|
||||||
|
ir_attachment_obj.unlink(cr, uid, attach_ids, context=context)
|
||||||
|
return True
|
||||||
|
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
# Email specific
|
# Email specific
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
@ -455,7 +475,9 @@ class mail_thread(osv.AbstractModel):
|
||||||
return ret_dict
|
return ret_dict
|
||||||
|
|
||||||
def _message_find_partners(self, cr, uid, message, header_fields=['From'], context=None):
|
def _message_find_partners(self, cr, uid, message, header_fields=['From'], context=None):
|
||||||
""" Find partners related to some header fields of the message. """
|
""" Find partners related to some header fields of the message.
|
||||||
|
|
||||||
|
TDE TODO: merge me with other partner finding methods in 8.0 """
|
||||||
partner_obj = self.pool.get('res.partner')
|
partner_obj = self.pool.get('res.partner')
|
||||||
partner_ids = []
|
partner_ids = []
|
||||||
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
|
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
|
||||||
|
@ -467,6 +489,7 @@ class mail_thread(osv.AbstractModel):
|
||||||
return partner_ids
|
return partner_ids
|
||||||
|
|
||||||
def _message_find_user_id(self, cr, uid, message, context=None):
|
def _message_find_user_id(self, cr, uid, message, context=None):
|
||||||
|
""" TDE TODO: check and maybe merge me with other user finding methods in 8.0 """
|
||||||
from_local_part = tools.email_split(decode(message.get('From')))[0]
|
from_local_part = tools.email_split(decode(message.get('From')))[0]
|
||||||
# FP Note: canonification required, the minimu: .lower()
|
# FP Note: canonification required, the minimu: .lower()
|
||||||
user_ids = self.pool.get('res.users').search(cr, uid, ['|',
|
user_ids = self.pool.get('res.users').search(cr, uid, ['|',
|
||||||
|
@ -892,7 +915,8 @@ class mail_thread(osv.AbstractModel):
|
||||||
recipient in the result dictionary. The form is :
|
recipient in the result dictionary. The form is :
|
||||||
partner_id, partner_name<partner_email> or partner_name, reason """
|
partner_id, partner_name<partner_email> or partner_name, reason """
|
||||||
if email and not partner:
|
if email and not partner:
|
||||||
partner_info = self.message_get_partner_info_from_emails(cr, uid, [email], context=context)[0]
|
# get partner info from email
|
||||||
|
partner_info = self.message_get_partner_info_from_emails(cr, uid, [email], context=context, res_id=obj.id)[0]
|
||||||
if partner_info.get('partner_id'):
|
if partner_info.get('partner_id'):
|
||||||
partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, [partner_info.get('partner_id')], context=context)[0]
|
partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, [partner_info.get('partner_id')], context=context)[0]
|
||||||
if email and email in [val[1] for val in result[obj.id]]: # already existing email -> skip
|
if email and email in [val[1] for val in result[obj.id]]: # already existing email -> skip
|
||||||
|
@ -920,29 +944,49 @@ class mail_thread(osv.AbstractModel):
|
||||||
self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._all_columns['user_id'].column.string, context=context)
|
self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._all_columns['user_id'].column.string, context=context)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def message_get_partner_info_from_emails(self, cr, uid, emails, link_mail=False, context=None):
|
def message_get_partner_info_from_emails(self, cr, uid, emails, link_mail=False, context=None, res_id=None):
|
||||||
|
""" Wrapper with weird order parameter because of 7.0 fix.
|
||||||
|
|
||||||
|
TDE TODO: remove me in 8.0 """
|
||||||
|
return self.message_find_partner_from_emails(cr, uid, res_id, emails, link_mail=link_mail, context=context)
|
||||||
|
|
||||||
|
def message_find_partner_from_emails(self, cr, uid, id, emails, link_mail=False, context=None):
|
||||||
""" Convert a list of emails into a list partner_ids and a list
|
""" Convert a list of emails into a list partner_ids and a list
|
||||||
new_partner_ids. The return value is non conventional because
|
new_partner_ids. The return value is non conventional because
|
||||||
it is meant to be used by the mail widget.
|
it is meant to be used by the mail widget.
|
||||||
|
|
||||||
:return dict: partner_ids and new_partner_ids
|
:return dict: partner_ids and new_partner_ids
|
||||||
"""
|
|
||||||
|
TDE TODO: merge me with other partner finding methods in 8.0 """
|
||||||
mail_message_obj = self.pool.get('mail.message')
|
mail_message_obj = self.pool.get('mail.message')
|
||||||
partner_obj = self.pool.get('res.partner')
|
partner_obj = self.pool.get('res.partner')
|
||||||
result = list()
|
result = list()
|
||||||
|
if id and self._name != 'mail.thread':
|
||||||
|
obj = self.browse(cr, SUPERUSER_ID, id, context=context)
|
||||||
|
else:
|
||||||
|
obj = None
|
||||||
for email in emails:
|
for email in emails:
|
||||||
partner_info = {'full_name': email, 'partner_id': False}
|
partner_info = {'full_name': email, 'partner_id': False}
|
||||||
m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
|
m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
|
||||||
if not m:
|
if not m:
|
||||||
continue
|
continue
|
||||||
email_address = m.group(3)
|
email_address = m.group(3)
|
||||||
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', '=', email_address)], context=context)
|
# first try: check in document's followers
|
||||||
if ids:
|
if obj:
|
||||||
partner_info['partner_id'] = ids[0]
|
for follower in obj.message_follower_ids:
|
||||||
|
if follower.email == email_address:
|
||||||
|
partner_info['partner_id'] = follower.id
|
||||||
|
# second try: check in partners
|
||||||
|
if not partner_info.get('partner_id'):
|
||||||
|
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', 'ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
|
||||||
|
if not ids:
|
||||||
|
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', 'ilike', email_address)], limit=1, context=context)
|
||||||
|
if ids:
|
||||||
|
partner_info['partner_id'] = ids[0]
|
||||||
result.append(partner_info)
|
result.append(partner_info)
|
||||||
|
|
||||||
# link mail with this from mail to the new partner id
|
# link mail with this from mail to the new partner id
|
||||||
if link_mail and ids:
|
if link_mail and partner_info['partner_id']:
|
||||||
message_ids = mail_message_obj.search(cr, SUPERUSER_ID, [
|
message_ids = mail_message_obj.search(cr, SUPERUSER_ID, [
|
||||||
'|',
|
'|',
|
||||||
('email_from', '=', email),
|
('email_from', '=', email),
|
||||||
|
@ -950,7 +994,7 @@ class mail_thread(osv.AbstractModel):
|
||||||
('author_id', '=', False)
|
('author_id', '=', False)
|
||||||
], context=context)
|
], context=context)
|
||||||
if message_ids:
|
if message_ids:
|
||||||
mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'author_id': ids[0]}, context=context)
|
mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'author_id': partner_info['partner_id']}, context=context)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
||||||
|
@ -1046,7 +1090,6 @@ class mail_thread(osv.AbstractModel):
|
||||||
if attachment_ids:
|
if attachment_ids:
|
||||||
filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
|
filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
|
||||||
('res_model', '=', 'mail.compose.message'),
|
('res_model', '=', 'mail.compose.message'),
|
||||||
('res_id', '=', 0),
|
|
||||||
('create_uid', '=', uid),
|
('create_uid', '=', uid),
|
||||||
('id', 'in', attachment_ids)], context=context)
|
('id', 'in', attachment_ids)], context=context)
|
||||||
if filtered_attachment_ids:
|
if filtered_attachment_ids:
|
||||||
|
|
|
@ -57,6 +57,9 @@
|
||||||
.openerp .oe_mail .oe_msg.oe_msg_nobody{
|
.openerp .oe_mail .oe_msg.oe_msg_nobody{
|
||||||
background: #F8F8F8;
|
background: #F8F8F8;
|
||||||
}
|
}
|
||||||
|
.openerp .oe_mail .oe_msg.oe_msg_notification{
|
||||||
|
background: #F8F8F8;
|
||||||
|
}
|
||||||
.openerp .oe_mail .oe_msg .oe_msg_left{
|
.openerp .oe_mail .oe_msg .oe_msg_left{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left:0; top: 0; bottom: 0; width: 40px;
|
left:0; top: 0; bottom: 0; width: 40px;
|
||||||
|
|
|
@ -217,6 +217,7 @@ openerp.mail = function (session) {
|
||||||
this.res_id = datasets.res_id || this.context.default_res_id || false,
|
this.res_id = datasets.res_id || this.context.default_res_id || false,
|
||||||
this.parent_id = datasets.parent_id || false,
|
this.parent_id = datasets.parent_id || false,
|
||||||
this.type = datasets.type || false,
|
this.type = datasets.type || false,
|
||||||
|
this.subtype = datasets.subtype || false,
|
||||||
this.is_author = datasets.is_author || false,
|
this.is_author = datasets.is_author || false,
|
||||||
this.is_private = datasets.is_private || false,
|
this.is_private = datasets.is_private || false,
|
||||||
this.subject = datasets.subject || false,
|
this.subject = datasets.subject || false,
|
||||||
|
@ -619,7 +620,10 @@ openerp.mail = function (session) {
|
||||||
// have unknown names -> call message_get_partner_info_from_emails to try to find partner_id
|
// have unknown names -> call message_get_partner_info_from_emails to try to find partner_id
|
||||||
var find_done = $.Deferred();
|
var find_done = $.Deferred();
|
||||||
if (names_to_find.length > 0) {
|
if (names_to_find.length > 0) {
|
||||||
find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [names_to_find]);
|
var values = {
|
||||||
|
'res_id': this.context.default_res_id,
|
||||||
|
}
|
||||||
|
find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [names_to_find], values);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
find_done.resolve([]);
|
find_done.resolve([]);
|
||||||
|
@ -665,7 +669,11 @@ openerp.mail = function (session) {
|
||||||
var new_names_to_find = _.difference(names_to_find, names_to_remove);
|
var new_names_to_find = _.difference(names_to_find, names_to_remove);
|
||||||
find_done = $.Deferred();
|
find_done = $.Deferred();
|
||||||
if (new_names_to_find.length > 0) {
|
if (new_names_to_find.length > 0) {
|
||||||
find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [new_names_to_find, true]);
|
var values = {
|
||||||
|
'link_mail': true,
|
||||||
|
'res_id': self.context.default_res_id,
|
||||||
|
}
|
||||||
|
find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [new_names_to_find], values);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
find_done.resolve([]);
|
find_done.resolve([]);
|
||||||
|
|
|
@ -148,8 +148,6 @@ openerp_mail_followers = function(session, mail) {
|
||||||
}).then(self.proxy('display_generic'));
|
}).then(self.proxy('display_generic'));
|
||||||
},
|
},
|
||||||
_format_followers: function(count){
|
_format_followers: function(count){
|
||||||
// TDE note: why redefining _t ?
|
|
||||||
function _t(str) { return str; }
|
|
||||||
var str = '';
|
var str = '';
|
||||||
if(count <= 0){
|
if(count <= 0){
|
||||||
str = _t('No followers');
|
str = _t('No followers');
|
||||||
|
|
|
@ -226,7 +226,7 @@
|
||||||
|
|
||||||
<!-- default layout -->
|
<!-- default layout -->
|
||||||
<t t-name="mail.thread.message">
|
<t t-name="mail.thread.message">
|
||||||
<div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} #{widget.partner_ids.length == 0 ? 'oe_msg_nobody' : ''} oe_msg_#{widget.type}">
|
<div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} #{widget.subtype ? '' : 'oe_msg_nobody'} oe_msg_#{widget.type}">
|
||||||
|
|
||||||
<div class='oe_msg_left'>
|
<div class='oe_msg_left'>
|
||||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
|
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
|
||||||
|
@ -263,13 +263,23 @@
|
||||||
</t>
|
</t>
|
||||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[2]"/></a>
|
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[2]"/></a>
|
||||||
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[2]"/></span>
|
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[2]"/></span>
|
||||||
<t t-if="widget.partner_ids.length == 0">
|
<t t-if="widget.type == 'notification'">
|
||||||
|
updated document
|
||||||
|
<t t-if="widget.partner_ids.length > 0">
|
||||||
|
<span class='oe_subtle'>•</span>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<t t-if="widget.type == 'comment' and ! widget.subtype">
|
||||||
logged a note
|
logged a note
|
||||||
</t>
|
</t>
|
||||||
<t t-if="widget.partner_ids.length > 0">
|
<t t-if="(widget.type == 'comment' or widget.type == 'email') and widget.subtype">
|
||||||
to
|
to
|
||||||
|
<t t-if="widget.partner_ids.length == 0">
|
||||||
|
nobody
|
||||||
|
</t>
|
||||||
</t>
|
</t>
|
||||||
<t t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
|
<t t-if="widget.type == 'notification' or ( (widget.type == 'email' or widget.type == 'comment') and widget.subtype)"
|
||||||
|
t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
|
||||||
<span t-attf-class="oe_partner_follower">
|
<span t-attf-class="oe_partner_follower">
|
||||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-raw="partner[1]"/></a>
|
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-raw="partner[1]"/></a>
|
||||||
<t t-if="!widget.options.show_link" t-raw="partner[1]"/>
|
<t t-if="!widget.options.show_link" t-raw="partner[1]"/>
|
||||||
|
@ -279,6 +289,9 @@
|
||||||
<t t-if="widget.partner_ids.length > 3">
|
<t t-if="widget.partner_ids.length > 3">
|
||||||
<span t-att-title="widget.extra_partners_str">and <t t-raw="widget.extra_partners_nbr"/> more</span>
|
<span t-att-title="widget.extra_partners_str">and <t t-raw="widget.extra_partners_nbr"/> more</span>
|
||||||
</t>
|
</t>
|
||||||
|
<t t-if="widget.type == 'notification' and widget.partner_ids.length > 0">
|
||||||
|
notified
|
||||||
|
</t>
|
||||||
<span class='oe_subtle'>•</span>
|
<span class='oe_subtle'>•</span>
|
||||||
<span t-att-title="widget.date"><t t-if="widget.timerelative" t-raw="widget.timerelative"/><t t-if="!widget.timerelative" t-raw="widget.date"/></span>
|
<span t-att-title="widget.date"><t t-if="widget.timerelative" t-raw="widget.timerelative"/><t t-if="!widget.timerelative" t-raw="widget.date"/></span>
|
||||||
<span t-if="!widget.options.readonly" class='oe_subtle'>•</span>
|
<span t-if="!widget.options.readonly" class='oe_subtle'>•</span>
|
||||||
|
|
|
@ -83,7 +83,47 @@ Sylvie
|
||||||
|
|
||||||
class TestMailgateway(TestMailBase):
|
class TestMailgateway(TestMailBase):
|
||||||
|
|
||||||
def test_00_message_process(self):
|
def test_00_partner_find_from_email(self):
|
||||||
|
""" Tests designed for partner fetch based on emails. """
|
||||||
|
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Data creation
|
||||||
|
# --------------------------------------------------
|
||||||
|
# 1 - Partner ARaoul
|
||||||
|
p_a_id = self.res_partner.create(cr, uid, {'name': 'ARaoul', 'email': 'test@test.fr'})
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# CASE1: without object
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
# Do: find partner with email -> first partner should be found
|
||||||
|
partner_info = self.mail_thread.message_find_partner_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||||
|
self.assertEqual(partner_info['full_name'], 'Maybe Raoul <test@test.fr>',
|
||||||
|
'mail_thread: message_find_partner_from_emails did not handle email')
|
||||||
|
self.assertEqual(partner_info['partner_id'], p_a_id,
|
||||||
|
'mail_thread: message_find_partner_from_emails wrong partner found')
|
||||||
|
|
||||||
|
# Data: add some data about partners
|
||||||
|
# 2 - User BRaoul
|
||||||
|
p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
|
||||||
|
|
||||||
|
# Do: find partner with email -> first user should be found
|
||||||
|
partner_info = self.mail_thread.message_find_partner_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||||
|
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||||
|
'mail_thread: message_find_partner_from_emails wrong partner found')
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# CASE1: with object
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
# Do: find partner in group where there is a follower with the email -> should be taken
|
||||||
|
self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
|
||||||
|
partner_info = self.mail_group.message_find_partner_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||||
|
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||||
|
'mail_thread: message_find_partner_from_emails wrong partner found')
|
||||||
|
|
||||||
|
def test_10_message_process(self):
|
||||||
""" Testing incoming emails processing. """
|
""" Testing incoming emails processing. """
|
||||||
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
|
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
|
||||||
|
|
||||||
|
@ -325,7 +365,7 @@ class TestMailgateway(TestMailBase):
|
||||||
self.assertEqual(msg.body, '<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>',
|
self.assertEqual(msg.body, '<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>',
|
||||||
'message_process: plaintext incoming email incorrectly parsed')
|
'message_process: plaintext incoming email incorrectly parsed')
|
||||||
|
|
||||||
def test_10_thread_parent_resolution(self):
|
def test_20_thread_parent_resolution(self):
|
||||||
""" Testing parent/child relationships are correctly established when processing incoming mails """
|
""" Testing parent/child relationships are correctly established when processing incoming mails """
|
||||||
cr, uid = self.cr, self.uid
|
cr, uid = self.cr, self.uid
|
||||||
|
|
||||||
|
@ -370,7 +410,7 @@ class TestMailgateway(TestMailBase):
|
||||||
self.assertEqual(6, len(group_pigs.message_ids), 'message_process: group should contain 6 messages')
|
self.assertEqual(6, len(group_pigs.message_ids), 'message_process: group should contain 6 messages')
|
||||||
self.assertEqual(3, len(msg1.child_ids), 'message_process: msg1 should have 3 children now')
|
self.assertEqual(3, len(msg1.child_ids), 'message_process: msg1 should have 3 children now')
|
||||||
|
|
||||||
def test_20_private_discussion(self):
|
def test_30_private_discussion(self):
|
||||||
""" Testing private discussion between partners. """
|
""" Testing private discussion between partners. """
|
||||||
cr, uid = self.cr, self.uid
|
cr, uid = self.cr, self.uid
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,8 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
import base64
|
|
||||||
import re
|
import re
|
||||||
from openerp import tools
|
from openerp import tools
|
||||||
|
|
||||||
from openerp import SUPERUSER_ID
|
from openerp import SUPERUSER_ID
|
||||||
from openerp.osv import osv
|
from openerp.osv import osv
|
||||||
from openerp.osv import fields
|
from openerp.osv import fields
|
||||||
|
@ -230,33 +228,35 @@ class mail_compose_message(osv.TransientModel):
|
||||||
email(s), rendering any template patterns on the fly if needed. """
|
email(s), rendering any template patterns on the fly if needed. """
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
ir_attachment_obj = self.pool.get('ir.attachment')
|
||||||
active_ids = context.get('active_ids')
|
active_ids = context.get('active_ids')
|
||||||
is_log = context.get('mail_compose_log', False)
|
is_log = context.get('mail_compose_log', False)
|
||||||
|
|
||||||
for wizard in self.browse(cr, uid, ids, context=context):
|
for wizard in self.browse(cr, uid, ids, context=context):
|
||||||
mass_mail_mode = wizard.composition_mode == 'mass_mail'
|
mass_mail_mode = wizard.composition_mode == 'mass_mail'
|
||||||
if mass_mail_mode: # mass mail: avoid any auto subscription because this could lead to people being follower of plenty of documents
|
|
||||||
context['mail_create_nosubscribe'] = True
|
|
||||||
active_model_pool = self.pool[wizard.model if wizard.model else 'mail.thread']
|
active_model_pool = self.pool[wizard.model if wizard.model else 'mail.thread']
|
||||||
|
|
||||||
# wizard works in batch mode: [res_id] or active_ids
|
# wizard works in batch mode: [res_id] or active_ids
|
||||||
res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id]
|
res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id]
|
||||||
for res_id in res_ids:
|
for res_id in res_ids:
|
||||||
# default values, according to the wizard options
|
# mail.message values, according to the wizard options
|
||||||
post_values = {
|
post_values = {
|
||||||
'subject': wizard.subject,
|
'subject': wizard.subject,
|
||||||
'body': wizard.body,
|
'body': wizard.body,
|
||||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||||
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
||||||
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
|
'attachment_ids': [attach.id for attach in wizard.attachment_ids],
|
||||||
}
|
}
|
||||||
# mass mailing: render and override default values
|
# mass mailing: render and override default values
|
||||||
if mass_mail_mode and wizard.model:
|
if mass_mail_mode and wizard.model:
|
||||||
email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
|
email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
|
||||||
new_partner_ids = email_dict.pop('partner_ids', [])
|
post_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
||||||
post_values['partner_ids'] += new_partner_ids
|
post_values['attachments'] = email_dict.pop('attachments', [])
|
||||||
new_attachments = email_dict.pop('attachments', [])
|
attachment_ids = []
|
||||||
post_values['attachments'] += new_attachments
|
for attach_id in post_values.pop('attachment_ids'):
|
||||||
|
new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
|
||||||
|
attachment_ids.append(new_attach_id)
|
||||||
|
post_values['attachment_ids'] = attachment_ids
|
||||||
post_values.update(email_dict)
|
post_values.update(email_dict)
|
||||||
# email_from: mass mailing only can specify another email_from
|
# email_from: mass mailing only can specify another email_from
|
||||||
if email_dict.get('email_from'):
|
if email_dict.get('email_from'):
|
||||||
|
|
|
@ -50,7 +50,7 @@ class pos_config(osv.osv):
|
||||||
required=True, help="An internal identification of the point of sale"),
|
required=True, help="An internal identification of the point of sale"),
|
||||||
'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel',
|
'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel',
|
||||||
'pos_config_id', 'journal_id', 'Available Payment Methods',
|
'pos_config_id', 'journal_id', 'Available Payment Methods',
|
||||||
domain="[('journal_user', '=', True )]",),
|
domain="[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",),
|
||||||
'shop_id' : fields.many2one('sale.shop', 'Shop',
|
'shop_id' : fields.many2one('sale.shop', 'Shop',
|
||||||
required=True),
|
required=True),
|
||||||
'journal_id' : fields.many2one('account.journal', 'Sale Journal',
|
'journal_id' : fields.many2one('account.journal', 'Sale Journal',
|
||||||
|
@ -408,6 +408,9 @@ class pos_session(osv.osv):
|
||||||
if not self.pool.get('ir.model.access').check_groups(cr, uid, "point_of_sale.group_pos_manager"):
|
if not self.pool.get('ir.model.access').check_groups(cr, uid, "point_of_sale.group_pos_manager"):
|
||||||
raise osv.except_osv( _('Error!'),
|
raise osv.except_osv( _('Error!'),
|
||||||
_("Your ending balance is too different from the theorical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff))
|
_("Your ending balance is too different from the theorical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff))
|
||||||
|
if (st.journal_id.type not in ['bank', 'cash']):
|
||||||
|
raise osv.except_osv(_('Error!'),
|
||||||
|
_("The type of the journal for your payment method should be bank or cash "))
|
||||||
if st.difference and st.journal_id.cash_control == True:
|
if st.difference and st.journal_id.cash_control == True:
|
||||||
if st.difference > 0.0:
|
if st.difference > 0.0:
|
||||||
name= _('Point of Sale Profit')
|
name= _('Point of Sale Profit')
|
||||||
|
@ -428,7 +431,6 @@ class pos_session(osv.osv):
|
||||||
|
|
||||||
if st.journal_id.type == 'bank':
|
if st.journal_id.type == 'bank':
|
||||||
st.write({'balance_end_real' : st.balance_end})
|
st.write({'balance_end_real' : st.balance_end})
|
||||||
|
|
||||||
getattr(st, 'button_confirm_%s' % st.journal_id.type)(context=context)
|
getattr(st, 'button_confirm_%s' % st.journal_id.type)(context=context)
|
||||||
self._confirm_orders(cr, uid, ids, context=context)
|
self._confirm_orders(cr, uid, ids, context=context)
|
||||||
self.write(cr, uid, ids, {'state' : 'closed'}, context=context)
|
self.write(cr, uid, ids, {'state' : 'closed'}, context=context)
|
||||||
|
@ -522,6 +524,24 @@ class pos_order(osv.osv):
|
||||||
self.signal_paid(cr, uid, [order_id])
|
self.signal_paid(cr, uid, [order_id])
|
||||||
return order_ids
|
return order_ids
|
||||||
|
|
||||||
|
def write(self, cr, uid, ids, vals, context=None):
|
||||||
|
res = super(pos_order, self).write(cr, uid, ids, vals, context=context)
|
||||||
|
#If you change the partner of the PoS order, change also the partner of the associated bank statement lines
|
||||||
|
partner_obj = self.pool.get('res.partner')
|
||||||
|
bsl_obj = self.pool.get("account.bank.statement.line")
|
||||||
|
if 'partner_id' in vals:
|
||||||
|
for posorder in self.browse(cr, uid, ids, context=context):
|
||||||
|
if posorder.invoice_id:
|
||||||
|
raise osv.except_osv( _('Error!'), _("You cannot change the partner of a POS order for which an invoice has already been issued."))
|
||||||
|
if vals['partner_id']:
|
||||||
|
p_id = partner_obj.browse(cr, uid, vals['partner_id'], context=context)
|
||||||
|
part_id = partner_obj._find_accounting_partner(p_id).id
|
||||||
|
else:
|
||||||
|
part_id = False
|
||||||
|
bsl_ids = [x.id for x in posorder.statement_ids]
|
||||||
|
bsl_obj.write(cr, uid, bsl_ids, {'partner_id': part_id}, context=context)
|
||||||
|
return res
|
||||||
|
|
||||||
def unlink(self, cr, uid, ids, context=None):
|
def unlink(self, cr, uid, ids, context=None):
|
||||||
for rec in self.browse(cr, uid, ids, context=context):
|
for rec in self.browse(cr, uid, ids, context=context):
|
||||||
if rec.state not in ('draft','cancel'):
|
if rec.state not in ('draft','cancel'):
|
||||||
|
@ -948,9 +968,9 @@ class pos_order(osv.osv):
|
||||||
})
|
})
|
||||||
|
|
||||||
if data_type == 'product':
|
if data_type == 'product':
|
||||||
key = ('product', values['product_id'],)
|
key = ('product', values['partner_id'], values['product_id'])
|
||||||
elif data_type == 'tax':
|
elif data_type == 'tax':
|
||||||
key = ('tax', values['tax_code_id'],)
|
key = ('tax', values['partner_id'], values['tax_code_id'],)
|
||||||
elif data_type == 'counter_part':
|
elif data_type == 'counter_part':
|
||||||
key = ('counter_part', values['partner_id'], values['account_id'])
|
key = ('counter_part', values['partner_id'], values['account_id'])
|
||||||
else:
|
else:
|
||||||
|
@ -1026,7 +1046,7 @@ class pos_order(osv.osv):
|
||||||
'debit': ((amount<0) and -amount) or 0.0,
|
'debit': ((amount<0) and -amount) or 0.0,
|
||||||
'tax_code_id': tax_code_id,
|
'tax_code_id': tax_code_id,
|
||||||
'tax_amount': tax_amount,
|
'tax_amount': tax_amount,
|
||||||
'partner_id': order.partner_id and order.partner_id.id or False
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
|
||||||
})
|
})
|
||||||
|
|
||||||
# For each remaining tax with a code, whe create a move line
|
# For each remaining tax with a code, whe create a move line
|
||||||
|
@ -1044,6 +1064,7 @@ class pos_order(osv.osv):
|
||||||
'debit': 0.0,
|
'debit': 0.0,
|
||||||
'tax_code_id': tax_code_id,
|
'tax_code_id': tax_code_id,
|
||||||
'tax_amount': tax_amount,
|
'tax_amount': tax_amount,
|
||||||
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
|
||||||
})
|
})
|
||||||
|
|
||||||
# Create a move for each tax group
|
# Create a move for each tax group
|
||||||
|
@ -1060,6 +1081,7 @@ class pos_order(osv.osv):
|
||||||
'debit': ((tax_amount<0) and -tax_amount) or 0.0,
|
'debit': ((tax_amount<0) and -tax_amount) or 0.0,
|
||||||
'tax_code_id': key[tax_code_pos],
|
'tax_code_id': key[tax_code_pos],
|
||||||
'tax_amount': tax_amount,
|
'tax_amount': tax_amount,
|
||||||
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
|
||||||
})
|
})
|
||||||
|
|
||||||
# counterpart
|
# counterpart
|
||||||
|
@ -1068,14 +1090,17 @@ class pos_order(osv.osv):
|
||||||
'account_id': order_account,
|
'account_id': order_account,
|
||||||
'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
|
'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
|
||||||
'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
|
'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
|
||||||
'partner_id': order.partner_id and order.partner_id.id or False
|
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
|
||||||
})
|
})
|
||||||
|
|
||||||
order.write({'state':'done', 'account_move': move_id})
|
order.write({'state':'done', 'account_move': move_id})
|
||||||
|
|
||||||
|
all_lines = []
|
||||||
for group_key, group_data in grouped_data.iteritems():
|
for group_key, group_data in grouped_data.iteritems():
|
||||||
for value in group_data:
|
for value in group_data:
|
||||||
account_move_line_obj.create(cr, uid, value, context=context)
|
all_lines.append((0, 0, value),)
|
||||||
|
if move_id: #In case no order was changed
|
||||||
|
self.pool.get("account.move").write(cr, uid, [move_id], {'line_id':all_lines}, context=context)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -17,19 +17,5 @@
|
||||||
<field name="groups" eval="[(4, ref('portal.group_portal'))]"/>
|
<field name="groups" eval="[(4, ref('portal.group_portal'))]"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="portal_stock_move_user_rule" model="ir.rule">
|
|
||||||
<field name="name">Portal Personal Stock Moves</field>
|
|
||||||
<field name="model_id" ref="stock.model_stock_move"/>
|
|
||||||
<field name="domain_force">[('message_follower_ids','in',[user.partner_id.id])]</field>
|
|
||||||
<field name="groups" eval="[(4, ref('portal.group_portal'))]"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="portal_stock_warehouse_orderpoint_user_rule" model="ir.rule">
|
|
||||||
<field name="name">Portal Personal Stock Warehouse Orderpoints</field>
|
|
||||||
<field name="model_id" ref="procurement.model_stock_warehouse_orderpoint"/>
|
|
||||||
<field name="domain_force">[('message_follower_ids','in',[user.partner_id.id])]</field>
|
|
||||||
<field name="groups" eval="[(4, ref('portal.group_portal'))]"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -155,7 +155,7 @@ class procurement_order(osv.osv):
|
||||||
warehouse_obj = self.pool.get('stock.warehouse')
|
warehouse_obj = self.pool.get('stock.warehouse')
|
||||||
|
|
||||||
warehouse_ids = warehouse_obj.search(cr, uid, [], context=context)
|
warehouse_ids = warehouse_obj.search(cr, uid, [], context=context)
|
||||||
products_ids = product_obj.search(cr, uid, [('purchase_ok', '=', True)], order='id', context=context)
|
products_ids = product_obj.search(cr, uid, [], order='id', context=context)
|
||||||
|
|
||||||
for warehouse in warehouse_obj.browse(cr, uid, warehouse_ids, context=context):
|
for warehouse in warehouse_obj.browse(cr, uid, warehouse_ids, context=context):
|
||||||
context['warehouse'] = warehouse
|
context['warehouse'] = warehouse
|
||||||
|
|
|
@ -283,9 +283,11 @@ class account_analytic_account(osv.osv):
|
||||||
res['value']['use_phases'] = template.use_phases
|
res['value']['use_phases'] = template.use_phases
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def _trigger_project_creation(self, cr, uid, vals, context=None):
|
def _trigger_project_creation(self, cr, uid, vals, context=None):
|
||||||
res= super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
|
if context is None: context = {}
|
||||||
return res or vals.get('use_phases')
|
res = super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
|
||||||
|
return res or (vals.get('use_phases') and not 'project_creation_in_progress' in context)
|
||||||
|
|
||||||
account_analytic_account()
|
account_analytic_account()
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
assert (not project and not account) or project.analytic_account_id == account, "Project does not correspond."
|
assert (not project and not account) or project.analytic_account_id == account, "Project does not correspond."
|
||||||
planned_hours = self._convert_qty_company_hours(cr, uid, procurement, context=context)
|
planned_hours = self._convert_qty_company_hours(cr, uid, procurement, context=context)
|
||||||
assert task.planned_hours == planned_hours, 'Planned Hours do not correspond.'
|
assert task.planned_hours == planned_hours, 'Planned Hours do not correspond.'
|
||||||
assert datetime.strptime(task.date_deadline, '%Y-%m-%d') == datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S'), 'Deadline does not correspond.'
|
assert datetime.strptime(task.date_deadline, '%Y-%m-%d') == datetime.strptime(procurement.date_planned[:10], '%Y-%m-%d'), 'Deadline does not correspond.'
|
||||||
if procurement.product_id.product_manager:
|
if procurement.product_id.product_manager:
|
||||||
assert task.user_id.id == procurement.product_id.product_manager.id, 'Allocated Person does not correspond with Service Product Manager.'
|
assert task.user_id.id == procurement.product_id.product_manager.id, 'Allocated Person does not correspond with Service Product Manager.'
|
||||||
-
|
-
|
||||||
|
|
|
@ -24,8 +24,12 @@ from openerp.osv import osv,fields
|
||||||
class company(osv.osv):
|
class company(osv.osv):
|
||||||
_inherit = 'res.company'
|
_inherit = 'res.company'
|
||||||
_columns = {
|
_columns = {
|
||||||
'po_lead': fields.float('Purchase Lead Time', required=True,
|
'po_lead': fields.float(
|
||||||
help="This is the leads/security time for each purchase order."),
|
'Purchase Lead Time', required=True,
|
||||||
|
help="Margin of error for supplier lead times. When the system"\
|
||||||
|
"generates Purchase Orders for procuring products,"\
|
||||||
|
"they will be scheduled that many days earlier "\
|
||||||
|
"to cope with unexpected supplier delays."),
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'po_lead': lambda *a: 1.0,
|
'po_lead': lambda *a: 1.0,
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import pytz
|
||||||
|
from openerp import SUPERUSER_ID
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
@ -592,11 +594,34 @@ class purchase_order(osv.osv):
|
||||||
self.signal_purchase_cancel(cr, uid, ids)
|
self.signal_purchase_cancel(cr, uid, ids)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def date_to_datetime(self, cr, uid, userdate, context=None):
|
||||||
|
""" Convert date values expressed in user's timezone to
|
||||||
|
server-side UTC timestamp, assuming a default arbitrary
|
||||||
|
time of 12:00 AM - because a time is needed.
|
||||||
|
|
||||||
|
:param str userdate: date string in in user time zone
|
||||||
|
:return: UTC datetime string for server-side use
|
||||||
|
"""
|
||||||
|
# TODO: move to fields.datetime in server after 7.0
|
||||||
|
user_date = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT)
|
||||||
|
if context and context.get('tz'):
|
||||||
|
tz_name = context['tz']
|
||||||
|
else:
|
||||||
|
tz_name = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
|
||||||
|
if tz_name:
|
||||||
|
utc = pytz.timezone('UTC')
|
||||||
|
context_tz = pytz.timezone(tz_name)
|
||||||
|
user_datetime = user_date + relativedelta(hours=12.0)
|
||||||
|
local_timestamp = context_tz.localize(user_datetime, is_dst=False)
|
||||||
|
user_datetime = local_timestamp.astimezone(utc)
|
||||||
|
return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
return user_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
|
||||||
def _prepare_order_picking(self, cr, uid, order, context=None):
|
def _prepare_order_picking(self, cr, uid, order, context=None):
|
||||||
return {
|
return {
|
||||||
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
|
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
|
||||||
'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
|
'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
|
||||||
'date': order.date_order,
|
'date': self.date_to_datetime(cr, uid, order.date_order, context),
|
||||||
'partner_id': order.dest_address_id.id or order.partner_id.id,
|
'partner_id': order.dest_address_id.id or order.partner_id.id,
|
||||||
'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
|
'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
|
||||||
'type': 'in',
|
'type': 'in',
|
||||||
|
@ -614,8 +639,8 @@ class purchase_order(osv.osv):
|
||||||
'product_uos_qty': order_line.product_qty,
|
'product_uos_qty': order_line.product_qty,
|
||||||
'product_uom': order_line.product_uom.id,
|
'product_uom': order_line.product_uom.id,
|
||||||
'product_uos': order_line.product_uom.id,
|
'product_uos': order_line.product_uom.id,
|
||||||
'date': order_line.date_planned,
|
'date': self.date_to_datetime(cr, uid, order.date_order, context),
|
||||||
'date_expected': order_line.date_planned,
|
'date_expected': self.date_to_datetime(cr, uid, order.date_order, context),
|
||||||
'location_id': order.partner_id.property_stock_supplier.id,
|
'location_id': order.partner_id.property_stock_supplier.id,
|
||||||
'location_dest_id': order.location_id.id,
|
'location_dest_id': order.location_id.id,
|
||||||
'picking_id': picking_id,
|
'picking_id': picking_id,
|
||||||
|
|
|
@ -38,5 +38,22 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Replace analytic_id with analytics_id in account.invoice.line -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="invoice_supplier_form_inherit">
|
||||||
|
<field name="name">account.invoice.supplier.form.inherit</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||||
|
<field name="priority">2</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="account_analytic_id" position="replace">
|
||||||
|
<field name="analytics_id" domain="[('plan_id','<>',False)]" context="{'journal_id':parent.journal_id}" groups="analytic.group_analytic_accounting"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -211,8 +211,7 @@ class sale_order(osv.osv):
|
||||||
'order_policy': fields.selection([
|
'order_policy': fields.selection([
|
||||||
('manual', 'On Demand'),
|
('manual', 'On Demand'),
|
||||||
], 'Create Invoice', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
], 'Create Invoice', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
||||||
help="""This field controls how invoice and delivery operations are synchronized.
|
help="""This field controls how invoice and delivery operations are synchronized."""),
|
||||||
- With 'Before Delivery', a draft invoice is created, and it must be paid before delivery."""),
|
|
||||||
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current sales order."),
|
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current sales order."),
|
||||||
'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", string="Currency", readonly=True, required=True),
|
'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", string="Currency", readonly=True, required=True),
|
||||||
'project_id': fields.many2one('account.analytic.account', 'Contract / Analytic', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order."),
|
'project_id': fields.many2one('account.analytic.account', 'Contract / Analytic', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order."),
|
||||||
|
|
|
@ -38,5 +38,19 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Replace analytic_id with analytics_id in account.invoice.line -->
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_invoice_line_form_inherit">
|
||||||
|
<field name="name">account.invoice.line.form.inherit</field>
|
||||||
|
<field name="model">account.invoice.line</field>
|
||||||
|
<field name="inherit_id" ref="account.view_invoice_line_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="account_analytic_id" position="replace">
|
||||||
|
<field name="analytics_id" context="{'journal_id':parent.journal_id}" domain="[('plan_id','<>',False)]" groups="analytic.group_analytic_accounting"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -68,6 +68,21 @@ class res_users(osv.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class sale_crm_lead(osv.Model):
|
||||||
|
_inherit = 'crm.lead'
|
||||||
|
|
||||||
|
def on_change_user(self, cr, uid, ids, user_id, context=None):
|
||||||
|
""" Override of on change user_id on lead/opportunity; when having sale
|
||||||
|
the new logic is :
|
||||||
|
- use user.default_section_id
|
||||||
|
- or fallback on previous behavior """
|
||||||
|
if user_id:
|
||||||
|
user = self.pool.get('res.users').browse(cr, uid, user_id, context=context)
|
||||||
|
if user.default_section_id and user.default_section_id.id:
|
||||||
|
return {'value': {'section_id': user.default_section_id.id}}
|
||||||
|
return super(sale_crm_lead, self).on_change_user(cr, uid, ids, user_id, context=context)
|
||||||
|
|
||||||
|
|
||||||
class account_invoice(osv.osv):
|
class account_invoice(osv.osv):
|
||||||
_inherit = 'account.invoice'
|
_inherit = 'account.invoice'
|
||||||
_columns = {
|
_columns = {
|
||||||
|
|
|
@ -24,9 +24,12 @@ from openerp.osv import fields, osv
|
||||||
class company(osv.osv):
|
class company(osv.osv):
|
||||||
_inherit = 'res.company'
|
_inherit = 'res.company'
|
||||||
_columns = {
|
_columns = {
|
||||||
'security_lead': fields.float('Security Days', required=True,
|
'security_lead': fields.float(
|
||||||
help="This is the days added to what you promise to customers "\
|
'Security Days', required=True,
|
||||||
"for security purpose"),
|
help="Margin of error for dates promised to customers. "\
|
||||||
|
"Products will be scheduled for procurement and delivery "\
|
||||||
|
"that many days earlier than the actual promised date, to "\
|
||||||
|
"cope with unexpected delays in the supply chain."),
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'security_lead': 0.0,
|
'security_lead': 0.0,
|
||||||
|
|
|
@ -64,17 +64,6 @@
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Process Condition
|
|
||||||
-->
|
|
||||||
|
|
||||||
<record id="process_condition_conditionafterdelivery0" model="process.condition">
|
|
||||||
<field name="model_id" ref="sale.model_sale_order"/>
|
|
||||||
<field name="node_id" ref="process_node_invoiceafterdelivery0"/>
|
|
||||||
<field eval=""""object.order_policy=='postpaid'"""" name="model_states"/>
|
|
||||||
<field eval=""""condition_after_delivery"""" name="name"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Process Transition
|
Process Transition
|
||||||
-->
|
-->
|
||||||
|
@ -106,7 +95,7 @@
|
||||||
<record id="process_transition_invoiceafterdelivery0" model="process.transition">
|
<record id="process_transition_invoiceafterdelivery0" model="process.transition">
|
||||||
<field eval="[(6,0,[])]" name="transition_ids"/>
|
<field eval="[(6,0,[])]" name="transition_ids"/>
|
||||||
<field eval=""""Create Invoice"""" name="name"/>
|
<field eval=""""Create Invoice"""" name="name"/>
|
||||||
<field eval=""""The invoice is created automatically if the shipping policy is 'Invoice from pick' or 'Invoice on order after delivery'."""" name="note"/>
|
<field eval=""""If the sale order was set to create the invoice 'On Delivery Order', then an invoice is automatically created based on what you delivered. If you rather want to create your invoice based on your sale order, you can set the sale order to create invoice 'On Demand', then track and process the sales order that have been fully delivered and invoice them from there."""" name="note"/>
|
||||||
<field model="process.node" name="target_node_id" ref="process_node_invoiceafterdelivery0"/>
|
<field model="process.node" name="target_node_id" ref="process_node_invoiceafterdelivery0"/>
|
||||||
<field model="process.node" name="source_node_id" ref="process_node_packinglist0"/>
|
<field model="process.node" name="source_node_id" ref="process_node_packinglist0"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
@ -24,6 +24,8 @@ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FO
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from openerp.osv import fields, osv
|
from openerp.osv import fields, osv
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
|
import pytz
|
||||||
|
from openerp import SUPERUSER_ID
|
||||||
|
|
||||||
class sale_shop(osv.osv):
|
class sale_shop(osv.osv):
|
||||||
_inherit = "sale.shop"
|
_inherit = "sale.shop"
|
||||||
|
@ -229,6 +231,29 @@ class sale_order(osv.osv):
|
||||||
res.append(line.procurement_id.id)
|
res.append(line.procurement_id.id)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def date_to_datetime(self, cr, uid, userdate, context=None):
|
||||||
|
""" Convert date values expressed in user's timezone to
|
||||||
|
server-side UTC timestamp, assuming a default arbitrary
|
||||||
|
time of 12:00 AM - because a time is needed.
|
||||||
|
|
||||||
|
:param str userdate: date string in in user time zone
|
||||||
|
:return: UTC datetime string for server-side use
|
||||||
|
"""
|
||||||
|
# TODO: move to fields.datetime in server after 7.0
|
||||||
|
user_date = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT)
|
||||||
|
if context and context.get('tz'):
|
||||||
|
tz_name = context['tz']
|
||||||
|
else:
|
||||||
|
tz_name = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
|
||||||
|
if tz_name:
|
||||||
|
utc = pytz.timezone('UTC')
|
||||||
|
context_tz = pytz.timezone(tz_name)
|
||||||
|
user_datetime = user_date + relativedelta(hours=12.0)
|
||||||
|
local_timestamp = context_tz.localize(user_datetime, is_dst=False)
|
||||||
|
user_datetime = local_timestamp.astimezone(utc)
|
||||||
|
return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
return user_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
|
||||||
# if mode == 'finished':
|
# if mode == 'finished':
|
||||||
# returns True if all lines are done, False otherwise
|
# returns True if all lines are done, False otherwise
|
||||||
# if mode == 'canceled':
|
# if mode == 'canceled':
|
||||||
|
@ -311,7 +336,7 @@ class sale_order(osv.osv):
|
||||||
return {
|
return {
|
||||||
'name': pick_name,
|
'name': pick_name,
|
||||||
'origin': order.name,
|
'origin': order.name,
|
||||||
'date': order.date_order,
|
'date': self.date_to_datetime(cr, uid, order.date_order, context),
|
||||||
'type': 'out',
|
'type': 'out',
|
||||||
'state': 'auto',
|
'state': 'auto',
|
||||||
'move_type': order.picking_policy,
|
'move_type': order.picking_policy,
|
||||||
|
@ -345,7 +370,8 @@ class sale_order(osv.osv):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _get_date_planned(self, cr, uid, order, line, start_date, context=None):
|
def _get_date_planned(self, cr, uid, order, line, start_date, context=None):
|
||||||
date_planned = datetime.strptime(start_date, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=line.delay or 0.0)
|
start_date = self.date_to_datetime(cr, uid, start_date, context)
|
||||||
|
date_planned = datetime.strptime(start_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=line.delay or 0.0)
|
||||||
date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
return date_planned
|
return date_planned
|
||||||
|
|
||||||
|
@ -605,11 +631,6 @@ class sale_advance_payment_inv(osv.osv_memory):
|
||||||
sale_line_obj = self.pool.get('sale.order.line')
|
sale_line_obj = self.pool.get('sale.order.line')
|
||||||
wizard = self.browse(cr, uid, [result], context)
|
wizard = self.browse(cr, uid, [result], context)
|
||||||
sale = sale_obj.browse(cr, uid, sale_id, context=context)
|
sale = sale_obj.browse(cr, uid, sale_id, context=context)
|
||||||
if sale.order_policy == 'postpaid':
|
|
||||||
raise osv.except_osv(
|
|
||||||
_('Error!'),
|
|
||||||
_("You cannot make an advance on a sales order \
|
|
||||||
that is defined as 'Automatic Invoice after delivery'."))
|
|
||||||
|
|
||||||
# If invoice on picking: add the cost on the SO
|
# If invoice on picking: add the cost on the SO
|
||||||
# If not, the advance will be deduced when generating the final invoice
|
# If not, the advance will be deduced when generating the final invoice
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
order = self.browse(cr, uid, ref("sale.sale_order_6"))
|
order = self.browse(cr, uid, ref("sale.sale_order_6"))
|
||||||
for order_line in order.order_line:
|
for order_line in order.order_line:
|
||||||
procurement = order_line.procurement_id
|
procurement = order_line.procurement_id
|
||||||
date_planned = datetime.strptime(order.date_order, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
sale_order_date = self.date_to_datetime(cr, uid, order.date_order, context)
|
||||||
|
date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
||||||
date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
assert procurement.date_planned == date_planned, "Scheduled date is not correspond."
|
assert procurement.date_planned == date_planned, "Scheduled date is not correspond."
|
||||||
assert procurement.product_id.id == order_line.product_id.id, "Product is not correspond."
|
assert procurement.product_id.id == order_line.product_id.id, "Product is not correspond."
|
||||||
|
@ -60,7 +61,8 @@
|
||||||
output_id = sale_order.shop_id.warehouse_id.lot_output_id.id
|
output_id = sale_order.shop_id.warehouse_id.lot_output_id.id
|
||||||
for move in picking.move_lines:
|
for move in picking.move_lines:
|
||||||
order_line = move.sale_line_id
|
order_line = move.sale_line_id
|
||||||
date_planned = datetime.strptime(sale_order.date_order, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
sale_order_date = self.date_to_datetime(cr, uid, sale_order.date_order, context)
|
||||||
|
date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
||||||
date_planned = (date_planned - timedelta(days=sale_order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
date_planned = (date_planned - timedelta(days=sale_order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
assert datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) == datetime.strptime(date_planned, DEFAULT_SERVER_DATETIME_FORMAT), "Excepted Date is not correspond with Planned Date."
|
assert datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) == datetime.strptime(date_planned, DEFAULT_SERVER_DATETIME_FORMAT), "Excepted Date is not correspond with Planned Date."
|
||||||
assert move.product_id.id == order_line.product_id.id,"Product is not correspond."
|
assert move.product_id.id == order_line.product_id.id,"Product is not correspond."
|
||||||
|
|
|
@ -30,6 +30,7 @@ from openerp.report.render import render
|
||||||
|
|
||||||
import stock_graph
|
import stock_graph
|
||||||
import StringIO
|
import StringIO
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
class external_pdf(render):
|
class external_pdf(render):
|
||||||
def __init__(self, pdf):
|
def __init__(self, pdf):
|
||||||
|
@ -107,7 +108,12 @@ class report_stock(report_int):
|
||||||
io = StringIO.StringIO()
|
io = StringIO.StringIO()
|
||||||
gt = stock_graph.stock_graph(io)
|
gt = stock_graph.stock_graph(io)
|
||||||
for prod_id in products:
|
for prod_id in products:
|
||||||
gt.add(prod_id, names.get(prod_id, 'Unknown'), products[prod_id])
|
prod_name = names.get(prod_id,'Unknown')
|
||||||
|
if isinstance(prod_name, str):
|
||||||
|
prod_name = prod_name.decode('utf-8')
|
||||||
|
prod_name = unicodedata.normalize('NFKD',prod_name)
|
||||||
|
prod_name = prod_name.encode('ascii','replace')
|
||||||
|
gt.add(prod_id, prod_name, products[prod_id])
|
||||||
gt.draw()
|
gt.draw()
|
||||||
gt.close()
|
gt.close()
|
||||||
self.obj = external_pdf(io.getvalue())
|
self.obj = external_pdf(io.getvalue())
|
||||||
|
|
|
@ -193,6 +193,7 @@ CREATE OR REPLACE view report_stock_inventory AS (
|
||||||
LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
|
LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
|
||||||
LEFT JOIN product_uom u ON (m.product_uom=u.id)
|
LEFT JOIN product_uom u ON (m.product_uom=u.id)
|
||||||
LEFT JOIN stock_location l ON (m.location_id=l.id)
|
LEFT JOIN stock_location l ON (m.location_id=l.id)
|
||||||
|
WHERE m.state != 'cancel'
|
||||||
GROUP BY
|
GROUP BY
|
||||||
m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
|
m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
|
||||||
m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM')
|
m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM')
|
||||||
|
@ -216,6 +217,7 @@ CREATE OR REPLACE view report_stock_inventory AS (
|
||||||
LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
|
LEFT JOIN product_uom pu2 ON (m.product_uom=pu2.id)
|
||||||
LEFT JOIN product_uom u ON (m.product_uom=u.id)
|
LEFT JOIN product_uom u ON (m.product_uom=u.id)
|
||||||
LEFT JOIN stock_location l ON (m.location_dest_id=l.id)
|
LEFT JOIN stock_location l ON (m.location_dest_id=l.id)
|
||||||
|
WHERE m.state != 'cancel'
|
||||||
GROUP BY
|
GROUP BY
|
||||||
m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
|
m.id, m.product_id, m.product_uom, pt.categ_id, m.partner_id, m.location_id, m.location_dest_id,
|
||||||
m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM')
|
m.prodlot_id, m.date, m.state, l.usage, l.scrap_location, m.company_id, pt.uom_id, to_char(m.date, 'YYYY'), to_char(m.date, 'MM')
|
||||||
|
|
|
@ -2944,6 +2944,12 @@ class stock_picking_in(osv.osv):
|
||||||
_table = "stock_picking"
|
_table = "stock_picking"
|
||||||
_description = "Incoming Shipments"
|
_description = "Incoming Shipments"
|
||||||
|
|
||||||
|
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
|
||||||
|
return self.pool.get('stock.picking').search(cr, user, args, offset, limit, order, context, count)
|
||||||
|
|
||||||
|
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
|
||||||
|
return self.pool.get('stock.picking').read(cr, uid, ids, fields=fields, context=context, load=load)
|
||||||
|
|
||||||
def check_access_rights(self, cr, uid, operation, raise_exception=True):
|
def check_access_rights(self, cr, uid, operation, raise_exception=True):
|
||||||
#override in order to redirect the check of acces rights on the stock.picking object
|
#override in order to redirect the check of acces rights on the stock.picking object
|
||||||
return self.pool.get('stock.picking').check_access_rights(cr, uid, operation, raise_exception=raise_exception)
|
return self.pool.get('stock.picking').check_access_rights(cr, uid, operation, raise_exception=raise_exception)
|
||||||
|
@ -2999,6 +3005,12 @@ class stock_picking_out(osv.osv):
|
||||||
_table = "stock_picking"
|
_table = "stock_picking"
|
||||||
_description = "Delivery Orders"
|
_description = "Delivery Orders"
|
||||||
|
|
||||||
|
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
|
||||||
|
return self.pool.get('stock.picking').search(cr, user, args, offset, limit, order, context, count)
|
||||||
|
|
||||||
|
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
|
||||||
|
return self.pool.get('stock.picking').read(cr, uid, ids, fields=fields, context=context, load=load)
|
||||||
|
|
||||||
def check_access_rights(self, cr, uid, operation, raise_exception=True):
|
def check_access_rights(self, cr, uid, operation, raise_exception=True):
|
||||||
#override in order to redirect the check of acces rights on the stock.picking object
|
#override in order to redirect the check of acces rights on the stock.picking object
|
||||||
return self.pool.get('stock.picking').check_access_rights(cr, uid, operation, raise_exception=raise_exception)
|
return self.pool.get('stock.picking').check_access_rights(cr, uid, operation, raise_exception=raise_exception)
|
||||||
|
|
Loading…
Reference in New Issue