Skip to content

Commit

Permalink
[MERGE] forward port branch saas-11.4 up to 8285630
Browse files Browse the repository at this point in the history
  • Loading branch information
KangOl committed Jul 16, 2018
2 parents e891313 + 8285630 commit 182c2c6
Show file tree
Hide file tree
Showing 63 changed files with 709 additions and 147 deletions.
8 changes: 5 additions & 3 deletions addons/account/models/account_move.py
Expand Up @@ -391,6 +391,9 @@ def reverse_moves(self, date=None, journal_id=None, auto=False):
date = date or fields.Date.today()
reversed_moves = self.env['account.move']
for ac_move in self:
#unreconcile all lines reversed
aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity')
aml.remove_move_reconcile()
reversed_move = ac_move._reverse_move(date=date,
journal_id=journal_id,
auto=auto)
Expand Down Expand Up @@ -464,7 +467,7 @@ def _amount_residual(self):
for unreconciled lines, and something in-between for partially reconciled lines.
"""
for line in self:
if not line.account_id.reconcile:
if not line.account_id.reconcile and line.account_id.internal_type != 'liquidity':
line.reconciled = False
line.amount_residual = 0
line.amount_residual_currency = 0
Expand Down Expand Up @@ -607,7 +610,7 @@ def _get_counterpart(self):
help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.")
date = fields.Date(related='move_id.date', string='Date', index=True, store=True, copy=False) # related is required
analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines")
tax_ids = fields.Many2many('account.tax', string='Taxes')
tax_ids = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
tax_line_id = fields.Many2one('account.tax', string='Originator tax', ondelete='restrict')
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', index=True)
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
Expand Down Expand Up @@ -1552,7 +1555,6 @@ def unlink(self):
""" When removing a partial reconciliation, also unlink its full reconciliation if it exists """
full_to_unlink = self.env['account.full.reconcile']
for rec in self:
#without the deleted partial reconciliations, the full reconciliation won't be full anymore
if rec.full_reconcile_id:
full_to_unlink |= rec.full_reconcile_id
#reverse the tax basis move created at the reconciliation time
Expand Down
68 changes: 68 additions & 0 deletions addons/account/tests/test_reconciliation.py
Expand Up @@ -817,6 +817,74 @@ def test_aged_report(self):
self.assertEqual(positive_line[0]['amount'], 50.0, 'The amount of the amls should be 50')
self.assertEqual(negative_line[0]['amount'], -50.0, 'The amount of the amls should be -50')

def test_revert_payment_and_reconcile_exchange(self):

# A reversal of a reconciled payment which created a currency exchange entry, should create reversal moves
# which move lines should be reconciled two by two with the original move's lines

def _determine_debit_credit_line(move):
line_ids_reconciliable = move.line_ids.filtered(lambda l: l.account_id.reconcile or l.account_id.internal_type == 'liquidity')
return line_ids_reconciliable.filtered(lambda l: l.debit), line_ids_reconciliable.filtered(lambda l: l.credit)

def _move_revert_test_pair(move, revert):
self.assertTrue(move.line_ids)
self.assertTrue(revert.line_ids)

move_lines = _determine_debit_credit_line(move)
revert_lines = _determine_debit_credit_line(revert)

# in the case of the exchange entry, only one pair of lines will be found
if move_lines[0] and revert_lines[1]:
self.assertTrue(move_lines[0].full_reconcile_id.exists())
self.assertEqual(move_lines[0].full_reconcile_id.id, revert_lines[1].full_reconcile_id.id)

if move_lines[1] and revert_lines[0]:
self.assertTrue(move_lines[1].full_reconcile_id.exists())
self.assertEqual(move_lines[1].full_reconcile_id.id, revert_lines[0].full_reconcile_id.id)

self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-07-01',
'rate': 1.0,
'currency_id': self.currency_usd_id,
'company_id': self.env.ref('base.main_company').id
})
self.env['res.currency.rate'].create({
'name': time.strftime('%Y') + '-08-01',
'rate': 0.5,
'currency_id': self.currency_usd_id,
'company_id': self.env.ref('base.main_company').id
})
inv = self.create_invoice(invoice_amount=111, currency_id=self.currency_usd_id)
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 111,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
'payment_date': time.strftime('%Y') + '-08-01',
})
payment.post()

credit_aml = payment.move_line_ids.filtered('credit')
inv.assign_outstanding_credit(credit_aml.id)
self.assertTrue(inv.state == 'paid', 'The invoice should be paid')

exchange_reconcile = payment.move_line_ids.mapped('full_reconcile_id')
exchange_move = exchange_reconcile.exchange_move_id
payment_move = payment.move_line_ids[0].move_id

reverted_payment_move = self.env['account.move'].browse(payment_move.reverse_moves(time.strftime('%Y') + '-08-01'))

# After reversal of payment, the invoice should be open
self.assertTrue(inv.state == 'open', 'The invoice should be open again')
self.assertFalse(exchange_reconcile.exists())

reverted_exchange_move = self.env['account.move'].search([('journal_id', '=', exchange_move.journal_id.id), ('ref', 'ilike', exchange_move.name)], limit=1)
_move_revert_test_pair(payment_move, reverted_payment_move)
_move_revert_test_pair(exchange_move, reverted_exchange_move)

def test_partial_reconcile_currencies_02(self):
####
# Day 1: Invoice Cust/001 to customer (expressed in USD)
Expand Down
2 changes: 1 addition & 1 deletion addons/account/views/account_view.xml
Expand Up @@ -1373,7 +1373,7 @@
<filter string="Partner" name="partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Journal" name="journal" domain="[]" context="{'group_by':'journal_id'}"/>
<filter string="Account" name="account" context="{'group_by':'account_id'}"/>
<filter string="Date" name="date_filter" domain="[]" context="{'group_by':'date'}"/>
<filter string="Date" name="groupby_date" domain="[]" context="{'group_by':'date'}"/>
</group>
</search>
</field>
Expand Down
4 changes: 4 additions & 0 deletions addons/auth_oauth/controllers/main.py
Expand Up @@ -131,6 +131,8 @@ class OAuthController(http.Controller):
def signin(self, **kw):
state = json.loads(kw['state'])
dbname = state['d']
if not http.db_filter([dbname]):
return BadRequest()
provider = state['p']
context = state.get('c', {})
registry = registry_get(dbname)
Expand Down Expand Up @@ -180,6 +182,8 @@ def oea(self, **kw):
dbname = db_monodb()
if not dbname:
return BadRequest()
if not http.db_filter([dbname]):
return BadRequest()

registry = registry_get(dbname)
with registry.cursor() as cr:
Expand Down
31 changes: 30 additions & 1 deletion addons/calendar/models/calendar.py
Expand Up @@ -17,6 +17,7 @@

from odoo import api, fields, models
from odoo import tools
from odoo.osv import expression
from odoo.tools.translate import _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, pycompat
from odoo.exceptions import UserError, ValidationError
Expand Down Expand Up @@ -947,6 +948,14 @@ def _onchange_duration(self):
self.start = self.start_datetime
self.stop = fields.Datetime.to_string(start + timedelta(hours=self.duration))

@api.onchange('start_date')
def _onchange_start_date(self):
self.start = self.start_date

@api.onchange('stop_date')
def _onchange_stop_date(self):
self.stop = self.stop_date

####################################################
# Calendar Business, Reccurency, ...
####################################################
Expand Down Expand Up @@ -1098,6 +1107,24 @@ def get_recurrent_ids(self, domain, order=None):
if 'id' not in order_fields:
order_fields.append('id')

leaf_evaluations = None
#compose a query of the type SELECT id, condition1 as domain1, condition2 as domaine2
#This allows to load leaf interpretation of the where clause in one query
#leaf_evaluations is then used when running custom interpretation of domain for recuring events
if self:
select_fields = ["id"]
where_params_list = []
for pos, arg in enumerate(domain):
if not arg[0] in ('start', 'stop', 'final_date', '&', '|'):
e = expression.expression([arg], self)
where_clause, where_params = e.to_sql() # CAUTION, wont work if field is autojoin, not supported
select_fields.append("%s as \"%s\"" % (where_clause, str(pos)))
where_params_list += where_params
if len(select_fields) > 1:
query = "SELECT %s FROM calendar_event WHERE id in %%s" % (", ".join(select_fields)) # could be improved by only taking event with recurency ?
where_params_list += [tuple(self.ids)]
self._cr.execute(query, where_params_list)
leaf_evaluations = dict([(row['id'], row) for row in self._cr.dictfetchall()])
result_data = []
result = []
for meeting in self:
Expand All @@ -1115,7 +1142,7 @@ def get_recurrent_ids(self, domain, order=None):
pile = []
ok = True
r_date = r_start_date # default for empty domain
for arg in domain:
for pos, arg in enumerate(domain):
if str(arg[0]) in ('start', 'stop', 'final_date'):
if str(arg[0]) == 'start':
r_date = r_start_date
Expand All @@ -1140,6 +1167,8 @@ def get_recurrent_ids(self, domain, order=None):
pile.append(ok)
elif str(arg) == str('&') or str(arg) == str('|'):
pile.append(arg)
elif leaf_evaluations and meeting.id in leaf_evaluations:
pile.append(bool(leaf_evaluations[meeting.id][str(pos)]))
else:
pile.append(True)
pile.reverse()
Expand Down
14 changes: 11 additions & 3 deletions addons/calendar/models/res_users.py
Expand Up @@ -4,31 +4,39 @@
import datetime

from odoo import api, fields, models, modules, _
from pytz import timezone, UTC


class Users(models.Model):
_inherit = 'res.users'

def _systray_get_calendar_event_domain(self):
tz = self.env.user.tz
start_dt = datetime.datetime.utcnow()
start_date = datetime.date.today()
if tz:
start_date = timezone(tz).localize(start_dt).astimezone(UTC).date()
else:
start_date = datetime.date.today()
end_dt = datetime.datetime.combine(start_date, datetime.time.max)
if tz:
end_dt = timezone(tz).localize(end_dt).astimezone(UTC)

return ['&', '|',
'&',
'|', ['start', '>=', fields.Datetime.to_string(start_dt)], ['stop', '>=', fields.Datetime.to_string(start_dt)],
['start', '<=', fields.Datetime.to_string(end_dt)],
'&', ['allday', '=', True], ['start_date', '=', fields.Date.to_string(start_date)],
'&', ('attendee_ids.partner_id', '=', self.env.user.partner_id.id), ('attendee_ids.state', '!=', 'declined')]
('attendee_ids.partner_id', '=', self.env.user.partner_id.id)]

@api.model
def systray_get_activities(self):
res = super(Users, self).systray_get_activities()

meetings_lines = self.env['calendar.event'].search_read(
self._systray_get_calendar_event_domain(),
['id', 'start', 'name', 'allday'],
['id', 'start', 'name', 'allday', 'attendee_status'],
order='start')
meetings_lines = [line for line in meetings_lines if line['attendee_status'] != 'declined']
if meetings_lines:
meeting_label = _("Today's Meetings")
meetings_systray = {
Expand Down
27 changes: 27 additions & 0 deletions addons/calendar/tests/test_calendar_recurrent_event_case2.py
Expand Up @@ -169,3 +169,30 @@ def test_recurrent_meeting5(self):

# virtual_dates are used by the calendar view and I check if the stop date for the first virtual event is correct.
self.assertEqual(virutal_dates[2], '2012-04-13 12:00:00', "The virtual event doesn't have the correct stop date !")

def test_recurrent_meeting6(self):
ev = self.CalendarEvent.create({
'name': 'Rec1',
'start': '2018-06-28 11:00:00',
'stop': '2018-06-28 12:00:00',
'day': 0.0,
'duration': 1.0,
'count': ' 2',
'end_type': 'count',
'fr': True,
'recurrency': True,
'allday': False,
'rrule_type': 'weekly'
})
#event 2018-07-06 and 2018-06-29
meetings = self.CalendarEvent.with_context({'virtual_id': True}).search([
'|', '&', ('start', '>=', '2018-06-30 00:00:00'), ('start', '<=', '2018-06-30 23:59:59'), ('allday', '=', True)
])
base_ids = [calendar_id2real_id(meeting.id, with_date=False) for meeting in meetings]
self.assertNotIn(ev.id, base_ids, "Event does not match the domain")

meetings = self.CalendarEvent.with_context({'virtual_id': True}).search([
'|', '&', ('start', '>=', '2018-06-29 00:00:00'), ('start', '<=', '2018-06-29 23:59:59'), ('allday', '=', True)
])
base_ids = [calendar_id2real_id(meeting.id, with_date=False) for meeting in meetings]
self.assertIn(ev.id, base_ids, "Event does match the domain")
7 changes: 4 additions & 3 deletions addons/crm/models/crm_lead.py
Expand Up @@ -553,7 +553,8 @@ def _mail_body(self, fields):
for field in fields:
value = getattr(self, field.name, False)
if field.ttype == 'selection':
value = dict(field.get_values(self.env)).get(value, value)
selections = self.fields_get()[field.name]['selection']
value = next((v[1] for v in selections if v[0] == value), value)
elif field.ttype == 'many2one':
if value:
value = value.sudo().name_get()[0][1]
Expand Down Expand Up @@ -1186,7 +1187,7 @@ def message_new(self, msg_dict, custom_values=None):
defaults.update(custom_values)
return super(Lead, self).message_new(msg_dict, custom_values=defaults)

def _message_post_after_hook(self, message, values, notif_layout, notif_values):
def _message_post_after_hook(self, message, *args, **kwargs):
if self.email_from and not self.partner_id:
# we consider that posting a message with a specified recipient (not a follower, a specific one)
# on a document without customer means that it was created through the chatter using
Expand All @@ -1197,7 +1198,7 @@ def _message_post_after_hook(self, message, values, notif_layout, notif_values):
('partner_id', '=', False),
('email_from', '=', new_partner.email),
('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id})
return super(Lead, self)._message_post_after_hook(message, values, notif_layout, notif_values)
return super(Lead, self)._message_post_after_hook(message, *args, **kwargs)

@api.multi
def message_partner_info_from_emails(self, emails, link_mail=False):
Expand Down
4 changes: 2 additions & 2 deletions addons/event/models/event.py
Expand Up @@ -459,7 +459,7 @@ def message_get_suggested_recipients(self):
pass
return recipients

def _message_post_after_hook(self, message, values, notif_layout, notif_values):
def _message_post_after_hook(self, message, *args, **kwargs):
if self.email and not self.partner_id:
# we consider that posting a message with a specified recipient (not a follower, a specific one)
# on a document without customer means that it was created through the chatter using
Expand All @@ -471,7 +471,7 @@ def _message_post_after_hook(self, message, values, notif_layout, notif_values):
('email', '=', new_partner.email),
('state', 'not in', ['cancel']),
]).write({'partner_id': new_partner.id})
return super(EventRegistration, self)._message_post_after_hook(message, values, notif_layout, notif_values)
return super(EventRegistration, self)._message_post_after_hook(message, *args, **kwargs)

@api.multi
def action_send_badge_email(self):
Expand Down
9 changes: 0 additions & 9 deletions addons/gamification/models/challenge.py
Expand Up @@ -553,9 +553,6 @@ def report_progress(self, users=(), subset_goals=False):
partner_ids=challenge.mapped('user_ids.partner_id.id'),
subtype='mail.mt_comment',
notif_layout='mail.mail_notification_light',
notif_values={
'model_description': challenge._description.lower(),
}
)
if challenge.report_message_group_id:
challenge.report_message_group_id.message_post(
Expand All @@ -580,18 +577,12 @@ def report_progress(self, users=(), subset_goals=False):
partner_ids=[(4, user.partner_id.id)],
subtype='mail.mt_comment',
notif_layout='mail.mail_notification_light',
notif_values={
'model_description': self._description.lower(),
}
)
if challenge.report_message_group_id:
challenge.report_message_group_id.message_post(
body=body_html,
subtype='mail.mt_comment',
notif_layout='mail.mail_notification_light',
notif_values={
'model_description': self._description.lower(),
}
)
return challenge.write({'last_report_date': fields.Date.today()})

Expand Down
5 changes: 1 addition & 4 deletions addons/gamification/models/goal.py
Expand Up @@ -224,12 +224,9 @@ def _check_remind_delay(self):
.render_template(template.body_html, 'gamification.goal', self.id)
self.env['mail.thread'].message_post(
body=body_html,
partner_ids=[ self.user_id.partner_id.id],
partner_ids=[self.user_id.partner_id.id],
subtype='mail.mt_comment',
notif_layout='mail.mail_notification_light',
notif_values={
'model_description': self._description.lower(),
}
)

return {'to_update': True}
Expand Down
4 changes: 2 additions & 2 deletions addons/hr_recruitment/models/hr_recruitment.py
Expand Up @@ -397,7 +397,7 @@ def message_new(self, msg, custom_values=None):
defaults.update(custom_values)
return super(Applicant, self).message_new(msg, custom_values=defaults)

def _message_post_after_hook(self, message, values, notif_layout, notif_values):
def _message_post_after_hook(self, message, *args, **kwargs):
if self.email_from and not self.partner_id:
# we consider that posting a message with a specified recipient (not a follower, a specific one)
# on a document without customer means that it was created through the chatter using
Expand All @@ -408,7 +408,7 @@ def _message_post_after_hook(self, message, values, notif_layout, notif_values):
('partner_id', '=', False),
('email_from', '=', new_partner.email),
('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id})
return super(Applicant, self)._message_post_after_hook(message, values, notif_layout, notif_values)
return super(Applicant, self)._message_post_after_hook(message, *args, **kwargs)

@api.multi
def create_employee_from_applicant(self):
Expand Down

0 comments on commit 182c2c6

Please sign in to comment.