[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')
|
||||
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")
|
||||
|
||||
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':
|
||||
# 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
|
||||
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):
|
||||
return base_amount * self.amount / 100
|
||||
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)
|
||||
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")
|
||||
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)]})
|
||||
@@ -289,58 +289,54 @@ class AccountBankStatement(models.Model):
|
||||
"""
|
||||
statements = self
|
||||
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,
|
||||
# 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:
|
||||
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
|
||||
automatic_reconciliation_entries = []
|
||||
st_lines_left = self.env['account.bank.statement.line']
|
||||
for st_line in bsl_obj.search(st_lines_filter):
|
||||
res = st_line.auto_reconcile()
|
||||
if not res:
|
||||
st_lines_left = (st_lines_left | st_line)
|
||||
else:
|
||||
automatic_reconciliation_entries.append(res.ids)
|
||||
|
||||
# Try to set statement line's partner
|
||||
for st_line in st_lines_left:
|
||||
if st_line.name and not st_line.partner_id:
|
||||
additional_domain = [('ref', '=', st_line.name)]
|
||||
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:
|
||||
st_line.write({'partner_id': match_recs[0].partner_id.id})
|
||||
|
||||
# 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
|
||||
}
|
||||
}]
|
||||
|
||||
lines = []
|
||||
for el in statements:
|
||||
lines.extend(el.line_ids.ids)
|
||||
lines = list(set(lines))
|
||||
#try to assign partner to bank_statement_line
|
||||
stl_to_assign_partner = [stl.id for stl in st_lines_left if not stl.partner_id]
|
||||
refs = list(set([st.name for st in st_lines_left if not stl.partner_id]))
|
||||
if st_lines_left and stl_to_assign_partner and refs:
|
||||
sql_query = """SELECT aml.partner_id, aml.ref, stl.id
|
||||
FROM account_move_line aml
|
||||
JOIN account_account acc ON acc.id = aml.account_id
|
||||
JOIN account_bank_statement_line stl ON aml.ref = stl.name
|
||||
WHERE (aml.company_id = %s
|
||||
AND aml.partner_id IS NOT NULL)
|
||||
AND (
|
||||
(aml.statement_id IS NULL AND aml.account_id IN %s)
|
||||
OR
|
||||
(acc.internal_type IN ('payable', 'receivable') AND aml.reconciled = false)
|
||||
)
|
||||
AND aml.ref IN %s
|
||||
"""
|
||||
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))
|
||||
if statements:
|
||||
sql_query += 'AND stl.id IN %s'
|
||||
params += (tuple(stl_to_assign_partner),)
|
||||
self.env.cr.execute(sql_query, params)
|
||||
results = self.env.cr.dictfetchall()
|
||||
st_line = self.env['account.bank.statement.line']
|
||||
for line in results:
|
||||
st_line.browse(line.get('id')).write({'partner_id': line.get('partner_id')})
|
||||
|
||||
return {
|
||||
'st_lines_ids': st_lines_left.ids,
|
||||
'notifications': notifications,
|
||||
'notifications': [],
|
||||
'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
|
||||
@@ -449,6 +445,40 @@ class AccountBankStatementLine(models.Model):
|
||||
# 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
|
||||
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 """
|
||||
@@ -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")
|
||||
|
||||
def _get_domain_maker_move_line_amount(self):
|
||||
""" Returns a function that can create the appropriate domain to search on move.line amount based on statement.line currency/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
|
||||
field = currency and 'amount_residual_currency' or 'amount_residual'
|
||||
precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
|
||||
|
||||
def ret(comparator, amount, p=precision, f=field, c=currency):
|
||||
if comparator == '<':
|
||||
if amount < 0:
|
||||
domain = [(f, '<', 0), (f, '>', amount)]
|
||||
else:
|
||||
domain = [(f, '>', 0), (f, '<', amount)]
|
||||
elif comparator == '=':
|
||||
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_common_sql_query(self, overlook_partner = False, excluded_ids = None, split = False):
|
||||
acc_type = "acc.internal_type IN ('payable', 'receivable')" if (self.partner_id or overlook_partner) else "acc.reconcile = true"
|
||||
select_clause = "SELECT aml.id "
|
||||
from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id "
|
||||
where_clause = """WHERE aml.company_id = %(company_id)s
|
||||
AND (
|
||||
(aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s)
|
||||
OR
|
||||
("""+acc_type+""" AND aml.reconciled = false)
|
||||
)"""
|
||||
where_clause = where_clause + ' AND aml.partner_id = %(partner_id)s' if self.partner_id else where_clause
|
||||
where_clause = where_clause + ' AND aml.id NOT IN %(excluded_ids)s' if excluded_ids else where_clause
|
||||
if split:
|
||||
return select_clause, from_clause, where_clause
|
||||
return select_clause + from_clause + where_clause
|
||||
|
||||
def get_reconciliation_proposition(self, excluded_ids=None):
|
||||
""" 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.
|
||||
"""
|
||||
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
|
||||
if self.name:
|
||||
overlook_partner = not self.partner_id # If the transaction has no partner, look for match in payable and receivable account anyway
|
||||
domain = [('ref', '=', self.name)]
|
||||
match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
|
||||
if match_recs and len(match_recs) == 1:
|
||||
return match_recs
|
||||
elif len(match_recs) == 0:
|
||||
move = self.env['account.move'].search([('name', '=', self.name)], limit=1)
|
||||
if move:
|
||||
domain = [('move_id', '=', move.id)]
|
||||
match_recs = self.get_move_lines_for_reconciliation(excluded_ids=excluded_ids, limit=2, additional_domain=domain, overlook_partner=overlook_partner)
|
||||
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
|
||||
add_to_select = ", CASE WHEN aml.ref = %(ref)s THEN 1 ELSE 2 END as temp_field_order "
|
||||
add_to_from = " JOIN account_move m ON m.id = aml.move_id "
|
||||
select_clause, from_clause, where_clause = self._get_common_sql_query(overlook_partner=True, excluded_ids=excluded_ids, split=True)
|
||||
sql_query = select_clause + add_to_select + from_clause + add_to_from + where_clause
|
||||
sql_query += " AND (aml.ref= %(ref)s or m.name = %(ref)s) \
|
||||
ORDER BY temp_field_order, date_maturity asc, aml.id asc"
|
||||
self.env.cr.execute(sql_query, params)
|
||||
results = self.env.cr.fetchone()
|
||||
if results:
|
||||
return self.env['account.move.line'].browse(results[0])
|
||||
|
||||
# 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))
|
||||
if match_recs:
|
||||
return match_recs
|
||||
field = currency and 'amount_residual_currency' or 'amount_residual'
|
||||
liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit'
|
||||
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']
|
||||
|
||||
# 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
|
||||
return self.env['account.move.line']
|
||||
|
||||
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 """
|
||||
@@ -654,28 +664,44 @@ class AccountBankStatementLine(models.Model):
|
||||
self.ensure_one()
|
||||
match_recs = self.env['account.move.line']
|
||||
|
||||
# How to compare statement line amount and move lines amount
|
||||
amount_domain_maker = self._get_domain_maker_move_line_amount()
|
||||
equal_amount_domain = amount_domain_maker('=', self.amount_currency or self.amount)
|
||||
|
||||
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,
|
||||
'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
|
||||
if self.name:
|
||||
overlook_partner = not self.partner_id # If the transaction has no partner, look for match in payable and receivable account anyway
|
||||
domain = equal_amount_domain + [('ref', '=', self.name)]
|
||||
match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=domain, overlook_partner=overlook_partner)
|
||||
if match_recs and len(match_recs) != 1:
|
||||
sql_query = self._get_common_sql_query() + \
|
||||
" AND aml.ref = %(ref)s 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
|
||||
|
||||
# Look for a single move line with the same partner, the same amount
|
||||
if not match_recs:
|
||||
if self.partner_id:
|
||||
match_recs = self.get_move_lines_for_reconciliation(limit=2, additional_domain=equal_amount_domain)
|
||||
if match_recs and len(match_recs) != 1:
|
||||
sql_query = self._get_common_sql_query() + \
|
||||
" 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
|
||||
|
||||
if not match_recs:
|
||||
return False
|
||||
|
||||
match_recs = self.env['account.move.line'].browse([aml.get('id') for aml in match_recs])
|
||||
# Now reconcile
|
||||
counterpart_aml_dicts = []
|
||||
payment_aml_rec = self.env['account.move.line']
|
||||
|
||||
@@ -117,7 +117,7 @@ class account_journal(models.Model):
|
||||
for i in range(0,6):
|
||||
if i == 0:
|
||||
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)+"')"
|
||||
else:
|
||||
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'))]
|
||||
|
||||
ctx = self._context.copy()
|
||||
ctx.pop('group_by', None)
|
||||
ctx.update({
|
||||
'journal_type': self.type,
|
||||
'default_journal_id': self.id,
|
||||
@@ -336,6 +337,7 @@ class account_journal(models.Model):
|
||||
'default_payment_type': payment_type,
|
||||
'default_journal_id': self.id
|
||||
})
|
||||
ctx.pop('group_by', None)
|
||||
action_rec = self.env['ir.model.data'].xmlid_to_object('account.action_account_payments')
|
||||
if action_rec:
|
||||
action = action_rec.read([])[0]
|
||||
@@ -351,6 +353,7 @@ class account_journal(models.Model):
|
||||
ctx = dict(self.env.context, default_journal_id=self.id)
|
||||
if ctx.get('search_default_journal', False):
|
||||
ctx.update(search_default_journal_id=self.id)
|
||||
ctx.pop('group_by', None)
|
||||
ir_model_obj = self.pool['ir.model.data']
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
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")
|
||||
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_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"
|
||||
_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')
|
||||
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.
|
||||
@@ -363,7 +373,7 @@ class AccountMoveLine(models.Model):
|
||||
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)
|
||||
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")
|
||||
statement_id = fields.Many2one('account.bank.statement', string='Statement',
|
||||
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
|
||||
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")
|
||||
|
||||
_sql_constraints = [
|
||||
@@ -1134,8 +1144,6 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
@api.multi
|
||||
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:
|
||||
raise UserError(_('You cannot use deprecated account.'))
|
||||
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
|
||||
#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({
|
||||
'partial_reconcile_ids': [(6, 0, partial_rec_ids)],
|
||||
'reconciled_line_ids': [(6, 0, aml_ids)],
|
||||
'partial_reconcile_ids': [(4, p_id) for p_id in partial_rec_ids],
|
||||
'reconciled_line_ids': [(4, a_id) for a_id in aml_ids],
|
||||
'exchange_move_id': exchange_move_id,
|
||||
'exchange_partial_rec_id': exchange_partial_rec_id,
|
||||
})
|
||||
})
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -338,7 +338,7 @@ class ResPartner(models.Model):
|
||||
else:
|
||||
domain += [('partner_id', 'in', self.ids)]
|
||||
#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:
|
||||
domain += overdue_domain
|
||||
return domain
|
||||
|
||||
@@ -1127,6 +1127,7 @@ var bankStatementReconciliation = abstractReconciliation.extend({
|
||||
this.reconciliation_menu_id = false; // Used to update the needaction badge
|
||||
// The same move line cannot be selected for multiple reconciliations
|
||||
this.excluded_move_lines_ids = {};
|
||||
this.widget_childrens = [];
|
||||
},
|
||||
|
||||
serverPreprocessResultHandler: function(data) {
|
||||
@@ -1178,44 +1179,64 @@ var bankStatementReconciliation = abstractReconciliation.extend({
|
||||
single_statement: self.single_statement,
|
||||
total_lines: self.num_already_reconciled_lines+self.lines.length
|
||||
}));
|
||||
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);
|
||||
|
||||
// If everything is reconciled, show end message
|
||||
if (self.lines.length === 0) {
|
||||
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();
|
||||
}
|
||||
self.$el.find('.js_automatic_reconciliation').click(function() {
|
||||
// Let odoo try to reconcile entries for the user
|
||||
self.model_bank_statement_line
|
||||
.call("reconciliation_widget_auto_reconcile", [self.lines || undefined, self.num_already_reconciled_lines])
|
||||
.then(function(data){ self.serverPreprocessResultHandler(data); })
|
||||
.then(function(){ self.$('.js_automatic_reconciliation').hide();
|
||||
return self.display_reconciliation_propositions();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
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() {
|
||||
if (! this.single_statement) return;
|
||||
this.$(".statement_name span").hide();
|
||||
@@ -1320,6 +1341,7 @@ var bankStatementReconciliation = abstractReconciliation.extend({
|
||||
reconciliation_proposition: initial_data_provided ? reconciliation_proposition : undefined,
|
||||
};
|
||||
var widget = new self.children_widget(self, context);
|
||||
this.widget_childrens.push(widget);
|
||||
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>
|
||||
</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="notification_area"></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.exceptions import UserError, ValidationError
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||
from openerp.tools import float_compare
|
||||
|
||||
|
||||
class AccountAssetCategory(models.Model):
|
||||
@@ -417,11 +418,12 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
categ_type = line.asset_id.category_id.type
|
||||
debit_account = line.asset_id.category_id.account_asset_id.id
|
||||
credit_account = line.asset_id.category_id.account_depreciation_id.id
|
||||
prec = self.env['decimal.precision'].precision_get('Account')
|
||||
move_line_1 = {
|
||||
'name': asset_name,
|
||||
'account_id': credit_account,
|
||||
'debit': 0.0,
|
||||
'credit': amount,
|
||||
'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||
'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||
'journal_id': journal_id,
|
||||
'partner_id': partner_id,
|
||||
'currency_id': company_currency != current_currency and current_currency.id or False,
|
||||
@@ -432,8 +434,8 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
move_line_2 = {
|
||||
'name': asset_name,
|
||||
'account_id': debit_account,
|
||||
'credit': 0.0,
|
||||
'debit': amount,
|
||||
'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount,
|
||||
'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0,
|
||||
'journal_id': journal_id,
|
||||
'partner_id': partner_id,
|
||||
'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':
|
||||
res.update({'method_end': asset.method_end})
|
||||
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
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -102,8 +102,8 @@ class AccountVoucher(models.Model):
|
||||
pay_now = fields.Selection([
|
||||
('pay_now', 'Pay Directly'),
|
||||
('pay_later', 'Pay Later'),
|
||||
], 'Payment', select=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later')
|
||||
date_due = fields.Date('Due Date', readonly=True, select=True, states={'draft': [('readonly', False)]})
|
||||
], 'Payment', index=True, readonly=True, states={'draft': [('readonly', False)]}, default='pay_later')
|
||||
date_due = fields.Date('Due Date', readonly=True, index=True, states={'draft': [('readonly', False)]})
|
||||
|
||||
@api.onchange('partner_id', 'pay_now')
|
||||
def onchange_partner_id(self):
|
||||
|
||||
@@ -411,7 +411,7 @@ var DataImport = Widget.extend(ControlPanelMixin, {
|
||||
return $.when([{
|
||||
type: 'error',
|
||||
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')
|
||||
email = fields.Char(string='Email')
|
||||
phone = fields.Char(string='Phone')
|
||||
name = fields.Char(string='Attendee Name', select=True)
|
||||
name = fields.Char(string='Attendee Name', index=True)
|
||||
|
||||
@api.one
|
||||
@api.constrains('event_id', 'state')
|
||||
|
||||
@@ -68,7 +68,7 @@ class RegistrationEditorLine(models.TransientModel):
|
||||
event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket')
|
||||
email = fields.Char(string='Email')
|
||||
phone = fields.Char(string='Phone')
|
||||
name = fields.Char(string='Name', select=True)
|
||||
name = fields.Char(string='Name', index=True)
|
||||
|
||||
@api.one
|
||||
def get_registration_data(self):
|
||||
|
||||
+2
-2
@@ -261,7 +261,7 @@ class hr_employee(osv.osv):
|
||||
user_field_lst.append(name)
|
||||
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):
|
||||
@@ -291,7 +291,7 @@ class hr_department(osv.osv):
|
||||
}
|
||||
|
||||
_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):
|
||||
|
||||
@@ -236,7 +236,7 @@ class HrEquipmentRequest(models.Model):
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee_get)
|
||||
department_id = fields.Many2one('hr.department', string='Department')
|
||||
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')
|
||||
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')
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#-*- coding:utf-8 -*-
|
||||
# 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.osv import fields, osv
|
||||
@@ -94,13 +92,12 @@ class hr_payslip(osv.osv):
|
||||
move_pool = self.pool.get('account.move')
|
||||
hr_payslip_line_pool = self.pool['hr.payslip.line']
|
||||
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):
|
||||
line_ids = []
|
||||
debit_sum = 0.0
|
||||
credit_sum = 0.0
|
||||
date = timenow
|
||||
date = slip.date or slip.date_to
|
||||
|
||||
name = _('Payslip of %s') % (slip.employee_id.name)
|
||||
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))
|
||||
adjust_credit = (0, 0, {
|
||||
'name': _('Adjustment Entry'),
|
||||
'date': timenow,
|
||||
'partner_id': False,
|
||||
'account_id': acc_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")
|
||||
probability = fields.Float("Probability")
|
||||
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)
|
||||
stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', track_visibility='onchange',
|
||||
domain="['|', ('job_id', '=', False), ('job_id', '=', job_id)]",
|
||||
copy=False, select=1,
|
||||
copy=False, index=True,
|
||||
default=_default_stage_id)
|
||||
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.")
|
||||
categ_ids = fields.Many2many('hr.applicant.category', string="Tags")
|
||||
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)
|
||||
date_closed = fields.Datetime("Closed", readonly=True, select=True)
|
||||
date_open = fields.Datetime("Assigned", readonly=True, select=True)
|
||||
date_last_stage_update = fields.Datetime("Last Stage Update", select=True, default=fields.Datetime.now)
|
||||
date_closed = fields.Datetime("Closed", readonly=True, index=True)
|
||||
date_open = fields.Datetime("Assigned", readonly=True, index=True)
|
||||
date_last_stage_update = fields.Datetime("Last Stage Update", index=True, default=fields.Datetime.now)
|
||||
date_action = fields.Date("Next Action Date")
|
||||
title_action = fields.Char("Next Action", size=64)
|
||||
priority = fields.Selection(AVAILABLE_PRIORITIES, "Appreciation", default='0')
|
||||
|
||||
@@ -147,7 +147,7 @@ class LunchOrderLine(models.Model):
|
||||
('confirmed', 'Received'),
|
||||
('ordered', 'Ordered'),
|
||||
('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')
|
||||
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'),
|
||||
('week', 'Every Week'),
|
||||
('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)
|
||||
monday = fields.Boolean('Monday')
|
||||
tuesday = fields.Boolean('Tuesday')
|
||||
|
||||
@@ -23,6 +23,7 @@ class MailChatController(openerp.addons.bus.controllers.main.BusController):
|
||||
partner_id = request.env.user.partner_id.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])]):
|
||||
channels.append((request.db, 'mail.channel', mail_channel.id))
|
||||
# personal and needaction channel
|
||||
|
||||
@@ -45,7 +45,7 @@ class Channel(models.Model):
|
||||
('channel', 'Channel')],
|
||||
'Channel Type', default='channel')
|
||||
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)
|
||||
# multi users channel
|
||||
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'
|
||||
|
||||
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(
|
||||
'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(
|
||||
'res.partner', string='Related Partner', ondelete='cascade', select=1)
|
||||
'res.partner', string='Related Partner', ondelete='cascade', index=True)
|
||||
channel_id = fields.Many2one(
|
||||
'mail.channel', string='Listener', ondelete='cascade', select=1)
|
||||
'mail.channel', string='Listener', ondelete='cascade', index=True)
|
||||
subtype_ids = fields.Many2many(
|
||||
'mail.message.subtype', string='Subtype',
|
||||
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 '
|
||||
'through this field.')
|
||||
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.")
|
||||
child_ids = fields.One2many('mail.message', 'parent_id', 'Child Messages')
|
||||
# related document
|
||||
model = fields.Char('Related Document Model', select=1)
|
||||
res_id = fields.Integer('Related Document ID', select=1)
|
||||
model = fields.Char('Related Document Model', index=True)
|
||||
res_id = fields.Integer('Related Document ID', index=True)
|
||||
record_name = fields.Char('Message Record Name', help="Name get of the related document.")
|
||||
# characteristics
|
||||
message_type = fields.Selection([
|
||||
@@ -73,13 +73,13 @@ class Message(models.Model):
|
||||
help="Message type: email for email message, notification for system "
|
||||
"message, comment for other messages such as user replies",
|
||||
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
|
||||
email_from = fields.Char(
|
||||
'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.")
|
||||
author_id = fields.Many2one(
|
||||
'res.partner', 'Author', select=1,
|
||||
'res.partner', 'Author', index=True,
|
||||
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.")
|
||||
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 threading for answers',
|
||||
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.')
|
||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ class MailTemplate(models.Model):
|
||||
|
||||
name = fields.Char('Name')
|
||||
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',
|
||||
help="Optional translation language (ISO code) to select when sending out an email. "
|
||||
"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_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
|
||||
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
|
||||
return result
|
||||
|
||||
res_model = fields.Char('Related Document Model', required=True, select=1, help='Model of the followed resource')
|
||||
res_id = fields.Integer('Related Document ID', select=1, help='Id of the followed resource')
|
||||
res_model = fields.Char('Related Document Model', required=True, index=True, help='Model 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.")
|
||||
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')])
|
||||
|
||||
@@ -120,7 +120,7 @@ class MailComposer(models.TransientModel):
|
||||
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)')
|
||||
template_id = fields.Many2one(
|
||||
'mail.template', 'Use template', select=True,
|
||||
'mail.template', 'Use template', index=True,
|
||||
domain="[('model', '=', model)]")
|
||||
# mail_message updated fields
|
||||
message_type = fields.Selection(default="comment")
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
</tr>
|
||||
<tr t-foreach="get_children(o.bom_line_ids)" t-as="l">
|
||||
<td style="padding-left: 20px;">
|
||||
<span style="color: white;" t-esc="'... '*(l['level'])"/>[
|
||||
<span t-esc="l['pcode']"/>]
|
||||
<span style="color: white;" t-esc="'... '*(l['level'])"/>
|
||||
<span t-esc="l['pname']"/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -11,8 +11,8 @@ class SaleOrderLine(models.Model):
|
||||
res = super(SaleOrderLine, self)._action_procurement_create()
|
||||
orders = list(set(x.order_id for x in self))
|
||||
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:
|
||||
reassign.do_unreserve()
|
||||
reassign.action_assign()
|
||||
return res
|
||||
return res
|
||||
|
||||
@@ -87,6 +87,22 @@ class AccountInvoice(models.Model):
|
||||
if purchase_ids:
|
||||
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
|
||||
def invoice_line_move_line_get(self):
|
||||
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"""
|
||||
_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,
|
||||
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)],
|
||||
}
|
||||
|
||||
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,\
|
||||
help="Reference of the document that generated this purchase order "
|
||||
"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 "
|
||||
"products as this reference is usually written on the "
|
||||
"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.")
|
||||
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')
|
||||
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. "\
|
||||
@@ -118,7 +118,7 @@ class PurchaseOrder(models.Model):
|
||||
('purchase', 'Purchase Order'),
|
||||
('done', 'Locked'),
|
||||
('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)
|
||||
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_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_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')
|
||||
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,\
|
||||
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',\
|
||||
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
|
||||
def name_search(self, name, args=None, operator='ilike', limit=100):
|
||||
@@ -281,6 +281,8 @@ class PurchaseOrder(models.Model):
|
||||
@api.multi
|
||||
def button_confirm(self):
|
||||
for order in self:
|
||||
if order.state not in ['draft', 'sent']:
|
||||
continue
|
||||
order._add_supplier_to_product()
|
||||
# Deal with double validation process
|
||||
if order.company_id.po_double_validation == 'one_step'\
|
||||
@@ -516,7 +518,7 @@ class PurchaseOrderLine(models.Model):
|
||||
|
||||
name = fields.Text(string='Description', 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)])
|
||||
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)
|
||||
@@ -527,7 +529,7 @@ class PurchaseOrderLine(models.Model):
|
||||
price_total = fields.Monetary(compute='_compute_amount', string='Total', 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')])
|
||||
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)
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<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="default_location_dest_id_usage" invisible="1"/>
|
||||
<field name="incoterm_id"/>
|
||||
|
||||
@@ -257,11 +257,6 @@ class purchase_requisition(osv.osv):
|
||||
if not confirm:
|
||||
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
|
||||
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):
|
||||
""" Unreserve quants then try to reassign quants."""
|
||||
if context is None:
|
||||
context = {}
|
||||
stock_move_obj = self.pool.get('stock.move')
|
||||
if not move_ids:
|
||||
self.do_unreserve(cr, uid, [picking.id], context=context)
|
||||
self.action_assign(cr, uid, [picking.id], context=context)
|
||||
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)
|
||||
|
||||
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")):
|
||||
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['no_state_change'] = True
|
||||
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)
|
||||
|
||||
@@ -2047,10 +2054,11 @@ class stock_move(osv.osv):
|
||||
if move.state in ('done', 'cancel'):
|
||||
raise UserError(_('Cannot unreserve a done move'))
|
||||
quant_obj.quants_unreserve(cr, uid, move, context=context)
|
||||
if self.find_move_ancestors(cr, uid, move, context=context):
|
||||
self.write(cr, uid, [move.id], {'state': 'waiting'}, context=context)
|
||||
else:
|
||||
self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context)
|
||||
if not context.get('no_state_change'):
|
||||
if self.find_move_ancestors(cr, uid, move, context=context):
|
||||
self.write(cr, uid, [move.id], {'state': 'waiting'}, context=context)
|
||||
else:
|
||||
self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context)
|
||||
|
||||
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 "/")
|
||||
@@ -2625,6 +2633,8 @@ class stock_move(osv.osv):
|
||||
operations = set()
|
||||
move_qty = {}
|
||||
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
|
||||
for link in move.linked_move_operation_ids:
|
||||
operations.add(link.operation_id)
|
||||
@@ -4890,11 +4900,6 @@ class stock_picking_type(osv.osv):
|
||||
name = record.name
|
||||
if record.warehouse_id:
|
||||
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))
|
||||
return res
|
||||
|
||||
|
||||
@@ -233,6 +233,16 @@ class sale_order_line(osv.osv):
|
||||
return result
|
||||
|
||||
@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):
|
||||
if not self.product_id:
|
||||
return
|
||||
|
||||
@@ -25,11 +25,6 @@ import werkzeug.utils
|
||||
import werkzeug.wrappers
|
||||
from openerp.api import Environment
|
||||
|
||||
try:
|
||||
import xlwt
|
||||
except ImportError:
|
||||
xlwt = None
|
||||
|
||||
import openerp
|
||||
import openerp.modules.registry
|
||||
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.translate import _
|
||||
from openerp.tools import ustr
|
||||
from openerp.tools.misc import str2bool
|
||||
from openerp.tools.misc import str2bool, xlwt
|
||||
from openerp import http
|
||||
from openerp.http import request, serialize_exception as _serialize_exception
|
||||
from openerp.exceptions import AccessError
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
from openerp import http
|
||||
import json
|
||||
from openerp.http import request, serialize_exception as _serialize_exception
|
||||
from openerp.tools.misc import xlwt
|
||||
from cStringIO import StringIO
|
||||
from collections import deque
|
||||
|
||||
try:
|
||||
import xlwt
|
||||
except ImportError:
|
||||
xlwt = None
|
||||
|
||||
class TableExporter(http.Controller):
|
||||
|
||||
@@ -21,7 +18,7 @@ class TableExporter(http.Controller):
|
||||
jdata = json.loads(data)
|
||||
nbr_measures = jdata['nbr_measures']
|
||||
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_plain = xlwt.easyxf("pattern: pattern solid, fore_colour gray25;")
|
||||
bold = xlwt.easyxf("font: bold on;")
|
||||
|
||||
@@ -688,7 +688,7 @@ var DataSetSearch = DataSet.extend({
|
||||
.limit(options.limit || false);
|
||||
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
|
||||
q.count().done(function (count) { self._length = count; });
|
||||
self.ids = _(records).pluck('id');
|
||||
|
||||
@@ -478,6 +478,7 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin,
|
||||
this.$('.o_debug_input').val(this.get('value'));
|
||||
}
|
||||
} else {
|
||||
this.$('.o_form_input').val('');
|
||||
this.$('.o_count').text(_t('No selected record'));
|
||||
var $arrow = this.$('button span').detach();
|
||||
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 () {
|
||||
var self = this;
|
||||
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] : "");
|
||||
}
|
||||
});
|
||||
@@ -1528,13 +1529,17 @@ var FieldStatus = common.AbstractField.extend({
|
||||
return fields;
|
||||
});
|
||||
},
|
||||
on_click_stage: function (ev) {
|
||||
on_click_stage: _.debounce(function (ev) {
|
||||
var self = this;
|
||||
var $li = $(ev.currentTarget);
|
||||
var ul = $li.closest('.oe_form_field_status');
|
||||
if (this.view.is_disabled) {
|
||||
return;
|
||||
}
|
||||
var val;
|
||||
if (ul.attr('disabled')) {
|
||||
return;
|
||||
}
|
||||
if (this.field.type == "many2one") {
|
||||
val = parseInt($li.data("id"), 10);
|
||||
}
|
||||
@@ -1551,13 +1556,16 @@ var FieldStatus = common.AbstractField.extend({
|
||||
this.view.recursive_save().done(function() {
|
||||
var change = {};
|
||||
change[self.name] = val;
|
||||
ul.attr('disabled', true);
|
||||
self.view.dataset.write(self.view.datarecord.id, change).done(function() {
|
||||
self.view.reload();
|
||||
}).always(function() {
|
||||
ul.removeAttr('disabled');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}, 300),
|
||||
});
|
||||
|
||||
var FieldMonetary = FieldFloat.extend({
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
var modal = $(this).parentsUntil('body', '.modal');
|
||||
if (modal.hasClass('o_database_backup')) {
|
||||
$(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
|
||||
|
||||
|
||||
@openerp.tests.common.at_install(False)
|
||||
@openerp.tests.common.post_install(True)
|
||||
class TestUi(openerp.tests.HttpCase):
|
||||
|
||||
post_install = True
|
||||
|
||||
@@ -561,6 +561,10 @@
|
||||
<xpath expr="//div[@id='wrap']" position="inside">
|
||||
<div class="oe_structure">
|
||||
<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"/>
|
||||
<small>Odoo Version <t t-raw="version.get('server_version')"/></small>
|
||||
</h1>
|
||||
@@ -608,6 +612,7 @@
|
||||
</t>
|
||||
</dl>
|
||||
</div>
|
||||
</t>
|
||||
</section>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -228,8 +228,8 @@ class WebsiteBlog(http.Controller):
|
||||
all_post_ids = blog_post_obj.search(cr, uid, [('blog_id', '=', blog.id)], context=context)
|
||||
# should always return at least the current post
|
||||
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 \
|
||||
else current_blog_post_index + 1]
|
||||
nb_posts = len(all_post_ids)
|
||||
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
|
||||
|
||||
values = {
|
||||
|
||||
@@ -216,7 +216,7 @@ class WebsiteForm(http.Controller):
|
||||
# If some attachments didn't match a field on the model,
|
||||
# we create a mail.message to link them to the record
|
||||
if orphan_attachment_ids:
|
||||
if model.name != 'mail.mail':
|
||||
if model.model != 'mail.mail':
|
||||
values = {
|
||||
'body': _('<p>Attached files : </p>'),
|
||||
'model': model.model,
|
||||
|
||||
@@ -209,14 +209,14 @@ class Post(models.Model):
|
||||
)
|
||||
|
||||
# history
|
||||
create_date = fields.Datetime('Asked on', select=True, readonly=True)
|
||||
create_uid = fields.Many2one('res.users', string='Created by', select=True, readonly=True)
|
||||
write_date = fields.Datetime('Update on', select=True, readonly=True)
|
||||
create_date = fields.Datetime('Asked on', index=True, readonly=True)
|
||||
create_uid = fields.Many2one('res.users', string='Created by', index=True, readonly=True)
|
||||
write_date = fields.Datetime('Update on', index=True, readonly=True)
|
||||
bump_date = fields.Datetime('Bumped on', readonly=True,
|
||||
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 "
|
||||
"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)
|
||||
|
||||
# vote
|
||||
@@ -244,7 +244,7 @@ class Post(models.Model):
|
||||
|
||||
# closing
|
||||
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)
|
||||
|
||||
# karma calculation and access
|
||||
@@ -812,7 +812,7 @@ class Vote(models.Model):
|
||||
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)
|
||||
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)
|
||||
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']))
|
||||
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)
|
||||
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")
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
.o_website_quote .bs-sidebar {
|
||||
position: fixed;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index : 1;
|
||||
background-color: #f7f5fa;
|
||||
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 {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
@@ -56,14 +50,6 @@
|
||||
.o_website_quote .bs-sidebar .nav > .active > ul {
|
||||
display: block;
|
||||
}
|
||||
.o_website_quote .bs-sidebar {
|
||||
width: 213px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.o_website_quote .bs-sidebar {
|
||||
width: 263px;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
|
||||
@@ -2,6 +2,7 @@ odoo.define('website_quote.website_quote', function (require) {
|
||||
'use strict';
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var config = require('web.config');
|
||||
var Widget = require('web.Widget');
|
||||
var website = require('website.website');
|
||||
|
||||
@@ -175,6 +176,22 @@ if(!$('.o_website_quote').length) {
|
||||
nav_menu.setElement($('[data-id="quote_sidebar"]'));
|
||||
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) {
|
||||
|
||||
@@ -295,8 +295,8 @@ class website_slides(http.Controller):
|
||||
payload = request.httprequest.content_length
|
||||
# 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.
|
||||
if (payload / 1024 / 1024 > 15):
|
||||
return {'error': _('File is too big. File size cannot exceed 15MB')}
|
||||
if (payload / 1024 / 1024 > 25):
|
||||
return {'error': _('File is too big. File size cannot exceed 25MB')}
|
||||
|
||||
values = dict((fname, post[fname]) for fname in [
|
||||
'name', 'url', 'tag_ids', 'slide_type', 'channel_id',
|
||||
|
||||
@@ -192,7 +192,7 @@ class EmbeddedSlide(models.Model):
|
||||
_description = 'Embedded Slides View Counter'
|
||||
_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)
|
||||
count_views = fields.Integer('# Views', default=1)
|
||||
|
||||
|
||||
@@ -86,8 +86,8 @@ var SlideDialog = Widget.extend({
|
||||
this.reset_file();
|
||||
return;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 15) {
|
||||
this.display_alert(_t("File is too big. File size cannot exceed 15MB"));
|
||||
if (file.size / 1024 / 1024 > 25) {
|
||||
this.display_alert(_t("File is too big. File size cannot exceed 25MB"));
|
||||
this.reset_file();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
<main class="container-fluid {{ ' '.join(classes) }}">
|
||||
{% if pagename != master_doc %}
|
||||
<div class="row">
|
||||
{% if 'has-toc' not in meta %}
|
||||
{% if 'has-toc' not in meta and not (pagename in toc) %}
|
||||
<aside>
|
||||
<div class="navbar-aside text-center">
|
||||
{{ toc }}
|
||||
@@ -242,7 +242,7 @@
|
||||
</div>
|
||||
</aside>
|
||||
{% 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 %}
|
||||
{% block body %} {% endblock %}
|
||||
{% 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;
|
||||
}
|
||||
}
|
||||
article.doc-body.index-category {
|
||||
min-height: 300px;
|
||||
min-height: 30vh;
|
||||
}
|
||||
article.doc-body.index-category li.toctree-l1 {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.content-switcher {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
@@ -742,6 +742,15 @@ article.doc-body {
|
||||
.lead;
|
||||
}
|
||||
}
|
||||
|
||||
&.index-category {
|
||||
min-height: 300px;
|
||||
min-height: 30vh;
|
||||
|
||||
li.toctree-l1 {
|
||||
padding: 5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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
|
||||
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
|
||||
--------------------
|
||||
|
||||
@@ -422,3 +428,4 @@ which will generate a 32 characters pseudorandom printable string.
|
||||
.. _use an SSH tunnel:
|
||||
http://www.postgresql.org/docs/9.3/static/ssh-tunnels.html
|
||||
.. _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()
|
||||
for data in self.browse(cr, uid, ids, context):
|
||||
model = data.model.model
|
||||
model_obj = self.pool.get(model)
|
||||
if not model_obj:
|
||||
continue
|
||||
if model in self.pool:
|
||||
table = self.pool[model]._table
|
||||
else:
|
||||
table = model.replace('.', '_')
|
||||
name = openerp.tools.ustr(data.name)
|
||||
typ = data.type
|
||||
|
||||
@@ -641,17 +642,17 @@ class ir_model_constraint(Model):
|
||||
if typ == 'f':
|
||||
# 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)
|
||||
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():
|
||||
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)
|
||||
|
||||
if typ == 'u':
|
||||
# test if constraint exists
|
||||
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():
|
||||
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)
|
||||
|
||||
self.unlink(cr, uid, ids, context)
|
||||
|
||||
@@ -227,10 +227,12 @@ class CurrencyRate(models.Model):
|
||||
_description = "Currency Rate"
|
||||
_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')
|
||||
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
|
||||
def name_search(self, name, args=None, operator='ilike', limit=80):
|
||||
|
||||
@@ -23,6 +23,28 @@
|
||||
</field>
|
||||
</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
|
||||
id="act_view_currency_rates"
|
||||
name="Currency Rates"
|
||||
|
||||
@@ -128,11 +128,14 @@ def main(args):
|
||||
# bit overkill, but better safe than sorry I guess
|
||||
csv.field_size_limit(500 * 1024 * 1024)
|
||||
|
||||
if config["db_name"]:
|
||||
try:
|
||||
openerp.service.db._create_empty_database(config["db_name"])
|
||||
except openerp.service.db.DatabaseExists:
|
||||
pass
|
||||
preload = []
|
||||
if config['db_name']:
|
||||
preload = config['db_name'].split(',')
|
||||
for db_name in preload:
|
||||
try:
|
||||
openerp.service.db._create_empty_database(db_name)
|
||||
except openerp.service.db.DatabaseExists:
|
||||
pass
|
||||
|
||||
if config["test_file"]:
|
||||
config["test_enable"] = True
|
||||
@@ -150,10 +153,6 @@ def main(args):
|
||||
if config['workers']:
|
||||
openerp.multi_process = True
|
||||
|
||||
preload = []
|
||||
if config['db_name']:
|
||||
preload = config['db_name'].split(',')
|
||||
|
||||
stop = config["stop_after_init"]
|
||||
|
||||
setup_pid_file()
|
||||
|
||||
+2
-2
@@ -210,7 +210,7 @@ class WebRequest(object):
|
||||
to a database.
|
||||
"""
|
||||
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)
|
||||
|
||||
@lazy_property
|
||||
@@ -246,7 +246,7 @@ class WebRequest(object):
|
||||
# can not be a lazy_property because manual rollback in _call_function
|
||||
# if already set (?)
|
||||
if not self.db:
|
||||
return RuntimeError('request not bound to a database')
|
||||
raise RuntimeError('request not bound to a database')
|
||||
if not self._cr:
|
||||
self._cr = self.registry.cursor()
|
||||
return self._cr
|
||||
|
||||
+1
-1
@@ -1354,7 +1354,7 @@ class BaseModel(object):
|
||||
except ValidationError, e:
|
||||
raise
|
||||
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
|
||||
def default_get(self, fields_list):
|
||||
|
||||
@@ -859,11 +859,7 @@ def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kin
|
||||
fp.close()
|
||||
|
||||
def convert_sql_import(cr, fp):
|
||||
queries = fp.read().split(';')
|
||||
for query in queries:
|
||||
new_query = ' '.join(query.split())
|
||||
if new_query:
|
||||
cr.execute(new_query)
|
||||
cr.execute(fp.read())
|
||||
|
||||
def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
|
||||
noupdate=False):
|
||||
|
||||
+22
-1
@@ -14,6 +14,7 @@ import subprocess
|
||||
import logging
|
||||
import os
|
||||
import passlib.utils
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
@@ -333,6 +334,26 @@ def topological_sort(elems):
|
||||
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 that stores an updateable string (used in wizards)
|
||||
"""
|
||||
@@ -928,7 +949,7 @@ class CountingStream(object):
|
||||
|
||||
def stripped_sys_argv(*strip_args):
|
||||
"""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)
|
||||
takes_value = dict((s, config.parser.get_option(s).takes_value()) for s in strip_args)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user