[MERGE] forward port branch 9.0 up to 58cbcba
This commit is contained in:
@@ -231,7 +231,7 @@ class AccountJournal(models.Model):
|
|||||||
|
|
||||||
#groups_id = fields.Many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', string='Groups')
|
#groups_id = fields.Many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', string='Groups')
|
||||||
currency_id = fields.Many2one('res.currency', help='The currency used to enter statement', string="Currency", oldname='currency')
|
currency_id = fields.Many2one('res.currency', help='The currency used to enter statement', string="Currency", oldname='currency')
|
||||||
company_id = fields.Many2one('res.company', string='Company', required=True, index=1, default=lambda self: self.env.user.company_id,
|
company_id = fields.Many2one('res.company', string='Company', required=True, index=True, default=lambda self: self.env.user.company_id,
|
||||||
help="Company related to this journal")
|
help="Company related to this journal")
|
||||||
|
|
||||||
refund_sequence = fields.Boolean(string='Dedicated Refund Sequence', help="Check this box if you don't want to share the same sequence for invoices and refunds made from this journal", default=False)
|
refund_sequence = fields.Boolean(string='Dedicated Refund Sequence', help="Check this box if you don't want to share the same sequence for invoices and refunds made from this journal", default=False)
|
||||||
@@ -632,7 +632,16 @@ class AccountTax(models.Model):
|
|||||||
if self.amount_type == 'fixed':
|
if self.amount_type == 'fixed':
|
||||||
# Use copysign to take into account the sign of the base amount which includes the sign
|
# Use copysign to take into account the sign of the base amount which includes the sign
|
||||||
# of the quantity and the sign of the price_unit
|
# of the quantity and the sign of the price_unit
|
||||||
return math.copysign(quantity, base_amount) * self.amount
|
# Amount is the fixed price for the tax, it can be negative
|
||||||
|
# Base amount included the sign of the quantity and the sign of the unit price and when
|
||||||
|
# a product is returned, it can be done either by changing the sign of quantity or by changing the
|
||||||
|
# sign of the price unit.
|
||||||
|
# When the price unit is equal to 0, the sign of the quantity is absorbed in base_amount then
|
||||||
|
# a "else" case is needed.
|
||||||
|
if base_amount:
|
||||||
|
return math.copysign(quantity, base_amount) * self.amount
|
||||||
|
else:
|
||||||
|
return quantity * self.amount
|
||||||
if (self.amount_type == 'percent' and not self.price_include) or (self.amount_type == 'division' and self.price_include):
|
if (self.amount_type == 'percent' and not self.price_include) or (self.amount_type == 'division' and self.price_include):
|
||||||
return base_amount * self.amount / 100
|
return base_amount * self.amount / 100
|
||||||
if self.amount_type == 'percent' and self.price_include:
|
if self.amount_type == 'percent' and self.price_include:
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class AccountBankStatement(models.Model):
|
|||||||
|
|
||||||
name = fields.Char(string='Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True)
|
name = fields.Char(string='Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True)
|
||||||
reference = fields.Char(string='External Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True, help="Used to hold the reference of the external mean that created this statement (name of imported file, reference of online synchronization...)")
|
reference = fields.Char(string='External Reference', states={'open': [('readonly', False)]}, copy=False, readonly=True, help="Used to hold the reference of the external mean that created this statement (name of imported file, reference of online synchronization...)")
|
||||||
date = fields.Date(required=True, states={'confirm': [('readonly', True)]}, select=True, copy=False, default=fields.Date.context_today)
|
date = fields.Date(required=True, states={'confirm': [('readonly', True)]}, index=True, copy=False, default=fields.Date.context_today)
|
||||||
date_done = fields.Datetime(string="Closed On")
|
date_done = fields.Datetime(string="Closed On")
|
||||||
balance_start = fields.Monetary(string='Starting Balance', states={'confirm': [('readonly', True)]}, default=_default_opening_balance)
|
balance_start = fields.Monetary(string='Starting Balance', states={'confirm': [('readonly', True)]}, default=_default_opening_balance)
|
||||||
balance_end_real = fields.Monetary('Ending Balance', states={'confirm': [('readonly', True)]})
|
balance_end_real = fields.Monetary('Ending Balance', states={'confirm': [('readonly', True)]})
|
||||||
@@ -289,58 +289,54 @@ class AccountBankStatement(models.Model):
|
|||||||
"""
|
"""
|
||||||
statements = self
|
statements = self
|
||||||
bsl_obj = self.env['account.bank.statement.line']
|
bsl_obj = self.env['account.bank.statement.line']
|
||||||
|
|
||||||
# NB : The field account_id can be used at the statement line creation/import to avoid the reconciliation process on it later on,
|
# NB : The field account_id can be used at the statement line creation/import to avoid the reconciliation process on it later on,
|
||||||
# this is why we filter out statements lines where account_id is set
|
# this is why we filter out statements lines where account_id is set
|
||||||
st_lines_filter = [('journal_entry_ids', '=', False), ('account_id', '=', False)]
|
|
||||||
|
sql_query = """SELECT stl.id
|
||||||
|
FROM account_bank_statement_line stl
|
||||||
|
WHERE account_id IS NULL AND not exists (select 1 from account_move m where m.statement_line_id = stl.id)
|
||||||
|
AND company_id = %s
|
||||||
|
"""
|
||||||
|
params = (self.env.user.company_id.id,)
|
||||||
if statements:
|
if statements:
|
||||||
st_lines_filter += [('statement_id', 'in', statements.ids)]
|
sql_query += ' AND stl.statement_id IN %s'
|
||||||
|
params += (tuple(statements.ids),)
|
||||||
|
sql_query += ' ORDER BY stl.id'
|
||||||
|
self.env.cr.execute(sql_query, params)
|
||||||
|
st_lines_left = self.env['account.bank.statement.line'].browse([line.get('id') for line in self.env.cr.dictfetchall()])
|
||||||
|
|
||||||
# Try to automatically reconcile statement lines
|
#try to assign partner to bank_statement_line
|
||||||
automatic_reconciliation_entries = []
|
stl_to_assign_partner = [stl.id for stl in st_lines_left if not stl.partner_id]
|
||||||
st_lines_left = self.env['account.bank.statement.line']
|
refs = list(set([st.name for st in st_lines_left if not stl.partner_id]))
|
||||||
for st_line in bsl_obj.search(st_lines_filter):
|
if st_lines_left and stl_to_assign_partner and refs:
|
||||||
res = st_line.auto_reconcile()
|
sql_query = """SELECT aml.partner_id, aml.ref, stl.id
|
||||||
if not res:
|
FROM account_move_line aml
|
||||||
st_lines_left = (st_lines_left | st_line)
|
JOIN account_account acc ON acc.id = aml.account_id
|
||||||
else:
|
JOIN account_bank_statement_line stl ON aml.ref = stl.name
|
||||||
automatic_reconciliation_entries.append(res.ids)
|
WHERE (aml.company_id = %s
|
||||||
|
AND aml.partner_id IS NOT NULL)
|
||||||
# Try to set statement line's partner
|
AND (
|
||||||
for st_line in st_lines_left:
|
(aml.statement_id IS NULL AND aml.account_id IN %s)
|
||||||
if st_line.name and not st_line.partner_id:
|
OR
|
||||||
additional_domain = [('ref', '=', st_line.name)]
|
(acc.internal_type IN ('payable', 'receivable') AND aml.reconciled = false)
|
||||||
match_recs = st_line.get_move_lines_for_reconciliation(limit=1, additional_domain=additional_domain, overlook_partner=True)
|
)
|
||||||
if match_recs and match_recs[0].partner_id:
|
AND aml.ref IN %s
|
||||||
st_line.write({'partner_id': match_recs[0].partner_id.id})
|
"""
|
||||||
|
params = (self.env.user.company_id.id, (st_lines_left[0].journal_id.default_credit_account_id.id, st_lines_left[0].journal_id.default_debit_account_id.id), tuple(refs))
|
||||||
# Collect various informations for the reconciliation widget
|
if statements:
|
||||||
notifications = []
|
sql_query += 'AND stl.id IN %s'
|
||||||
num_auto_reconciled = len(automatic_reconciliation_entries)
|
params += (tuple(stl_to_assign_partner),)
|
||||||
if num_auto_reconciled > 0:
|
self.env.cr.execute(sql_query, params)
|
||||||
auto_reconciled_message = num_auto_reconciled > 1 \
|
results = self.env.cr.dictfetchall()
|
||||||
and _("%d transactions were automatically reconciled.") % num_auto_reconciled \
|
st_line = self.env['account.bank.statement.line']
|
||||||
or _("1 transaction was automatically reconciled.")
|
for line in results:
|
||||||
notifications += [{
|
st_line.browse(line.get('id')).write({'partner_id': line.get('partner_id')})
|
||||||
'type': 'info',
|
|
||||||
'message': auto_reconciled_message,
|
|
||||||
'details': {
|
|
||||||
'name': _("Automatically reconciled items"),
|
|
||||||
'model': 'account.move',
|
|
||||||
'ids': automatic_reconciliation_entries
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
for el in statements:
|
|
||||||
lines.extend(el.line_ids.ids)
|
|
||||||
lines = list(set(lines))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'st_lines_ids': st_lines_left.ids,
|
'st_lines_ids': st_lines_left.ids,
|
||||||
'notifications': notifications,
|
'notifications': [],
|
||||||
'statement_name': len(statements) == 1 and statements[0].name or False,
|
'statement_name': len(statements) == 1 and statements[0].name or False,
|
||||||
'num_already_reconciled_lines': statements and bsl_obj.search_count([('journal_entry_ids', '!=', False), ('id', 'in', lines)]) or 0,
|
'num_already_reconciled_lines': 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -449,6 +445,40 @@ class AccountBankStatementLine(models.Model):
|
|||||||
# Reconciliation interface methods
|
# Reconciliation interface methods
|
||||||
####################################################
|
####################################################
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def reconciliation_widget_auto_reconcile(self, num_already_reconciled_lines):
|
||||||
|
automatic_reconciliation_entries = self.env['account.bank.statement.line']
|
||||||
|
unreconciled = self.env['account.bank.statement.line']
|
||||||
|
for stl in self:
|
||||||
|
res = stl.auto_reconcile()
|
||||||
|
if res:
|
||||||
|
automatic_reconciliation_entries += stl
|
||||||
|
else:
|
||||||
|
unreconciled += stl
|
||||||
|
|
||||||
|
# Collect various informations for the reconciliation widget
|
||||||
|
notifications = []
|
||||||
|
num_auto_reconciled = len(automatic_reconciliation_entries)
|
||||||
|
if num_auto_reconciled > 0:
|
||||||
|
auto_reconciled_message = num_auto_reconciled > 1 \
|
||||||
|
and _("%d transactions were automatically reconciled.") % num_auto_reconciled \
|
||||||
|
or _("1 transaction was automatically reconciled.")
|
||||||
|
notifications += [{
|
||||||
|
'type': 'info',
|
||||||
|
'message': auto_reconciled_message,
|
||||||
|
'details': {
|
||||||
|
'name': _("Automatically reconciled items"),
|
||||||
|
'model': 'account.move',
|
||||||
|
'ids': automatic_reconciliation_entries.ids
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
return {
|
||||||
|
'st_lines_ids': unreconciled.ids,
|
||||||
|
'notifications': notifications,
|
||||||
|
'statement_name': False,
|
||||||
|
'num_already_reconciled_lines': num_auto_reconciled + num_already_reconciled_lines,
|
||||||
|
}
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def get_data_for_reconciliation_widget(self, excluded_ids=None):
|
def get_data_for_reconciliation_widget(self, excluded_ids=None):
|
||||||
""" Returns the data required to display a reconciliation widget, for each statement line in self """
|
""" Returns the data required to display a reconciliation widget, for each statement line in self """
|
||||||
@@ -561,86 +591,66 @@ class AccountBankStatementLine(models.Model):
|
|||||||
|
|
||||||
return self.env['account.move.line'].search(domain, offset=offset, limit=limit, order="date_maturity asc, id asc")
|
return self.env['account.move.line'].search(domain, offset=offset, limit=limit, order="date_maturity asc, id asc")
|
||||||
|
|
||||||
def _get_domain_maker_move_line_amount(self):
|
def _get_common_sql_query(self, overlook_partner = False, excluded_ids = None, split = False):
|
||||||
""" Returns a function that can create the appropriate domain to search on move.line amount based on statement.line currency/amount """
|
acc_type = "acc.internal_type IN ('payable', 'receivable')" if (self.partner_id or overlook_partner) else "acc.reconcile = true"
|
||||||
company_currency = self.journal_id.company_id.currency_id
|
select_clause = "SELECT aml.id "
|
||||||
st_line_currency = self.currency_id or self.journal_id.currency_id
|
from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id "
|
||||||
currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
|
where_clause = """WHERE aml.company_id = %(company_id)s
|
||||||
field = currency and 'amount_residual_currency' or 'amount_residual'
|
AND (
|
||||||
precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
|
(aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s)
|
||||||
|
OR
|
||||||
def ret(comparator, amount, p=precision, f=field, c=currency):
|
("""+acc_type+""" AND aml.reconciled = false)
|
||||||
if comparator == '<':
|
)"""
|
||||||
if amount < 0:
|
where_clause = where_clause + ' AND aml.partner_id = %(partner_id)s' if self.partner_id else where_clause
|
||||||
domain = [(f, '<', 0), (f, '>', amount)]
|
where_clause = where_clause + ' AND aml.id NOT IN %(excluded_ids)s' if excluded_ids else where_clause
|
||||||
else:
|
if split:
|
||||||
domain = [(f, '>', 0), (f, '<', amount)]
|
return select_clause, from_clause, where_clause
|
||||||
elif comparator == '=':
|
return select_clause + from_clause + where_clause
|
||||||
if f == 'amount_residual':
|
|
||||||
liquidity_field = amount > 0 and 'debit' or 'credit'
|
|
||||||
domain = [
|
|
||||||
'|', (f, '=', float_round(amount, precision_digits=p)),
|
|
||||||
'&', ('account_id.internal_type', '=', 'liquidity'),
|
|
||||||
(liquidity_field, '=', amount),
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
domain = [
|
|
||||||
'|', (f, '=', float_round(amount, precision_digits=p)),
|
|
||||||
'&', ('account_id.internal_type', '=', 'liquidity'),
|
|
||||||
('amount_currency', '=', amount),
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
raise UserError(_("Programmation error : domain_maker_move_line_amount requires comparator '=' or '<'"))
|
|
||||||
domain += [('currency_id', '=', c)]
|
|
||||||
return domain
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def get_reconciliation_proposition(self, excluded_ids=None):
|
def get_reconciliation_proposition(self, excluded_ids=None):
|
||||||
""" Returns move lines that constitute the best guess to reconcile a statement line
|
""" Returns move lines that constitute the best guess to reconcile a statement line
|
||||||
Note: it only looks for move lines in the same currency as the statement line.
|
Note: it only looks for move lines in the same currency as the statement line.
|
||||||
"""
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
if not excluded_ids:
|
||||||
|
excluded_ids = []
|
||||||
|
amount = self.amount_currency or self.amount
|
||||||
|
company_currency = self.journal_id.company_id.currency_id
|
||||||
|
st_line_currency = self.currency_id or self.journal_id.currency_id
|
||||||
|
currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
|
||||||
|
precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
|
||||||
|
params = {'company_id': self.env.user.company_id.id,
|
||||||
|
'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id),
|
||||||
|
'amount': float_round(amount, precision_digits=precision),
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'excluded_ids': tuple(excluded_ids),
|
||||||
|
'ref': self.name,
|
||||||
|
}
|
||||||
# Look for structured communication match
|
# Look for structured communication match
|
||||||
if self.name:
|
if self.name:
|
||||||
overlook_partner = not self.partner_id # If the transaction has no partner, look for match in payable and receivable account anyway
|
add_to_select = ", CASE WHEN aml.ref = %(ref)s THEN 1 ELSE 2 END as temp_field_order "
|
||||||
domain = [('ref', '=', self.name)]
|
add_to_from = " JOIN account_move m ON m.id = aml.move_id "
|
||||||
match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
|
select_clause, from_clause, where_clause = self._get_common_sql_query(overlook_partner=True, excluded_ids=excluded_ids, split=True)
|
||||||
if match_recs and len(match_recs) == 1:
|
sql_query = select_clause + add_to_select + from_clause + add_to_from + where_clause
|
||||||
return match_recs
|
sql_query += " AND (aml.ref= %(ref)s or m.name = %(ref)s) \
|
||||||
elif len(match_recs) == 0:
|
ORDER BY temp_field_order, date_maturity asc, aml.id asc"
|
||||||
move = self.env['account.move'].search([('name', '=', self.name)], limit=1)
|
self.env.cr.execute(sql_query, params)
|
||||||
if move:
|
results = self.env.cr.fetchone()
|
||||||
domain = [('move_id', '=', move.id)]
|
if results:
|
||||||
match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
|
return self.env['account.move.line'].browse(results[0])
|
||||||
if match_recs and len(match_recs) == 1:
|
|
||||||
return match_recs
|
|
||||||
|
|
||||||
# How to compare statement line amount and move lines amount
|
|
||||||
amount_domain_maker = self._get_domain_maker_move_line_amount()
|
|
||||||
amount = self.amount_currency or self.amount
|
|
||||||
|
|
||||||
# Look for a single move line with the same amount
|
# Look for a single move line with the same amount
|
||||||
match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=1, additional_domain=amount_domain_maker('=', amount))
|
field = currency and 'amount_residual_currency' or 'amount_residual'
|
||||||
if match_recs:
|
liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit'
|
||||||
return match_recs
|
sql_query = self._get_common_sql_query(excluded_ids=excluded_ids) + \
|
||||||
|
" AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
|
||||||
|
ORDER BY date_maturity asc, aml.id asc LIMIT 1"
|
||||||
|
self.env.cr.execute(sql_query, params)
|
||||||
|
results = self.env.cr.fetchone()
|
||||||
|
if results:
|
||||||
|
return self.env['account.move.line'].browse(results[0])
|
||||||
|
|
||||||
if not self.partner_id:
|
return self.env['account.move.line']
|
||||||
return self.env['account.move.line']
|
|
||||||
|
|
||||||
# Select move lines until their total amount is greater than the statement line amount
|
|
||||||
domain = [('reconciled', '=', False)]
|
|
||||||
domain += [('account_id.user_type_id.type', '=', amount > 0 and 'receivable' or 'payable')] # Make sure we can't mix receivable and payable
|
|
||||||
domain += amount_domain_maker('<', amount) # Will also enforce > 0
|
|
||||||
mv_lines = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=5, additional_domain=domain)
|
|
||||||
st_line_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id
|
|
||||||
ret = self.env['account.move.line']
|
|
||||||
total = 0
|
|
||||||
for line in mv_lines:
|
|
||||||
total += line.currency_id and line.amount_residual_currency or line.amount_residual
|
|
||||||
if float_compare(total, abs(amount), precision_digits=st_line_currency.rounding) != -1:
|
|
||||||
break
|
|
||||||
ret = (ret | line)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _get_move_lines_for_auto_reconcile(self):
|
def _get_move_lines_for_auto_reconcile(self):
|
||||||
""" Returns the move lines that the method auto_reconcile can use to try to reconcile the statement line """
|
""" Returns the move lines that the method auto_reconcile can use to try to reconcile the statement line """
|
||||||
@@ -654,28 +664,44 @@ class AccountBankStatementLine(models.Model):
|
|||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
match_recs = self.env['account.move.line']
|
match_recs = self.env['account.move.line']
|
||||||
|
|
||||||
# How to compare statement line amount and move lines amount
|
amount = self.amount_currency or self.amount
|
||||||
amount_domain_maker = self._get_domain_maker_move_line_amount()
|
company_currency = self.journal_id.company_id.currency_id
|
||||||
equal_amount_domain = amount_domain_maker('=', self.amount_currency or self.amount)
|
st_line_currency = self.currency_id or self.journal_id.currency_id
|
||||||
|
currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
|
||||||
|
precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
|
||||||
|
params = {'company_id': self.env.user.company_id.id,
|
||||||
|
'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id),
|
||||||
|
'amount': float_round(amount, precision_digits=precision),
|
||||||
|
'partner_id': self.partner_id.id,
|
||||||
|
'ref': self.name,
|
||||||
|
}
|
||||||
|
field = currency and 'amount_residual_currency' or 'amount_residual'
|
||||||
|
liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit'
|
||||||
# Look for structured communication match
|
# Look for structured communication match
|
||||||
if self.name:
|
if self.name:
|
||||||
overlook_partner = not self.partner_id # If the transaction has no partner, look for match in payable and receivable account anyway
|
sql_query = self._get_common_sql_query() + \
|
||||||
domain = equal_amount_domain + [('ref', '=', self.name)]
|
" AND aml.ref = %(ref)s AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
|
||||||
match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=domain, overlook_partner=overlook_partner)
|
ORDER BY date_maturity asc, aml.id asc"
|
||||||
if match_recs and len(match_recs) != 1:
|
self.env.cr.execute(sql_query, params)
|
||||||
|
match_recs = self.env.cr.dictfetchall()
|
||||||
|
if len(match_recs) > 1:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Look for a single move line with the same partner, the same amount
|
# Look for a single move line with the same partner, the same amount
|
||||||
if not match_recs:
|
if not match_recs:
|
||||||
if self.partner_id:
|
if self.partner_id:
|
||||||
match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=equal_amount_domain)
|
sql_query = self._get_common_sql_query() + \
|
||||||
if match_recs and len(match_recs) != 1:
|
" AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
|
||||||
|
ORDER BY date_maturity asc, aml.id asc"
|
||||||
|
self.env.cr.execute(sql_query, params)
|
||||||
|
match_recs = self.env.cr.dictfetchall()
|
||||||
|
if len(match_recs) > 1:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not match_recs:
|
if not match_recs:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
match_recs = self.env['account.move.line'].browse([aml.get('id') for aml in match_recs])
|
||||||
# Now reconcile
|
# Now reconcile
|
||||||
counterpart_aml_dicts = []
|
counterpart_aml_dicts = []
|
||||||
payment_aml_rec = self.env['account.move.line']
|
payment_aml_rec = self.env['account.move.line']
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class account_journal(models.Model):
|
|||||||
for i in range(0,6):
|
for i in range(0,6):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
query += "("+select_sql_clause+" and date < '"+start_date.strftime(DF)+"')"
|
query += "("+select_sql_clause+" and date < '"+start_date.strftime(DF)+"')"
|
||||||
elif i == 6:
|
elif i == 5:
|
||||||
query += " UNION ALL ("+select_sql_clause+" and date >= '"+start_date.strftime(DF)+"')"
|
query += " UNION ALL ("+select_sql_clause+" and date >= '"+start_date.strftime(DF)+"')"
|
||||||
else:
|
else:
|
||||||
next_date = start_date + timedelta(days=7)
|
next_date = start_date + timedelta(days=7)
|
||||||
@@ -303,6 +303,7 @@ class account_journal(models.Model):
|
|||||||
invoice_type = _journal_invoice_type_map[(self.type, self._context.get('invoice_type'))]
|
invoice_type = _journal_invoice_type_map[(self.type, self._context.get('invoice_type'))]
|
||||||
|
|
||||||
ctx = self._context.copy()
|
ctx = self._context.copy()
|
||||||
|
ctx.pop('group_by', None)
|
||||||
ctx.update({
|
ctx.update({
|
||||||
'journal_type': self.type,
|
'journal_type': self.type,
|
||||||
'default_journal_id': self.id,
|
'default_journal_id': self.id,
|
||||||
@@ -336,6 +337,7 @@ class account_journal(models.Model):
|
|||||||
'default_payment_type': payment_type,
|
'default_payment_type': payment_type,
|
||||||
'default_journal_id': self.id
|
'default_journal_id': self.id
|
||||||
})
|
})
|
||||||
|
ctx.pop('group_by', None)
|
||||||
action_rec = self.env['ir.model.data'].xmlid_to_object('account.action_account_payments')
|
action_rec = self.env['ir.model.data'].xmlid_to_object('account.action_account_payments')
|
||||||
if action_rec:
|
if action_rec:
|
||||||
action = action_rec.read([])[0]
|
action = action_rec.read([])[0]
|
||||||
@@ -351,6 +353,7 @@ class account_journal(models.Model):
|
|||||||
ctx = dict(self.env.context, default_journal_id=self.id)
|
ctx = dict(self.env.context, default_journal_id=self.id)
|
||||||
if ctx.get('search_default_journal', False):
|
if ctx.get('search_default_journal', False):
|
||||||
ctx.update(search_default_journal_id=self.id)
|
ctx.update(search_default_journal_id=self.id)
|
||||||
|
ctx.pop('group_by', None)
|
||||||
ir_model_obj = self.pool['ir.model.data']
|
ir_model_obj = self.pool['ir.model.data']
|
||||||
model, action_id = ir_model_obj.get_object_reference(self._cr, self._uid, 'account', action_name)
|
model, action_id = ir_model_obj.get_object_reference(self._cr, self._uid, 'account', action_name)
|
||||||
action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
|
action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class AccountMove(models.Model):
|
|||||||
company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', store=True, readonly=True,
|
company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', store=True, readonly=True,
|
||||||
default=lambda self: self.env.user.company_id)
|
default=lambda self: self.env.user.company_id)
|
||||||
matched_percentage = fields.Float('Percentage Matched', compute='_compute_matched_percentage', digits=0, store=True, readonly=True, help="Technical field used in cash basis method")
|
matched_percentage = fields.Float('Percentage Matched', compute='_compute_matched_percentage', digits=0, store=True, readonly=True, help="Technical field used in cash basis method")
|
||||||
statement_line_id = fields.Many2one('account.bank.statement.line', string='Bank statement line reconciled with this entry', copy=False, readonly=True)
|
statement_line_id = fields.Many2one('account.bank.statement.line', index=True, string='Bank statement line reconciled with this entry', copy=False, readonly=True)
|
||||||
# Dummy Account field to search on account.move by account_id
|
# Dummy Account field to search on account.move by account_id
|
||||||
dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False)
|
dummy_account_id = fields.Many2one('account.account', related='line_ids.account_id', string='Account', store=False)
|
||||||
|
|
||||||
@@ -230,6 +230,16 @@ class AccountMoveLine(models.Model):
|
|||||||
_description = "Journal Item"
|
_description = "Journal Item"
|
||||||
_order = "date desc, id desc"
|
_order = "date desc, id desc"
|
||||||
|
|
||||||
|
def init(self, cr):
|
||||||
|
""" change index on partner_id to a multi-column index on (partner_id, ref), the new index will behave in the
|
||||||
|
same way when we search on partner_id, with the addition of being optimal when having a query that will
|
||||||
|
search on partner_id and ref at the same time (which is the case when we open the bank reconciliation widget)
|
||||||
|
"""
|
||||||
|
cr.execute('DROP INDEX IF EXISTS account_move_line_partner_id_index')
|
||||||
|
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('account_move_line_partner_id_ref_idx',))
|
||||||
|
if not cr.fetchone():
|
||||||
|
cr.execute('CREATE INDEX account_move_line_partner_id_ref_idx ON account_move_line (partner_id, ref)')
|
||||||
|
|
||||||
@api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'account_id.currency_id', 'move_id.state')
|
@api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'account_id.currency_id', 'move_id.state')
|
||||||
def _amount_residual(self):
|
def _amount_residual(self):
|
||||||
""" Computes the residual amount of a move line from a reconciliable account in the company currency and the line's currency.
|
""" Computes the residual amount of a move line from a reconciliable account in the company currency and the line's currency.
|
||||||
@@ -363,7 +373,7 @@ class AccountMoveLine(models.Model):
|
|||||||
move_id = fields.Many2one('account.move', string='Journal Entry', ondelete="cascade",
|
move_id = fields.Many2one('account.move', string='Journal Entry', ondelete="cascade",
|
||||||
help="The move of this entry line.", index=True, required=True, auto_join=True)
|
help="The move of this entry line.", index=True, required=True, auto_join=True)
|
||||||
narration = fields.Text(related='move_id.narration', string='Internal Note')
|
narration = fields.Text(related='move_id.narration', string='Internal Note')
|
||||||
ref = fields.Char(related='move_id.ref', string='Partner Reference', store=True, copy=False)
|
ref = fields.Char(related='move_id.ref', string='Partner Reference', store=True, copy=False, index=True)
|
||||||
payment_id = fields.Many2one('account.payment', string="Originator Payment", help="Payment that created this entry")
|
payment_id = fields.Many2one('account.payment', string="Originator Payment", help="Payment that created this entry")
|
||||||
statement_id = fields.Many2one('account.bank.statement', string='Statement',
|
statement_id = fields.Many2one('account.bank.statement', string='Statement',
|
||||||
help="The bank statement used for bank reconciliation", index=True, copy=False)
|
help="The bank statement used for bank reconciliation", index=True, copy=False)
|
||||||
@@ -389,7 +399,7 @@ class AccountMoveLine(models.Model):
|
|||||||
|
|
||||||
# TODO: put the invoice link and partner_id on the account_move
|
# TODO: put the invoice link and partner_id on the account_move
|
||||||
invoice_id = fields.Many2one('account.invoice', oldname="invoice")
|
invoice_id = fields.Many2one('account.invoice', oldname="invoice")
|
||||||
partner_id = fields.Many2one('res.partner', string='Partner', index=True, ondelete='restrict')
|
partner_id = fields.Many2one('res.partner', string='Partner', ondelete='restrict')
|
||||||
user_type_id = fields.Many2one('account.account.type', related='account_id.user_type_id', index=True, store=True, oldname="user_type")
|
user_type_id = fields.Many2one('account.account.type', related='account_id.user_type_id', index=True, store=True, oldname="user_type")
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
@@ -1134,8 +1144,6 @@ class AccountMoveLine(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if vals.get('tax_line_id') or vals.get('tax_ids'):
|
|
||||||
raise UserError(_('You cannot change the tax, you should remove and recreate lines.'))
|
|
||||||
if ('account_id' in vals) and self.env['account.account'].browse(vals['account_id']).deprecated:
|
if ('account_id' in vals) and self.env['account.account'].browse(vals['account_id']).deprecated:
|
||||||
raise UserError(_('You cannot use deprecated account.'))
|
raise UserError(_('You cannot use deprecated account.'))
|
||||||
if any(key in vals for key in ('account_id', 'journal_id', 'date', 'move_id', 'debit', 'credit')):
|
if any(key in vals for key in ('account_id', 'journal_id', 'date', 'move_id', 'debit', 'credit')):
|
||||||
@@ -1420,11 +1428,11 @@ class AccountPartialReconcile(models.Model):
|
|||||||
exchange_partial_rec_id = rate_diff_partial_rec.id
|
exchange_partial_rec_id = rate_diff_partial_rec.id
|
||||||
#mark the reference of the full reconciliation on the partial ones and on the entries
|
#mark the reference of the full reconciliation on the partial ones and on the entries
|
||||||
self.env['account.full.reconcile'].with_context(check_move_validity=False).create({
|
self.env['account.full.reconcile'].with_context(check_move_validity=False).create({
|
||||||
'partial_reconcile_ids': [(6, 0, partial_rec_ids)],
|
'partial_reconcile_ids': [(4, p_id) for p_id in partial_rec_ids],
|
||||||
'reconciled_line_ids': [(6, 0, aml_ids)],
|
'reconciled_line_ids': [(4, a_id) for a_id in aml_ids],
|
||||||
'exchange_move_id': exchange_move_id,
|
'exchange_move_id': exchange_move_id,
|
||||||
'exchange_partial_rec_id': exchange_partial_rec_id,
|
'exchange_partial_rec_id': exchange_partial_rec_id,
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ class ResPartner(models.Model):
|
|||||||
else:
|
else:
|
||||||
domain += [('partner_id', 'in', self.ids)]
|
domain += [('partner_id', 'in', self.ids)]
|
||||||
#adding the overdue lines
|
#adding the overdue lines
|
||||||
overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<=', date), '&', ('date_maturity', '=', False), ('date', '<=', date)]
|
overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<', date), '&', ('date_maturity', '=', False), ('date', '<', date)]
|
||||||
if overdue_only:
|
if overdue_only:
|
||||||
domain += overdue_domain
|
domain += overdue_domain
|
||||||
return domain
|
return domain
|
||||||
|
|||||||
@@ -1127,6 +1127,7 @@ var bankStatementReconciliation = abstractReconciliation.extend({
|
|||||||
this.reconciliation_menu_id = false; // Used to update the needaction badge
|
this.reconciliation_menu_id = false; // Used to update the needaction badge
|
||||||
// The same move line cannot be selected for multiple reconciliations
|
// The same move line cannot be selected for multiple reconciliations
|
||||||
this.excluded_move_lines_ids = {};
|
this.excluded_move_lines_ids = {};
|
||||||
|
this.widget_childrens = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
serverPreprocessResultHandler: function(data) {
|
serverPreprocessResultHandler: function(data) {
|
||||||
@@ -1178,44 +1179,64 @@ var bankStatementReconciliation = abstractReconciliation.extend({
|
|||||||
single_statement: self.single_statement,
|
single_statement: self.single_statement,
|
||||||
total_lines: self.num_already_reconciled_lines+self.lines.length
|
total_lines: self.num_already_reconciled_lines+self.lines.length
|
||||||
}));
|
}));
|
||||||
self.updateProgressbar();
|
self.$el.find('.js_automatic_reconciliation').click(function() {
|
||||||
var reconciliations_to_show = self.lines.slice(0, self.num_reconciliations_fetched_in_batch);
|
// Let odoo try to reconcile entries for the user
|
||||||
self.last_displayed_reconciliation_index = reconciliations_to_show.length;
|
self.model_bank_statement_line
|
||||||
self.$(".reconciliation_lines_container").css("opacity", 0);
|
.call("reconciliation_widget_auto_reconcile", [self.lines || undefined, self.num_already_reconciled_lines])
|
||||||
|
.then(function(data){ self.serverPreprocessResultHandler(data); })
|
||||||
// If everything is reconciled, show end message
|
.then(function(){ self.$('.js_automatic_reconciliation').hide();
|
||||||
if (self.lines.length === 0) {
|
return self.display_reconciliation_propositions();
|
||||||
if (self.notifications)
|
|
||||||
self.displayNotifications(self.notifications, 0);
|
|
||||||
self.displayDoneMessage(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display the reconciliations
|
|
||||||
return self.model_bank_statement_line
|
|
||||||
.call("get_data_for_reconciliation_widget", [reconciliations_to_show])
|
|
||||||
.then(function (data) {
|
|
||||||
var child_promises = [];
|
|
||||||
var datum = data.shift();
|
|
||||||
if (datum !== undefined)
|
|
||||||
child_promises.push(self.displayReconciliation(datum.st_line.id, 'match', false, true, datum.st_line, datum.reconciliation_proposition));
|
|
||||||
while ((datum = data.shift()) !== undefined)
|
|
||||||
child_promises.push(self.displayReconciliation(datum.st_line.id, 'inactive', false, true, datum.st_line, datum.reconciliation_proposition));
|
|
||||||
|
|
||||||
// When reconciliations are instanciated, make an entrance
|
|
||||||
$.when.apply($, child_promises).then(function(){
|
|
||||||
self.$(".reconciliation_lines_container").animate({opacity: 1}, self.aestetic_animation_speed, function() {
|
|
||||||
if (self.notifications) {
|
|
||||||
self.displayNotifications(self.notifications);
|
|
||||||
self.updateShowMoreButton();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
return self.display_reconciliation_propositions();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
display_reconciliation_propositions: function() {
|
||||||
|
var self = this;
|
||||||
|
self.updateProgressbar();
|
||||||
|
var reconciliations_to_show = self.lines.slice(0, self.num_reconciliations_fetched_in_batch);
|
||||||
|
self.last_displayed_reconciliation_index = reconciliations_to_show.length;
|
||||||
|
self.$(".reconciliation_lines_container").css("opacity", 0);
|
||||||
|
// Delete previous bankStatementReconciliationLine
|
||||||
|
$.each(self.widget_childrens, function(index, child) {
|
||||||
|
child.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// If everything is reconciled, show end message
|
||||||
|
if (self.lines.length === 0) {
|
||||||
|
self.$(".reconciliation_lines_container").hide();
|
||||||
|
if (self.notifications)
|
||||||
|
self.displayNotifications(self.notifications, 0);
|
||||||
|
self.displayDoneMessage(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the reconciliations
|
||||||
|
return self.model_bank_statement_line
|
||||||
|
.call("get_data_for_reconciliation_widget", [reconciliations_to_show])
|
||||||
|
.then(function (data) {
|
||||||
|
var child_promises = [];
|
||||||
|
var datum = data.shift();
|
||||||
|
if (datum !== undefined)
|
||||||
|
child_promises.push(self.displayReconciliation(datum.st_line.id, 'match', false, true, datum.st_line, datum.reconciliation_proposition));
|
||||||
|
while ((datum = data.shift()) !== undefined)
|
||||||
|
child_promises.push(self.displayReconciliation(datum.st_line.id, 'inactive', false, true, datum.st_line, datum.reconciliation_proposition));
|
||||||
|
|
||||||
|
// When reconciliations are instanciated, make an entrance
|
||||||
|
$.when.apply($, child_promises).then(function(){
|
||||||
|
self.$(".reconciliation_lines_container").animate({opacity: 1}, self.aestetic_animation_speed, function() {
|
||||||
|
if (self.notifications) {
|
||||||
|
self.displayNotifications(self.notifications);
|
||||||
|
self.updateShowMoreButton();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
statementNameClickHandler: function() {
|
statementNameClickHandler: function() {
|
||||||
if (! this.single_statement) return;
|
if (! this.single_statement) return;
|
||||||
this.$(".statement_name span").hide();
|
this.$(".statement_name span").hide();
|
||||||
@@ -1320,6 +1341,7 @@ var bankStatementReconciliation = abstractReconciliation.extend({
|
|||||||
reconciliation_proposition: initial_data_provided ? reconciliation_proposition : undefined,
|
reconciliation_proposition: initial_data_provided ? reconciliation_proposition : undefined,
|
||||||
};
|
};
|
||||||
var widget = new self.children_widget(self, context);
|
var widget = new self.children_widget(self, context);
|
||||||
|
this.widget_childrens.push(widget);
|
||||||
return widget.appendTo(self.$(".reconciliation_lines_container"));
|
return widget.appendTo(self.$(".reconciliation_lines_container"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" t-att-aria-valuemax="total_lines" style="width: 0%;">
|
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" t-att-aria-valuemax="total_lines" style="width: 0%;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="btn btn-default js_automatic_reconciliation pull-right" title="Let odoo try to reconcile entries for the user">Automatic reconciliation</button>
|
||||||
<div class="oe_clear o_clear"></div>
|
<div class="oe_clear o_clear"></div>
|
||||||
<div class="notification_area"></div>
|
<div class="notification_area"></div>
|
||||||
<div class="interface_options oe_form o_form"></div>
|
<div class="interface_options oe_form o_form"></div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta
|
|||||||
from openerp import api, fields, models, _
|
from openerp import api, fields, models, _
|
||||||
from openerp.exceptions import UserError, ValidationError
|
from openerp.exceptions import UserError, ValidationError
|
||||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||||
|
from openerp.tools import float_compare
|
||||||
|
|
||||||
|
|
||||||
class AccountAssetCategory(models.Model):
|
class AccountAssetCategory(models.Model):
|
||||||
@@ -417,11 +418,12 @@ class AccountAssetDepreciationLine(models.Model):
|
|||||||
categ_type = line.asset_id.category_id.type
|
categ_type = line.asset_id.category_id.type
|
||||||
debit_account = line.asset_id.category_id.account_asset_id.id
|
debit_account = line.asset_id.category_id.account_asset_id.id
|
||||||
credit_account = line.asset_id.category_id.account_depreciation_id.id
|
credit_account = line.asset_id.category_id.account_depreciation_id.id
|
||||||
|
prec = self.env['decimal.precision'].precision_get('Account')
|
||||||
move_line_1 = {
|
move_line_1 = {
|
||||||
'name': asset_name,
|
'name': asset_name,
|
||||||
'account_id': credit_account,
|
'account_id': credit_account,
|
||||||
'debit': 0.0,
|
'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||||
'credit': amount,
|
'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||||
'journal_id': journal_id,
|
'journal_id': journal_id,
|
||||||
'partner_id': partner_id,
|
'partner_id': partner_id,
|
||||||
'currency_id': company_currency != current_currency and current_currency.id or False,
|
'currency_id': company_currency != current_currency and current_currency.id or False,
|
||||||
@@ -432,8 +434,8 @@ class AccountAssetDepreciationLine(models.Model):
|
|||||||
move_line_2 = {
|
move_line_2 = {
|
||||||
'name': asset_name,
|
'name': asset_name,
|
||||||
'account_id': debit_account,
|
'account_id': debit_account,
|
||||||
'credit': 0.0,
|
'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||||
'debit': amount,
|
'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||||
'journal_id': journal_id,
|
'journal_id': journal_id,
|
||||||
'partner_id': partner_id,
|
'partner_id': partner_id,
|
||||||
'currency_id': company_currency != current_currency and current_currency.id or False,
|
'currency_id': company_currency != current_currency and current_currency.id or False,
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ class AssetModify(models.TransientModel):
|
|||||||
if 'method_end' in fields and asset.method_time == 'end':
|
if 'method_end' in fields and asset.method_time == 'end':
|
||||||
res.update({'method_end': asset.method_end})
|
res.update({'method_end': asset.method_end})
|
||||||
if self.env.context.get('active_id'):
|
if self.env.context.get('active_id'):
|
||||||
res['asset_method_time'] = self._get_asset_method_time()
|
asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
|
||||||
|
res['asset_method_time'] = asset.method_time
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ class AccountVoucher(models.Model):
|
|||||||
pay_now = fields.Selection([
|
pay_now = fields.Selection([
|
||||||
('pay_now', 'Pay Directly'),
|
('pay_now', 'Pay Directly'),
|
||||||
('pay_later', 'Pay Later'),
|
('pay_later', 'Pay Later'),
|
||||||
], 'Payment', select=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later')
|
], 'Payment', index=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later')
|
||||||
date_due = fields.Date('Due Date', readonly=True, select=True, states={'draft': [('readonly', False)]})
|
date_due = fields.Date('Due Date', readonly=True, index=True, states={'draft': [('readonly', False)]})
|
||||||
|
|
||||||
@api.onchange('partner_id', 'pay_now')
|
@api.onchange('partner_id', 'pay_now')
|
||||||
def onchange_partner_id(self):
|
def onchange_partner_id(self):
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ var DataImport = Widget.extend(ControlPanelMixin, {
|
|||||||
return $.when([{
|
return $.when([{
|
||||||
type: 'error',
|
type: 'error',
|
||||||
record: false,
|
record: false,
|
||||||
message: error.data.arguments[1],
|
message: error.data.arguments && error.data.arguments[1] || error.message,
|
||||||
}]);
|
}]);
|
||||||
}) ;
|
}) ;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ class event_registration(models.Model):
|
|||||||
string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
|
string='Status', default='draft', readonly=True, copy=False, track_visibility='onchange')
|
||||||
email = fields.Char(string='Email')
|
email = fields.Char(string='Email')
|
||||||
phone = fields.Char(string='Phone')
|
phone = fields.Char(string='Phone')
|
||||||
name = fields.Char(string='Attendee Name', select=True)
|
name = fields.Char(string='Attendee Name', index=True)
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
@api.constrains('event_id', 'state')
|
@api.constrains('event_id', 'state')
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class RegistrationEditorLine(models.TransientModel):
|
|||||||
event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket')
|
event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket')
|
||||||
email = fields.Char(string='Email')
|
email = fields.Char(string='Email')
|
||||||
phone = fields.Char(string='Phone')
|
phone = fields.Char(string='Phone')
|
||||||
name = fields.Char(string='Name', select=True)
|
name = fields.Char(string='Name', index=True)
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def get_registration_data(self):
|
def get_registration_data(self):
|
||||||
|
|||||||
+2
-2
@@ -261,7 +261,7 @@ class hr_employee(osv.osv):
|
|||||||
user_field_lst.append(name)
|
user_field_lst.append(name)
|
||||||
return user_field_lst
|
return user_field_lst
|
||||||
|
|
||||||
_constraints = [(osv.osv._check_recursion, _('Error! You cannot create recursive hierarchy of Employee(s).'), ['parent_id']),]
|
_constraints = [(osv.osv._check_recursion, 'Error! You cannot create recursive hierarchy of Employee(s).', ['parent_id']),]
|
||||||
|
|
||||||
|
|
||||||
class hr_department(osv.osv):
|
class hr_department(osv.osv):
|
||||||
@@ -291,7 +291,7 @@ class hr_department(osv.osv):
|
|||||||
}
|
}
|
||||||
|
|
||||||
_constraints = [
|
_constraints = [
|
||||||
(osv.osv._check_recursion, _('Error! You cannot create recursive departments.'), ['parent_id'])
|
(osv.osv._check_recursion, 'Error! You cannot create recursive departments.', ['parent_id'])
|
||||||
]
|
]
|
||||||
|
|
||||||
def name_get(self, cr, uid, ids, context=None):
|
def name_get(self, cr, uid, ids, context=None):
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ class HrEquipmentRequest(models.Model):
|
|||||||
employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee_get)
|
employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee_get)
|
||||||
department_id = fields.Many2one('hr.department', string='Department')
|
department_id = fields.Many2one('hr.department', string='Department')
|
||||||
category_id = fields.Many2one('hr.equipment.category', string='Category')
|
category_id = fields.Many2one('hr.equipment.category', string='Category')
|
||||||
equipment_id = fields.Many2one('hr.equipment', string='Asset', select=True)
|
equipment_id = fields.Many2one('hr.equipment', string='Asset', index=True)
|
||||||
user_id = fields.Many2one('res.users', string='Assigned to', track_visibility='onchange')
|
user_id = fields.Many2one('res.users', string='Assigned to', track_visibility='onchange')
|
||||||
stage_id = fields.Many2one('hr.equipment.stage', string='Stage', track_visibility='onchange', default=_default_stage)
|
stage_id = fields.Many2one('hr.equipment.stage', string='Stage', track_visibility='onchange', default=_default_stage)
|
||||||
priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority')
|
priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority')
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#-*- coding:utf-8 -*-
|
#-*- coding:utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
import time
|
|
||||||
from datetime import date, datetime, timedelta
|
|
||||||
|
|
||||||
from openerp import api
|
from openerp import api
|
||||||
from openerp.osv import fields, osv
|
from openerp.osv import fields, osv
|
||||||
@@ -94,13 +92,12 @@ class hr_payslip(osv.osv):
|
|||||||
move_pool = self.pool.get('account.move')
|
move_pool = self.pool.get('account.move')
|
||||||
hr_payslip_line_pool = self.pool['hr.payslip.line']
|
hr_payslip_line_pool = self.pool['hr.payslip.line']
|
||||||
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Payroll')
|
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Payroll')
|
||||||
timenow = time.strftime('%Y-%m-%d')
|
|
||||||
|
|
||||||
for slip in self.browse(cr, uid, ids, context=context):
|
for slip in self.browse(cr, uid, ids, context=context):
|
||||||
line_ids = []
|
line_ids = []
|
||||||
debit_sum = 0.0
|
debit_sum = 0.0
|
||||||
credit_sum = 0.0
|
credit_sum = 0.0
|
||||||
date = timenow
|
date = slip.date or slip.date_to
|
||||||
|
|
||||||
name = _('Payslip of %s') % (slip.employee_id.name)
|
name = _('Payslip of %s') % (slip.employee_id.name)
|
||||||
move = {
|
move = {
|
||||||
@@ -152,7 +149,6 @@ class hr_payslip(osv.osv):
|
|||||||
raise UserError(_('The Expense Journal "%s" has not properly configured the Credit Account!') % (slip.journal_id.name))
|
raise UserError(_('The Expense Journal "%s" has not properly configured the Credit Account!') % (slip.journal_id.name))
|
||||||
adjust_credit = (0, 0, {
|
adjust_credit = (0, 0, {
|
||||||
'name': _('Adjustment Entry'),
|
'name': _('Adjustment Entry'),
|
||||||
'date': timenow,
|
|
||||||
'partner_id': False,
|
'partner_id': False,
|
||||||
'account_id': acc_id,
|
'account_id': acc_id,
|
||||||
'journal_id': slip.journal_id.id,
|
'journal_id': slip.journal_id.id,
|
||||||
|
|||||||
@@ -120,20 +120,20 @@ class Applicant(models.Model):
|
|||||||
help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma")
|
help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma")
|
||||||
probability = fields.Float("Probability")
|
probability = fields.Float("Probability")
|
||||||
partner_id = fields.Many2one('res.partner', "Contact")
|
partner_id = fields.Many2one('res.partner', "Contact")
|
||||||
create_date = fields.Datetime("Creation Date", readonly=True, select=True)
|
create_date = fields.Datetime("Creation Date", readonly=True, index=True)
|
||||||
write_date = fields.Datetime("Update Date", readonly=True)
|
write_date = fields.Datetime("Update Date", readonly=True)
|
||||||
stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', track_visibility='onchange',
|
stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', track_visibility='onchange',
|
||||||
domain="['|', ('job_id', '=', False), ('job_id', '=', job_id)]",
|
domain="['|', ('job_id', '=', False), ('job_id', '=', job_id)]",
|
||||||
copy=False, select=1,
|
copy=False, index=True,
|
||||||
default=_default_stage_id)
|
default=_default_stage_id)
|
||||||
last_stage_id = fields.Many2one('hr.recruitment.stage', "Last Stage",
|
last_stage_id = fields.Many2one('hr.recruitment.stage', "Last Stage",
|
||||||
help="Stage of the applicant before being in the current stage. Used for lost cases analysis.")
|
help="Stage of the applicant before being in the current stage. Used for lost cases analysis.")
|
||||||
categ_ids = fields.Many2many('hr.applicant.category', string="Tags")
|
categ_ids = fields.Many2many('hr.applicant.category', string="Tags")
|
||||||
company_id = fields.Many2one('res.company', "Company", default=_default_company_id)
|
company_id = fields.Many2one('res.company', "Company", default=_default_company_id)
|
||||||
user_id = fields.Many2one('res.users', "Responsible", track_visibility="onchange", default=lambda self: self.env.uid)
|
user_id = fields.Many2one('res.users', "Responsible", track_visibility="onchange", default=lambda self: self.env.uid)
|
||||||
date_closed = fields.Datetime("Closed", readonly=True, select=True)
|
date_closed = fields.Datetime("Closed", readonly=True, index=True)
|
||||||
date_open = fields.Datetime("Assigned", readonly=True, select=True)
|
date_open = fields.Datetime("Assigned", readonly=True, index=True)
|
||||||
date_last_stage_update = fields.Datetime("Last Stage Update", select=True, default=fields.Datetime.now)
|
date_last_stage_update = fields.Datetime("Last Stage Update", index=True, default=fields.Datetime.now)
|
||||||
date_action = fields.Date("Next Action Date")
|
date_action = fields.Date("Next Action Date")
|
||||||
title_action = fields.Char("Next Action", size=64)
|
title_action = fields.Char("Next Action", size=64)
|
||||||
priority = fields.Selection(AVAILABLE_PRIORITIES, "Appreciation", default='0')
|
priority = fields.Selection(AVAILABLE_PRIORITIES, "Appreciation", default='0')
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class LunchOrderLine(models.Model):
|
|||||||
('confirmed', 'Received'),
|
('confirmed', 'Received'),
|
||||||
('ordered', 'Ordered'),
|
('ordered', 'Ordered'),
|
||||||
('cancelled', 'Cancelled')],
|
('cancelled', 'Cancelled')],
|
||||||
'Status', readonly=True, select=True, default='new')
|
'Status', readonly=True, index=True, default='new')
|
||||||
cashmove = fields.One2many('lunch.cashmove', 'order_id', 'Cash Move')
|
cashmove = fields.One2many('lunch.cashmove', 'order_id', 'Cash Move')
|
||||||
currency_id = fields.Many2one('res.currency', related='order_id.currency_id')
|
currency_id = fields.Many2one('res.currency', related='order_id.currency_id')
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ class LunchAlert(models.Model):
|
|||||||
alert_type = fields.Selection([('specific', 'Specific Day'),
|
alert_type = fields.Selection([('specific', 'Specific Day'),
|
||||||
('week', 'Every Week'),
|
('week', 'Every Week'),
|
||||||
('days', 'Every Day')],
|
('days', 'Every Day')],
|
||||||
string='Recurrency', required=True, select=True, default='specific')
|
string='Recurrency', required=True, index=True, default='specific')
|
||||||
specific_day = fields.Date('Day', default=fields.Date.context_today)
|
specific_day = fields.Date('Day', default=fields.Date.context_today)
|
||||||
monday = fields.Boolean('Monday')
|
monday = fields.Boolean('Monday')
|
||||||
tuesday = fields.Boolean('Tuesday')
|
tuesday = fields.Boolean('Tuesday')
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class MailChatController(openerp.addons.bus.controllers.main.BusController):
|
|||||||
partner_id = request.env.user.partner_id.id
|
partner_id = request.env.user.partner_id.id
|
||||||
|
|
||||||
if partner_id:
|
if partner_id:
|
||||||
|
channels = list(channels) # do not alter original list
|
||||||
for mail_channel in request.env['mail.channel'].search([('channel_partner_ids', 'in', [partner_id])]):
|
for mail_channel in request.env['mail.channel'].search([('channel_partner_ids', 'in', [partner_id])]):
|
||||||
channels.append((request.db, 'mail.channel', mail_channel.id))
|
channels.append((request.db, 'mail.channel', mail_channel.id))
|
||||||
# personal and needaction channel
|
# personal and needaction channel
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class Channel(models.Model):
|
|||||||
('channel', 'Channel')],
|
('channel', 'Channel')],
|
||||||
'Channel Type', default='channel')
|
'Channel Type', default='channel')
|
||||||
description = fields.Text('Description')
|
description = fields.Text('Description')
|
||||||
uuid = fields.Char('UUID', size=50, select=True, default=lambda self: '%s' % uuid.uuid4())
|
uuid = fields.Char('UUID', size=50, index=True, default=lambda self: '%s' % uuid.uuid4())
|
||||||
email_send = fields.Boolean('Send messages by email', default=False)
|
email_send = fields.Boolean('Send messages by email', default=False)
|
||||||
# multi users channel
|
# multi users channel
|
||||||
channel_last_seen_partner_ids = fields.One2many('mail.channel.partner', 'channel_id', string='Last Seen')
|
channel_last_seen_partner_ids = fields.One2many('mail.channel.partner', 'channel_id', string='Last Seen')
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ class Followers(models.Model):
|
|||||||
_description = 'Document Followers'
|
_description = 'Document Followers'
|
||||||
|
|
||||||
res_model = fields.Char(
|
res_model = fields.Char(
|
||||||
'Related Document Model', required=True, select=1, help='Model of the followed resource')
|
'Related Document Model', required=True, index=True, help='Model of the followed resource')
|
||||||
res_id = fields.Integer(
|
res_id = fields.Integer(
|
||||||
'Related Document ID', select=1, help='Id of the followed resource')
|
'Related Document ID', index=True, help='Id of the followed resource')
|
||||||
partner_id = fields.Many2one(
|
partner_id = fields.Many2one(
|
||||||
'res.partner', string='Related Partner', ondelete='cascade', select=1)
|
'res.partner', string='Related Partner', ondelete='cascade', index=True)
|
||||||
channel_id = fields.Many2one(
|
channel_id = fields.Many2one(
|
||||||
'mail.channel', string='Listener', ondelete='cascade', select=1)
|
'mail.channel', string='Listener', ondelete='cascade', index=True)
|
||||||
subtype_ids = fields.Many2many(
|
subtype_ids = fields.Many2many(
|
||||||
'mail.message.subtype', string='Subtype',
|
'mail.message.subtype', string='Subtype',
|
||||||
help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall.")
|
help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall.")
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ class Message(models.Model):
|
|||||||
help='Attachments are linked to a document through model / res_id and to the message '
|
help='Attachments are linked to a document through model / res_id and to the message '
|
||||||
'through this field.')
|
'through this field.')
|
||||||
parent_id = fields.Many2one(
|
parent_id = fields.Many2one(
|
||||||
'mail.message', 'Parent Message', select=True, ondelete='set null',
|
'mail.message', 'Parent Message', index=True, ondelete='set null',
|
||||||
help="Initial thread message.")
|
help="Initial thread message.")
|
||||||
child_ids = fields.One2many('mail.message', 'parent_id', 'Child Messages')
|
child_ids = fields.One2many('mail.message', 'parent_id', 'Child Messages')
|
||||||
# related document
|
# related document
|
||||||
model = fields.Char('Related Document Model', select=1)
|
model = fields.Char('Related Document Model', index=True)
|
||||||
res_id = fields.Integer('Related Document ID', select=1)
|
res_id = fields.Integer('Related Document ID', index=True)
|
||||||
record_name = fields.Char('Message Record Name', help="Name get of the related document.")
|
record_name = fields.Char('Message Record Name', help="Name get of the related document.")
|
||||||
# characteristics
|
# characteristics
|
||||||
message_type = fields.Selection([
|
message_type = fields.Selection([
|
||||||
@@ -73,13 +73,13 @@ class Message(models.Model):
|
|||||||
help="Message type: email for email message, notification for system "
|
help="Message type: email for email message, notification for system "
|
||||||
"message, comment for other messages such as user replies",
|
"message, comment for other messages such as user replies",
|
||||||
oldname='type')
|
oldname='type')
|
||||||
subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', ondelete='set null', select=1)
|
subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', ondelete='set null', index=True)
|
||||||
# origin
|
# origin
|
||||||
email_from = fields.Char(
|
email_from = fields.Char(
|
||||||
'From', default=_get_default_from,
|
'From', default=_get_default_from,
|
||||||
help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.")
|
help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.")
|
||||||
author_id = fields.Many2one(
|
author_id = fields.Many2one(
|
||||||
'res.partner', 'Author', select=1,
|
'res.partner', 'Author', index=True,
|
||||||
ondelete='set null', default=_get_default_author,
|
ondelete='set null', default=_get_default_author,
|
||||||
help="Author of the message. If not set, email_from may hold an email address that did not match any partner.")
|
help="Author of the message. If not set, email_from may hold an email address that did not match any partner.")
|
||||||
author_avatar = fields.Binary("Author's avatar", related='author_id.image_small')
|
author_avatar = fields.Binary("Author's avatar", related='author_id.image_small')
|
||||||
@@ -108,7 +108,7 @@ class Message(models.Model):
|
|||||||
no_auto_thread = fields.Boolean(
|
no_auto_thread = fields.Boolean(
|
||||||
'No threading for answers',
|
'No threading for answers',
|
||||||
help='Answers do not go in the original document discussion thread. This has an impact on the generated message-id.')
|
help='Answers do not go in the original document discussion thread. This has an impact on the generated message-id.')
|
||||||
message_id = fields.Char('Message-Id', help='Message unique identifier', select=1, readonly=1, copy=False)
|
message_id = fields.Char('Message-Id', help='Message unique identifier', index=True, readonly=1, copy=False)
|
||||||
reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.')
|
reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.')
|
||||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
|
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class MailTemplate(models.Model):
|
|||||||
|
|
||||||
name = fields.Char('Name')
|
name = fields.Char('Name')
|
||||||
model_id = fields.Many2one('ir.model', 'Applies to', help="The type of document this template can be used with")
|
model_id = fields.Many2one('ir.model', 'Applies to', help="The type of document this template can be used with")
|
||||||
model = fields.Char('Related Document Model', related='model_id.model', select=True, store=True, readonly=True)
|
model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True, readonly=True)
|
||||||
lang = fields.Char('Language',
|
lang = fields.Char('Language',
|
||||||
help="Optional translation language (ISO code) to select when sending out an email. "
|
help="Optional translation language (ISO code) to select when sending out an email. "
|
||||||
"If not set, the english version will be used. "
|
"If not set, the english version will be used. "
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class MailTracking(models.Model):
|
|||||||
new_value_text = fields.Text('New Value Text', readonly=1)
|
new_value_text = fields.Text('New Value Text', readonly=1)
|
||||||
new_value_datetime = fields.Datetime('New Value Datetime', readonly=1)
|
new_value_datetime = fields.Datetime('New Value Datetime', readonly=1)
|
||||||
|
|
||||||
mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, select=True, ondelete='cascade')
|
mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, index=True, ondelete='cascade')
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def create_tracking_values(self, initial_value, new_value, col_name, col_info):
|
def create_tracking_values(self, initial_value, new_value, col_name, col_info):
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class Invite(models.TransientModel):
|
|||||||
result['message'] = _('<div><p>Hello,</p><p>%s invited you to follow a new document.</p></div>') % user_name
|
result['message'] = _('<div><p>Hello,</p><p>%s invited you to follow a new document.</p></div>') % user_name
|
||||||
return result
|
return result
|
||||||
|
|
||||||
res_model = fields.Char('Related Document Model', required=True, select=1, help='Model of the followed resource')
|
res_model = fields.Char('Related Document Model', required=True, index=True, help='Model of the followed resource')
|
||||||
res_id = fields.Integer('Related Document ID', select=1, help='Id of the followed resource')
|
res_id = fields.Integer('Related Document ID', index=True, help='Id of the followed resource')
|
||||||
partner_ids = fields.Many2many('res.partner', string='Recipients', help="List of partners that will be added as follower of the current document.")
|
partner_ids = fields.Many2many('res.partner', string='Recipients', help="List of partners that will be added as follower of the current document.")
|
||||||
channel_ids = fields.Many2many('mail.channel', string='Channels', help='List of channels that will be added as listeners of the current document.',
|
channel_ids = fields.Many2many('mail.channel', string='Channels', help='List of channels that will be added as listeners of the current document.',
|
||||||
domain=[('channel_type', '=', 'channel')])
|
domain=[('channel_type', '=', 'channel')])
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class MailComposer(models.TransientModel):
|
|||||||
auto_delete = fields.Boolean('Delete Emails', help='Delete sent emails (mass mailing only)')
|
auto_delete = fields.Boolean('Delete Emails', help='Delete sent emails (mass mailing only)')
|
||||||
auto_delete_message = fields.Boolean('Delete Message Copy', help='Do not keep a copy of the email in the document communication history (mass mailing only)')
|
auto_delete_message = fields.Boolean('Delete Message Copy', help='Do not keep a copy of the email in the document communication history (mass mailing only)')
|
||||||
template_id = fields.Many2one(
|
template_id = fields.Many2one(
|
||||||
'mail.template', 'Use template', select=True,
|
'mail.template', 'Use template', index=True,
|
||||||
domain="[('model', '=', model)]")
|
domain="[('model', '=', model)]")
|
||||||
# mail_message updated fields
|
# mail_message updated fields
|
||||||
message_type = fields.Selection(default="comment")
|
message_type = fields.Selection(default="comment")
|
||||||
|
|||||||
@@ -31,8 +31,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr t-foreach="get_children(o.bom_line_ids)" t-as="l">
|
<tr t-foreach="get_children(o.bom_line_ids)" t-as="l">
|
||||||
<td style="padding-left: 20px;">
|
<td style="padding-left: 20px;">
|
||||||
<span style="color: white;" t-esc="'... '*(l['level'])"/>[
|
<span style="color: white;" t-esc="'... '*(l['level'])"/>
|
||||||
<span t-esc="l['pcode']"/>]
|
|
||||||
<span t-esc="l['pname']"/>
|
<span t-esc="l['pname']"/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ class SaleOrderLine(models.Model):
|
|||||||
res = super(SaleOrderLine, self)._action_procurement_create()
|
res = super(SaleOrderLine, self)._action_procurement_create()
|
||||||
orders = list(set(x.order_id for x in self))
|
orders = list(set(x.order_id for x in self))
|
||||||
for order in orders:
|
for order in orders:
|
||||||
reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or ((x.state=='partially_available') and not x.printed))
|
reassign = order.picking_ids.filtered(lambda x: x.state=='confirmed' or ((x.state in ['partially_available', 'waiting']) and not x.printed))
|
||||||
if reassign:
|
if reassign:
|
||||||
reassign.do_unreserve()
|
reassign.do_unreserve()
|
||||||
reassign.action_assign()
|
reassign.action_assign()
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -87,6 +87,22 @@ class AccountInvoice(models.Model):
|
|||||||
if purchase_ids:
|
if purchase_ids:
|
||||||
self.origin = ', '.join(purchase_ids.mapped('name'))
|
self.origin = ', '.join(purchase_ids.mapped('name'))
|
||||||
|
|
||||||
|
@api.onchange('partner_id', 'company_id')
|
||||||
|
def _onchange_partner_id(self):
|
||||||
|
res = super(AccountInvoice, self)._onchange_partner_id()
|
||||||
|
if not self.env.context.get('default_journal_id') and self.partner_id and self.currency_id and\
|
||||||
|
self.type in ['in_invoice', 'in_refund'] and\
|
||||||
|
self.currency_id != self.partner_id.property_purchase_currency_id:
|
||||||
|
journal_domain = [
|
||||||
|
('type', '=', 'purchase'),
|
||||||
|
('company_id', '=', self.company_id.id),
|
||||||
|
('currency_id', '=', self.partner_id.property_purchase_currency_id.id),
|
||||||
|
]
|
||||||
|
default_journal_id = self.env['account.journal'].search(journal_domain, limit=1)
|
||||||
|
if default_journal_id:
|
||||||
|
self.journal_id = default_journal_id
|
||||||
|
return res
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def invoice_line_move_line_get(self):
|
def invoice_line_move_line_get(self):
|
||||||
res = super(AccountInvoice, self).invoice_line_move_line_get()
|
res = super(AccountInvoice, self).invoice_line_move_line_get()
|
||||||
@@ -163,6 +179,6 @@ class AccountInvoiceLine(models.Model):
|
|||||||
""" Override AccountInvoice_line to add the link to the purchase order line it is related to"""
|
""" Override AccountInvoice_line to add the link to the purchase order line it is related to"""
|
||||||
_inherit = 'account.invoice.line'
|
_inherit = 'account.invoice.line'
|
||||||
|
|
||||||
purchase_line_id = fields.Many2one('purchase.order.line', 'Purchase Order Line', ondelete='set null', select=True, readonly=True)
|
purchase_line_id = fields.Many2one('purchase.order.line', 'Purchase Order Line', ondelete='set null', index=True, readonly=True)
|
||||||
purchase_id = fields.Many2one('purchase.order', related='purchase_line_id.order_id', string='Purchase Order', store=False, readonly=True,
|
purchase_id = fields.Many2one('purchase.order', related='purchase_line_id.order_id', string='Purchase Order', store=False, readonly=True,
|
||||||
help='Associated Purchase Order. Filled in automatically when a PO is chosen on the vendor bill.')
|
help='Associated Purchase Order. Filled in automatically when a PO is chosen on the vendor bill.')
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class PurchaseOrder(models.Model):
|
|||||||
'cancel': [('readonly', True)],
|
'cancel': [('readonly', True)],
|
||||||
}
|
}
|
||||||
|
|
||||||
name = fields.Char('Order Reference', required=True, select=True, copy=False, default='New')
|
name = fields.Char('Order Reference', required=True, index=True, copy=False, default='New')
|
||||||
origin = fields.Char('Source Document', copy=False,\
|
origin = fields.Char('Source Document', copy=False,\
|
||||||
help="Reference of the document that generated this purchase order "
|
help="Reference of the document that generated this purchase order "
|
||||||
"request (e.g. a sale order or an internal procurement request)")
|
"request (e.g. a sale order or an internal procurement request)")
|
||||||
@@ -102,9 +102,9 @@ class PurchaseOrder(models.Model):
|
|||||||
"It's used to do the matching when you receive the "
|
"It's used to do the matching when you receive the "
|
||||||
"products as this reference is usually written on the "
|
"products as this reference is usually written on the "
|
||||||
"delivery order sent by your vendor.")
|
"delivery order sent by your vendor.")
|
||||||
date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, select=True, copy=False, default=fields.Datetime.now,\
|
date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, index=True, copy=False, default=fields.Datetime.now,\
|
||||||
help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
|
help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
|
||||||
date_approve = fields.Date('Approval Date', readonly=1, select=True, copy=False)
|
date_approve = fields.Date('Approval Date', readonly=1, index=True, copy=False)
|
||||||
partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
|
partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
|
||||||
dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,\
|
dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,\
|
||||||
help="Put an address if you want to deliver directly from the vendor to the customer. "\
|
help="Put an address if you want to deliver directly from the vendor to the customer. "\
|
||||||
@@ -118,7 +118,7 @@ class PurchaseOrder(models.Model):
|
|||||||
('purchase', 'Purchase Order'),
|
('purchase', 'Purchase Order'),
|
||||||
('done', 'Locked'),
|
('done', 'Locked'),
|
||||||
('cancel', 'Cancelled')
|
('cancel', 'Cancelled')
|
||||||
], string='Status', readonly=True, select=True, copy=False, default='draft', track_visibility='onchange')
|
], string='Status', readonly=True, index=True, copy=False, default='draft', track_visibility='onchange')
|
||||||
order_line = fields.One2many('purchase.order.line', 'order_id', string='Order Lines', states=READONLY_STATES, copy=True)
|
order_line = fields.One2many('purchase.order.line', 'order_id', string='Order Lines', states=READONLY_STATES, copy=True)
|
||||||
notes = fields.Text('Terms and Conditions')
|
notes = fields.Text('Terms and Conditions')
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ class PurchaseOrder(models.Model):
|
|||||||
picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0)
|
picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0)
|
||||||
picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False)
|
picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False)
|
||||||
|
|
||||||
date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', inverse='_inverse_date_planned', required=True, select=True, oldname='minimum_planned_date')
|
date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', inverse='_inverse_date_planned', required=True, index=True, oldname='minimum_planned_date')
|
||||||
|
|
||||||
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
|
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
|
||||||
amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all')
|
amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all')
|
||||||
@@ -145,13 +145,13 @@ class PurchaseOrder(models.Model):
|
|||||||
|
|
||||||
product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
|
product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
|
||||||
create_uid = fields.Many2one('res.users', 'Responsible')
|
create_uid = fields.Many2one('res.users', 'Responsible')
|
||||||
company_id = fields.Many2one('res.company', 'Company', required=True, select=1, states=READONLY_STATES, default=lambda self: self.env.user.company_id.id)
|
company_id = fields.Many2one('res.company', 'Company', required=True, index=True, states=READONLY_STATES, default=lambda self: self.env.user.company_id.id)
|
||||||
|
|
||||||
picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
|
picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
|
||||||
help="This will determine picking type of incoming shipment")
|
help="This will determine picking type of incoming shipment")
|
||||||
default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
|
default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
|
||||||
help="Technical field used to display the Drop Ship Address", readonly=True)
|
help="Technical field used to display the Drop Ship Address", readonly=True)
|
||||||
group_id = fields.Many2one('procurement.group', string="Procurement Group")
|
group_id = fields.Many2one('procurement.group', string="Procurement Group", copy=False)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def name_search(self, name, args=None, operator='ilike', limit=100):
|
def name_search(self, name, args=None, operator='ilike', limit=100):
|
||||||
@@ -281,6 +281,8 @@ class PurchaseOrder(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def button_confirm(self):
|
def button_confirm(self):
|
||||||
for order in self:
|
for order in self:
|
||||||
|
if order.state not in ['draft', 'sent']:
|
||||||
|
continue
|
||||||
order._add_supplier_to_product()
|
order._add_supplier_to_product()
|
||||||
# Deal with double validation process
|
# Deal with double validation process
|
||||||
if order.company_id.po_double_validation == 'one_step'\
|
if order.company_id.po_double_validation == 'one_step'\
|
||||||
@@ -516,7 +518,7 @@ class PurchaseOrderLine(models.Model):
|
|||||||
|
|
||||||
name = fields.Text(string='Description', required=True)
|
name = fields.Text(string='Description', required=True)
|
||||||
product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True)
|
product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True)
|
||||||
date_planned = fields.Datetime(string='Scheduled Date', required=True, select=True)
|
date_planned = fields.Datetime(string='Scheduled Date', required=True, index=True)
|
||||||
taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
|
taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
|
||||||
product_uom = fields.Many2one('product.uom', string='Product Unit of Measure', required=True)
|
product_uom = fields.Many2one('product.uom', string='Product Unit of Measure', required=True)
|
||||||
product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True)
|
product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True)
|
||||||
@@ -527,7 +529,7 @@ class PurchaseOrderLine(models.Model):
|
|||||||
price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
|
price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
|
||||||
price_tax = fields.Monetary(compute='_compute_amount', string='Tax', store=True)
|
price_tax = fields.Monetary(compute='_compute_amount', string='Tax', store=True)
|
||||||
|
|
||||||
order_id = fields.Many2one('purchase.order', string='Order Reference', select=True, required=True, ondelete='cascade')
|
order_id = fields.Many2one('purchase.order', string='Order Reference', index=True, required=True, ondelete='cascade')
|
||||||
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account', domain=[('account_type', '=', 'normal')])
|
account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account', domain=[('account_type', '=', 'normal')])
|
||||||
company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True)
|
company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True)
|
||||||
state = fields.Selection(related='order_id.state', stored=True)
|
state = fields.Selection(related='order_id.state', stored=True)
|
||||||
|
|||||||
@@ -250,7 +250,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="date_planned"/>
|
<field name="date_planned"/>
|
||||||
<field name="picking_type_id" domain="[('code','=','incoming')]" options="{'no_create': True}" context="{'special_shortened_wh_name': True}" groups="stock.group_stock_multi_locations"/>
|
<field name="picking_type_id" domain="[('code','=','incoming')]" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
|
||||||
<field name="dest_address_id" groups="stock.group_stock_multi_locations" attrs="{'invisible': [('default_location_dest_id_usage', '!=', 'customer')], 'required': [('default_location_dest_id_usage', '=', 'customer')]}"/>
|
<field name="dest_address_id" groups="stock.group_stock_multi_locations" attrs="{'invisible': [('default_location_dest_id_usage', '!=', 'customer')], 'required': [('default_location_dest_id_usage', '=', 'customer')]}"/>
|
||||||
<field name="default_location_dest_id_usage" invisible="1"/>
|
<field name="default_location_dest_id_usage" invisible="1"/>
|
||||||
<field name="incoterm_id"/>
|
<field name="incoterm_id"/>
|
||||||
|
|||||||
@@ -257,11 +257,6 @@ class purchase_requisition(osv.osv):
|
|||||||
if not confirm:
|
if not confirm:
|
||||||
raise UserError(_('You have no line selected for buying.'))
|
raise UserError(_('You have no line selected for buying.'))
|
||||||
|
|
||||||
#check for complete RFQ
|
|
||||||
for quotation in tender.purchase_ids:
|
|
||||||
if (self.check_valid_quotation(cr, uid, quotation, context=context)):
|
|
||||||
#Set PO state to confirm
|
|
||||||
po.button_confirm(cr, uid, [quotation.id], context=context)
|
|
||||||
|
|
||||||
#get other confirmed lines per supplier
|
#get other confirmed lines per supplier
|
||||||
for po_line in tender.po_line_ids:
|
for po_line in tender.po_line_ids:
|
||||||
|
|||||||
+15
-10
@@ -1555,12 +1555,18 @@ class stock_picking(models.Model):
|
|||||||
|
|
||||||
def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
|
def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
|
||||||
""" Unreserve quants then try to reassign quants."""
|
""" Unreserve quants then try to reassign quants."""
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
stock_move_obj = self.pool.get('stock.move')
|
stock_move_obj = self.pool.get('stock.move')
|
||||||
if not move_ids:
|
if not move_ids:
|
||||||
self.do_unreserve(cr, uid, [picking.id], context=context)
|
self.do_unreserve(cr, uid, [picking.id], context=context)
|
||||||
self.action_assign(cr, uid, [picking.id], context=context)
|
self.action_assign(cr, uid, [picking.id], context=context)
|
||||||
else:
|
else:
|
||||||
stock_move_obj.do_unreserve(cr, uid, move_ids, context=context)
|
if 'no_state_change' in context:
|
||||||
|
move = stock_move_obj.browse(cr, uid, move_ids, context=context)
|
||||||
|
stock_move_obj.do_unreserve(cr, uid, [m.id for m in move if m.reserved_quant_ids], context=context)
|
||||||
|
else:
|
||||||
|
stock_move_obj.do_unreserve(cr, uid, move_ids, context=context)
|
||||||
stock_move_obj.action_assign(cr, uid, move_ids, no_prepare=True, context=context)
|
stock_move_obj.action_assign(cr, uid, move_ids, no_prepare=True, context=context)
|
||||||
|
|
||||||
def do_new_transfer(self, cr, uid, ids, context=None):
|
def do_new_transfer(self, cr, uid, ids, context=None):
|
||||||
@@ -1667,6 +1673,7 @@ class stock_picking(models.Model):
|
|||||||
if moves_reassign and (picking.location_id.usage not in ("supplier", "production", "inventory")):
|
if moves_reassign and (picking.location_id.usage not in ("supplier", "production", "inventory")):
|
||||||
ctx = dict(context)
|
ctx = dict(context)
|
||||||
ctx['reserve_only_ops'] = True #unnecessary to assign other quants than those involved with pack operations as they will be unreserved anyways.
|
ctx['reserve_only_ops'] = True #unnecessary to assign other quants than those involved with pack operations as they will be unreserved anyways.
|
||||||
|
ctx['no_state_change'] = True
|
||||||
self.rereserve_quants(cr, uid, picking, move_ids=picking.move_lines.ids, context=ctx)
|
self.rereserve_quants(cr, uid, picking, move_ids=picking.move_lines.ids, context=ctx)
|
||||||
self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context)
|
self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context)
|
||||||
|
|
||||||
@@ -2047,10 +2054,11 @@ class stock_move(osv.osv):
|
|||||||
if move.state in ('done', 'cancel'):
|
if move.state in ('done', 'cancel'):
|
||||||
raise UserError(_('Cannot unreserve a done move'))
|
raise UserError(_('Cannot unreserve a done move'))
|
||||||
quant_obj.quants_unreserve(cr, uid, move, context=context)
|
quant_obj.quants_unreserve(cr, uid, move, context=context)
|
||||||
if self.find_move_ancestors(cr, uid, move, context=context):
|
if not context.get('no_state_change'):
|
||||||
self.write(cr, uid, [move.id], {'state': 'waiting'}, context=context)
|
if self.find_move_ancestors(cr, uid, move, context=context):
|
||||||
else:
|
self.write(cr, uid, [move.id], {'state': 'waiting'}, context=context)
|
||||||
self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context)
|
else:
|
||||||
|
self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context)
|
||||||
|
|
||||||
def _prepare_procurement_from_move(self, cr, uid, move, context=None):
|
def _prepare_procurement_from_move(self, cr, uid, move, context=None):
|
||||||
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or move.origin or move.picking_id.name or "/")
|
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or move.origin or move.picking_id.name or "/")
|
||||||
@@ -2625,6 +2633,8 @@ class stock_move(osv.osv):
|
|||||||
operations = set()
|
operations = set()
|
||||||
move_qty = {}
|
move_qty = {}
|
||||||
for move in self.browse(cr, uid, ids, context=context):
|
for move in self.browse(cr, uid, ids, context=context):
|
||||||
|
if move.picking_id:
|
||||||
|
pickings.add(move.picking_id.id)
|
||||||
move_qty[move.id] = move.product_qty
|
move_qty[move.id] = move.product_qty
|
||||||
for link in move.linked_move_operation_ids:
|
for link in move.linked_move_operation_ids:
|
||||||
operations.add(link.operation_id)
|
operations.add(link.operation_id)
|
||||||
@@ -4890,11 +4900,6 @@ class stock_picking_type(osv.osv):
|
|||||||
name = record.name
|
name = record.name
|
||||||
if record.warehouse_id:
|
if record.warehouse_id:
|
||||||
name = record.warehouse_id.name + ': ' +name
|
name = record.warehouse_id.name + ': ' +name
|
||||||
if context.get('special_shortened_wh_name'):
|
|
||||||
if record.warehouse_id:
|
|
||||||
name = record.warehouse_id.name
|
|
||||||
else:
|
|
||||||
name = _('Customer') + ' (' + record.name + ')'
|
|
||||||
res.append((record.id, name))
|
res.append((record.id, name))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,16 @@ class sale_order_line(osv.osv):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@api.onchange('product_id')
|
@api.onchange('product_id')
|
||||||
|
def product_id_change(self):
|
||||||
|
warning = self.onchange_product_id_warning()
|
||||||
|
product_info = self.product_id
|
||||||
|
if product_info.sale_line_warn != 'no-message':
|
||||||
|
if product_info.sale_line_warn == 'block':
|
||||||
|
return warning
|
||||||
|
result = super(sale_order_line, self).product_id_change()
|
||||||
|
result['warning'] = warning and warning.get('warning')
|
||||||
|
return result
|
||||||
|
|
||||||
def onchange_product_id_warning(self):
|
def onchange_product_id_warning(self):
|
||||||
if not self.product_id:
|
if not self.product_id:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -25,11 +25,6 @@ import werkzeug.utils
|
|||||||
import werkzeug.wrappers
|
import werkzeug.wrappers
|
||||||
from openerp.api import Environment
|
from openerp.api import Environment
|
||||||
|
|
||||||
try:
|
|
||||||
import xlwt
|
|
||||||
except ImportError:
|
|
||||||
xlwt = None
|
|
||||||
|
|
||||||
import openerp
|
import openerp
|
||||||
import openerp.modules.registry
|
import openerp.modules.registry
|
||||||
from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
|
from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
|
||||||
@@ -37,7 +32,7 @@ from openerp.modules import get_resource_path
|
|||||||
from openerp.tools import topological_sort
|
from openerp.tools import topological_sort
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
from openerp.tools import ustr
|
from openerp.tools import ustr
|
||||||
from openerp.tools.misc import str2bool
|
from openerp.tools.misc import str2bool, xlwt
|
||||||
from openerp import http
|
from openerp import http
|
||||||
from openerp.http import request, serialize_exception as _serialize_exception
|
from openerp.http import request, serialize_exception as _serialize_exception
|
||||||
from openerp.exceptions import AccessError
|
from openerp.exceptions import AccessError
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
from openerp import http
|
from openerp import http
|
||||||
import json
|
import json
|
||||||
from openerp.http import request, serialize_exception as _serialize_exception
|
from openerp.http import request, serialize_exception as _serialize_exception
|
||||||
|
from openerp.tools.misc import xlwt
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
try:
|
|
||||||
import xlwt
|
|
||||||
except ImportError:
|
|
||||||
xlwt = None
|
|
||||||
|
|
||||||
class TableExporter(http.Controller):
|
class TableExporter(http.Controller):
|
||||||
|
|
||||||
@@ -21,7 +18,7 @@ class TableExporter(http.Controller):
|
|||||||
jdata = json.loads(data)
|
jdata = json.loads(data)
|
||||||
nbr_measures = jdata['nbr_measures']
|
nbr_measures = jdata['nbr_measures']
|
||||||
workbook = xlwt.Workbook()
|
workbook = xlwt.Workbook()
|
||||||
worksheet = workbook.add_sheet(jdata['title'][:30])
|
worksheet = workbook.add_sheet(jdata['title'])
|
||||||
header_bold = xlwt.easyxf("font: bold on; pattern: pattern solid, fore_colour gray25;")
|
header_bold = xlwt.easyxf("font: bold on; pattern: pattern solid, fore_colour gray25;")
|
||||||
header_plain = xlwt.easyxf("pattern: pattern solid, fore_colour gray25;")
|
header_plain = xlwt.easyxf("pattern: pattern solid, fore_colour gray25;")
|
||||||
bold = xlwt.easyxf("font: bold on;")
|
bold = xlwt.easyxf("font: bold on;")
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ var DataSetSearch = DataSet.extend({
|
|||||||
.limit(options.limit || false);
|
.limit(options.limit || false);
|
||||||
q = q.order_by.apply(q, this._sort);
|
q = q.order_by.apply(q, this._sort);
|
||||||
|
|
||||||
return q.all().done(function (records) {
|
return this.orderer.add(q.all()).done(function (records) {
|
||||||
// FIXME: not sure about that one, *could* have discarded count
|
// FIXME: not sure about that one, *could* have discarded count
|
||||||
q.count().done(function (count) { self._length = count; });
|
q.count().done(function (count) { self._length = count; });
|
||||||
self.ids = _(records).pluck('id');
|
self.ids = _(records).pluck('id');
|
||||||
|
|||||||
@@ -478,6 +478,7 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin,
|
|||||||
this.$('.o_debug_input').val(this.get('value'));
|
this.$('.o_debug_input').val(this.get('value'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.$('.o_form_input').val('');
|
||||||
this.$('.o_count').text(_t('No selected record'));
|
this.$('.o_count').text(_t('No selected record'));
|
||||||
var $arrow = this.$('button span').detach();
|
var $arrow = this.$('button span').detach();
|
||||||
this.$('button').text(_('Select records ')).append($("<span/>").addClass('fa fa-arrow-right'));
|
this.$('button').text(_('Select records ')).append($("<span/>").addClass('fa fa-arrow-right'));
|
||||||
@@ -1073,7 +1074,7 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, {
|
|||||||
render_value: function () {
|
render_value: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
|
this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
|
||||||
this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
|
this.$("input").prop("checked", false).filter(function () {return this.value == self.get_value();}).prop("checked", true);
|
||||||
this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
|
this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1528,13 +1529,17 @@ var FieldStatus = common.AbstractField.extend({
|
|||||||
return fields;
|
return fields;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
on_click_stage: function (ev) {
|
on_click_stage: _.debounce(function (ev) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var $li = $(ev.currentTarget);
|
var $li = $(ev.currentTarget);
|
||||||
|
var ul = $li.closest('.oe_form_field_status');
|
||||||
if (this.view.is_disabled) {
|
if (this.view.is_disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var val;
|
var val;
|
||||||
|
if (ul.attr('disabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.field.type == "many2one") {
|
if (this.field.type == "many2one") {
|
||||||
val = parseInt($li.data("id"), 10);
|
val = parseInt($li.data("id"), 10);
|
||||||
}
|
}
|
||||||
@@ -1551,13 +1556,16 @@ var FieldStatus = common.AbstractField.extend({
|
|||||||
this.view.recursive_save().done(function() {
|
this.view.recursive_save().done(function() {
|
||||||
var change = {};
|
var change = {};
|
||||||
change[self.name] = val;
|
change[self.name] = val;
|
||||||
|
ul.attr('disabled', true);
|
||||||
self.view.dataset.write(self.view.datarecord.id, change).done(function() {
|
self.view.dataset.write(self.view.datarecord.id, change).done(function() {
|
||||||
self.view.reload();
|
self.view.reload();
|
||||||
|
}).always(function() {
|
||||||
|
ul.removeAttr('disabled');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, 300),
|
||||||
});
|
});
|
||||||
|
|
||||||
var FieldMonetary = FieldFloat.extend({
|
var FieldMonetary = FieldFloat.extend({
|
||||||
|
|||||||
@@ -32,7 +32,9 @@
|
|||||||
var modal = $(this).parentsUntil('body', '.modal');
|
var modal = $(this).parentsUntil('body', '.modal');
|
||||||
if (modal.hasClass('o_database_backup')) {
|
if (modal.hasClass('o_database_backup')) {
|
||||||
$(modal).modal('hide');
|
$(modal).modal('hide');
|
||||||
$('.list-group').before("<div class='alert alert-info'>The backup may take some time before being ready</div>");
|
if (!$('.alert-backup-long').length) {
|
||||||
|
$('.list-group').before("<div class='alert alert-info alert-backup-long'>The backup may take some time before being ready</div>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import openerp.tests
|
import openerp.tests
|
||||||
|
|
||||||
|
@openerp.tests.common.at_install(False)
|
||||||
|
@openerp.tests.common.post_install(True)
|
||||||
class TestUi(openerp.tests.HttpCase):
|
class TestUi(openerp.tests.HttpCase):
|
||||||
|
|
||||||
post_install = True
|
post_install = True
|
||||||
|
|||||||
@@ -561,6 +561,10 @@
|
|||||||
<xpath expr="//div[@id='wrap']" position="inside">
|
<xpath expr="//div[@id='wrap']" position="inside">
|
||||||
<div class="oe_structure">
|
<div class="oe_structure">
|
||||||
<section class="container">
|
<section class="container">
|
||||||
|
<t t-if="not version">
|
||||||
|
<meta http-equiv="refresh" content="0;URL='/website/info'" />
|
||||||
|
</t>
|
||||||
|
<t t-if="version">
|
||||||
<h1><t t-esc="res_company.name"/>
|
<h1><t t-esc="res_company.name"/>
|
||||||
<small>Odoo Version <t t-raw="version.get('server_version')"/></small>
|
<small>Odoo Version <t t-raw="version.get('server_version')"/></small>
|
||||||
</h1>
|
</h1>
|
||||||
@@ -608,6 +612,7 @@
|
|||||||
</t>
|
</t>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
</t>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|||||||
@@ -228,8 +228,8 @@ class WebsiteBlog(http.Controller):
|
|||||||
all_post_ids = blog_post_obj.search(cr, uid, [('blog_id', '=', blog.id)], context=context)
|
all_post_ids = blog_post_obj.search(cr, uid, [('blog_id', '=', blog.id)], context=context)
|
||||||
# should always return at least the current post
|
# should always return at least the current post
|
||||||
current_blog_post_index = all_post_ids.index(blog_post.id)
|
current_blog_post_index = all_post_ids.index(blog_post.id)
|
||||||
next_post_id = all_post_ids[0 if current_blog_post_index == len(all_post_ids) - 1 \
|
nb_posts = len(all_post_ids)
|
||||||
else current_blog_post_index + 1]
|
next_post_id = all_post_ids[(current_blog_post_index + 1) % nb_posts] if nb_posts > 1 else None
|
||||||
next_post = next_post_id and blog_post_obj.browse(cr, uid, next_post_id, context=context) or False
|
next_post = next_post_id and blog_post_obj.browse(cr, uid, next_post_id, context=context) or False
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class WebsiteForm(http.Controller):
|
|||||||
# If some attachments didn't match a field on the model,
|
# If some attachments didn't match a field on the model,
|
||||||
# we create a mail.message to link them to the record
|
# we create a mail.message to link them to the record
|
||||||
if orphan_attachment_ids:
|
if orphan_attachment_ids:
|
||||||
if model.name != 'mail.mail':
|
if model.model != 'mail.mail':
|
||||||
values = {
|
values = {
|
||||||
'body': _('<p>Attached files : </p>'),
|
'body': _('<p>Attached files : </p>'),
|
||||||
'model': model.model,
|
'model': model.model,
|
||||||
|
|||||||
@@ -209,14 +209,14 @@ class Post(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# history
|
# history
|
||||||
create_date = fields.Datetime('Asked on', select=True, readonly=True)
|
create_date = fields.Datetime('Asked on', index=True, readonly=True)
|
||||||
create_uid = fields.Many2one('res.users', string='Created by', select=True, readonly=True)
|
create_uid = fields.Many2one('res.users', string='Created by', index=True, readonly=True)
|
||||||
write_date = fields.Datetime('Update on', select=True, readonly=True)
|
write_date = fields.Datetime('Update on', index=True, readonly=True)
|
||||||
bump_date = fields.Datetime('Bumped on', readonly=True,
|
bump_date = fields.Datetime('Bumped on', readonly=True,
|
||||||
help="Technical field allowing to bump a question. Writing on this field will trigger "
|
help="Technical field allowing to bump a question. Writing on this field will trigger "
|
||||||
"a write on write_date and therefore bump the post. Directly writing on write_date "
|
"a write on write_date and therefore bump the post. Directly writing on write_date "
|
||||||
"is currently not supported and this field is a workaround.")
|
"is currently not supported and this field is a workaround.")
|
||||||
write_uid = fields.Many2one('res.users', string='Updated by', select=True, readonly=True)
|
write_uid = fields.Many2one('res.users', string='Updated by', index=True, readonly=True)
|
||||||
relevancy = fields.Float('Relevance', compute="_compute_relevancy", store=True)
|
relevancy = fields.Float('Relevance', compute="_compute_relevancy", store=True)
|
||||||
|
|
||||||
# vote
|
# vote
|
||||||
@@ -244,7 +244,7 @@ class Post(models.Model):
|
|||||||
|
|
||||||
# closing
|
# closing
|
||||||
closed_reason_id = fields.Many2one('forum.post.reason', string='Reason')
|
closed_reason_id = fields.Many2one('forum.post.reason', string='Reason')
|
||||||
closed_uid = fields.Many2one('res.users', string='Closed by', select=1)
|
closed_uid = fields.Many2one('res.users', string='Closed by', index=True)
|
||||||
closed_date = fields.Datetime('Closed on', readonly=True)
|
closed_date = fields.Datetime('Closed on', readonly=True)
|
||||||
|
|
||||||
# karma calculation and access
|
# karma calculation and access
|
||||||
@@ -812,7 +812,7 @@ class Vote(models.Model):
|
|||||||
post_id = fields.Many2one('forum.post', string='Post', ondelete='cascade', required=True)
|
post_id = fields.Many2one('forum.post', string='Post', ondelete='cascade', required=True)
|
||||||
user_id = fields.Many2one('res.users', string='User', required=True, default=lambda self: self._uid)
|
user_id = fields.Many2one('res.users', string='User', required=True, default=lambda self: self._uid)
|
||||||
vote = fields.Selection([('1', '1'), ('-1', '-1'), ('0', '0')], string='Vote', required=True, default='1')
|
vote = fields.Selection([('1', '1'), ('-1', '-1'), ('0', '0')], string='Vote', required=True, default='1')
|
||||||
create_date = fields.Datetime('Create Date', select=True, readonly=True)
|
create_date = fields.Datetime('Create Date', index=True, readonly=True)
|
||||||
forum_id = fields.Many2one('forum.forum', string='Forum', related="post_id.forum_id", store=True)
|
forum_id = fields.Many2one('forum.forum', string='Forum', related="post_id.forum_id", store=True)
|
||||||
recipient_id = fields.Many2one('res.users', string='To', related="post_id.create_uid", store=True)
|
recipient_id = fields.Many2one('res.users', string='To', related="post_id.create_uid", store=True)
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class Users(models.Model):
|
|||||||
['country_id', 'city', 'website', 'website_description', 'website_published']))
|
['country_id', 'city', 'website', 'website_description', 'website_published']))
|
||||||
return init_res
|
return init_res
|
||||||
|
|
||||||
create_date = fields.Datetime('Create Date', readonly=True, copy=False, select=True)
|
create_date = fields.Datetime('Create Date', readonly=True, copy=False, index=True)
|
||||||
karma = fields.Integer('Karma', default=0)
|
karma = fields.Integer('Karma', default=0)
|
||||||
badge_ids = fields.One2many('gamification.badge.user', 'user_id', string='Badges', copy=False)
|
badge_ids = fields.One2many('gamification.badge.user', 'user_id', string='Badges', copy=False)
|
||||||
gold_badge = fields.Integer('Gold badges count', compute="_get_user_badge_level")
|
gold_badge = fields.Integer('Gold badges count', compute="_get_user_badge_level")
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
.o_website_quote .bs-sidebar {
|
.o_website_quote .bs-sidebar {
|
||||||
position: fixed;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
z-index : 1;
|
z-index : 1;
|
||||||
background-color: #f7f5fa;
|
background-color: #f7f5fa;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mobile preview: the left menu overlaps the quote */
|
|
||||||
@media (max-width: 992px) {
|
|
||||||
.o_website_quote .bs-sidebar {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.o_website_quote .bs-sidenav {
|
.o_website_quote .bs-sidenav {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
@@ -56,14 +50,6 @@
|
|||||||
.o_website_quote .bs-sidebar .nav > .active > ul {
|
.o_website_quote .bs-sidebar .nav > .active > ul {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.o_website_quote .bs-sidebar {
|
|
||||||
width: 213px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.o_website_quote .bs-sidebar {
|
|
||||||
width: 263px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ odoo.define('website_quote.website_quote', function (require) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var ajax = require('web.ajax');
|
var ajax = require('web.ajax');
|
||||||
|
var config = require('web.config');
|
||||||
var Widget = require('web.Widget');
|
var Widget = require('web.Widget');
|
||||||
var website = require('website.website');
|
var website = require('website.website');
|
||||||
|
|
||||||
@@ -175,6 +176,22 @@ if(!$('.o_website_quote').length) {
|
|||||||
nav_menu.setElement($('[data-id="quote_sidebar"]'));
|
nav_menu.setElement($('[data-id="quote_sidebar"]'));
|
||||||
nav_menu.start($('body[data-target=".navspy"]'));
|
nav_menu.start($('body[data-target=".navspy"]'));
|
||||||
|
|
||||||
|
var $bs_sidebar = $(".o_website_quote .bs-sidebar");
|
||||||
|
$(window).on('resize', _.throttle(adapt_sidebar_position, 200, {leading: false}));
|
||||||
|
adapt_sidebar_position();
|
||||||
|
|
||||||
|
function adapt_sidebar_position() {
|
||||||
|
$bs_sidebar.css({
|
||||||
|
position: "",
|
||||||
|
width: "",
|
||||||
|
});
|
||||||
|
if (config.device.size_class >= config.device.SIZES.MD) {
|
||||||
|
$bs_sidebar.css({
|
||||||
|
position: "fixed",
|
||||||
|
width: $bs_sidebar.outerWidth(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
odoo.define('website_quote.payment_method', function (require) {
|
odoo.define('website_quote.payment_method', function (require) {
|
||||||
|
|||||||
@@ -295,8 +295,8 @@ class website_slides(http.Controller):
|
|||||||
payload = request.httprequest.content_length
|
payload = request.httprequest.content_length
|
||||||
# payload is total request content size so it's not exact size of file.
|
# payload is total request content size so it's not exact size of file.
|
||||||
# already add client validation this is for double check if client alter.
|
# already add client validation this is for double check if client alter.
|
||||||
if (payload / 1024 / 1024 > 15):
|
if (payload / 1024 / 1024 > 25):
|
||||||
return {'error': _('File is too big. File size cannot exceed 15MB')}
|
return {'error': _('File is too big. File size cannot exceed 25MB')}
|
||||||
|
|
||||||
values = dict((fname, post[fname]) for fname in [
|
values = dict((fname, post[fname]) for fname in [
|
||||||
'name', 'url', 'tag_ids', 'slide_type', 'channel_id',
|
'name', 'url', 'tag_ids', 'slide_type', 'channel_id',
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ class EmbeddedSlide(models.Model):
|
|||||||
_description = 'Embedded Slides View Counter'
|
_description = 'Embedded Slides View Counter'
|
||||||
_rec_name = 'slide_id'
|
_rec_name = 'slide_id'
|
||||||
|
|
||||||
slide_id = fields.Many2one('slide.slide', string="Presentation", required=True, select=1)
|
slide_id = fields.Many2one('slide.slide', string="Presentation", required=True, index=True)
|
||||||
url = fields.Char('Third Party Website URL', required=True)
|
url = fields.Char('Third Party Website URL', required=True)
|
||||||
count_views = fields.Integer('# Views', default=1)
|
count_views = fields.Integer('# Views', default=1)
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ var SlideDialog = Widget.extend({
|
|||||||
this.reset_file();
|
this.reset_file();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.size / 1024 / 1024 > 15) {
|
if (file.size / 1024 / 1024 > 25) {
|
||||||
this.display_alert(_t("File is too big. File size cannot exceed 15MB"));
|
this.display_alert(_t("File is too big. File size cannot exceed 25MB"));
|
||||||
this.reset_file();
|
this.reset_file();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,7 +230,7 @@
|
|||||||
<main class="container-fluid {{ ' '.join(classes) }}">
|
<main class="container-fluid {{ ' '.join(classes) }}">
|
||||||
{% if pagename != master_doc %}
|
{% if pagename != master_doc %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if 'has-toc' not in meta %}
|
{% if 'has-toc' not in meta and not (pagename in toc) %}
|
||||||
<aside>
|
<aside>
|
||||||
<div class="navbar-aside text-center">
|
<div class="navbar-aside text-center">
|
||||||
{{ toc }}
|
{{ toc }}
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<article class="doc-body {% if 'has-toc' in meta %}doc-toc{% endif %}">
|
<article class="doc-body {% if 'has-toc' in meta %}doc-toc{% endif %}{% if pagename in toc%}index-category{% endif %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block body %} {% endblock %}
|
{% block body %} {% endblock %}
|
||||||
{% if pagename != master_doc %}</article>
|
{% if pagename != master_doc %}</article>
|
||||||
|
|||||||
@@ -12353,6 +12353,13 @@ article.doc-body section.doc-content:first-of-type > p:first-child {
|
|||||||
font-size: 22.5px;
|
font-size: 22.5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
article.doc-body.index-category {
|
||||||
|
min-height: 300px;
|
||||||
|
min-height: 30vh;
|
||||||
|
}
|
||||||
|
article.doc-body.index-category li.toctree-l1 {
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
.content-switcher {
|
.content-switcher {
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -742,6 +742,15 @@ article.doc-body {
|
|||||||
.lead;
|
.lead;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.index-category {
|
||||||
|
min-height: 300px;
|
||||||
|
min-height: 30vh;
|
||||||
|
|
||||||
|
li.toctree-l1 {
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-switcher {
|
.content-switcher {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Belgium, 2016-08-09
|
||||||
|
|
||||||
|
BeOpen agrees to the terms of the Odoo Corporate Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Kim Huysmans kim.huysmans@rhea.be https://github.com/huysmki
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Kim Huysmans kim.huysmans@rhea.be https://github.com/huysmki
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
France, 2016-05-18
|
||||||
|
|
||||||
|
iRaiser agrees to the terms of the Odoo Corporate Contributor License
|
||||||
|
Agreement v1.0.
|
||||||
|
|
||||||
|
I declare that I am authorized and able to make this agreement and sign this
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Signed,
|
||||||
|
|
||||||
|
Bruno PLANCHER bplancher@iraiser.eu https://github.com/bplancher
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Bruno PLANCHER bplancher@iraiser.eu https://github.com/bplancher
|
||||||
|
Damien NICOLAS dnicolas@iraiser.eu https://github.com/gordonzola
|
||||||
|
|
||||||
@@ -231,6 +231,12 @@ just about any SSL termination proxy, but requires the following setup:
|
|||||||
* your SSL termination proxy should also automatically redirect non-secure
|
* your SSL termination proxy should also automatically redirect non-secure
|
||||||
connections to the secure port
|
connections to the secure port
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
In case you are using the Point of Sale module in combinaison with a `POSBox`_,
|
||||||
|
you must disable the HTTPS configuration for the route ``/pos/web`` to avoid
|
||||||
|
mix-content errors.
|
||||||
|
|
||||||
Configuration sample
|
Configuration sample
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@@ -422,3 +428,4 @@ which will generate a 32 characters pseudorandom printable string.
|
|||||||
.. _use an SSH tunnel:
|
.. _use an SSH tunnel:
|
||||||
http://www.postgresql.org/docs/9.3/static/ssh-tunnels.html
|
http://www.postgresql.org/docs/9.3/static/ssh-tunnels.html
|
||||||
.. _WSGI: http://wsgi.readthedocs.org/
|
.. _WSGI: http://wsgi.readthedocs.org/
|
||||||
|
.. _POSBox: https://www.odoo.com/page/point-of-sale-hardware#part_2
|
||||||
@@ -625,9 +625,10 @@ class ir_model_constraint(Model):
|
|||||||
ids.reverse()
|
ids.reverse()
|
||||||
for data in self.browse(cr, uid, ids, context):
|
for data in self.browse(cr, uid, ids, context):
|
||||||
model = data.model.model
|
model = data.model.model
|
||||||
model_obj = self.pool.get(model)
|
if model in self.pool:
|
||||||
if not model_obj:
|
table = self.pool[model]._table
|
||||||
continue
|
else:
|
||||||
|
table = model.replace('.', '_')
|
||||||
name = openerp.tools.ustr(data.name)
|
name = openerp.tools.ustr(data.name)
|
||||||
typ = data.type
|
typ = data.type
|
||||||
|
|
||||||
@@ -641,17 +642,17 @@ class ir_model_constraint(Model):
|
|||||||
if typ == 'f':
|
if typ == 'f':
|
||||||
# test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
|
# test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
|
||||||
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
|
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
|
||||||
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
|
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, table))
|
||||||
if cr.fetchone():
|
if cr.fetchone():
|
||||||
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
|
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (table, name),)
|
||||||
_logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
|
_logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
|
||||||
|
|
||||||
if typ == 'u':
|
if typ == 'u':
|
||||||
# test if constraint exists
|
# test if constraint exists
|
||||||
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
|
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
|
||||||
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
|
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, table))
|
||||||
if cr.fetchone():
|
if cr.fetchone():
|
||||||
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
|
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (table, name),)
|
||||||
_logger.info('Dropped CONSTRAINT %s@%s', name, model)
|
_logger.info('Dropped CONSTRAINT %s@%s', name, model)
|
||||||
|
|
||||||
self.unlink(cr, uid, ids, context)
|
self.unlink(cr, uid, ids, context)
|
||||||
|
|||||||
@@ -227,10 +227,12 @@ class CurrencyRate(models.Model):
|
|||||||
_description = "Currency Rate"
|
_description = "Currency Rate"
|
||||||
_order = "name desc"
|
_order = "name desc"
|
||||||
|
|
||||||
name = fields.Datetime(string='Date', required=True, index=True, default=lambda self:fields.Date.today() + ' 00:00:00')
|
name = fields.Datetime(string='Date', required=True, index=True,
|
||||||
|
default=lambda self: fields.Date.today() + ' 00:00:00')
|
||||||
rate = fields.Float(digits=(12, 6), help='The rate of the currency to the currency of rate 1')
|
rate = fields.Float(digits=(12, 6), help='The rate of the currency to the currency of rate 1')
|
||||||
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True)
|
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True)
|
||||||
company_id = fields.Many2one('res.company', string='Company')
|
company_id = fields.Many2one('res.company', string='Company',
|
||||||
|
default=lambda self: self.env.user._get_company())
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def name_search(self, name, args=None, operator='ilike', limit=80):
|
def name_search(self, name, args=None, operator='ilike', limit=80):
|
||||||
|
|||||||
@@ -23,6 +23,28 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="view_currency_rate_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.currency.rate.form</field>
|
||||||
|
<field name="model">res.currency.rate</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Currency Rate">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="rate"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="currency_id"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<act_window
|
<act_window
|
||||||
id="act_view_currency_rates"
|
id="act_view_currency_rates"
|
||||||
name="Currency Rates"
|
name="Currency Rates"
|
||||||
|
|||||||
@@ -128,11 +128,14 @@ def main(args):
|
|||||||
# bit overkill, but better safe than sorry I guess
|
# bit overkill, but better safe than sorry I guess
|
||||||
csv.field_size_limit(500 * 1024 * 1024)
|
csv.field_size_limit(500 * 1024 * 1024)
|
||||||
|
|
||||||
if config["db_name"]:
|
preload = []
|
||||||
try:
|
if config['db_name']:
|
||||||
openerp.service.db._create_empty_database(config["db_name"])
|
preload = config['db_name'].split(',')
|
||||||
except openerp.service.db.DatabaseExists:
|
for db_name in preload:
|
||||||
pass
|
try:
|
||||||
|
openerp.service.db._create_empty_database(db_name)
|
||||||
|
except openerp.service.db.DatabaseExists:
|
||||||
|
pass
|
||||||
|
|
||||||
if config["test_file"]:
|
if config["test_file"]:
|
||||||
config["test_enable"] = True
|
config["test_enable"] = True
|
||||||
@@ -150,10 +153,6 @@ def main(args):
|
|||||||
if config['workers']:
|
if config['workers']:
|
||||||
openerp.multi_process = True
|
openerp.multi_process = True
|
||||||
|
|
||||||
preload = []
|
|
||||||
if config['db_name']:
|
|
||||||
preload = config['db_name'].split(',')
|
|
||||||
|
|
||||||
stop = config["stop_after_init"]
|
stop = config["stop_after_init"]
|
||||||
|
|
||||||
setup_pid_file()
|
setup_pid_file()
|
||||||
|
|||||||
+2
-2
@@ -210,7 +210,7 @@ class WebRequest(object):
|
|||||||
to a database.
|
to a database.
|
||||||
"""
|
"""
|
||||||
if not self.db:
|
if not self.db:
|
||||||
return RuntimeError('request not bound to a database')
|
raise RuntimeError('request not bound to a database')
|
||||||
return openerp.api.Environment(self.cr, self.uid, self.context)
|
return openerp.api.Environment(self.cr, self.uid, self.context)
|
||||||
|
|
||||||
@lazy_property
|
@lazy_property
|
||||||
@@ -246,7 +246,7 @@ class WebRequest(object):
|
|||||||
# can not be a lazy_property because manual rollback in _call_function
|
# can not be a lazy_property because manual rollback in _call_function
|
||||||
# if already set (?)
|
# if already set (?)
|
||||||
if not self.db:
|
if not self.db:
|
||||||
return RuntimeError('request not bound to a database')
|
raise RuntimeError('request not bound to a database')
|
||||||
if not self._cr:
|
if not self._cr:
|
||||||
self._cr = self.registry.cursor()
|
self._cr = self.registry.cursor()
|
||||||
return self._cr
|
return self._cr
|
||||||
|
|||||||
+1
-1
@@ -1354,7 +1354,7 @@ class BaseModel(object):
|
|||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
raise
|
raise
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
raise ValidationError("Error while validating constraint\n\n%s" % tools.ustr(e))
|
raise ValidationError("%s\n\n%s" % (_("Error while validating constraint"), tools.ustr(e)))
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def default_get(self, fields_list):
|
def default_get(self, fields_list):
|
||||||
|
|||||||
@@ -859,11 +859,7 @@ def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kin
|
|||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
def convert_sql_import(cr, fp):
|
def convert_sql_import(cr, fp):
|
||||||
queries = fp.read().split(';')
|
cr.execute(fp.read())
|
||||||
for query in queries:
|
|
||||||
new_query = ' '.join(query.split())
|
|
||||||
if new_query:
|
|
||||||
cr.execute(new_query)
|
|
||||||
|
|
||||||
def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
|
def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
|
||||||
noupdate=False):
|
noupdate=False):
|
||||||
|
|||||||
+22
-1
@@ -14,6 +14,7 @@ import subprocess
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import passlib.utils
|
import passlib.utils
|
||||||
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@@ -333,6 +334,26 @@ def topological_sort(elems):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import xlwt
|
||||||
|
|
||||||
|
# add some sanitizations to respect the excel sheet name restrictions
|
||||||
|
# as the sheet name is often translatable, can not control the input
|
||||||
|
class PatchedWorkbook(xlwt.Workbook):
|
||||||
|
def add_sheet(self, name):
|
||||||
|
# invalid Excel character: []:*?/\
|
||||||
|
name = re.sub(r'[\[\]:*?/\\]', '', name)
|
||||||
|
|
||||||
|
# maximum size is 31 characters
|
||||||
|
name = name[:31]
|
||||||
|
return super(PatchedWorkbook, self).add_sheet(name)
|
||||||
|
|
||||||
|
xlwt.Workbook = PatchedWorkbook
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
xlwt = None
|
||||||
|
|
||||||
|
|
||||||
class UpdateableStr(local):
|
class UpdateableStr(local):
|
||||||
""" Class that stores an updateable string (used in wizards)
|
""" Class that stores an updateable string (used in wizards)
|
||||||
"""
|
"""
|
||||||
@@ -928,7 +949,7 @@ class CountingStream(object):
|
|||||||
|
|
||||||
def stripped_sys_argv(*strip_args):
|
def stripped_sys_argv(*strip_args):
|
||||||
"""Return sys.argv with some arguments stripped, suitable for reexecution or subprocesses"""
|
"""Return sys.argv with some arguments stripped, suitable for reexecution or subprocesses"""
|
||||||
strip_args = sorted(set(strip_args) | set(['-s', '--save', '-u', '--update', '-i', '--init']))
|
strip_args = sorted(set(strip_args) | set(['-s', '--save', '-u', '--update', '-i', '--init', '--i18n-overwrite']))
|
||||||
assert all(config.parser.has_option(s) for s in strip_args)
|
assert all(config.parser.has_option(s) for s in strip_args)
|
||||||
takes_value = dict((s, config.parser.get_option(s).takes_value()) for s in strip_args)
|
takes_value = dict((s, config.parser.get_option(s).takes_value()) for s in strip_args)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user