Merge remote-tracking branch 'odoo/master' into master-sass-in-bundles-fme
This commit is contained in:
commit
0e9aca1013
|
@ -1,2 +0,0 @@
|
||||||
.*
|
|
||||||
**/node_modules
|
|
|
@ -26,6 +26,9 @@ from openerp.report import report_sxw
|
||||||
|
|
||||||
class account_bank_statement(osv.osv):
|
class account_bank_statement(osv.osv):
|
||||||
def create(self, cr, uid, vals, context=None):
|
def create(self, cr, uid, vals, context=None):
|
||||||
|
if vals.get('name', '/') == '/':
|
||||||
|
journal_id = vals.get('journal_id', self._default_journal_id(cr, uid, context=context))
|
||||||
|
vals['name'] = self._compute_default_statement_name(cr, uid, journal_id, context=context)
|
||||||
if 'line_ids' in vals:
|
if 'line_ids' in vals:
|
||||||
for idx, line in enumerate(vals['line_ids']):
|
for idx, line in enumerate(vals['line_ids']):
|
||||||
line[2]['sequence'] = idx + 1
|
line[2]['sequence'] = idx + 1
|
||||||
|
@ -65,17 +68,14 @@ class account_bank_statement(osv.osv):
|
||||||
return periods[0]
|
return periods[0]
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _compute_default_statement_name(self, cr, uid, context=None):
|
def _compute_default_statement_name(self, cr, uid, journal_id, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
obj_seq = self.pool.get('ir.sequence')
|
obj_seq = self.pool.get('ir.sequence')
|
||||||
default_journal_id = self._default_journal_id(cr, uid, context=context)
|
period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context)
|
||||||
if default_journal_id != False:
|
context['fiscalyear_id'] = period.fiscalyear_id.id
|
||||||
period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context)
|
journal = self.pool.get('account.journal').browse(cr, uid, journal_id, None)
|
||||||
context['fiscalyear_id'] = period.fiscalyear_id.id
|
return obj_seq.next_by_id(cr, uid, journal.sequence_id.id, context=context)
|
||||||
journal = self.pool.get('account.journal').browse(cr, uid, default_journal_id, None)
|
|
||||||
return obj_seq.next_by_id(cr, uid, journal.sequence_id.id, context=context)
|
|
||||||
return obj_seq.next_by_code(cr, uid, 'account.bank.statement', context=context)
|
|
||||||
|
|
||||||
def _currency(self, cursor, user, ids, name, args, context=None):
|
def _currency(self, cursor, user, ids, name, args, context=None):
|
||||||
res = {}
|
res = {}
|
||||||
|
@ -150,7 +150,7 @@ class account_bank_statement(osv.osv):
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'name': _compute_default_statement_name,
|
'name': '/',
|
||||||
'date': fields.date.context_today,
|
'date': fields.date.context_today,
|
||||||
'state': 'draft',
|
'state': 'draft',
|
||||||
'journal_id': _default_journal_id,
|
'journal_id': _default_journal_id,
|
||||||
|
@ -213,11 +213,8 @@ class account_bank_statement(osv.osv):
|
||||||
|
|
||||||
def _get_counter_part_account(sefl, cr, uid, st_line, context=None):
|
def _get_counter_part_account(sefl, cr, uid, st_line, context=None):
|
||||||
"""Retrieve the account to use in the counterpart move.
|
"""Retrieve the account to use in the counterpart move.
|
||||||
This method may be overridden to implement custom move generation (making sure to
|
|
||||||
call super() to establish a clean extension chain).
|
|
||||||
|
|
||||||
:param browse_record st_line: account.bank.statement.line record to
|
:param browse_record st_line: account.bank.statement.line record to create the move from.
|
||||||
create the move from.
|
|
||||||
:return: int/long of the account.account to use as counterpart
|
:return: int/long of the account.account to use as counterpart
|
||||||
"""
|
"""
|
||||||
if st_line.amount >= 0:
|
if st_line.amount >= 0:
|
||||||
|
@ -226,26 +223,19 @@ class account_bank_statement(osv.osv):
|
||||||
|
|
||||||
def _get_counter_part_partner(sefl, cr, uid, st_line, context=None):
|
def _get_counter_part_partner(sefl, cr, uid, st_line, context=None):
|
||||||
"""Retrieve the partner to use in the counterpart move.
|
"""Retrieve the partner to use in the counterpart move.
|
||||||
This method may be overridden to implement custom move generation (making sure to
|
|
||||||
call super() to establish a clean extension chain).
|
|
||||||
|
|
||||||
:param browse_record st_line: account.bank.statement.line record to
|
:param browse_record st_line: account.bank.statement.line record to create the move from.
|
||||||
create the move from.
|
|
||||||
:return: int/long of the res.partner to use as counterpart
|
:return: int/long of the res.partner to use as counterpart
|
||||||
"""
|
"""
|
||||||
return st_line.partner_id and st_line.partner_id.id or False
|
return st_line.partner_id and st_line.partner_id.id or False
|
||||||
|
|
||||||
def _prepare_bank_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, context=None):
|
def _prepare_bank_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, context=None):
|
||||||
"""Compute the args to build the dict of values to create the counter part move line from a
|
"""Compute the args to build the dict of values to create the counter part move line from a
|
||||||
statement line by calling the _prepare_move_line_vals. This method may be
|
statement line by calling the _prepare_move_line_vals.
|
||||||
overridden to implement custom move generation (making sure to call super() to
|
|
||||||
establish a clean extension chain).
|
|
||||||
|
|
||||||
:param browse_record st_line: account.bank.statement.line record to
|
:param browse_record st_line: account.bank.statement.line record to create the move from.
|
||||||
create the move from.
|
|
||||||
:param int/long move_id: ID of the account.move to link the move line
|
:param int/long move_id: ID of the account.move to link the move line
|
||||||
:param float amount: amount of the move line
|
:param float amount: amount of the move line
|
||||||
:param int/long account_id: ID of account to use as counter part
|
|
||||||
:param int/long company_currency_id: ID of currency of the concerned company
|
:param int/long company_currency_id: ID of currency of the concerned company
|
||||||
:return: dict of value to create() the bank account.move.line
|
:return: dict of value to create() the bank account.move.line
|
||||||
"""
|
"""
|
||||||
|
@ -258,7 +248,6 @@ class account_bank_statement(osv.osv):
|
||||||
if st_line.statement_id.currency.id != company_currency_id:
|
if st_line.statement_id.currency.id != company_currency_id:
|
||||||
amt_cur = st_line.amount
|
amt_cur = st_line.amount
|
||||||
cur_id = st_line.currency_id or st_line.statement_id.currency.id
|
cur_id = st_line.currency_id or st_line.statement_id.currency.id
|
||||||
# TODO : FIXME the amount should be in the journal currency
|
|
||||||
if st_line.currency_id and st_line.amount_currency:
|
if st_line.currency_id and st_line.amount_currency:
|
||||||
amt_cur = st_line.amount_currency
|
amt_cur = st_line.amount_currency
|
||||||
cur_id = st_line.currency_id.id
|
cur_id = st_line.currency_id.id
|
||||||
|
@ -269,9 +258,7 @@ class account_bank_statement(osv.osv):
|
||||||
def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id=False,
|
def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id=False,
|
||||||
amount_currency=False, account_id=False, partner_id=False, context=None):
|
amount_currency=False, account_id=False, partner_id=False, context=None):
|
||||||
"""Prepare the dict of values to create the move line from a
|
"""Prepare the dict of values to create the move line from a
|
||||||
statement line. All non-mandatory args will replace the default computed one.
|
statement line.
|
||||||
This method may be overridden to implement custom move generation (making sure to
|
|
||||||
call super() to establish a clean extension chain).
|
|
||||||
|
|
||||||
:param browse_record st_line: account.bank.statement.line record to
|
:param browse_record st_line: account.bank.statement.line record to
|
||||||
create the move from.
|
create the move from.
|
||||||
|
@ -342,21 +329,29 @@ class account_bank_statement(osv.osv):
|
||||||
move_ids.append(st_line.journal_entry_id.id)
|
move_ids.append(st_line.journal_entry_id.id)
|
||||||
self.pool.get('account.move').post(cr, uid, move_ids, context=context)
|
self.pool.get('account.move').post(cr, uid, move_ids, context=context)
|
||||||
self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st.name,), context=context)
|
self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st.name,), context=context)
|
||||||
|
self.link_bank_to_partner(cr, uid, ids, context=context)
|
||||||
return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
|
return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
|
||||||
|
|
||||||
def button_cancel(self, cr, uid, ids, context=None):
|
def button_cancel(self, cr, uid, ids, context=None):
|
||||||
done = []
|
|
||||||
account_move_obj = self.pool.get('account.move')
|
account_move_obj = self.pool.get('account.move')
|
||||||
|
reconcile_pool = self.pool.get('account.move.reconcile')
|
||||||
|
move_line_pool = self.pool.get('account.move.line')
|
||||||
|
move_ids = []
|
||||||
for st in self.browse(cr, uid, ids, context=context):
|
for st in self.browse(cr, uid, ids, context=context):
|
||||||
if st.state=='draft':
|
|
||||||
continue
|
|
||||||
move_ids = []
|
|
||||||
for line in st.line_ids:
|
for line in st.line_ids:
|
||||||
move_ids += [x.id for x in line.move_ids]
|
if line.journal_entry_id:
|
||||||
|
move_ids.append(line.journal_entry_id.id)
|
||||||
|
for aml in line.journal_entry_id.line_id:
|
||||||
|
if aml.reconcile_id:
|
||||||
|
move_lines = [l.id for l in aml.reconcile_id.line_id]
|
||||||
|
move_lines.remove(aml.id)
|
||||||
|
reconcile_pool.unlink(cr, uid, [aml.reconcile_id.id], context=context)
|
||||||
|
if len(move_lines) >= 2:
|
||||||
|
move_line_pool.reconcile_partial(cr, uid, move_lines, 'auto', context=context)
|
||||||
|
if move_ids:
|
||||||
account_move_obj.button_cancel(cr, uid, move_ids, context=context)
|
account_move_obj.button_cancel(cr, uid, move_ids, context=context)
|
||||||
account_move_obj.unlink(cr, uid, move_ids, context)
|
account_move_obj.unlink(cr, uid, move_ids, context)
|
||||||
done.append(st.id)
|
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
|
||||||
return self.write(cr, uid, done, {'state':'draft'}, context=context)
|
|
||||||
|
|
||||||
def _compute_balance_end_real(self, cr, uid, journal_id, context=None):
|
def _compute_balance_end_real(self, cr, uid, journal_id, context=None):
|
||||||
res = False
|
res = False
|
||||||
|
@ -417,18 +412,35 @@ class account_bank_statement(osv.osv):
|
||||||
|
|
||||||
def number_of_lines_reconciled(self, cr, uid, id, context=None):
|
def number_of_lines_reconciled(self, cr, uid, id, context=None):
|
||||||
bsl_obj = self.pool.get('account.bank.statement.line')
|
bsl_obj = self.pool.get('account.bank.statement.line')
|
||||||
return bsl_obj.search_count(cr, uid, [('statement_id','=',id), ('journal_entry_id','!=',False)], context=context)
|
return bsl_obj.search_count(cr, uid, [('statement_id', '=', id), ('journal_entry_id', '!=', False)], context=context)
|
||||||
|
|
||||||
def get_format_currency_js_function(self, cr, uid, id, context=None):
|
def get_format_currency_js_function(self, cr, uid, id, context=None):
|
||||||
""" Returns a string that can be used to instanciate a javascript function.
|
""" Returns a string that can be used to instanciate a javascript function.
|
||||||
That function formats a number according to the statement's journal currency """
|
That function formats a number according to the statement line's currency or the statement currency"""
|
||||||
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
|
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
|
||||||
currency_obj = id and self.browse(cr, uid, id, context=context).journal_id.currency or company_currency
|
st = id and self.browse(cr, uid, id, context=context)
|
||||||
|
if not st:
|
||||||
|
return
|
||||||
|
statement_currency = st.journal_id.currency or company_currency
|
||||||
digits = 2 # TODO : from currency_obj
|
digits = 2 # TODO : from currency_obj
|
||||||
if currency_obj.position == 'after':
|
function = ""
|
||||||
return "return amount.toFixed("+str(digits)+") + ' "+currency_obj.symbol+"';"
|
done_currencies = []
|
||||||
elif currency_obj.position == 'before':
|
for st_line in st.line_ids:
|
||||||
return "return '"+currency_obj.symbol+" ' + amount.toFixed("+str(digits)+");"
|
st_line_currency = st_line.currency_id or statement_currency
|
||||||
|
if st_line_currency.id not in done_currencies:
|
||||||
|
if st_line_currency.position == 'after':
|
||||||
|
return_str = "return amount.toFixed(" + str(digits) + ") + ' " + st_line_currency.symbol + "';"
|
||||||
|
else:
|
||||||
|
return_str = "return '" + st_line_currency.symbol + " ' + amount.toFixed(" + str(digits) + ");"
|
||||||
|
function += "if (currency_id === " + str(st_line_currency.id) + "){ " + return_str + " }"
|
||||||
|
done_currencies.append(st_line_currency.id)
|
||||||
|
return function
|
||||||
|
|
||||||
|
def link_bank_to_partner(self, cr, uid, ids, context=None):
|
||||||
|
for statement in self.browse(cr, uid, ids, context=context):
|
||||||
|
for st_line in statement.line_ids:
|
||||||
|
if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id.id != st_line.partner_id.id:
|
||||||
|
self.pool.get('res.partner.bank').write(cr, uid, [st_line.bank_account_id.id], {'partner_id': st_line.partner_id.id}, context=context)
|
||||||
|
|
||||||
class account_bank_statement_line(osv.osv):
|
class account_bank_statement_line(osv.osv):
|
||||||
|
|
||||||
|
@ -444,35 +456,40 @@ class account_bank_statement_line(osv.osv):
|
||||||
}
|
}
|
||||||
for mv_line in reconciliation_data['reconciliation_proposition']:
|
for mv_line in reconciliation_data['reconciliation_proposition']:
|
||||||
mv_line_ids_selected.append(mv_line['id'])
|
mv_line_ids_selected.append(mv_line['id'])
|
||||||
ret.append(reconciliation_data);
|
ret.append(reconciliation_data)
|
||||||
|
|
||||||
# Check if, now that 'candidate' move lines were selected, there are moves left for statement lines
|
# Check if, now that 'candidate' move lines were selected, there are moves left for statement lines
|
||||||
for reconciliation_data in ret:
|
#for reconciliation_data in ret:
|
||||||
if not reconciliation_data['st_line']['has_no_partner']:
|
# if not reconciliation_data['st_line']['has_no_partner']:
|
||||||
if self.get_move_lines_counterparts(cr, uid, reconciliation_data['st_line']['id'], excluded_ids=mv_line_ids_selected, count=True, context=context) == 0:
|
# st_line = self.browse(cr, uid, reconciliation_data['st_line']['id'], context=context)
|
||||||
reconciliation_data['st_line']['no_match'] = True
|
# if not self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=mv_line_ids_selected, count=True, context=context):
|
||||||
|
# reconciliation_data['st_line']['no_match'] = True
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_statement_line_for_reconciliation(self, cr, uid, id, context=None):
|
def get_statement_line_for_reconciliation(self, cr, uid, id, context=None):
|
||||||
""" Returns the data required by the bank statement reconciliation use case """
|
""" Returns the data required by the bank statement reconciliation use case """
|
||||||
line = self.browse(cr, uid, id, context=context)
|
line = self.browse(cr, uid, id, context=context)
|
||||||
statement_currency = line.journal_id.currency or line.journal_id.company_id.currency_id
|
statement_currency = line.journal_id.currency or line.journal_id.company_id.currency_id
|
||||||
|
amount = line.amount
|
||||||
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_widget', context=context)
|
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_widget', context=context)
|
||||||
amount_str = line.amount > 0 and line.amount or -line.amount
|
amount_str = line.amount > 0 and line.amount or -line.amount
|
||||||
amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency)
|
amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency)
|
||||||
amount_currency_str = ""
|
amount_currency_str = ""
|
||||||
if line.amount_currency and line.currency_id:
|
if line.amount_currency and line.currency_id:
|
||||||
amount_currency_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
|
amount_currency_str = amount_str
|
||||||
|
amount_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
|
||||||
|
amount = line.amount_currency
|
||||||
|
|
||||||
dict = {
|
data = {
|
||||||
'id': line.id,
|
'id': line.id,
|
||||||
'ref': line.ref,
|
'ref': line.ref,
|
||||||
'note': line.note or "",
|
'note': line.note or "",
|
||||||
'name': line.name,
|
'name': line.name,
|
||||||
'date': line.date,
|
'date': line.date,
|
||||||
'amount': line.amount,
|
'amount': amount,
|
||||||
'amount_str': amount_str,
|
'amount_str': amount_str,
|
||||||
'no_match': self.get_move_lines_counterparts(cr, uid, id, count=True, context=context) == 0 and line.partner_id.id,
|
'currency_id': line.currency_id.id or statement_currency.id,
|
||||||
|
'no_match': self.get_move_lines_counterparts(cr, uid, line, count=True, context=context) == 0,
|
||||||
'partner_id': line.partner_id.id,
|
'partner_id': line.partner_id.id,
|
||||||
'statement_id': line.statement_id.id,
|
'statement_id': line.statement_id.id,
|
||||||
'account_code': line.journal_id.default_debit_account_id.code,
|
'account_code': line.journal_id.default_debit_account_id.code,
|
||||||
|
@ -482,93 +499,116 @@ class account_bank_statement_line(osv.osv):
|
||||||
'has_no_partner': not line.partner_id.id,
|
'has_no_partner': not line.partner_id.id,
|
||||||
}
|
}
|
||||||
if line.partner_id.id:
|
if line.partner_id.id:
|
||||||
if line.amount > 0:
|
data['open_balance_account_id'] = line.partner_id.property_account_payable.id
|
||||||
dict['open_balance_account_id'] = line.partner_id.property_account_receivable.id
|
if amount > 0:
|
||||||
else:
|
data['open_balance_account_id'] = line.partner_id.property_account_receivable.id
|
||||||
dict['open_balance_account_id'] = line.partner_id.property_account_payable.id
|
return data
|
||||||
return dict
|
|
||||||
|
def search_structured_com(self, cr, uid, st_line, context=None):
|
||||||
|
if not st_line.ref:
|
||||||
|
return
|
||||||
|
domain = [('ref', '=', st_line.ref)]
|
||||||
|
if st_line.partner_id:
|
||||||
|
domain += [('partner_id', '=', st_line.partner_id.id)]
|
||||||
|
ids = self.pool.get('account.move.line').search(cr, uid, domain, limit=1, context=context)
|
||||||
|
return ids and ids[0] or False
|
||||||
|
|
||||||
def get_reconciliation_proposition(self, cr, uid, id, excluded_ids=[], context=None):
|
def get_reconciliation_proposition(self, cr, uid, id, excluded_ids=[], context=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. """
|
||||||
st_line = self.browse(cr, uid, id, context=context)
|
st_line = self.browse(cr, uid, id, context=context)
|
||||||
company_currency = st_line.journal_id.company_id.currency_id.id
|
company_currency = st_line.journal_id.company_id.currency_id.id
|
||||||
statement_currency = st_line.journal_id.currency.id or company_currency
|
statement_currency = st_line.journal_id.currency.id or company_currency
|
||||||
|
|
||||||
# either use the unsigned debit/credit fields or the signed amount_currency field
|
# either use the unsigned debit/credit fields or the signed amount_currency field
|
||||||
sign = 1
|
sign = 1
|
||||||
if statement_currency == company_currency:
|
if statement_currency == company_currency:
|
||||||
|
amount_field = 'credit'
|
||||||
if st_line.amount > 0:
|
if st_line.amount > 0:
|
||||||
amount_field = 'debit'
|
amount_field = 'debit'
|
||||||
else:
|
|
||||||
amount_field = 'credit'
|
|
||||||
else:
|
else:
|
||||||
amount_field = 'amount_currency'
|
amount_field = 'amount_currency'
|
||||||
if st_line.amount < 0:
|
if st_line.amount < 0:
|
||||||
sign = -1
|
sign = -1
|
||||||
|
|
||||||
|
# look for structured communication
|
||||||
|
exact_match_id = self.search_structured_com(cr, uid, st_line, context=context)
|
||||||
|
if exact_match_id:
|
||||||
|
return self.make_counter_part_lines(cr, uid, st_line, [exact_match_id], count=False, context=context)
|
||||||
|
#we don't propose anything if there is no partner detected
|
||||||
|
if not st_line.partner_id.id:
|
||||||
|
return []
|
||||||
# look for exact match
|
# look for exact match
|
||||||
exact_match_id = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=1, additional_domain=[(amount_field,'=',(sign*st_line.amount))])
|
exact_match_id = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=1, additional_domain=[(amount_field, '=', (sign * st_line.amount))])
|
||||||
if exact_match_id:
|
if exact_match_id:
|
||||||
return exact_match_id
|
return exact_match_id
|
||||||
|
|
||||||
# select oldest move lines
|
# select oldest move lines
|
||||||
if sign == -1:
|
if sign == -1:
|
||||||
mv_lines = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field,'<',0)])
|
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '<', 0)])
|
||||||
else:
|
else:
|
||||||
mv_lines = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field,'>',0)])
|
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '>', 0)])
|
||||||
ret = []
|
ret = []
|
||||||
total = 0
|
total = 0
|
||||||
# get_move_lines_counterparts inverts debit and credit
|
# get_move_lines_counterparts inverts debit and credit
|
||||||
amount_field = 'debit' if amount_field == 'credit' else 'credit'
|
amount_field = 'debit' if amount_field == 'credit' else 'credit'
|
||||||
for line in mv_lines:
|
for line in mv_lines:
|
||||||
if total + line[amount_field] <= st_line.amount:
|
if total + line[amount_field] <= abs(st_line.amount):
|
||||||
ret.append(line)
|
ret.append(line)
|
||||||
total += line[amount_field]
|
total += line[amount_field]
|
||||||
else:
|
if total >= abs(st_line.amount):
|
||||||
break
|
break
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_move_lines_counterparts(self, cr, uid, id, excluded_ids=[], str="", offset=0, limit=None, count=False, additional_domain=[], context=None):
|
def get_move_lines_counterparts_id(self, cr, uid, st_line_id, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None):
|
||||||
|
st_line = self.browse(cr, uid, st_line_id, context=context)
|
||||||
|
return self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids, filter_str, offset, limit, count, additional_domain, context=context)
|
||||||
|
|
||||||
|
def get_move_lines_counterparts(self, cr, uid, st_line, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None):
|
||||||
""" Find the move lines that could be used to reconcile a statement line and returns the counterpart that could be created to reconcile them
|
""" Find the move lines that could be used to reconcile a statement line and returns the counterpart that could be created to reconcile them
|
||||||
If count is true, only returns the count.
|
If count is true, only returns the count.
|
||||||
|
|
||||||
:param integer id: the id of the statement line
|
:param st_line: the browse record of the statement line
|
||||||
:param integers list excluded_ids: ids of move lines that should not be fetched
|
:param integers list excluded_ids: ids of move lines that should not be fetched
|
||||||
:param string str: string to filter lines
|
:param string filter_str: string to filter lines
|
||||||
:param integer offset: offset of the request
|
:param integer offset: offset of the request
|
||||||
:param integer limit: number of lines to fetch
|
:param integer limit: number of lines to fetch
|
||||||
:param boolean count: just return the number of records
|
:param boolean count: just return the number of records
|
||||||
:param tuples list domain: additional domain restrictions
|
:param tuples list domain: additional domain restrictions
|
||||||
"""
|
"""
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
|
|
||||||
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_counterpart_widget', context=context)
|
|
||||||
st_line = self.browse(cr, uid, id, context=context)
|
|
||||||
company_currency = st_line.journal_id.company_id.currency_id
|
|
||||||
statement_currency = st_line.journal_id.currency or company_currency
|
|
||||||
mv_line_pool = self.pool.get('account.move.line')
|
mv_line_pool = self.pool.get('account.move.line')
|
||||||
currency_obj = self.pool.get('res.currency')
|
|
||||||
|
|
||||||
domain = additional_domain + [
|
domain = additional_domain + [
|
||||||
('partner_id', '=', st_line.partner_id.id),
|
|
||||||
('reconcile_id', '=', False),
|
('reconcile_id', '=', False),
|
||||||
('state','=','valid'),
|
('state', '=', 'valid'),
|
||||||
'|',('account_id.type', '=', 'receivable'),
|
|
||||||
('account_id.type', '=', 'payable'), #Let the front-end warn the user if he tries to mix payable and receivable in the same reconciliation
|
|
||||||
]
|
]
|
||||||
|
if st_line.partner_id.id:
|
||||||
|
domain += [('partner_id', '=', st_line.partner_id.id),
|
||||||
|
'|', ('account_id.type', '=', 'receivable'),
|
||||||
|
('account_id.type', '=', 'payable')]
|
||||||
|
else:
|
||||||
|
domain += [('account_id.reconcile', '=', True)]
|
||||||
|
#domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')]
|
||||||
if excluded_ids:
|
if excluded_ids:
|
||||||
domain.append(('id', 'not in', excluded_ids))
|
domain.append(('id', 'not in', excluded_ids))
|
||||||
if str:
|
if filter_str:
|
||||||
domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)]
|
if not st_line.partner_id:
|
||||||
|
domain += [ '|', ('partner_id.name', 'ilike', filter_str)]
|
||||||
|
domain += ['|', ('move_id.name', 'ilike', filter_str), ('move_id.ref', 'ilike', filter_str)]
|
||||||
|
line_ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context)
|
||||||
|
return self.make_counter_part_lines(cr, uid, st_line, line_ids, count=count, context=context)
|
||||||
|
|
||||||
|
def make_counter_part_lines(self, cr, uid, st_line, line_ids, count=False, context=None):
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
mv_line_pool = self.pool.get('account.move.line')
|
||||||
|
currency_obj = self.pool.get('res.currency')
|
||||||
|
company_currency = st_line.journal_id.company_id.currency_id
|
||||||
|
statement_currency = st_line.journal_id.currency or company_currency
|
||||||
|
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_counterpart_widget', context=context)
|
||||||
#partially reconciled lines can be displayed only once
|
#partially reconciled lines can be displayed only once
|
||||||
reconcile_partial_ids = []
|
reconcile_partial_ids = []
|
||||||
ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context)
|
|
||||||
|
|
||||||
if count:
|
if count:
|
||||||
nb_lines = 0
|
nb_lines = 0
|
||||||
for line in mv_line_pool.browse(cr, uid, ids, context=context):
|
for line in mv_line_pool.browse(cr, uid, line_ids, context=context):
|
||||||
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
|
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
|
||||||
continue
|
continue
|
||||||
nb_lines += 1
|
nb_lines += 1
|
||||||
|
@ -577,7 +617,7 @@ class account_bank_statement_line(osv.osv):
|
||||||
return nb_lines
|
return nb_lines
|
||||||
else:
|
else:
|
||||||
ret = []
|
ret = []
|
||||||
for line in mv_line_pool.browse(cr, uid, ids, context=context):
|
for line in mv_line_pool.browse(cr, uid, line_ids, context=context):
|
||||||
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
|
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
|
||||||
continue
|
continue
|
||||||
amount_currency_str = ""
|
amount_currency_str = ""
|
||||||
|
@ -595,8 +635,12 @@ class account_bank_statement_line(osv.osv):
|
||||||
'period_name': line.period_id.name,
|
'period_name': line.period_id.name,
|
||||||
'journal_name': line.journal_id.name,
|
'journal_name': line.journal_id.name,
|
||||||
'amount_currency_str': amount_currency_str,
|
'amount_currency_str': amount_currency_str,
|
||||||
|
'partner_id': line.partner_id.id,
|
||||||
|
'partner_name': line.partner_id.name,
|
||||||
|
'has_no_partner': not bool(st_line.partner_id.id),
|
||||||
}
|
}
|
||||||
if statement_currency.id != company_currency.id and line.currency_id and line.currency_id.id == statement_currency.id:
|
st_line_currency = st_line.currency_id or statement_currency
|
||||||
|
if st_line.currency_id and line.currency_id and line.currency_id.id == st_line.currency_id.id:
|
||||||
if line.amount_residual_currency < 0:
|
if line.amount_residual_currency < 0:
|
||||||
ret_line['debit'] = 0
|
ret_line['debit'] = 0
|
||||||
ret_line['credit'] = -line.amount_residual_currency
|
ret_line['credit'] = -line.amount_residual_currency
|
||||||
|
@ -613,21 +657,46 @@ class account_bank_statement_line(osv.osv):
|
||||||
ret_line['credit'] = line.amount_residual if line.debit != 0 else 0
|
ret_line['credit'] = line.amount_residual if line.debit != 0 else 0
|
||||||
ctx = context.copy()
|
ctx = context.copy()
|
||||||
ctx.update({'date': st_line.date})
|
ctx.update({'date': st_line.date})
|
||||||
ret_line['debit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, ret_line['debit'], context=ctx)
|
ret_line['debit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['debit'], context=ctx)
|
||||||
ret_line['credit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, ret_line['credit'], context=ctx)
|
ret_line['credit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['credit'], context=ctx)
|
||||||
ret_line['debit_str'] = rml_parser.formatLang(ret_line['debit'], currency_obj=statement_currency)
|
ret_line['debit_str'] = rml_parser.formatLang(ret_line['debit'], currency_obj=st_line_currency)
|
||||||
ret_line['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=statement_currency)
|
ret_line['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=st_line_currency)
|
||||||
ret.append(ret_line)
|
ret.append(ret_line)
|
||||||
if line.reconcile_partial_id:
|
if line.reconcile_partial_id:
|
||||||
reconcile_partial_ids.append(line.reconcile_partial_id.id)
|
reconcile_partial_ids.append(line.reconcile_partial_id.id)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def get_currency_rate_line(self, cr, uid, st_line, currency_diff, move_id, context=None):
|
||||||
|
if currency_diff < 0:
|
||||||
|
account_id = st_line.company_id.expense_currency_exchange_account_id.id
|
||||||
|
if not account_id:
|
||||||
|
raise osv.except_osv(_('Insufficient Configuration!'), _("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
|
||||||
|
else:
|
||||||
|
account_id = st_line.company_id.income_currency_exchange_account_id.id
|
||||||
|
if not account_id:
|
||||||
|
raise osv.except_osv(_('Insufficient Configuration!'), _("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
|
||||||
|
return {
|
||||||
|
'move_id': move_id,
|
||||||
|
'name': _('change') + ': ' + (st_line.name or '/'),
|
||||||
|
'period_id': st_line.statement_id.period_id.id,
|
||||||
|
'journal_id': st_line.journal_id.id,
|
||||||
|
'partner_id': st_line.partner_id.id,
|
||||||
|
'company_id': st_line.company_id.id,
|
||||||
|
'statement_id': st_line.statement_id.id,
|
||||||
|
'debit': currency_diff < 0 and -currency_diff or 0,
|
||||||
|
'credit': currency_diff > 0 and currency_diff or 0,
|
||||||
|
'date': st_line.date,
|
||||||
|
'account_id': account_id
|
||||||
|
}
|
||||||
|
|
||||||
def process_reconciliation(self, cr, uid, id, mv_line_dicts, context=None):
|
def process_reconciliation(self, cr, uid, id, mv_line_dicts, context=None):
|
||||||
""" Creates a move line for each item of mv_line_dicts and for the statement line. Reconcile a new move line with its counterpart_move_line_id if specified. Finally, mark the statement line as reconciled by putting the newly created move id in the column journal_entry_id.
|
""" Creates a move line for each item of mv_line_dicts and for the statement line. Reconcile a new move line with its counterpart_move_line_id if specified. Finally, mark the statement line as reconciled by putting the newly created move id in the column journal_entry_id.
|
||||||
|
|
||||||
:param int id: id of the bank statement line
|
:param int id: id of the bank statement line
|
||||||
:param list of dicts mv_line_dicts: move lines to create. If counterpart_move_line_id is specified, reconcile with it
|
:param list of dicts mv_line_dicts: move lines to create. If counterpart_move_line_id is specified, reconcile with it
|
||||||
"""
|
"""
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
st_line = self.browse(cr, uid, id, context=context)
|
st_line = self.browse(cr, uid, id, context=context)
|
||||||
company_currency = st_line.journal_id.company_id.currency_id
|
company_currency = st_line.journal_id.company_id.currency_id
|
||||||
statement_currency = st_line.journal_id.currency or company_currency
|
statement_currency = st_line.journal_id.currency or company_currency
|
||||||
|
@ -637,7 +706,7 @@ class account_bank_statement_line(osv.osv):
|
||||||
currency_obj = self.pool.get('res.currency')
|
currency_obj = self.pool.get('res.currency')
|
||||||
|
|
||||||
# Checks
|
# Checks
|
||||||
if st_line.journal_entry_id.id != False:
|
if st_line.journal_entry_id.id:
|
||||||
raise osv.except_osv(_('Error!'), _('The bank statement line was already reconciled.'))
|
raise osv.except_osv(_('Error!'), _('The bank statement line was already reconciled.'))
|
||||||
for mv_line_dict in mv_line_dicts:
|
for mv_line_dict in mv_line_dicts:
|
||||||
for field in ['debit', 'credit', 'amount_currency']:
|
for field in ['debit', 'credit', 'amount_currency']:
|
||||||
|
@ -657,37 +726,50 @@ class account_bank_statement_line(osv.osv):
|
||||||
amount = currency_obj.compute(cr, uid, st_line.statement_id.currency.id, company_currency.id, st_line.amount, context=context)
|
amount = currency_obj.compute(cr, uid, st_line.statement_id.currency.id, company_currency.id, st_line.amount, context=context)
|
||||||
bank_st_move_vals = bs_obj._prepare_bank_move_line(cr, uid, st_line, move_id, amount, company_currency.id, context=context)
|
bank_st_move_vals = bs_obj._prepare_bank_move_line(cr, uid, st_line, move_id, amount, company_currency.id, context=context)
|
||||||
aml_obj.create(cr, uid, bank_st_move_vals, context=context)
|
aml_obj.create(cr, uid, bank_st_move_vals, context=context)
|
||||||
st_line_currency_rate = bank_st_move_vals['amount_currency'] and statement_currency.id == company_currency.id and (bank_st_move_vals['amount_currency'] / st_line.amount) or False
|
|
||||||
st_line_currency = bank_st_move_vals['currency_id']
|
|
||||||
# Complete the dicts
|
# Complete the dicts
|
||||||
st_line_statement_id = st_line.statement_id.id
|
st_line_currency = st_line.currency_id or statement_currency
|
||||||
st_line_journal_id = st_line.journal_id.id
|
st_line_currency_rate = st_line.currency_id and statement_currency.id == company_currency.id and (st_line.amount_currency / st_line.amount) or False
|
||||||
st_line_partner_id = st_line.partner_id.id
|
to_create = []
|
||||||
st_line_company_id = st_line.company_id.id
|
|
||||||
st_line_period_id = st_line.statement_id.period_id.id
|
|
||||||
for mv_line_dict in mv_line_dicts:
|
for mv_line_dict in mv_line_dicts:
|
||||||
mv_line_dict['ref'] = move_name
|
mv_line_dict['ref'] = move_name
|
||||||
mv_line_dict['move_id'] = move_id
|
mv_line_dict['move_id'] = move_id
|
||||||
mv_line_dict['period_id'] = st_line_period_id
|
mv_line_dict['period_id'] = st_line.statement_id.period_id.id
|
||||||
mv_line_dict['journal_id'] = st_line_journal_id
|
mv_line_dict['journal_id'] = st_line.journal_id.id
|
||||||
mv_line_dict['partner_id'] = st_line_partner_id
|
mv_line_dict['company_id'] = st_line.company_id.id
|
||||||
mv_line_dict['company_id'] = st_line_company_id
|
mv_line_dict['statement_id'] = st_line.statement_id.id
|
||||||
mv_line_dict['statement_id'] = st_line_statement_id
|
|
||||||
if mv_line_dict.get('counterpart_move_line_id'):
|
if mv_line_dict.get('counterpart_move_line_id'):
|
||||||
mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context)
|
mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context)
|
||||||
mv_line_dict['account_id'] = mv_line.account_id.id
|
mv_line_dict['account_id'] = mv_line.account_id.id
|
||||||
if statement_currency.id != company_currency.id:
|
if st_line_currency.id != company_currency.id:
|
||||||
mv_line_dict['amount_currency'] = mv_line_dict['debit'] - mv_line_dict['credit']
|
mv_line_dict['amount_currency'] = mv_line_dict['debit'] - mv_line_dict['credit']
|
||||||
mv_line_dict['currency_id'] = statement_currency.id
|
mv_line_dict['currency_id'] = st_line_currency.id
|
||||||
mv_line_dict['debit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['debit'])
|
if st_line.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
|
||||||
mv_line_dict['credit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['credit'])
|
debit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['debit'] / st_line_currency_rate)
|
||||||
elif st_line_currency and st_line_currency_rate:
|
credit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['credit'] / st_line_currency_rate)
|
||||||
mv_line_dict['amount_currency'] = self.pool.get('res.currency').round(cr, uid, st_line.currency_id, (mv_line_dict['debit'] - mv_line_dict['credit']) * st_line_currency_rate)
|
else:
|
||||||
mv_line_dict['currency_id'] = st_line_currency
|
debit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=context)
|
||||||
|
credit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=context)
|
||||||
|
if mv_line_dict.get('counterpart_move_line_id'):
|
||||||
|
#post an account line that use the same currency rate than the counterpart (to balance the account) and post the difference in another line
|
||||||
|
ctx = context.copy()
|
||||||
|
ctx['date'] = mv_line.date
|
||||||
|
debit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=ctx)
|
||||||
|
credit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx)
|
||||||
|
mv_line_dict['credit'] = credit_at_old_rate
|
||||||
|
mv_line_dict['debit'] = debit_at_old_rate
|
||||||
|
if debit_at_old_rate - debit_at_current_rate:
|
||||||
|
currency_diff = debit_at_current_rate - debit_at_old_rate
|
||||||
|
to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context))
|
||||||
|
if credit_at_old_rate - credit_at_current_rate:
|
||||||
|
currency_diff = credit_at_current_rate - credit_at_old_rate
|
||||||
|
to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context))
|
||||||
|
else:
|
||||||
|
mv_line_dict['debit'] = debit_at_current_rate
|
||||||
|
mv_line_dict['credit'] = credit_at_current_rate
|
||||||
|
to_create.append(mv_line_dict)
|
||||||
# Create move lines
|
# Create move lines
|
||||||
move_line_pairs_to_reconcile = []
|
move_line_pairs_to_reconcile = []
|
||||||
for mv_line_dict in mv_line_dicts:
|
for mv_line_dict in to_create:
|
||||||
counterpart_move_line_id = None # NB : this attribute is irrelevant for aml_obj.create() and needs to be removed from the dict
|
counterpart_move_line_id = None # NB : this attribute is irrelevant for aml_obj.create() and needs to be removed from the dict
|
||||||
if mv_line_dict.get('counterpart_move_line_id'):
|
if mv_line_dict.get('counterpart_move_line_id'):
|
||||||
counterpart_move_line_id = mv_line_dict['counterpart_move_line_id']
|
counterpart_move_line_id = mv_line_dict['counterpart_move_line_id']
|
||||||
|
@ -723,7 +805,7 @@ class account_bank_statement_line(osv.osv):
|
||||||
'bank_account_id': fields.many2one('res.partner.bank','Bank Account'),
|
'bank_account_id': fields.many2one('res.partner.bank','Bank Account'),
|
||||||
'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='cascade'),
|
'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='cascade'),
|
||||||
'journal_id': fields.related('statement_id', 'journal_id', type='many2one', relation='account.journal', string='Journal', store=True, readonly=True),
|
'journal_id': fields.related('statement_id', 'journal_id', type='many2one', relation='account.journal', string='Journal', store=True, readonly=True),
|
||||||
'ref': fields.char('Reference', size=32),
|
'ref': fields.char('Structured Communication'),
|
||||||
'note': fields.text('Notes'),
|
'note': fields.text('Notes'),
|
||||||
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of bank statement lines."),
|
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of bank statement lines."),
|
||||||
'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
|
'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
|
||||||
|
|
|
@ -127,8 +127,8 @@ class account_move_line(osv.osv):
|
||||||
|
|
||||||
if move_line.reconcile_id:
|
if move_line.reconcile_id:
|
||||||
continue
|
continue
|
||||||
if not move_line.account_id.type in ('payable', 'receivable'):
|
if not move_line.account_id.reconcile:
|
||||||
#this function does not suport to be used on move lines not related to payable or receivable accounts
|
#this function does not suport to be used on move lines not related to a reconcilable account
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if move_line.currency_id:
|
if move_line.currency_id:
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<field name="balance_end_real" eval="3707.58"/>
|
<field name="balance_end_real" eval="3707.58"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="demo_bank_statement_line_1" model="account.bank.statement.line">
|
<record id="demo_bank_statement_line_1" model="account.bank.statement.line">
|
||||||
<field name="ref">001</field>
|
<field name="ref"></field>
|
||||||
<field name="statement_id" ref="demo_bank_statement_1"/>
|
<field name="statement_id" ref="demo_bank_statement_1"/>
|
||||||
<field name="sequence" eval="1"/>
|
<field name="sequence" eval="1"/>
|
||||||
<field name="company_id" ref="base.main_company"/>
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<field name="partner_id" ref="base.res_partner_9"/>
|
<field name="partner_id" ref="base.res_partner_9"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="demo_bank_statement_line_2" model="account.bank.statement.line">
|
<record id="demo_bank_statement_line_2" model="account.bank.statement.line">
|
||||||
<field name="ref">002</field>
|
<field name="ref">SAJ2014002</field>
|
||||||
<field name="statement_id" ref="demo_bank_statement_1"/>
|
<field name="statement_id" ref="demo_bank_statement_1"/>
|
||||||
<field name="sequence" eval="2"/>
|
<field name="sequence" eval="2"/>
|
||||||
<field name="company_id" ref="base.main_company"/>
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<field name="partner_id" ref="base.res_partner_9"/>
|
<field name="partner_id" ref="base.res_partner_9"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="demo_bank_statement_line_3" model="account.bank.statement.line">
|
<record id="demo_bank_statement_line_3" model="account.bank.statement.line">
|
||||||
<field name="ref">003</field>
|
<field name="ref"></field>
|
||||||
<field name="statement_id" ref="demo_bank_statement_1"/>
|
<field name="statement_id" ref="demo_bank_statement_1"/>
|
||||||
<field name="sequence" eval="3"/>
|
<field name="sequence" eval="3"/>
|
||||||
<field name="company_id" ref="base.main_company"/>
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||||
</record>
|
</record>
|
||||||
<record id="demo_bank_statement_line_4" model="account.bank.statement.line">
|
<record id="demo_bank_statement_line_4" model="account.bank.statement.line">
|
||||||
<field name="ref">004</field>
|
<field name="ref"></field>
|
||||||
<field name="statement_id" ref="demo_bank_statement_1"/>
|
<field name="statement_id" ref="demo_bank_statement_1"/>
|
||||||
<field name="sequence" eval="4"/>
|
<field name="sequence" eval="4"/>
|
||||||
<field name="company_id" ref="base.main_company"/>
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
|
|
@ -120,8 +120,6 @@
|
||||||
cursor: pointer; }
|
cursor: pointer; }
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_match:not(.no_partner) .toggle_match {
|
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_match:not(.no_partner) .toggle_match {
|
||||||
visibility: hidden !important; }
|
visibility: hidden !important; }
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_partner .partner_name, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_partner .line_open_balance {
|
|
||||||
display: none !important; }
|
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line > table > tbody > tr:nth-child(1) > td table {
|
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line > table > tbody > tr:nth-child(1) > td table {
|
||||||
margin-bottom: 10px; }
|
margin-bottom: 10px; }
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line table.details td:first-child {
|
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line table.details td:first-child {
|
||||||
|
@ -202,10 +200,6 @@
|
||||||
cursor: pointer; }
|
cursor: pointer; }
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6) {
|
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6) {
|
||||||
border-left: 1px solid black; }
|
border-left: 1px solid black; }
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.initial_line > td:nth-child(5) {
|
|
||||||
border-top: 1px solid black; }
|
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.initial_line > td:nth-child(6) {
|
|
||||||
border-top: 1px solid black; }
|
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls {
|
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls {
|
||||||
padding: 0 0 5px 18px; }
|
padding: 0 0 5px 18px; }
|
||||||
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls .filter {
|
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls .filter {
|
||||||
|
|
|
@ -194,12 +194,6 @@ $initialLineBackground: #f0f0f0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no_partner {
|
|
||||||
.partner_name, .line_open_balance {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* gap between accounting_view and action view */
|
/* gap between accounting_view and action view */
|
||||||
> table > tbody > tr:nth-child(1) > td table {
|
> table > tbody > tr:nth-child(1) > td table {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -341,10 +335,6 @@ $initialLineBackground: #f0f0f0;
|
||||||
|
|
||||||
// accounting "T"
|
// accounting "T"
|
||||||
td:nth-child(6) { border-left: $accountingBorder; }
|
td:nth-child(6) { border-left: $accountingBorder; }
|
||||||
tr.initial_line > td {
|
|
||||||
&:nth-child(5) { border-top: $accountingBorder; }
|
|
||||||
&:nth-child(6) { border-top: $accountingBorder; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ openerp.account = function (instance) {
|
||||||
deferred_promises.push(self.model_bank_statement
|
deferred_promises.push(self.model_bank_statement
|
||||||
.call("get_format_currency_js_function", [self.statement_id])
|
.call("get_format_currency_js_function", [self.statement_id])
|
||||||
.then(function(data){
|
.then(function(data){
|
||||||
self.formatCurrency = new Function("amount", data);
|
self.formatCurrency = new Function("amount, currency_id", data);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ openerp.account = function (instance) {
|
||||||
|
|
||||||
keyboardShortcutsHandler: function(e) {
|
keyboardShortcutsHandler: function(e) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
|
if ((e.which === 13 || e.which === 10) && (e.ctrlKey || e.metaKey)) {
|
||||||
$.each(self.getChildren(), function(i, o){
|
$.each(self.getChildren(), function(i, o){
|
||||||
if (o.is_valid && o.persistAndDestroy()) {
|
if (o.is_valid && o.persistAndDestroy()) {
|
||||||
self.lines_reconciled_with_ctrl_enter++;
|
self.lines_reconciled_with_ctrl_enter++;
|
||||||
|
@ -789,6 +789,9 @@ openerp.account = function (instance) {
|
||||||
line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : "");
|
line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : "");
|
||||||
line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line});
|
line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line});
|
||||||
line.q_label = line.name;
|
line.q_label = line.name;
|
||||||
|
if (line.has_no_partner){
|
||||||
|
line.q_label = line.partner_name + ': ' +line.q_label;
|
||||||
|
}
|
||||||
|
|
||||||
// WARNING : pretty much of a ugly hack
|
// WARNING : pretty much of a ugly hack
|
||||||
// The value of account_move.ref is either the move's communication or it's name without the slashes
|
// The value of account_move.ref is either the move's communication or it's name without the slashes
|
||||||
|
@ -981,6 +984,7 @@ openerp.account = function (instance) {
|
||||||
lineOpenBalanceClickHandler: function() {
|
lineOpenBalanceClickHandler: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (self.get("mode") === "create") {
|
if (self.get("mode") === "create") {
|
||||||
|
self.addLineBeingEdited();
|
||||||
self.set("mode", "match");
|
self.set("mode", "match");
|
||||||
} else {
|
} else {
|
||||||
self.set("mode", "create");
|
self.set("mode", "create");
|
||||||
|
@ -1038,7 +1042,8 @@ openerp.account = function (instance) {
|
||||||
var slice_start = self.get("pager_index") * self.max_move_lines_displayed;
|
var slice_start = self.get("pager_index") * self.max_move_lines_displayed;
|
||||||
var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed;
|
var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed;
|
||||||
_( _.filter(self.mv_lines_deselected, function(o){
|
_( _.filter(self.mv_lines_deselected, function(o){
|
||||||
return o.name.indexOf(self.filter) !== -1 || o.ref.indexOf(self.filter) !== -1 })
|
return o.q_label.indexOf(self.filter) !== -1 || (o.ref && o.ref.indexOf(self.filter) !== -1)
|
||||||
|
})
|
||||||
.slice(slice_start, slice_end)).each(function(line){
|
.slice(slice_start, slice_end)).each(function(line){
|
||||||
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
|
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
|
||||||
self.bindPopoverTo($line.find(".line_info_button"));
|
self.bindPopoverTo($line.find(".line_info_button"));
|
||||||
|
@ -1057,7 +1062,6 @@ openerp.account = function (instance) {
|
||||||
|
|
||||||
updatePagerControls: function() {
|
updatePagerControls: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (self.get("pager_index") === 0)
|
if (self.get("pager_index") === 0)
|
||||||
self.$(".pager_control_left").addClass("disabled");
|
self.$(".pager_control_left").addClass("disabled");
|
||||||
else
|
else
|
||||||
|
@ -1075,7 +1079,7 @@ openerp.account = function (instance) {
|
||||||
balanceChanged: function() {
|
balanceChanged: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var balance = self.get("balance");
|
var balance = self.get("balance");
|
||||||
|
self.$(".tbody_open_balance").empty();
|
||||||
// Special case hack : no identified partner
|
// Special case hack : no identified partner
|
||||||
if (self.st_line.has_no_partner) {
|
if (self.st_line.has_no_partner) {
|
||||||
if (Math.abs(balance).toFixed(3) === "0.000") {
|
if (Math.abs(balance).toFixed(3) === "0.000") {
|
||||||
|
@ -1088,19 +1092,23 @@ openerp.account = function (instance) {
|
||||||
self.$(".button_ok").attr("disabled", "disabled");
|
self.$(".button_ok").attr("disabled", "disabled");
|
||||||
self.$(".button_ok").text("OK");
|
self.$(".button_ok").text("OK");
|
||||||
self.is_valid = false;
|
self.is_valid = false;
|
||||||
|
var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : "");
|
||||||
|
var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : "");
|
||||||
|
var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]}));
|
||||||
|
$line.find('.js_open_balance')[0].innerHTML = "Choose counterpart";
|
||||||
|
self.$(".tbody_open_balance").append($line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.$(".tbody_open_balance").empty();
|
|
||||||
if (Math.abs(balance).toFixed(3) === "0.000") {
|
if (Math.abs(balance).toFixed(3) === "0.000") {
|
||||||
self.$(".button_ok").addClass("oe_highlight");
|
self.$(".button_ok").addClass("oe_highlight");
|
||||||
self.$(".button_ok").text("OK");
|
self.$(".button_ok").text("OK");
|
||||||
} else {
|
} else {
|
||||||
self.$(".button_ok").removeClass("oe_highlight");
|
self.$(".button_ok").removeClass("oe_highlight");
|
||||||
self.$(".button_ok").text("Keep open");
|
self.$(".button_ok").text("Keep open");
|
||||||
var debit = (balance > 0 ? self.formatCurrency(balance) : "");
|
var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : "");
|
||||||
var credit = (balance < 0 ? self.formatCurrency(-1*balance) : "");
|
var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : "");
|
||||||
var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]}));
|
var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]}));
|
||||||
self.$(".tbody_open_balance").append($line);
|
self.$(".tbody_open_balance").append($line);
|
||||||
}
|
}
|
||||||
|
@ -1111,21 +1119,15 @@ openerp.account = function (instance) {
|
||||||
|
|
||||||
self.$(".action_pane.active").removeClass("active");
|
self.$(".action_pane.active").removeClass("active");
|
||||||
|
|
||||||
// Special case hack : if no_partner, either inactive or create
|
// Special case hack : if no_partner and mode == inactive
|
||||||
if (self.st_line.has_no_partner) {
|
if (self.st_line.has_no_partner) {
|
||||||
if (self.get("mode") === "inactive") {
|
if (self.get("mode") === "inactive") {
|
||||||
self.$(".match").slideUp(self.animation_speed);
|
self.$(".match").slideUp(self.animation_speed);
|
||||||
self.$(".create").slideUp(self.animation_speed);
|
self.$(".create").slideUp(self.animation_speed);
|
||||||
self.$(".toggle_match").removeClass("visible_toggle");
|
self.$(".toggle_match").removeClass("visible_toggle");
|
||||||
self.el.dataset.mode = "inactive";
|
self.el.dataset.mode = "inactive";
|
||||||
} else {
|
return;
|
||||||
self.initializeCreateForm();
|
}
|
||||||
self.$(".match").slideUp(self.animation_speed);
|
|
||||||
self.$(".create").slideDown(self.animation_speed);
|
|
||||||
self.$(".toggle_match").addClass("visible_toggle");
|
|
||||||
self.el.dataset.mode = "create";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.get("mode") === "inactive") {
|
if (self.get("mode") === "inactive") {
|
||||||
|
@ -1198,6 +1200,8 @@ openerp.account = function (instance) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var line_created_being_edited = self.get("line_created_being_edited");
|
var line_created_being_edited = self.get("line_created_being_edited");
|
||||||
line_created_being_edited[0][elt.corresponding_property] = val.newValue;
|
line_created_being_edited[0][elt.corresponding_property] = val.newValue;
|
||||||
|
|
||||||
|
line_created_being_edited[0].currency_id = self.st_line.currency_id;
|
||||||
|
|
||||||
// Specific cases
|
// Specific cases
|
||||||
if (elt === self.account_id_field)
|
if (elt === self.account_id_field)
|
||||||
|
@ -1215,7 +1219,7 @@ openerp.account = function (instance) {
|
||||||
var tax = data.taxes[0];
|
var tax = data.taxes[0];
|
||||||
var tax_account_id = (amount > 0 ? tax.account_collected_id : tax.account_paid_id)
|
var tax_account_id = (amount > 0 ? tax.account_collected_id : tax.account_paid_id)
|
||||||
line_created_being_edited[0].amount = (data.total.toFixed(3) === amount.toFixed(3) ? amount : data.total);
|
line_created_being_edited[0].amount = (data.total.toFixed(3) === amount.toFixed(3) ? amount : data.total);
|
||||||
line_created_being_edited[1] = {id: line_created_being_edited[0].id, account_id: tax_account_id, account_num: self.map_account_id_code[tax_account_id], label: tax.name, amount: tax.amount, no_remove_action: true};
|
line_created_being_edited[1] = {id: line_created_being_edited[0].id, account_id: tax_account_id, account_num: self.map_account_id_code[tax_account_id], label: tax.name, amount: tax.amount, no_remove_action: true, currency_id: self.st_line.currency_id};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1228,10 +1232,9 @@ openerp.account = function (instance) {
|
||||||
$.when(deferred_tax).then(function(){
|
$.when(deferred_tax).then(function(){
|
||||||
// Format amounts
|
// Format amounts
|
||||||
if (line_created_being_edited[0].amount)
|
if (line_created_being_edited[0].amount)
|
||||||
line_created_being_edited[0].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[0].amount));
|
line_created_being_edited[0].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[0].amount), line_created_being_edited[0].currency_id);
|
||||||
if (line_created_being_edited[1] && line_created_being_edited[1].amount)
|
if (line_created_being_edited[1] && line_created_being_edited[1].amount)
|
||||||
line_created_being_edited[1].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[1].amount));
|
line_created_being_edited[1].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[1].amount), line_created_being_edited[0].currency_id);
|
||||||
|
|
||||||
self.set("line_created_being_edited", line_created_being_edited);
|
self.set("line_created_being_edited", line_created_being_edited);
|
||||||
self.createdLinesChanged(); // TODO For some reason, previous line doesn't trigger change handler
|
self.createdLinesChanged(); // TODO For some reason, previous line doesn't trigger change handler
|
||||||
});
|
});
|
||||||
|
@ -1268,10 +1271,10 @@ openerp.account = function (instance) {
|
||||||
line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit;
|
line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit;
|
||||||
if (balance < 0) {
|
if (balance < 0) {
|
||||||
line.debit -= balance;
|
line.debit -= balance;
|
||||||
line.debit_str = self.formatCurrency(line.debit);
|
line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id);
|
||||||
} else {
|
} else {
|
||||||
line.credit -= balance;
|
line.credit -= balance;
|
||||||
line.credit_str = self.formatCurrency(line.credit);
|
line.credit_str = self.formatCurrency(line.credit, self.st_line.currency_id);
|
||||||
}
|
}
|
||||||
line.propose_partial_reconcile = false;
|
line.propose_partial_reconcile = false;
|
||||||
line.partial_reconcile = true;
|
line.partial_reconcile = true;
|
||||||
|
@ -1291,12 +1294,13 @@ openerp.account = function (instance) {
|
||||||
},
|
},
|
||||||
|
|
||||||
unpartialReconcileLine: function(line) {
|
unpartialReconcileLine: function(line) {
|
||||||
|
var self = this;
|
||||||
if (line.initial_amount > 0) {
|
if (line.initial_amount > 0) {
|
||||||
line.debit = line.initial_amount;
|
line.debit = line.initial_amount;
|
||||||
line.debit_str = this.formatCurrency(line.debit);
|
line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id);
|
||||||
} else {
|
} else {
|
||||||
line.credit = -1 * line.initial_amount;
|
line.credit = -1 * line.initial_amount;
|
||||||
line.credit_str = this.formatCurrency(line.credit);
|
line.credit_str = self.formatCurrency(line.credit, self.st_line.currency_id);
|
||||||
}
|
}
|
||||||
line.propose_partial_reconcile = true;
|
line.propose_partial_reconcile = true;
|
||||||
line.partial_reconcile = false;
|
line.partial_reconcile = false;
|
||||||
|
@ -1359,16 +1363,15 @@ openerp.account = function (instance) {
|
||||||
if (limit > 0) {
|
if (limit > 0) {
|
||||||
// Load move lines
|
// Load move lines
|
||||||
deferred_move_lines = self.model_bank_statement_line
|
deferred_move_lines = self.model_bank_statement_line
|
||||||
.call("get_move_lines_counterparts", [self.st_line.id, excluded_ids, self.filter, offset, limit])
|
.call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, offset, limit])
|
||||||
.then(function (lines) {
|
.then(function (lines) {
|
||||||
_(lines).each(self.decorateMoveLine.bind(self));
|
_(lines).each(self.decorateMoveLine.bind(self));
|
||||||
move_lines = lines;
|
move_lines = lines;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the number of move lines corresponding to this statement line and this filter
|
// Fetch the number of move lines corresponding to this statement line and this filter
|
||||||
var deferred_total_move_lines_num = self.model_bank_statement_line
|
var deferred_total_move_lines_num = self.model_bank_statement_line
|
||||||
.call("get_move_lines_counterparts", [self.st_line.id, excluded_ids, self.filter, offset, limit, true])
|
.call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, 0, undefined, true])
|
||||||
.then(function(num){
|
.then(function(num){
|
||||||
move_lines_num = num;
|
move_lines_num = num;
|
||||||
});
|
});
|
||||||
|
|
|
@ -185,7 +185,7 @@
|
||||||
<td><span class="toggle_create glyphicon glyphicon-play"></span></td>
|
<td><span class="toggle_create glyphicon glyphicon-play"></span></td>
|
||||||
<td><t t-esc="account_code"/></td>
|
<td><t t-esc="account_code"/></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>Open balance</td>
|
<td class="js_open_balance">Open balance</td>
|
||||||
<td><t t-esc="debit"/></td>
|
<td><t t-esc="debit"/></td>
|
||||||
<td><t t-esc="credit"/></td>
|
<td><t t-esc="credit"/></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|
|
@ -45,13 +45,10 @@ class crm_claim_stage(osv.osv):
|
||||||
help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."),
|
help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."),
|
||||||
'case_default': fields.boolean('Common to All Teams',
|
'case_default': fields.boolean('Common to All Teams',
|
||||||
help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."),
|
help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."),
|
||||||
'fold': fields.boolean('Hide in Views when Empty',
|
|
||||||
help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'sequence': lambda *args: 1,
|
'sequence': lambda *args: 1,
|
||||||
'fold': False,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class crm_claim(osv.osv):
|
class crm_claim(osv.osv):
|
||||||
|
|
|
@ -61,7 +61,6 @@
|
||||||
<field name="name">Rejected</field>
|
<field name="name">Rejected</field>
|
||||||
<field name="sequence">29</field>
|
<field name="sequence">29</field>
|
||||||
<field name="case_default" eval="True"/>
|
<field name="case_default" eval="True"/>
|
||||||
<field name="fold" eval="True"/>
|
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,6 @@
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="case_default"/>
|
<field name="case_default"/>
|
||||||
<field name="sequence"/>
|
<field name="sequence"/>
|
||||||
<field name="fold"/>
|
|
||||||
</group>
|
</group>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
@ -59,25 +59,7 @@ A removal of one object in the CODA processing results in the removal of the
|
||||||
associated objects. The removal of a CODA File containing multiple Bank
|
associated objects. The removal of a CODA File containing multiple Bank
|
||||||
Statements will also remove those associated statements.
|
Statements will also remove those associated statements.
|
||||||
|
|
||||||
The following reconciliation logic has been implemented in the CODA processing:
|
Instead of a manual adjustment of the generated Bank Statements, you can also
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
1) The Company's Bank Account Number of the CODA statement is compared against
|
|
||||||
the Bank Account Number field of the Company's CODA Bank Account
|
|
||||||
configuration records (whereby bank accounts defined in type='info'
|
|
||||||
configuration records are ignored). If this is the case an 'internal transfer'
|
|
||||||
transaction is generated using the 'Internal Transfer Account' field of the
|
|
||||||
CODA File Import wizard.
|
|
||||||
2) As a second step the 'Structured Communication' field of the CODA transaction
|
|
||||||
line is matched against the reference field of in- and outgoing invoices
|
|
||||||
(supported : Belgian Structured Communication Type).
|
|
||||||
3) When the previous step doesn't find a match, the transaction counterparty is
|
|
||||||
located via the Bank Account Number configured on the OpenERP Customer and
|
|
||||||
Supplier records.
|
|
||||||
4) In case the previous steps are not successful, the transaction is generated
|
|
||||||
by using the 'Default Account for Unrecognized Movement' field of the CODA
|
|
||||||
File Import wizard in order to allow further manual processing.
|
|
||||||
|
|
||||||
In stead of a manual adjustment of the generated Bank Statements, you can also
|
|
||||||
re-import the CODA after updating the OpenERP database with the information that
|
re-import the CODA after updating the OpenERP database with the information that
|
||||||
was missing to allow automatic reconciliation.
|
was missing to allow automatic reconciliation.
|
||||||
|
|
||||||
|
|
|
@ -28,46 +28,4 @@ class account_bank_statement(osv.osv):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class account_bank_statement_line(osv.osv):
|
|
||||||
_inherit = 'account.bank.statement.line'
|
|
||||||
_columns = {
|
|
||||||
'coda_account_number': fields.char('Account Number', help="The Counter Party Account Number")
|
|
||||||
}
|
|
||||||
|
|
||||||
def create(self, cr, uid, data, context=None):
|
|
||||||
"""
|
|
||||||
This function creates a Bank Account Number if, for a bank statement line,
|
|
||||||
the partner_id field and the coda_account_number field are set,
|
|
||||||
and the account number does not exist in the database
|
|
||||||
"""
|
|
||||||
if 'partner_id' in data and data['partner_id'] and 'coda_account_number' in data and data['coda_account_number']:
|
|
||||||
acc_number_ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', data['coda_account_number'])])
|
|
||||||
if len(acc_number_ids) == 0:
|
|
||||||
try:
|
|
||||||
type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
|
|
||||||
type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
|
|
||||||
self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': data['coda_account_number'], 'partner_id': data['partner_id'], 'state': type_id.code}, context=context)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return super(account_bank_statement_line, self).create(cr, uid, data, context=context)
|
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
|
||||||
super(account_bank_statement_line, self).write(cr, uid, ids, vals, context)
|
|
||||||
"""
|
|
||||||
Same as create function above, but for write function
|
|
||||||
"""
|
|
||||||
if 'partner_id' in vals:
|
|
||||||
for line in self.pool.get('account.bank.statement.line').browse(cr, uid, ids, context=context):
|
|
||||||
if line.coda_account_number:
|
|
||||||
acc_number_ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', line.coda_account_number)])
|
|
||||||
if len(acc_number_ids) == 0:
|
|
||||||
try:
|
|
||||||
type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
|
|
||||||
type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
|
|
||||||
self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': line.coda_account_number, 'partner_id': vals['partner_id'], 'state': type_id.code}, context=context)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -32,5 +32,27 @@
|
||||||
<field eval="'2011-01-31'" name="date_stop"/>
|
<field eval="'2011-01-31'" name="date_stop"/>
|
||||||
<field name="company_id" ref="base.main_company"/>
|
<field name="company_id" ref="base.main_company"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- invoice with BBA -->
|
||||||
|
<record id="coda_demo_invoice_1" model="account.invoice">
|
||||||
|
<field name="currency_id" ref="base.EUR"/>
|
||||||
|
<field name="company_id" ref="base.main_company"/>
|
||||||
|
<field name="journal_id" ref="account.sales_journal"/>
|
||||||
|
<field name="period_id" ref="period_1_2011"/>
|
||||||
|
<field name="state">draft</field>
|
||||||
|
<field name="type">out_invoice</field>
|
||||||
|
<field name="account_id" ref="account.a_recv"/>
|
||||||
|
<field name="partner_id" ref="base.res_partner_9"/>
|
||||||
|
<field name="reference_type">bba</field>
|
||||||
|
<field name="reference">+++240/2838/42818+++</field>
|
||||||
|
</record>
|
||||||
|
<record id="invoice_1_line_1" model="account.invoice.line">
|
||||||
|
<field name="name">Otpez Laptop without OS</field>
|
||||||
|
<field name="invoice_id" ref="coda_demo_invoice_1"/>
|
||||||
|
<field name="price_unit">608.89</field>
|
||||||
|
<field name="quantity">10</field>
|
||||||
|
<field name="account_id" ref="account.a_sale"/>
|
||||||
|
</record>
|
||||||
|
<workflow action="invoice_open" model="account.invoice" ref="coda_demo_invoice_1"/>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
2200010000 GKCCBEBB 1 0
|
2200010000 GKCCBEBB 1 0
|
||||||
2300010000BE41063012345610 PARTNER 1 0 1
|
2300010000BE41063012345610 PARTNER 1 0 1
|
||||||
3100010001OL44483FW SCTOFBIONLO001010001001PARTNER 1 0 0
|
3100010001OL44483FW SCTOFBIONLO001010001001PARTNER 1 0 0
|
||||||
2100020000OL4414AC8BOVSOVSOVERS00000000030444501101110015000002010237 11011113501 0
|
2100020000OL4414AC8BOVSOVSOVERS0000000003044450110111001500001101240283842818 11011113501 0
|
||||||
2200020000 BBRUBEBB 1 0
|
2200020000 BBRUBEBB 1 0
|
||||||
2300020000BE61310126985517 PARTNER 2 0 1
|
2300020000BE61310126985517 PARTNER 2 0 1
|
||||||
3100020001OL4414AC8BOVSOVSOVERS001500001001PARTNER 2 1 0
|
3100020001OL4414AC8BOVSOVSOVERS001500001001PARTNER 2 1 0
|
||||||
|
|
|
@ -291,79 +291,38 @@ class account_coda_import(osv.osv_memory):
|
||||||
if 'counterpartyAddress' in line and line['counterpartyAddress'] != '':
|
if 'counterpartyAddress' in line and line['counterpartyAddress'] != '':
|
||||||
note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress'])
|
note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress'])
|
||||||
line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']]))
|
line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']]))
|
||||||
partner = None
|
|
||||||
partner_id = None
|
partner_id = None
|
||||||
invoice = False
|
structured_com = ""
|
||||||
|
bank_account_id = False
|
||||||
if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101':
|
if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101':
|
||||||
ids = self.pool.get('account.invoice').search(cr, uid, [('reference', '=', line['communication']), ('reference_type', '=', 'bba')])
|
structured_com = line['communication']
|
||||||
|
|
||||||
# Gère les communications structurées
|
|
||||||
# TODO : à faire primer sur resolution_proposition : si la communication indique une facture, on la sélectionne
|
|
||||||
|
|
||||||
# if ids:
|
|
||||||
# invoice = self.pool.get('account.invoice').browse(cr, uid, ids[0])
|
|
||||||
# partner = invoice.partner_id
|
|
||||||
# partner_id = partner.id
|
|
||||||
# if invoice.type in ['in_invoice', 'in_refund'] and line['debit'] == '1':
|
|
||||||
# line['transaction_type'] = 'supplier'
|
|
||||||
# elif invoice.type in ['out_invoice', 'out_refund'] and line['debit'] == '0':
|
|
||||||
# line['transaction_type'] = 'customer'
|
|
||||||
# line['account'] = invoice.account_id.id
|
|
||||||
# line['reconcile'] = False
|
|
||||||
# if invoice.type in ['in_invoice', 'out_invoice']:
|
|
||||||
# iml_ids = self.pool.get('account.move.line').search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)])
|
|
||||||
# if iml_ids:
|
|
||||||
# line['reconcile'] = iml_ids[0]
|
|
||||||
# if line['reconcile']:
|
|
||||||
# voucher_vals = {
|
|
||||||
# 'type': line['transaction_type'] == 'supplier' and 'payment' or 'receipt',
|
|
||||||
# 'name': line['name'],
|
|
||||||
# 'partner_id': partner_id,
|
|
||||||
# 'journal_id': statement['journal_id'].id,
|
|
||||||
# 'account_id': statement['journal_id'].default_credit_account_id.id,
|
|
||||||
# 'company_id': statement['journal_id'].company_id.id,
|
|
||||||
# 'currency_id': statement['journal_id'].company_id.currency_id.id,
|
|
||||||
# 'date': line['entryDate'],
|
|
||||||
# 'amount': abs(line['amount']),
|
|
||||||
# 'period_id': statement['period_id'],
|
|
||||||
# 'invoice_id': invoice.id,
|
|
||||||
# }
|
|
||||||
# context['invoice_id'] = invoice.id
|
|
||||||
# voucher_vals.update(self.pool.get('account.voucher').onchange_partner_id(cr, uid, [],
|
|
||||||
# partner_id=partner_id,
|
|
||||||
# journal_id=statement['journal_id'].id,
|
|
||||||
# amount=abs(line['amount']),
|
|
||||||
# currency_id=statement['journal_id'].company_id.currency_id.id,
|
|
||||||
# ttype=line['transaction_type'] == 'supplier' and 'payment' or 'receipt',
|
|
||||||
# date=line['transactionDate'],
|
|
||||||
# context=context
|
|
||||||
# )['value'])
|
|
||||||
# line_drs = []
|
|
||||||
# for line_dr in voucher_vals['line_dr_ids']:
|
|
||||||
# line_drs.append((0, 0, line_dr))
|
|
||||||
# voucher_vals['line_dr_ids'] = line_drs
|
|
||||||
# line_crs = []
|
|
||||||
# for line_cr in voucher_vals['line_cr_ids']:
|
|
||||||
# line_crs.append((0, 0, line_cr))
|
|
||||||
# voucher_vals['line_cr_ids'] = line_crs
|
|
||||||
# line['voucher_id'] = self.pool.get('account.voucher').create(cr, uid, voucher_vals, context=context)
|
|
||||||
if 'counterpartyNumber' in line and line['counterpartyNumber']:
|
if 'counterpartyNumber' in line and line['counterpartyNumber']:
|
||||||
ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))])
|
ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))])
|
||||||
if ids and len(ids) > 0:
|
if ids:
|
||||||
partner = self.pool.get('res.partner.bank').browse(cr, uid, ids[0], context=context).partner_id
|
bank_account_id = ids[0]
|
||||||
partner_id = partner.id
|
partner_id = self.pool.get('res.partner.bank').browse(cr, uid, bank_account_id, context=context).partner_id.id
|
||||||
|
else:
|
||||||
|
#create the bank account, not linked to any partner. The reconciliation will link the partner manually
|
||||||
|
#chosen at the bank statement final confirmation time.
|
||||||
|
try:
|
||||||
|
type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
|
||||||
|
type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
|
||||||
|
bank_code = type_id.code
|
||||||
|
except ValueError:
|
||||||
|
bank_code = 'bank'
|
||||||
|
bank_account_id = self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': str(line['counterpartyNumber']), 'state': bank_code}, context=context)
|
||||||
if 'communication' in line and line['communication'] != '':
|
if 'communication' in line and line['communication'] != '':
|
||||||
note.append(_('Communication') + ': ' + line['communication'])
|
note.append(_('Communication') + ': ' + line['communication'])
|
||||||
data = {
|
data = {
|
||||||
'name': line['name'],
|
'name': line['name'],
|
||||||
'note': "\n".join(note),
|
'note': "\n".join(note),
|
||||||
'date': line['entryDate'],
|
'date': line['entryDate'],
|
||||||
'amount': line['amount'],
|
'amount': line['amount'],
|
||||||
'partner_id': partner_id,
|
'partner_id': partner_id,
|
||||||
'statement_id': statement['id'],
|
'statement_id': statement['id'],
|
||||||
'ref': line['ref'],
|
'ref': structured_com,
|
||||||
'sequence': line['sequence'],
|
'sequence': line['sequence'],
|
||||||
'coda_account_number': line['counterpartyNumber'],
|
'bank_account_id': bank_account_id,
|
||||||
}
|
}
|
||||||
self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context)
|
self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context)
|
||||||
if statement['coda_note'] != '':
|
if statement['coda_note'] != '':
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<record id="impuestos_plantilla_isv_por_cobrar" model="account.tax.template">
|
<record id="impuestos_plantilla_isv_por_cobrar" model="account.tax.template">
|
||||||
<field name="chart_template_id" ref="cuentas_plantilla"/>
|
<field name="chart_template_id" ref="cuentas_plantilla"/>
|
||||||
<field name="name">ISV por Cobrar</field>
|
<field name="name">ISV por Cobrar</field>
|
||||||
<field name="amount" eval="0.12"/>
|
<field name="amount" eval="0.15"/>
|
||||||
<field name="type">percent</field>
|
<field name="type">percent</field>
|
||||||
<field name="account_collected_id" ref="cta110301"/>
|
<field name="account_collected_id" ref="cta110301"/>
|
||||||
<field name="account_paid_id" ref="cta110301"/>
|
<field name="account_paid_id" ref="cta110301"/>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
<record id="impuestos_plantilla_isv_por_pagar" model="account.tax.template">
|
<record id="impuestos_plantilla_isv_por_pagar" model="account.tax.template">
|
||||||
<field name="chart_template_id" ref="cuentas_plantilla"/>
|
<field name="chart_template_id" ref="cuentas_plantilla"/>
|
||||||
<field name="name">ISV por Pagar</field>
|
<field name="name">ISV por Pagar</field>
|
||||||
<field name="amount" eval="0.12"/>
|
<field name="amount" eval="0.15"/>
|
||||||
<field name="type">percent</field>
|
<field name="type">percent</field>
|
||||||
<field name="account_collected_id" ref="cta210201"/>
|
<field name="account_collected_id" ref="cta210201"/>
|
||||||
<field name="account_paid_id" ref="cta210201"/>
|
<field name="account_paid_id" ref="cta210201"/>
|
||||||
|
|
|
@ -746,7 +746,7 @@
|
||||||
<field name="move_lines2" nolabel="1" options="{'reload_on_button': true}">
|
<field name="move_lines2" nolabel="1" options="{'reload_on_button': true}">
|
||||||
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in ('confirmed','ready','in_production');gray:state == 'cancel' " string="Consumed Products" editable="bottom">
|
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in ('confirmed','ready','in_production');gray:state == 'cancel' " string="Consumed Products" editable="bottom">
|
||||||
<field name="product_id" readonly="1"/>
|
<field name="product_id" readonly="1"/>
|
||||||
<field name="restrict_lot_id" context="{'product_id': product_id}" groups="stock.group_tracking_lot"/>
|
<field name="restrict_lot_id" context="{'product_id': product_id}" groups="stock.group_production_lot"/>
|
||||||
<field name="product_qty" readonly="1"/>
|
<field name="product_qty" readonly="1"/>
|
||||||
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
|
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
|
||||||
<field name="state" invisible="1"/>
|
<field name="state" invisible="1"/>
|
||||||
|
@ -774,7 +774,7 @@
|
||||||
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in('confirmed','ready','in_production');gray:state in('cancel','done') " string="Finished Products">
|
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in('confirmed','ready','in_production');gray:state in('cancel','done') " string="Finished Products">
|
||||||
<field name="product_id" readonly="1"/>
|
<field name="product_id" readonly="1"/>
|
||||||
<field name="product_qty" readonly="1"/>
|
<field name="product_qty" readonly="1"/>
|
||||||
<field name="restrict_lot_id" groups="stock.group_tracking_lot"/>
|
<field name="restrict_lot_id" groups="stock.group_production_lot"/>
|
||||||
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
|
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
|
||||||
<field name="location_dest_id" readonly="1" string="Destination Loc." widget="selection" groups="stock.group_locations"/>
|
<field name="location_dest_id" readonly="1" string="Destination Loc." widget="selection" groups="stock.group_locations"/>
|
||||||
<field name="scrapped" invisible="1"/>
|
<field name="scrapped" invisible="1"/>
|
||||||
|
|
|
@ -18,16 +18,16 @@
|
||||||
<field name="lot_id" domain="[('product_id', '=', product_id)]"
|
<field name="lot_id" domain="[('product_id', '=', product_id)]"
|
||||||
context="{'default_product_id':product_id}"
|
context="{'default_product_id':product_id}"
|
||||||
attrs="{'required': [('track_production', '=', True), ('mode', '=', 'consume_produce')]}"
|
attrs="{'required': [('track_production', '=', True), ('mode', '=', 'consume_produce')]}"
|
||||||
groups="stock.group_tracking_lot"/>
|
groups="stock.group_production_lot"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="To Consume">
|
<group string="To Consume">
|
||||||
<field name="consume_lines">
|
<field name="consume_lines" nolabel="1">
|
||||||
<tree string="Consume Lines" editable="top">
|
<tree string="Consume Lines" editable="top">
|
||||||
<field name="product_id"/>
|
<field name="product_id"/>
|
||||||
<field name="product_qty"/>
|
<field name="product_qty"/>
|
||||||
<field name="lot_id" domain="[('product_id', '=', product_id)]"
|
<field name="lot_id" domain="[('product_id', '=', product_id)]"
|
||||||
context="{'default_product_id':product_id}"
|
context="{'default_product_id':product_id}"
|
||||||
groups="stock.group_tracking_lot"/>
|
groups="stock.group_production_lot"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<field name="product_qty" class="oe_inline"/>
|
<field name="product_qty" class="oe_inline"/>
|
||||||
<field name="product_uom" class="oe_inline" readonly="1" groups="product.group_uom"/>
|
<field name="product_uom" class="oe_inline" readonly="1" groups="product.group_uom"/>
|
||||||
</div>
|
</div>
|
||||||
<field name="restrict_lot_id" domain="[('product_id','=',product_id)]" groups="stock.group_tracking_lot"
|
<field name="restrict_lot_id" domain="[('product_id','=',product_id)]" groups="stock.group_production_lot"
|
||||||
context="{'default_product_id': product_id}"/>
|
context="{'default_product_id': product_id}"/>
|
||||||
<field name="location_id" groups="stock.group_locations"/>
|
<field name="location_id" groups="stock.group_locations"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
<span t-if="o.state != 'draft'">Repair Order N°:</span>
|
<span t-if="o.state != 'draft'">Repair Order N°:</span>
|
||||||
<span t-if="o.state == 'draft'">Repair Quotation N°:</span>
|
<span t-if="o.state == 'draft'">Repair Quotation N°:</span>
|
||||||
<span t-field="o.name"/>
|
<span t-field="o.name"/>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
@ -41,7 +41,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3" groups="stock.group_production_lot">
|
<div class="col-xs-3" groups="stock.group_production_lot">
|
||||||
<strong>Lot Number</strong>
|
<strong>Lot Number</strong>
|
||||||
<span t-field="o.prodlot_id.name"/>
|
<t t-if="o.lot_id">
|
||||||
|
<span t-field="o.lot_id.name"/>
|
||||||
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="o.guarantee_limit" class="col-xs-3">
|
<div t-if="o.guarantee_limit" class="col-xs-3">
|
||||||
<strong>Guarantee Limit:</strong>
|
<strong>Guarantee Limit:</strong>
|
||||||
|
|
|
@ -64,7 +64,7 @@ class procurement_group(osv.osv):
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'name': lambda self, cr, uid, c: self.pool.get('ir.sequence').get(cr, uid, 'procurement.group') or '',
|
'name': lambda self, cr, uid, c: self.pool.get('ir.sequence').get(cr, uid, 'procurement.group') or '',
|
||||||
'move_type': lambda self, cr, uid, c: 'one'
|
'move_type': lambda self, cr, uid, c: 'direct'
|
||||||
}
|
}
|
||||||
|
|
||||||
class procurement_rule(osv.osv):
|
class procurement_rule(osv.osv):
|
||||||
|
@ -267,7 +267,7 @@ class procurement_order(osv.osv):
|
||||||
#
|
#
|
||||||
# Scheduler
|
# Scheduler
|
||||||
#
|
#
|
||||||
def run_scheduler(self, cr, uid, use_new_cursor=False, context=None):
|
def run_scheduler(self, cr, uid, use_new_cursor=False, company_id = False, context=None):
|
||||||
'''
|
'''
|
||||||
Call the scheduler to check the procurement order. This is intented to be done for all existing companies at
|
Call the scheduler to check the procurement order. This is intented to be done for all existing companies at
|
||||||
the same time, so we're running all the methods as SUPERUSER to avoid intercompany and access rights issues.
|
the same time, so we're running all the methods as SUPERUSER to avoid intercompany and access rights issues.
|
||||||
|
@ -288,8 +288,11 @@ class procurement_order(osv.osv):
|
||||||
cr = openerp.registry(cr.dbname).cursor()
|
cr = openerp.registry(cr.dbname).cursor()
|
||||||
|
|
||||||
# Run confirmed procurements
|
# Run confirmed procurements
|
||||||
|
dom = [('state', '=', 'confirmed')]
|
||||||
|
if company_id:
|
||||||
|
dom += [('company_id', '=', company_id)]
|
||||||
while True:
|
while True:
|
||||||
ids = self.search(cr, SUPERUSER_ID, [('state', '=', 'confirmed')], context=context)
|
ids = self.search(cr, SUPERUSER_ID, dom, context=context)
|
||||||
if not ids:
|
if not ids:
|
||||||
break
|
break
|
||||||
self.run(cr, SUPERUSER_ID, ids, context=context)
|
self.run(cr, SUPERUSER_ID, ids, context=context)
|
||||||
|
@ -298,8 +301,11 @@ class procurement_order(osv.osv):
|
||||||
|
|
||||||
# Check if running procurements are done
|
# Check if running procurements are done
|
||||||
offset = 0
|
offset = 0
|
||||||
|
dom = [('state', '=', 'running')]
|
||||||
|
if company_id:
|
||||||
|
dom += [('company_id', '=', company_id)]
|
||||||
while True:
|
while True:
|
||||||
ids = self.search(cr, SUPERUSER_ID, [('state', '=', 'running')], offset=offset, context=context)
|
ids = self.search(cr, SUPERUSER_ID, dom, offset=offset, context=context)
|
||||||
if not ids:
|
if not ids:
|
||||||
break
|
break
|
||||||
done = self.check(cr, SUPERUSER_ID, ids, context=context)
|
done = self.check(cr, SUPERUSER_ID, ids, context=context)
|
||||||
|
|
|
@ -37,8 +37,12 @@ class procurement_compute_all(osv.osv_memory):
|
||||||
"""
|
"""
|
||||||
proc_obj = self.pool.get('procurement.order')
|
proc_obj = self.pool.get('procurement.order')
|
||||||
#As this function is in a new thread, i need to open a new cursor, because the old one may be closed
|
#As this function is in a new thread, i need to open a new cursor, because the old one may be closed
|
||||||
|
|
||||||
new_cr = self.pool.cursor()
|
new_cr = self.pool.cursor()
|
||||||
proc_obj.run_scheduler(new_cr, uid, use_new_cursor=new_cr.dbname, context=context)
|
user = self.pool.get('res.users').browse(new_cr, uid, uid, context=context)
|
||||||
|
comps = [x.id for x in user.company_ids]
|
||||||
|
for comp in comps:
|
||||||
|
proc_obj.run_scheduler(new_cr, uid, use_new_cursor=new_cr.dbname, company_id = comp, context=context)
|
||||||
#close the new cursor
|
#close the new cursor
|
||||||
new_cr.close()
|
new_cr.close()
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -92,7 +92,7 @@ class stock_quant(osv.osv):
|
||||||
|
|
||||||
def apply_removal_strategy(self, cr, uid, location, product, qty, domain, removal_strategy, context=None):
|
def apply_removal_strategy(self, cr, uid, location, product, qty, domain, removal_strategy, context=None):
|
||||||
if removal_strategy == 'fefo':
|
if removal_strategy == 'fefo':
|
||||||
order = 'removal_date, id'
|
order = 'removal_date, in_date, id'
|
||||||
return self._quants_get_order(cr, uid, location, product, qty, domain, order, context=context)
|
return self._quants_get_order(cr, uid, location, product, qty, domain, order, context=context)
|
||||||
return super(stock_quant, self).apply_removal_strategy(cr, uid, location, product, qty, domain, removal_strategy, context=context)
|
return super(stock_quant, self).apply_removal_strategy(cr, uid, location, product, qty, domain, removal_strategy, context=context)
|
||||||
|
|
||||||
|
|
|
@ -700,6 +700,7 @@ class purchase_order(osv.osv):
|
||||||
'origin': order.name,
|
'origin': order.name,
|
||||||
'route_ids': order.picking_type_id.warehouse_id and [(6, 0, [x.id for x in order.picking_type_id.warehouse_id.route_ids])] or [],
|
'route_ids': order.picking_type_id.warehouse_id and [(6, 0, [x.id for x in order.picking_type_id.warehouse_id.route_ids])] or [],
|
||||||
'warehouse_id':order.picking_type_id.warehouse_id.id,
|
'warehouse_id':order.picking_type_id.warehouse_id.id,
|
||||||
|
'invoice_state': order.invoice_method == 'picking' and '2binvoiced' or 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
diff_quantity = order_line.product_qty
|
diff_quantity = order_line.product_qty
|
||||||
|
@ -709,9 +710,10 @@ class purchase_order(osv.osv):
|
||||||
tmp.update({
|
tmp.update({
|
||||||
'product_uom_qty': min(procurement_qty, diff_quantity),
|
'product_uom_qty': min(procurement_qty, diff_quantity),
|
||||||
'product_uos_qty': min(procurement_qty, diff_quantity),
|
'product_uos_qty': min(procurement_qty, diff_quantity),
|
||||||
'move_dest_id': procurement.move_dest_id.id, # blabla
|
'move_dest_id': procurement.move_dest_id.id, #move destination is same as procurement destination
|
||||||
'group_id': procurement.group_id.id or group_id, # blabla to check ca devrait etre bon et groupé dans le meme picking qd meme
|
'group_id': procurement.group_id.id or group_id, #move group is same as group of procurements if it exists, otherwise take another group
|
||||||
'procurement_id': procurement.id,
|
'procurement_id': procurement.id,
|
||||||
|
'invoice_state': procurement.rule_id.invoice_state or (procurement.location_id and procurement.location_id.usage == 'customer' and procurement.invoice_state) or (order.invoice_method == 'picking' and '2binvoiced') or 'none', #dropship case takes from sale
|
||||||
})
|
})
|
||||||
diff_quantity -= min(procurement_qty, diff_quantity)
|
diff_quantity -= min(procurement_qty, diff_quantity)
|
||||||
res.append(tmp)
|
res.append(tmp)
|
||||||
|
@ -1299,6 +1301,7 @@ class procurement_order(osv.osv):
|
||||||
res[procurement.id] = False
|
res[procurement.id] = False
|
||||||
else:
|
else:
|
||||||
schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
|
schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
|
||||||
|
purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
|
||||||
line_vals = self._get_po_line_values_from_proc(cr, uid, procurement, partner, company, schedule_date, context=context)
|
line_vals = self._get_po_line_values_from_proc(cr, uid, procurement, partner, company, schedule_date, context=context)
|
||||||
#look for any other draft PO for the same supplier, to attach the new line on instead of creating a new draft one
|
#look for any other draft PO for the same supplier, to attach the new line on instead of creating a new draft one
|
||||||
available_draft_po_ids = po_obj.search(cr, uid, [
|
available_draft_po_ids = po_obj.search(cr, uid, [
|
||||||
|
@ -1306,6 +1309,10 @@ class procurement_order(osv.osv):
|
||||||
('location_id', '=', procurement.location_id.id), ('company_id', '=', procurement.company_id.id), ('dest_address_id', '=', procurement.partner_dest_id.id)], context=context)
|
('location_id', '=', procurement.location_id.id), ('company_id', '=', procurement.company_id.id), ('dest_address_id', '=', procurement.partner_dest_id.id)], context=context)
|
||||||
if available_draft_po_ids:
|
if available_draft_po_ids:
|
||||||
po_id = available_draft_po_ids[0]
|
po_id = available_draft_po_ids[0]
|
||||||
|
po_rec = po_obj.browse(cr, uid, po_id, context=context)
|
||||||
|
#if the product has to be ordered earlier those in the existing PO, we replace the purchase date on the order to avoid ordering it too late
|
||||||
|
if datetime.strptime(po_rec.date_order, DEFAULT_SERVER_DATE_FORMAT) > purchase_date:
|
||||||
|
po_obj.write(cr, uid, [po_id], {'date_order': purchase_date}, context=context)
|
||||||
#look for any other PO line in the selected PO with same product and UoM to sum quantities instead of creating a new po line
|
#look for any other PO line in the selected PO with same product and UoM to sum quantities instead of creating a new po line
|
||||||
available_po_line_ids = po_line_obj.search(cr, uid, [('order_id', '=', po_id), ('product_id', '=', line_vals['product_id']), ('product_uom', '=', line_vals['product_uom'])], context=context)
|
available_po_line_ids = po_line_obj.search(cr, uid, [('order_id', '=', po_id), ('product_id', '=', line_vals['product_id']), ('product_uom', '=', line_vals['product_uom'])], context=context)
|
||||||
if available_po_line_ids:
|
if available_po_line_ids:
|
||||||
|
@ -1318,7 +1325,6 @@ class procurement_order(osv.osv):
|
||||||
po_line_id = po_line_obj.create(cr, SUPERUSER_ID, line_vals, context=context)
|
po_line_id = po_line_obj.create(cr, SUPERUSER_ID, line_vals, context=context)
|
||||||
linked_po_ids.append(procurement.id)
|
linked_po_ids.append(procurement.id)
|
||||||
else:
|
else:
|
||||||
purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
|
|
||||||
name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
|
name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
|
||||||
po_vals = {
|
po_vals = {
|
||||||
'name': name,
|
'name': name,
|
||||||
|
@ -1363,6 +1369,13 @@ class product_template(osv.Model):
|
||||||
_name = 'product.template'
|
_name = 'product.template'
|
||||||
_inherit = 'product.template'
|
_inherit = 'product.template'
|
||||||
|
|
||||||
|
def _get_buy_route(self, cr, uid, context=None):
|
||||||
|
|
||||||
|
buy_route = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'purchase.route_warehouse0_buy')
|
||||||
|
if buy_route:
|
||||||
|
return [buy_route]
|
||||||
|
return []
|
||||||
|
|
||||||
def _purchase_count(self, cr, uid, ids, field_name, arg, context=None):
|
def _purchase_count(self, cr, uid, ids, field_name, arg, context=None):
|
||||||
res = dict.fromkeys(ids, 0)
|
res = dict.fromkeys(ids, 0)
|
||||||
for template in self.browse(cr, uid, ids, context=context):
|
for template in self.browse(cr, uid, ids, context=context):
|
||||||
|
@ -1374,6 +1387,7 @@ class product_template(osv.Model):
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'purchase_ok': 1,
|
'purchase_ok': 1,
|
||||||
|
'route_ids': _get_buy_route,
|
||||||
}
|
}
|
||||||
|
|
||||||
class product_product(osv.Model):
|
class product_product(osv.Model):
|
||||||
|
@ -1451,15 +1465,9 @@ class account_invoice_line(osv.Model):
|
||||||
readonly=True),
|
readonly=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
class product_product(osv.osv):
|
class product_template(osv.osv):
|
||||||
_inherit = "product.product"
|
_inherit = "product.template"
|
||||||
|
|
||||||
def _get_buy_route(self, cr, uid, context=None):
|
|
||||||
buy_route = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'route_warehouse0_buy')[1]
|
|
||||||
return [buy_route]
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'route_ids': _get_buy_route,
|
|
||||||
}
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -55,6 +55,33 @@ class stock_move(osv.osv):
|
||||||
})
|
})
|
||||||
return super(stock_move, self).copy(cr, uid, id, default, context)
|
return super(stock_move, self).copy(cr, uid, id, default, context)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
|
||||||
|
invoice_line_id = super(stock_move, self)._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
|
||||||
|
if move.purchase_line_id:
|
||||||
|
purchase_line = move.purchase_line_id
|
||||||
|
self.pool.get('purchase.order.line').write(cr, uid, [purchase_line.id], {
|
||||||
|
'invoice_lines': [(4, invoice_line_id)]
|
||||||
|
}, context=context)
|
||||||
|
self.pool.get('purchase.order').write(cr, uid, [purchase_line.order_id.id], {
|
||||||
|
'invoice_ids': [(4, invoice_line_vals['invoice_id'])],
|
||||||
|
})
|
||||||
|
return invoice_line_id
|
||||||
|
|
||||||
|
def _get_master_data(self, cr, uid, move, company, context=None):
|
||||||
|
if move.purchase_line_id:
|
||||||
|
purchase_order = move.purchase_line_id.order_id
|
||||||
|
return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.pricelist_id.currency_id.id
|
||||||
|
return super(stock_move, self)._get_master_data(cr, uid, move, company, context=context)
|
||||||
|
|
||||||
|
def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None):
|
||||||
|
res = super(stock_move, self)._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context)
|
||||||
|
if move.purchase_line_id:
|
||||||
|
purchase_line = move.purchase_line_id
|
||||||
|
res['invoice_line_tax_id'] = [(6, 0, [x.id for x in purchase_line.taxes_id])]
|
||||||
|
res['price_unit'] = purchase_line.price_unit
|
||||||
|
return res
|
||||||
|
|
||||||
class stock_picking(osv.osv):
|
class stock_picking(osv.osv):
|
||||||
_inherit = 'stock.picking'
|
_inherit = 'stock.picking'
|
||||||
|
|
||||||
|
|
|
@ -128,13 +128,12 @@ class sale_order(osv.osv):
|
||||||
sale_clause = ''
|
sale_clause = ''
|
||||||
no_invoiced = False
|
no_invoiced = False
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if arg[1] == '=':
|
if (arg[1] == '=' and arg[2]) or (arg[1] == '!=' and not arg[2]):
|
||||||
if arg[2]:
|
clause += 'AND inv.state = \'paid\''
|
||||||
clause += 'AND inv.state = \'paid\''
|
else:
|
||||||
else:
|
clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id '
|
||||||
clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id '
|
sale_clause = ', sale_order AS sale '
|
||||||
sale_clause = ', sale_order AS sale '
|
no_invoiced = True
|
||||||
no_invoiced = True
|
|
||||||
|
|
||||||
cursor.execute('SELECT rel.order_id ' \
|
cursor.execute('SELECT rel.order_id ' \
|
||||||
'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \
|
'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \
|
||||||
|
|
|
@ -388,7 +388,7 @@ class stock_move(osv.osv):
|
||||||
return super(stock_move, self).action_cancel(cr, uid, ids, context=context)
|
return super(stock_move, self).action_cancel(cr, uid, ids, context=context)
|
||||||
|
|
||||||
def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
|
def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
|
||||||
invoice_line_id = self.pool.get('account.invoice.line').create(cr, uid, invoice_line_vals, context=context)
|
invoice_line_id = super(stock_move, self)._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
|
||||||
if move.procurement_id and move.procurement_id.sale_line_id:
|
if move.procurement_id and move.procurement_id.sale_line_id:
|
||||||
sale_line = move.procurement_id.sale_line_id
|
sale_line = move.procurement_id.sale_line_id
|
||||||
self.pool.get('sale.order.line').write(cr, uid, [sale_line.id], {
|
self.pool.get('sale.order.line').write(cr, uid, [sale_line.id], {
|
||||||
|
|
|
@ -261,7 +261,7 @@ class procurement_order(osv.osv):
|
||||||
result['domain'] = "[('group_id','in',[" + ','.join(map(str, list(group_ids))) + "])]"
|
result['domain'] = "[('group_id','in',[" + ','.join(map(str, list(group_ids))) + "])]"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def run_scheduler(self, cr, uid, use_new_cursor=False, context=None):
|
def run_scheduler(self, cr, uid, use_new_cursor=False, company_id=False, context=None):
|
||||||
'''
|
'''
|
||||||
Call the scheduler in order to check the running procurements (super method), to check the minimum stock rules
|
Call the scheduler in order to check the running procurements (super method), to check the minimum stock rules
|
||||||
and the availability of moves. This function is intended to be run for all the companies at the same time, so
|
and the availability of moves. This function is intended to be run for all the companies at the same time, so
|
||||||
|
@ -286,8 +286,7 @@ class procurement_order(osv.osv):
|
||||||
move_obj = self.pool.get('stock.move')
|
move_obj = self.pool.get('stock.move')
|
||||||
|
|
||||||
#Minimum stock rules
|
#Minimum stock rules
|
||||||
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
|
self._procure_orderpoint_confirm(cr, SUPERUSER_ID, use_new_cursor=False, company_id=company_id, context=context)
|
||||||
self._procure_orderpoint_confirm(cr, SUPERUSER_ID, use_new_cursor=False, company_id=company.id, context=context)
|
|
||||||
|
|
||||||
#Search all confirmed stock_moves and try to assign them
|
#Search all confirmed stock_moves and try to assign them
|
||||||
confirmed_ids = move_obj.search(cr, uid, [('state', '=', 'confirmed')], limit=None, order='priority desc, date_expected asc', context=context)
|
confirmed_ids = move_obj.search(cr, uid, [('state', '=', 'confirmed')], limit=None, order='priority desc, date_expected asc', context=context)
|
||||||
|
@ -331,7 +330,7 @@ class procurement_order(osv.osv):
|
||||||
[order_point.product_id.id],
|
[order_point.product_id.id],
|
||||||
context={'location': order_point.location_id.id})[order_point.product_id.id]['virtual_available']
|
context={'location': order_point.location_id.id})[order_point.product_id.id]['virtual_available']
|
||||||
|
|
||||||
def _procure_orderpoint_confirm(self, cr, uid, use_new_cursor=False, company_id=False, context=None):
|
def _procure_orderpoint_confirm(self, cr, uid, use_new_cursor=False, company_id = False, context=None):
|
||||||
'''
|
'''
|
||||||
Create procurement based on Orderpoint
|
Create procurement based on Orderpoint
|
||||||
|
|
||||||
|
@ -341,14 +340,15 @@ class procurement_order(osv.osv):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
if use_new_cursor:
|
if use_new_cursor:
|
||||||
cr = openerp.registry(cr.dbname).db.cursor()
|
cr = openerp.registry(cr.dbname).cursor()
|
||||||
orderpoint_obj = self.pool.get('stock.warehouse.orderpoint')
|
orderpoint_obj = self.pool.get('stock.warehouse.orderpoint')
|
||||||
|
|
||||||
procurement_obj = self.pool.get('procurement.order')
|
procurement_obj = self.pool.get('procurement.order')
|
||||||
offset = 0
|
offset = 0
|
||||||
ids = [1]
|
ids = [1]
|
||||||
|
dom = company_id and [('company_id', '=', company_id)] or []
|
||||||
while ids:
|
while ids:
|
||||||
ids = orderpoint_obj.search(cr, uid, [('company_id', '=', company_id)], offset=offset, limit=100)
|
ids = orderpoint_obj.search(cr, uid, dom, offset=offset, limit=100)
|
||||||
for op in orderpoint_obj.browse(cr, uid, ids, context=context):
|
for op in orderpoint_obj.browse(cr, uid, ids, context=context):
|
||||||
prods = self._product_virtual_get(cr, uid, op)
|
prods = self._product_virtual_get(cr, uid, op)
|
||||||
if prods is None:
|
if prods is None:
|
||||||
|
|
|
@ -171,13 +171,13 @@ class product_product(osv.osv):
|
||||||
def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None):
|
def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None):
|
||||||
res = {}
|
res = {}
|
||||||
for product in self.browse(cr, uid, ids, context=context):
|
for product in self.browse(cr, uid, ids, context=context):
|
||||||
res[product.id] = str(product.qty_available) + _(" In Stock")
|
res[product.id] = str(product.qty_available) + _(" On Hand")
|
||||||
return res
|
return res
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'reception_count': fields.function(_stock_move_count, string="Reception", type='integer', multi='pickings'),
|
'reception_count': fields.function(_stock_move_count, string="Reception", type='integer', multi='pickings'),
|
||||||
'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'),
|
'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'),
|
||||||
'qty_in_stock': fields.function(_product_available_text, type='char'),
|
'qty_available_text': fields.function(_product_available_text, type='char'),
|
||||||
'qty_available': fields.function(_product_available, multi='qty_available',
|
'qty_available': fields.function(_product_available, multi='qty_available',
|
||||||
type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
|
type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||||
string='Quantity On Hand',
|
string='Quantity On Hand',
|
||||||
|
@ -277,6 +277,12 @@ class product_product(osv.osv):
|
||||||
res['fields']['qty_available']['string'] = _('Produced Qty')
|
res['fields']['qty_available']['string'] = _('Produced Qty')
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def action_view_routes(self, cr, uid, ids, context=None):
|
||||||
|
template_obj = self.pool.get("product.template")
|
||||||
|
templ_ids = list(set([x.product_tmpl_id.id for x in self.browse(cr, uid, ids, context=context)]))
|
||||||
|
return template_obj.action_view_routes(cr, uid, templ_ids, context=context)
|
||||||
|
|
||||||
class product_template(osv.osv):
|
class product_template(osv.osv):
|
||||||
_name = 'product.template'
|
_name = 'product.template'
|
||||||
_inherit = 'product.template'
|
_inherit = 'product.template'
|
||||||
|
|
|
@ -193,7 +193,7 @@
|
||||||
<field name="inherit_id" ref="product.product_normal_form_view"/>
|
<field name="inherit_id" ref="product.product_normal_form_view"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<group name="status" position="before">
|
<group name="status" position="before">
|
||||||
<group name="lot" groups="stock.group_tracking_lot,stock.group_production_lot" string="Lots">
|
<group name="lot" groups="stock.group_production_lot" string="Lots">
|
||||||
<field name="track_all" groups="stock.group_production_lot"/>
|
<field name="track_all" groups="stock.group_production_lot"/>
|
||||||
<field name="track_incoming" groups="stock.group_production_lot" attrs="{'invisible': [('track_all', '=', True)]}"/>
|
<field name="track_incoming" groups="stock.group_production_lot" attrs="{'invisible': [('track_all', '=', True)]}"/>
|
||||||
<field name="track_outgoing" groups="stock.group_production_lot" attrs="{'invisible': [('track_all', '=', True)]}"/>
|
<field name="track_outgoing" groups="stock.group_production_lot" attrs="{'invisible': [('track_all', '=', True)]}"/>
|
||||||
|
@ -202,13 +202,13 @@
|
||||||
<xpath expr="//div[@name='buttons']" position="inside">
|
<xpath expr="//div[@name='buttons']" position="inside">
|
||||||
<button class="oe_stat_button"
|
<button class="oe_stat_button"
|
||||||
name="%(product_open_quants)d"
|
name="%(product_open_quants)d"
|
||||||
icon="fa-bank"
|
icon="fa-building-o"
|
||||||
type="action" attrs="{'invisible':[('type', '=', 'service')]}" groups="stock.group_locations">
|
type="action" attrs="{'invisible':[('type', '=', 'service')]}" groups="stock.group_locations">
|
||||||
<div><field name="qty_in_stock"/></div>
|
<div><field name="qty_available_text"/></div>
|
||||||
</button>
|
</button>
|
||||||
<button class="oe_inline oe_stat_button" string="Moves" name= "%(act_product_stock_move_open)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}" groups="stock.group_stock_user" icon="fa-arrows-v"/>
|
<button class="oe_inline oe_stat_button" string="Moves" name= "%(act_product_stock_move_open)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}" groups="stock.group_stock_user" icon="fa-arrows-v"/>
|
||||||
<button class="oe_inline oe_stat_button" name="%(product_open_orderpoint)d" type="action"
|
<button class="oe_inline oe_stat_button" name="%(product_open_orderpoint)d" type="action"
|
||||||
attrs="{'invisible':[('type', '=', 'service')]}" icon="fa-pinterest" string="Reordering Rules"/>
|
attrs="{'invisible':[('type', '=', 'service')]}" icon="fa-refresh" string="Reordering Rules"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
@ -60,7 +60,7 @@ access_stock_location_path_internal_user,stock location path internal user,model
|
||||||
access_stock_location_path_sale_manager,stock.location.path partner salemanager,model_stock_location_path,base.group_sale_manager,1,1,1,1
|
access_stock_location_path_sale_manager,stock.location.path partner salemanager,model_stock_location_path,base.group_sale_manager,1,1,1,1
|
||||||
access_stock_location_path_stock_user,stock.location.path stock user,model_stock_location_path,stock.group_stock_user,1,1,1,1
|
access_stock_location_path_stock_user,stock.location.path stock user,model_stock_location_path,stock.group_stock_user,1,1,1,1
|
||||||
access_stock_location_path,stock.location.path,model_stock_location_path,base.group_sale_salesman,1,0,0,0
|
access_stock_location_path,stock.location.path,model_stock_location_path,base.group_sale_salesman,1,0,0,0
|
||||||
access_stock_location_route,stock.location.route,model_stock_location_route,stock.group_stock_manager,1,1,1,1
|
access_stock_location_route_stock_manager,stock.location.route,model_stock_location_route,stock.group_stock_manager,1,1,1,1
|
||||||
access_stock_location_route,stock.location.route,model_stock_location_route,base.group_user,1,0,0,0
|
access_stock_location_route,stock.location.route,model_stock_location_route,base.group_user,1,0,0,0
|
||||||
access_procurement_rule,procurement.rule.flow,model_procurement_rule,base.group_sale_salesman,1,0,0,0
|
access_procurement_rule,procurement.rule.flow,model_procurement_rule,base.group_sale_salesman,1,0,0,0
|
||||||
access_procurement_rule_internal,procurement.rule.flow internal,model_procurement_rule,base.group_user,1,0,0,0
|
access_procurement_rule_internal,procurement.rule.flow internal,model_procurement_rule,base.group_user,1,0,0,0
|
||||||
|
|
|
|
@ -50,7 +50,7 @@
|
||||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.rule" id="stock_picking_rule">
|
<record model="ir.rule" id="stock_picking_type_rule">
|
||||||
<field name="name">Stock Picking Type multi-company</field>
|
<field name="name">Stock Picking Type multi-company</field>
|
||||||
<field name="model_id" search="[('model','=','stock.picking.type')]" model="ir.model"/>
|
<field name="model_id" search="[('model','=','stock.picking.type')]" model="ir.model"/>
|
||||||
<field name="global" eval="True"/>
|
<field name="global" eval="True"/>
|
||||||
|
|
|
@ -272,27 +272,28 @@ class stock_quant(osv.osv):
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'name': fields.function(_get_quant_name, type='char', string='Identifier'),
|
'name': fields.function(_get_quant_name, type='char', string='Identifier'),
|
||||||
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete="restrict"),
|
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete="restrict", readonly=True),
|
||||||
'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="restrict"),
|
'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="restrict", readonly=True),
|
||||||
'qty': fields.float('Quantity', required=True, help="Quantity of products in this quant, in the default unit of measure of the product"),
|
'qty': fields.float('Quantity', required=True, help="Quantity of products in this quant, in the default unit of measure of the product", readonly=True),
|
||||||
'package_id': fields.many2one('stock.quant.package', string='Package', help="The package containing this quant"),
|
'package_id': fields.many2one('stock.quant.package', string='Package', help="The package containing this quant", readonly=True),
|
||||||
'packaging_type_id': fields.related('package_id', 'packaging_id', type='many2one', relation='product.packaging', string='Type of packaging', store=True),
|
'packaging_type_id': fields.related('package_id', 'packaging_id', type='many2one', relation='product.packaging', string='Type of packaging', readonly=True, store=True),
|
||||||
'reservation_id': fields.many2one('stock.move', 'Reserved for Move', help="The move the quant is reserved for"),
|
'reservation_id': fields.many2one('stock.move', 'Reserved for Move', help="The move the quant is reserved for", readonly=True),
|
||||||
'lot_id': fields.many2one('stock.production.lot', 'Lot'),
|
'lot_id': fields.many2one('stock.production.lot', 'Lot', readonly=True),
|
||||||
'cost': fields.float('Unit Cost'),
|
'cost': fields.float('Unit Cost'),
|
||||||
'owner_id': fields.many2one('res.partner', 'Owner', help="This is the owner of the quant"),
|
'owner_id': fields.many2one('res.partner', 'Owner', help="This is the owner of the quant", readonly=True),
|
||||||
|
|
||||||
'create_date': fields.datetime('Creation Date'),
|
'create_date': fields.datetime('Creation Date', readonly=True),
|
||||||
'in_date': fields.datetime('Incoming Date'),
|
'in_date': fields.datetime('Incoming Date', readonly=True),
|
||||||
|
|
||||||
'history_ids': fields.many2many('stock.move', 'stock_quant_move_rel', 'quant_id', 'move_id', 'Moves', help='Moves that operate(d) on this quant'),
|
'history_ids': fields.many2many('stock.move', 'stock_quant_move_rel', 'quant_id', 'move_id', 'Moves', help='Moves that operate(d) on this quant'),
|
||||||
'company_id': fields.many2one('res.company', 'Company', help="The company to which the quants belong", required=True),
|
'company_id': fields.many2one('res.company', 'Company', help="The company to which the quants belong", required=True, readonly=True),
|
||||||
'inventory_value': fields.function(_calc_inventory_value, string="Inventory Value", type='float', readonly=True),
|
'inventory_value': fields.function(_calc_inventory_value, string="Inventory Value", type='float', readonly=True),
|
||||||
|
|
||||||
# Used for negative quants to reconcile after compensated by a new positive one
|
# Used for negative quants to reconcile after compensated by a new positive one
|
||||||
'propagated_from_id': fields.many2one('stock.quant', 'Linked Quant', help='The negative quant this is coming from'),
|
'propagated_from_id': fields.many2one('stock.quant', 'Linked Quant', help='The negative quant this is coming from', readonly=True),
|
||||||
'negative_move_id': fields.many2one('stock.move', 'Move Negative Quant', help='If this is a negative quant, this will be the move that caused this negative quant.'),
|
'negative_move_id': fields.many2one('stock.move', 'Move Negative Quant', help='If this is a negative quant, this will be the move that caused this negative quant.', readonly=True),
|
||||||
'negative_dest_location_id': fields.related('negative_move_id', 'location_dest_id', type='many2one', relation='stock.location', string="Negative Destination Location", help="Technical field used to record the destination location of a move that created a negative quant"),
|
'negative_dest_location_id': fields.related('negative_move_id', 'location_dest_id', type='many2one', relation='stock.location', string="Negative Destination Location", readonly=True,
|
||||||
|
help="Technical field used to record the destination location of a move that created a negative quant"),
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
|
@ -606,7 +607,6 @@ class stock_quant(osv.osv):
|
||||||
raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name))
|
raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
# Stock Picking
|
# Stock Picking
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
@ -1248,6 +1248,7 @@ class stock_picking(osv.osv):
|
||||||
'''
|
'''
|
||||||
move_obj = self.pool.get('stock.move')
|
move_obj = self.pool.get('stock.move')
|
||||||
operation_obj = self.pool.get('stock.pack.operation')
|
operation_obj = self.pool.get('stock.pack.operation')
|
||||||
|
moves = []
|
||||||
for op in picking.pack_operation_ids:
|
for op in picking.pack_operation_ids:
|
||||||
for product_id, remaining_qty in operation_obj._get_remaining_prod_quantities(cr, uid, op, context=context).items():
|
for product_id, remaining_qty in operation_obj._get_remaining_prod_quantities(cr, uid, op, context=context).items():
|
||||||
if remaining_qty > 0:
|
if remaining_qty > 0:
|
||||||
|
@ -1260,9 +1261,12 @@ class stock_picking(osv.osv):
|
||||||
'product_uom': product.uom_id.id,
|
'product_uom': product.uom_id.id,
|
||||||
'product_uom_qty': remaining_qty,
|
'product_uom_qty': remaining_qty,
|
||||||
'name': _('Extra Move: ') + product.name,
|
'name': _('Extra Move: ') + product.name,
|
||||||
'state': 'confirmed',
|
'state': 'draft',
|
||||||
}
|
}
|
||||||
move_obj.create(cr, uid, vals, context=context)
|
moves.append(move_obj.create(cr, uid, vals, context=context))
|
||||||
|
if moves:
|
||||||
|
move_obj.action_confirm(cr, uid, moves, context=context)
|
||||||
|
return moves
|
||||||
|
|
||||||
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."""
|
||||||
|
@ -1289,11 +1293,13 @@ class stock_picking(osv.osv):
|
||||||
else:
|
else:
|
||||||
need_rereserve, all_op_processed = self.picking_recompute_remaining_quantities(cr, uid, picking, context=context)
|
need_rereserve, all_op_processed = self.picking_recompute_remaining_quantities(cr, uid, picking, context=context)
|
||||||
#create extra moves in the picking (unexpected product moves coming from pack operations)
|
#create extra moves in the picking (unexpected product moves coming from pack operations)
|
||||||
|
todo_move_ids = []
|
||||||
if not all_op_processed:
|
if not all_op_processed:
|
||||||
self._create_extra_moves(cr, uid, picking, context=context)
|
todo_move_ids += self._create_extra_moves(cr, uid, picking, context=context)
|
||||||
|
|
||||||
picking.refresh()
|
picking.refresh()
|
||||||
#split move lines eventually
|
#split move lines eventually
|
||||||
todo_move_ids = []
|
|
||||||
toassign_move_ids = []
|
toassign_move_ids = []
|
||||||
for move in picking.move_lines:
|
for move in picking.move_lines:
|
||||||
remaining_qty = move.remaining_qty
|
remaining_qty = move.remaining_qty
|
||||||
|
@ -1371,7 +1377,7 @@ class stock_picking(osv.osv):
|
||||||
op = operation
|
op = operation
|
||||||
if (operation.qty_done < operation.product_qty):
|
if (operation.qty_done < operation.product_qty):
|
||||||
new_operation = stock_operation_obj.copy(cr, uid, operation.id, {'product_qty': operation.qty_done,'qty_done': operation.qty_done}, context=context)
|
new_operation = stock_operation_obj.copy(cr, uid, operation.id, {'product_qty': operation.qty_done,'qty_done': operation.qty_done}, context=context)
|
||||||
stock_operation_obj.write(cr, uid, operation.id, {'product_qty': operation.product_qty - operation.qty_done,'qty_done': 0}, context=context)
|
stock_operation_obj.write(cr, uid, operation.id, {'product_qty': operation.product_qty - operation.qty_done,'qty_done': 0, 'lot_id': False}, context=context)
|
||||||
op = stock_operation_obj.browse(cr, uid, new_operation, context=context)
|
op = stock_operation_obj.browse(cr, uid, new_operation, context=context)
|
||||||
pack_operation_ids.append(op.id)
|
pack_operation_ids.append(op.id)
|
||||||
for record in op.linked_move_operation_ids:
|
for record in op.linked_move_operation_ids:
|
||||||
|
@ -1783,7 +1789,7 @@ class stock_move(osv.osv):
|
||||||
'move_dest_id': move.id,
|
'move_dest_id': move.id,
|
||||||
'group_id': group_id,
|
'group_id': group_id,
|
||||||
'route_ids': [(4, x.id) for x in move.route_ids],
|
'route_ids': [(4, x.id) for x in move.route_ids],
|
||||||
'warehouse_id': move.warehouse_id and move.warehouse_id.id or False,
|
'warehouse_id': move.warehouse_id.id or (move.picking_type_id and move.picking_type_id.warehouse_id.id or False),
|
||||||
'priority': move.priority,
|
'priority': move.priority,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3521,7 +3527,6 @@ class stock_location_path(osv.osv):
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|
||||||
from openerp.report import report_sxw
|
from openerp.report import report_sxw
|
||||||
report_sxw.report_sxw('report.stock.quant.package.barcode', 'stock.quant.package', 'addons/stock/report/package_barcode.rml')
|
|
||||||
|
|
||||||
class stock_package(osv.osv):
|
class stock_package(osv.osv):
|
||||||
"""
|
"""
|
||||||
|
@ -3818,8 +3823,8 @@ class stock_pack_operation(osv.osv):
|
||||||
if pack_op.qty_done < pack_op.product_qty:
|
if pack_op.qty_done < pack_op.product_qty:
|
||||||
# we split the operation in two
|
# we split the operation in two
|
||||||
op = self.copy(cr, uid, pack_op.id, {'product_qty': pack_op.qty_done, 'qty_done': pack_op.qty_done}, context=context)
|
op = self.copy(cr, uid, pack_op.id, {'product_qty': pack_op.qty_done, 'qty_done': pack_op.qty_done}, context=context)
|
||||||
self.write(cr, uid, ids, {'product_qty': pack_op.product_qty - pack_op.qty_done, 'qty_done': 0}, context=context)
|
self.write(cr, uid, [pack_op.id], {'product_qty': pack_op.product_qty - pack_op.qty_done, 'qty_done': 0, 'lot_id': False}, context=context)
|
||||||
processed_ids.append(op)
|
processed_ids.append(op)
|
||||||
self.write(cr, uid, processed_ids, {'processed': 'true'}, context=context)
|
self.write(cr, uid, processed_ids, {'processed': 'true'}, context=context)
|
||||||
|
|
||||||
def create_and_assign_lot(self, cr, uid, id, name, context=None):
|
def create_and_assign_lot(self, cr, uid, id, name, context=None):
|
||||||
|
@ -3828,10 +3833,16 @@ class stock_pack_operation(osv.osv):
|
||||||
obj = self.browse(cr,uid,id,context)
|
obj = self.browse(cr,uid,id,context)
|
||||||
product_id = obj.product_id.id
|
product_id = obj.product_id.id
|
||||||
val = {'product_id': product_id}
|
val = {'product_id': product_id}
|
||||||
|
new_lot_id = False
|
||||||
if name:
|
if name:
|
||||||
|
lots = self.pool.get('stock.production.lot').search(cr, uid, ['&', ('name', '=', name), ('product_id', '=', product_id)], context=context)
|
||||||
|
if lots:
|
||||||
|
new_lot_id = lots[0]
|
||||||
val.update({'name': name})
|
val.update({'name': name})
|
||||||
|
|
||||||
if not obj.lot_id:
|
if not obj.lot_id:
|
||||||
new_lot_id = self.pool.get('stock.production.lot').create(cr, uid, val, context=context)
|
if not new_lot_id:
|
||||||
|
new_lot_id = self.pool.get('stock.production.lot').create(cr, uid, val, context=context)
|
||||||
self.write(cr, uid, id, {'lot_id': new_lot_id}, context=context)
|
self.write(cr, uid, id, {'lot_id': new_lot_id}, context=context)
|
||||||
|
|
||||||
def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, increment=True, context=None):
|
def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, increment=True, context=None):
|
||||||
|
@ -3869,11 +3880,15 @@ class stock_pack_operation(osv.osv):
|
||||||
self.write(cr, uid, [operation_id], {'qty_done': qty}, context=context)
|
self.write(cr, uid, [operation_id], {'qty_done': qty}, context=context)
|
||||||
else:
|
else:
|
||||||
#no existing operation found for the given domain and picking => create a new one
|
#no existing operation found for the given domain and picking => create a new one
|
||||||
|
picking_obj = self.pool.get("stock.picking")
|
||||||
|
picking = picking_obj.browse(cr, uid, picking_id, context=context)
|
||||||
values = {
|
values = {
|
||||||
'picking_id': picking_id,
|
'picking_id': picking_id,
|
||||||
'product_qty': 0,
|
'product_qty': 0,
|
||||||
|
'location_id': picking.location_id.id,
|
||||||
|
'location_dest_id': picking.location_dest_id.id,
|
||||||
'qty_done': 1,
|
'qty_done': 1,
|
||||||
}
|
}
|
||||||
for key in domain:
|
for key in domain:
|
||||||
var_name, dummy, value = key
|
var_name, dummy, value = key
|
||||||
uom_id = False
|
uom_id = False
|
||||||
|
|
|
@ -355,7 +355,7 @@
|
||||||
<record model="ir.actions.act_window" id="location_open_quants">
|
<record model="ir.actions.act_window" id="location_open_quants">
|
||||||
<field name="context">{'search_default_productgroup': 1}</field>
|
<field name="context">{'search_default_productgroup': 1}</field>
|
||||||
<field name="domain">[('location_id', 'child_of', active_ids)]</field>
|
<field name="domain">[('location_id', 'child_of', active_ids)]</field>
|
||||||
<field name="name">Quants</field>
|
<field name="name">Current Stock</field>
|
||||||
<field name="res_model">stock.quant</field>
|
<field name="res_model">stock.quant</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
@ -1356,6 +1356,7 @@
|
||||||
<field name="model">stock.picking.type</field>
|
<field name="model">stock.picking.type</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Picking Types">
|
<tree string="Picking Types">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="warehouse_id"/>
|
<field name="warehouse_id"/>
|
||||||
<field name="sequence_id"/>
|
<field name="sequence_id"/>
|
||||||
|
@ -1794,7 +1795,7 @@
|
||||||
</record>
|
</record>
|
||||||
<record model="ir.actions.act_window" id="product_open_quants">
|
<record model="ir.actions.act_window" id="product_open_quants">
|
||||||
<field name="context">{'search_default_internal_loc': 1, 'search_default_product_id': active_id, 'search_default_locationgroup':1}</field>
|
<field name="context">{'search_default_internal_loc': 1, 'search_default_product_id': active_id, 'search_default_locationgroup':1}</field>
|
||||||
<field name="name">Quants</field>
|
<field name="name">Current Stock</field>
|
||||||
<field name="res_model">stock.quant</field>
|
<field name="res_model">stock.quant</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,15 @@
|
||||||
<t>
|
<t>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="oe_structure"/>
|
<div class="oe_structure"/>
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-4">
|
|
||||||
<img class="image" t-att-src="'data:image/png;base64,%s' % res_company.logo" style="border:auto;"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 mt6">
|
<div class="col-xs-6 mt6">
|
||||||
<table class="table table-condensed" style="border-bottom: 3px solid black !important;"><thead><th> </th></thead></table>
|
<table class="table table-condensed" style="border-bottom: 3px solid black !important;"><thead><th> </th></thead></table>
|
||||||
<img t-if="not o.loc_barcode" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', o.name, 600, 100)" style="width:300px;height:50px"/>
|
<img t-if="not o.loc_barcode" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', o.name, 600, 100)" style="width:300px;height:50px"/>
|
||||||
<img t-if="o.loc_barcode" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', o.loc_barcode, 600, 100)" style="width:300px;height:50px"/>
|
<img t-if="o.loc_barcode" t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', o.loc_barcode, 600, 100)" style="width:300px;height:50px"/>
|
||||||
<p class="text-center" t-if="not o.loc_barcode" t-field="o.name"></p>
|
<p>
|
||||||
<p class="text-center" t-if="o.loc_barcode" t-field="o.loc_barcode"></p>
|
<span t-if="not o.loc_barcode" t-field="o.name"/>
|
||||||
|
<span t-if="o.loc_barcode" t-field="o.loc_barcode"/>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,15 +7,15 @@
|
||||||
<t t-call="report.external_layout">
|
<t t-call="report.external_layout">
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="row"><div class="col-xs-4 pull-right">
|
<div class="row"><div class="col-xs-4 pull-right">
|
||||||
<img t-att-src="'/report/barcode/Code128/%s' % o.name"/>
|
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', o.name, 600, 100)" style="width:300px;height:50px;"/>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div t-if="o.picking_type_id.code=='incoming'">
|
<div t-if="o.picking_type_id.code=='incoming' and o.partner_id">
|
||||||
<span><strong>Supplier Address:</strong></span>
|
<span><strong>Supplier Address:</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="o.picking_type_id.code=='internal'">
|
<div t-if="o.picking_type_id.code=='internal' and o.partner_id">
|
||||||
<span><strong>Warehouse Address:</strong></span>
|
<span><strong>Warehouse Address:</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="o.picking_type_id.code=='outgoing'">
|
<div t-if="o.picking_type_id.code=='outgoing' and o.partner_id">
|
||||||
<span><strong>Customer Address:</strong></span>
|
<span><strong>Customer Address:</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="o.partner_id" name="partner_header">
|
<div t-if="o.partner_id" name="partner_header">
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
<t t-if="o.picking_type_id.code != 'incoming'"><td><span t-field="move.location_id"/></td></t>
|
<t t-if="o.picking_type_id.code != 'incoming'"><td><span t-field="move.location_id"/></td></t>
|
||||||
<td>
|
<td>
|
||||||
<span t-if="move.product_id and move.product_id.ean13">
|
<span t-if="move.product_id and move.product_id.ean13">
|
||||||
<img t-att-src="'/report/barcode/EAN13/%s' % move.product_id.ean13"/>
|
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('EAN13', move.product_id.ean13, 600, 100)" style="width:300px;height:50px"/>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<t t-if="o.picking_type_id.code != 'outgoing'"><td><span t-field="move.location_dest_id"/></td></t>
|
<t t-if="o.picking_type_id.code != 'outgoing'"><td><span t-field="move.location_dest_id"/></td></t>
|
||||||
|
@ -104,13 +104,13 @@
|
||||||
</t>
|
</t>
|
||||||
<td>
|
<td>
|
||||||
<span t-if="pack_operation.lot_id">
|
<span t-if="pack_operation.lot_id">
|
||||||
<img t-att-src="'/report/barcode/Code128/%s' % pack_operation.lot_id.name"/>
|
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', pack_operation.lot_id.name, 600, 100)" style="width:300px;height:50px"/>
|
||||||
</span>
|
</span>
|
||||||
<span t-if="pack_operation.product_id and not pack_operation.lot_id and pack_operation.product_id.ean13">
|
<span t-if="pack_operation.product_id and not pack_operation.lot_id and pack_operation.product_id.ean13">
|
||||||
<img t-att-src="'/report/barcode/EAN13/%s' % pack_operation.product_id.ean13"/>
|
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('EAN13', pack_operation.product_id.ean13, 600, 100)" style="width:300px;height:50px"/>
|
||||||
</span>
|
</span>
|
||||||
<span t-if="pack_operation.package_id and not pack_operation.product_id">
|
<span t-if="pack_operation.package_id and not pack_operation.product_id">
|
||||||
<img t-att-src="'/report/barcode/Code128/%s' % pack_operation.package_id.name"/>
|
<img t-att-src="'/report/barcode/?type=%s&value=%s&width=%s&height=%s' % ('Code128', pack_operation.package_id.name, 600, 100)" style="width:300px;height:50px"/>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<t t-if="o.picking_type_id.code != 'outgoing'"><td><span t-field="pack_operation.location_dest_id"/>
|
<t t-if="o.picking_type_id.code != 'outgoing'"><td><span t-field="pack_operation.location_dest_id"/>
|
||||||
|
|
|
@ -43,8 +43,10 @@ class procurement_compute(osv.osv_memory):
|
||||||
proc_obj = self.pool.get('procurement.order')
|
proc_obj = self.pool.get('procurement.order')
|
||||||
#As this function is in a new thread, I need to open a new cursor, because the old one may be closed
|
#As this function is in a new thread, I need to open a new cursor, because the old one may be closed
|
||||||
new_cr = self.pool.cursor()
|
new_cr = self.pool.cursor()
|
||||||
for proc in self.browse(new_cr, uid, ids, context=context):
|
user_obj = self.pool.get('res.users')
|
||||||
proc_obj._procure_orderpoint_confirm(new_cr, uid, use_new_cursor=new_cr.dbname, context=context)
|
user = user_obj.browse(new_cr, uid, uid, context=context)
|
||||||
|
for comp in user.company_ids:
|
||||||
|
proc_obj._procure_orderpoint_confirm(new_cr, uid, use_new_cursor=new_cr.dbname, company_id = comp.id, context=context)
|
||||||
#close the new cursor
|
#close the new cursor
|
||||||
new_cr.close()
|
new_cr.close()
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -53,6 +53,7 @@ Dashboard / Reports for Warehouse Management includes:
|
||||||
'wizard/stock_change_standard_price_view.xml',
|
'wizard/stock_change_standard_price_view.xml',
|
||||||
'wizard/stock_invoice_onshipping_view.xml',
|
'wizard/stock_invoice_onshipping_view.xml',
|
||||||
'wizard/stock_valuation_history_view.xml',
|
'wizard/stock_valuation_history_view.xml',
|
||||||
|
'wizard/stock_return_picking_view.xml',
|
||||||
'product_data.xml',
|
'product_data.xml',
|
||||||
'product_view.xml',
|
'product_view.xml',
|
||||||
'stock_account_view.xml',
|
'stock_account_view.xml',
|
||||||
|
|
|
@ -30,11 +30,7 @@
|
||||||
<field name="property_stock_account_input" domain="[('type','<>','view'),('type','<>','consolidation')]"/>
|
<field name="property_stock_account_input" domain="[('type','<>','view'),('type','<>','consolidation')]"/>
|
||||||
<field name="property_stock_account_output" domain="[('type','<>','view'),('type','<>','consolidation')]"/>
|
<field name="property_stock_account_output" domain="[('type','<>','view'),('type','<>','consolidation')]"/>
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='standard_price']" position='replace'>
|
|
||||||
<field name="standard_price" attrs="{'readonly':[('cost_method','=','average')]}"/>
|
|
||||||
<field name="cost_method" groups="stock_account.group_inventory_valuation"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,10 @@ class stock_location_path(osv.osv):
|
||||||
'invoice_state': fields.selection([
|
'invoice_state': fields.selection([
|
||||||
("invoiced", "Invoiced"),
|
("invoiced", "Invoiced"),
|
||||||
("2binvoiced", "To Be Invoiced"),
|
("2binvoiced", "To Be Invoiced"),
|
||||||
("none", "Not Applicable")], "Invoice Status",
|
("none", "Not Applicable")], "Invoice Status",),
|
||||||
required=True,),
|
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'invoice_state': 'none',
|
'invoice_state': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
@ -43,11 +42,10 @@ class procurement_rule(osv.osv):
|
||||||
'invoice_state': fields.selection([
|
'invoice_state': fields.selection([
|
||||||
("invoiced", "Invoiced"),
|
("invoiced", "Invoiced"),
|
||||||
("2binvoiced", "To Be Invoiced"),
|
("2binvoiced", "To Be Invoiced"),
|
||||||
("none", "Not Applicable")], "Invoice Status",
|
("none", "Not Applicable")], "Invoice Status",),
|
||||||
required=True),
|
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'invoice_state': 'none',
|
'invoice_state': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
@ -61,16 +59,16 @@ class procurement_order(osv.osv):
|
||||||
'invoice_state': fields.selection([("invoiced", "Invoiced"),
|
'invoice_state': fields.selection([("invoiced", "Invoiced"),
|
||||||
("2binvoiced", "To Be Invoiced"),
|
("2binvoiced", "To Be Invoiced"),
|
||||||
("none", "Not Applicable")
|
("none", "Not Applicable")
|
||||||
], "Invoice Control", required=True),
|
], "Invoice Control"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _run_move_create(self, cr, uid, procurement, context=None):
|
def _run_move_create(self, cr, uid, procurement, context=None):
|
||||||
res = super(procurement_order, self)._run_move_create(cr, uid, procurement, context=context)
|
res = super(procurement_order, self)._run_move_create(cr, uid, procurement, context=context)
|
||||||
res.update({'invoice_state': (procurement.rule_id.invoice_state in ('none', False) and procurement.invoice_state or procurement.rule_id.invoice_state) or 'none'})
|
res.update({'invoice_state': procurement.rule_id.invoice_state or procurement.invoice_state or 'none'})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'invoice_state': 'none'
|
'invoice_state': ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,7 +90,7 @@ class stock_move(osv.osv):
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_master_data(self, cr, uid, move, company, context=None):
|
def _get_master_data(self, cr, uid, move, company, context=None):
|
||||||
''' returns a tupple (browse_record(res.partner), ID(res.users), ID(res.currency)'''
|
''' returns a tuple (browse_record(res.partner), ID(res.users), ID(res.currency)'''
|
||||||
return move.picking_id.partner_id, uid, company.currency_id.id
|
return move.picking_id.partner_id, uid, company.currency_id.id
|
||||||
|
|
||||||
def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
|
def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
|
||||||
|
@ -204,7 +202,7 @@ class stock_picking(osv.osv):
|
||||||
for picking in self.browse(cr, uid, ids, context=context):
|
for picking in self.browse(cr, uid, ids, context=context):
|
||||||
key = group and picking.id or True
|
key = group and picking.id or True
|
||||||
for move in picking.move_lines:
|
for move in picking.move_lines:
|
||||||
if move.procurement_id and (move.procurement_id.invoice_state == '2binvoiced') or move.invoice_state == '2binvoiced':
|
if move.invoice_state == '2binvoiced':
|
||||||
if (move.state != 'cancel') and not move.scrapped:
|
if (move.state != 'cancel') and not move.scrapped:
|
||||||
todo.setdefault(key, [])
|
todo.setdefault(key, [])
|
||||||
todo[key].append(move)
|
todo[key].append(move)
|
||||||
|
@ -259,12 +257,7 @@ class stock_picking(osv.osv):
|
||||||
invoice_line_vals['origin'] = origin
|
invoice_line_vals['origin'] = origin
|
||||||
|
|
||||||
move_obj._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
|
move_obj._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
|
||||||
|
|
||||||
move_obj.write(cr, uid, move.id, {'invoice_state': 'invoiced'}, context=context)
|
move_obj.write(cr, uid, move.id, {'invoice_state': 'invoiced'}, context=context)
|
||||||
if move.procurement_id:
|
|
||||||
self.pool.get('procurement.order').write(cr, uid, [move.procurement_id.id], {
|
|
||||||
'invoice_state': 'invoiced',
|
|
||||||
}, context=context)
|
|
||||||
|
|
||||||
invoice_obj.button_compute(cr, uid, invoices.values(), context=context, set_total=(inv_type in ('in_invoice', 'in_refund')))
|
invoice_obj.button_compute(cr, uid, invoices.values(), context=context, set_total=(inv_type in ('in_invoice', 'in_refund')))
|
||||||
return invoices.values()
|
return invoices.values()
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//button[@name='do_partial_open_barcode']" position="after">
|
<xpath expr="//button[@name='do_partial_open_barcode']" position="after">
|
||||||
<button name="%(action_stock_invoice_onshipping)d" string="Create Invoice" attrs="{'invisible': ['|',('state','<>','done'),('invoice_state','<>','2binvoiced')]}" type="action" class="oe_highlight" groups="base.group_user"/>
|
<button name="%(action_stock_invoice_onshipping)d" string="Create Invoice" attrs="{'invisible': ['|',('state','<>','done'),('invoice_state','<>','2binvoiced')]}" type="action" class="oe_highlight" groups="base.group_user"/>
|
||||||
<button name="%(action_stock_invoice_onshipping)d" string="Refund Invoice" attrs="{'invisible': ['|',('state','<>','done'),('invoice_state','<>','invoiced')]}" type="action" class="oe_highlight" groups="base.group_user" context="{'inv_type': 'out_refund'}"/>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='move_type']" position="after">
|
<xpath expr="//field[@name='move_type']" position="after">
|
||||||
<field name="invoice_state" groups="account.group_account_invoice"/>
|
<field name="invoice_state" groups="account.group_account_invoice"/>
|
||||||
|
|
|
@ -22,3 +22,4 @@
|
||||||
import stock_change_standard_price
|
import stock_change_standard_price
|
||||||
import stock_invoice_onshipping
|
import stock_invoice_onshipping
|
||||||
import stock_valuation_history
|
import stock_valuation_history
|
||||||
|
import stock_return_picking
|
||||||
|
|
|
@ -24,34 +24,48 @@ from openerp.tools.translate import _
|
||||||
|
|
||||||
class stock_invoice_onshipping(osv.osv_memory):
|
class stock_invoice_onshipping(osv.osv_memory):
|
||||||
def _get_journal(self, cr, uid, context=None):
|
def _get_journal(self, cr, uid, context=None):
|
||||||
res = self._get_journal_id(cr, uid, context=context)
|
journal_obj = self.pool.get('account.journal')
|
||||||
if res:
|
journal_type = self._get_journal_type(cr, uid, context=context)
|
||||||
return res[0][0]
|
journals = journal_obj.search(cr, uid, [('type', '=', journal_type)])
|
||||||
return False
|
return journals and journals[0] or False
|
||||||
|
|
||||||
def _get_journal_id(self, cr, uid, context=None):
|
def _get_journal_type(self, cr, uid, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
journal_obj = self.pool.get('account.journal')
|
res_ids = context and context.get('active_ids', [])
|
||||||
value = journal_obj.search(cr, uid, [('type', 'in',('sale','sale_Refund'))])
|
pick_obj = self.pool.get('stock.picking')
|
||||||
|
pickings = pick_obj.browse(cr, uid, res_ids, context=context)
|
||||||
vals = []
|
vals = []
|
||||||
for jr_type in journal_obj.browse(cr, uid, value, context=context):
|
pick = pickings and pickings[0]
|
||||||
t1 = jr_type.id,jr_type.name
|
if not pick or not pick.move_lines:
|
||||||
if t1 not in vals:
|
return 'sale'
|
||||||
vals.append(t1)
|
src_usage = pick.move_lines[0].location_id.usage
|
||||||
return vals
|
dest_usage = pick.move_lines[0].location_dest_id.usage
|
||||||
|
type = pick.picking_type_id.code
|
||||||
|
if type == 'outgoing' and dest_usage == 'supplier':
|
||||||
|
journal_type = 'purchase_refund'
|
||||||
|
elif type == 'outgoing' and dest_usage == 'customer':
|
||||||
|
journal_type = 'sale'
|
||||||
|
elif type == 'incoming' and src_usage == 'supplier':
|
||||||
|
journal_type = 'purchase'
|
||||||
|
elif type == 'incoming' and src_usage == 'customer':
|
||||||
|
journal_type = 'sale_refund'
|
||||||
|
else:
|
||||||
|
journal_type = 'sale'
|
||||||
|
return journal_type
|
||||||
|
|
||||||
_name = "stock.invoice.onshipping"
|
_name = "stock.invoice.onshipping"
|
||||||
_description = "Stock Invoice Onshipping"
|
_description = "Stock Invoice Onshipping"
|
||||||
_columns = {
|
_columns = {
|
||||||
'journal_id': fields.selection(_get_journal_id, 'Destination Journal',required=True),
|
'journal_id': fields.many2one('account.journal', 'Destination Journal', required=True),
|
||||||
|
'journal_type': fields.selection([('purchase_refund', 'Refund Purchase'), ('purchase', 'Create Supplier Invoice'),
|
||||||
|
('sale_refund', 'Refund Sale'), ('sale', 'Create Customer Invoice')], 'Journal Type', readonly=True),
|
||||||
'group': fields.boolean("Group by partner"),
|
'group': fields.boolean("Group by partner"),
|
||||||
'inv_type': fields.selection([('out_invoice','Create Invoice'),('out_refund','Refund Invoice')], "Invoice Type"),
|
|
||||||
'invoice_date': fields.date('Invoice Date'),
|
'invoice_date': fields.date('Invoice Date'),
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
|
'journal_type': _get_journal_type,
|
||||||
'journal_id' : _get_journal,
|
'journal_id' : _get_journal,
|
||||||
'inv_type': lambda self,cr,uid,ctx: ctx.get('inv_type', 'out_invoice')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def view_init(self, cr, uid, fields_list, context=None):
|
def view_init(self, cr, uid, fields_list, context=None):
|
||||||
|
@ -71,24 +85,30 @@ class stock_invoice_onshipping(osv.osv_memory):
|
||||||
def open_invoice(self, cr, uid, ids, context=None):
|
def open_invoice(self, cr, uid, ids, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
invoice_ids = self.create_invoice(cr, uid, ids, context=context)
|
invoice_ids = self.create_invoice(cr, uid, ids, context=context)
|
||||||
if not invoice_ids:
|
if not invoice_ids:
|
||||||
raise osv.except_osv(_('Error!'), _('No invoice created!'))
|
raise osv.except_osv(_('Error!'), _('No invoice created!'))
|
||||||
|
|
||||||
onshipdata_obj = self.read(cr, uid, ids, ['journal_id', 'group', 'invoice_date', 'inv_type'])
|
data = self.browse(cr, uid, ids[0], context=context)
|
||||||
inv_type = onshipdata_obj[0]['inv_type']
|
|
||||||
|
|
||||||
action_model = False
|
action_model = False
|
||||||
action = {}
|
action = {}
|
||||||
|
|
||||||
|
journal2type = {'sale':'out_invoice', 'purchase':'in_invoice' , 'sale_refund':'out_refund', 'purchase_refund':'in_refund'}
|
||||||
|
inv_type = journal2type.get(data.journal_type) or 'out_invoice'
|
||||||
data_pool = self.pool.get('ir.model.data')
|
data_pool = self.pool.get('ir.model.data')
|
||||||
if inv_type == "out_refund":
|
if inv_type == "out_invoice":
|
||||||
action_model,action_id = data_pool.get_object_reference(cr, uid, 'account', "action_invoice_tree3")
|
action_id = data_pool.xmlid_to_res_id(cr, uid, 'account.action_invoice_tree1')
|
||||||
elif inv_type == "out_invoice":
|
elif inv_type == "in_invoice":
|
||||||
action_model,action_id = data_pool.get_object_reference(cr, uid, 'account', "action_invoice_tree1")
|
action_id = data_pool.xmlid_to_res_id(cr, uid, 'account.action_invoice_tree2')
|
||||||
|
elif inv_type == "out_refund":
|
||||||
|
action_id = data_pool.xmlid_to_res_id(cr, uid, 'account.action_invoice_tree3')
|
||||||
|
elif inv_type == "in_refund":
|
||||||
|
action_id = data_pool.xmlid_to_res_id(cr, uid, 'account.action_invoice_tree4')
|
||||||
|
|
||||||
if action_model:
|
if action_id:
|
||||||
action_pool = self.pool[action_model]
|
action_pool = self.pool['ir.actions.act_window']
|
||||||
action = action_pool.read(cr, uid, action_id, context=context)
|
action = action_pool.read(cr, uid, action_id, context=context)
|
||||||
action['domain'] = "[('id','in', ["+','.join(map(str,invoice_ids))+"])]"
|
action['domain'] = "[('id','in', ["+','.join(map(str,invoice_ids))+"])]"
|
||||||
return action
|
return action
|
||||||
|
@ -97,18 +117,17 @@ class stock_invoice_onshipping(osv.osv_memory):
|
||||||
def create_invoice(self, cr, uid, ids, context=None):
|
def create_invoice(self, cr, uid, ids, context=None):
|
||||||
context = context or {}
|
context = context or {}
|
||||||
picking_pool = self.pool.get('stock.picking')
|
picking_pool = self.pool.get('stock.picking')
|
||||||
onshipdata_obj = self.read(cr, uid, ids, ['journal_id', 'group', 'invoice_date', 'inv_type'])
|
data = self.browse(cr, uid, ids[0], context=context)
|
||||||
|
journal2type = {'sale':'out_invoice', 'purchase':'in_invoice', 'sale_refund':'out_refund', 'purchase_refund':'in_refund'}
|
||||||
context['date_inv'] = onshipdata_obj[0]['invoice_date']
|
context['date_inv'] = data.invoice_date
|
||||||
inv_type = onshipdata_obj[0]['inv_type']
|
acc_journal = self.pool.get("account.journal")
|
||||||
|
inv_type = journal2type.get(data.journal_type) or 'out_invoice'
|
||||||
context['inv_type'] = inv_type
|
context['inv_type'] = inv_type
|
||||||
|
|
||||||
active_ids = context.get('active_ids', [])
|
active_ids = context.get('active_ids', [])
|
||||||
if isinstance(onshipdata_obj[0]['journal_id'], tuple):
|
|
||||||
onshipdata_obj[0]['journal_id'] = onshipdata_obj[0]['journal_id'][0]
|
|
||||||
res = picking_pool.action_invoice_create(cr, uid, active_ids,
|
res = picking_pool.action_invoice_create(cr, uid, active_ids,
|
||||||
journal_id = onshipdata_obj[0]['journal_id'],
|
journal_id = data.journal_id.id,
|
||||||
group = onshipdata_obj[0]['group'],
|
group = data.group,
|
||||||
type = inv_type,
|
type = inv_type,
|
||||||
context=context)
|
context=context)
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Create invoice">
|
<form string="Create invoice">
|
||||||
<h1>
|
<h1>
|
||||||
<field name="inv_type" readonly="1"/>
|
<field name="journal_type" readonly="1"/>
|
||||||
</h1>
|
</h1>
|
||||||
<group>
|
<group>
|
||||||
<field name="journal_id"/>
|
<field name="journal_id" domain="[('type','=',journal_type)]"/>
|
||||||
<field name="group"/>
|
<field name="group"/>
|
||||||
<field name="invoice_date" />
|
<field name="invoice_date" />
|
||||||
</group>
|
</group>
|
||||||
|
@ -23,17 +23,6 @@
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<act_window name="Create Invoice"
|
|
||||||
res_model="stock.invoice.onshipping"
|
|
||||||
src_model="stock.picking"
|
|
||||||
key2="client_action_multi"
|
|
||||||
multi="True"
|
|
||||||
view_mode="form"
|
|
||||||
view_type="form"
|
|
||||||
target="new"
|
|
||||||
id="action_stock_invoice_onshipping"/>
|
|
||||||
|
|
||||||
<act_window name="Create Draft Invoices"
|
<act_window name="Create Draft Invoices"
|
||||||
res_model="stock.invoice.onshipping"
|
res_model="stock.invoice.onshipping"
|
||||||
src_model="stock.picking"
|
src_model="stock.picking"
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from openerp.osv import osv, fields
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
import openerp.addons.decimal_precision as dp
|
||||||
|
|
||||||
|
|
||||||
|
class stock_return_picking(osv.osv_memory):
|
||||||
|
_inherit = 'stock.return.picking'
|
||||||
|
_columns = {
|
||||||
|
'invoice_state': fields.selection([('2binvoiced', 'To be refunded/invoiced'), ('none', 'No invoicing')], 'Invoicing',required=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
def default_get(self, cr, uid, fields, context=None):
|
||||||
|
res = super(stock_return_picking, self).default_get(cr, uid, fields, context=context)
|
||||||
|
record_id = context and context.get('active_id', False) or False
|
||||||
|
pick_obj = self.pool.get('stock.picking')
|
||||||
|
pick = pick_obj.browse(cr, uid, record_id, context=context)
|
||||||
|
if pick:
|
||||||
|
if 'invoice_state' in fields:
|
||||||
|
if pick.invoice_state=='invoiced':
|
||||||
|
res.update({'invoice_state': '2binvoiced'})
|
||||||
|
else:
|
||||||
|
res.update({'invoice_state': 'none'})
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _create_returns(self, cr, uid, ids, context=None):
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
data = self.browse(cr, uid, ids[0], context=context)
|
||||||
|
new_picking, picking_type_id = super(stock_return_picking, self)._create_returns(cr, uid, ids, context=context)
|
||||||
|
if data.invoice_state == '2binvoiced':
|
||||||
|
pick_obj = self.pool.get("stock.picking")
|
||||||
|
move_obj = self.pool.get("stock.move")
|
||||||
|
move_ids = [x.id for x in pick_obj.browse(cr, uid, new_picking, context=context).move_lines]
|
||||||
|
move_obj.write(cr, uid, move_ids, {'invoice_state': '2binvoiced'})
|
||||||
|
return new_picking, picking_type_id
|
||||||
|
|
||||||
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,14 @@
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<record id="view_stock_return_picking_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">Return lines</field>
|
||||||
|
<field name="model">stock.return.picking</field>
|
||||||
|
<field name="inherit_id" ref="stock.view_stock_return_picking_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="product_return_moves" position="after">
|
||||||
|
<field name="invoice_state"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -55,12 +55,13 @@ class stock_history(osv.osv):
|
||||||
product_tmpl_obj = self.pool.get("product.template")
|
product_tmpl_obj = self.pool.get("product.template")
|
||||||
lines_rec = self.browse(cr, uid, lines, context=context)
|
lines_rec = self.browse(cr, uid, lines, context=context)
|
||||||
for line_rec in lines_rec:
|
for line_rec in lines_rec:
|
||||||
if not line_rec.product_id.id in prod_dict:
|
if line_rec.product_id.cost_method == 'real':
|
||||||
if line_rec.product_id.cost_method == 'real':
|
price = line_rec.price_unit_on_quant
|
||||||
prod_dict[line_rec.product_id.id] = line_rec.price_unit_on_quant
|
else:
|
||||||
else:
|
if not line_rec.product_id.id in prod_dict:
|
||||||
prod_dict[line_rec.product_id.id] = product_tmpl_obj.get_history_price(cr, uid, line_rec.product_id.product_tmpl_id.id, line_rec.company_id.id, date=date, context=context)
|
prod_dict[line_rec.product_id.id] = product_tmpl_obj.get_history_price(cr, uid, line_rec.product_id.product_tmpl_id.id, line_rec.company_id.id, date=date, context=context)
|
||||||
inv_value += prod_dict[line_rec.product_id.id] * line_rec.quantity
|
price = prod_dict[line_rec.product_id.id]
|
||||||
|
inv_value += price * line_rec.quantity
|
||||||
line['inventory_value'] = inv_value
|
line['inventory_value'] = inv_value
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<record id="picking_type_dropship" model="stock.picking.type">
|
<record id="picking_type_dropship" model="stock.picking.type">
|
||||||
<field name="name">Dropship</field>
|
<field name="name">Dropship</field>
|
||||||
<field name="sequence_id" ref="seq_picking_type_dropship"/>
|
<field name="sequence_id" ref="seq_picking_type_dropship"/>
|
||||||
<field name="code">incoming</field>
|
<field name="code">outgoing</field>
|
||||||
<field name="default_location_src_id" ref="stock.stock_location_suppliers"/>
|
<field name="default_location_src_id" ref="stock.stock_location_suppliers"/>
|
||||||
<field name="default_location_dest_id" ref="stock.stock_location_customers"/>
|
<field name="default_location_dest_id" ref="stock.stock_location_customers"/>
|
||||||
</record>
|
</record>
|
||||||
|
@ -37,7 +37,6 @@
|
||||||
<field name="procure_method">make_to_stock</field>
|
<field name="procure_method">make_to_stock</field>
|
||||||
<field name="route_id" ref="route_drop_shipping"/>
|
<field name="route_id" ref="route_drop_shipping"/>
|
||||||
<field name="picking_type_id" ref="picking_type_dropship"/>
|
<field name="picking_type_id" ref="picking_type_dropship"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -318,8 +318,9 @@ class WebsiteSurvey(http.Controller):
|
||||||
'filter_finish': filter_finish
|
'filter_finish': filter_finish
|
||||||
})
|
})
|
||||||
|
|
||||||
def prepare_result_dict(self,survey, current_filters=[]):
|
def prepare_result_dict(self,survey, current_filters=None):
|
||||||
"""Returns dictionary having values for rendering template"""
|
"""Returns dictionary having values for rendering template"""
|
||||||
|
current_filters = current_filters if current_filters else []
|
||||||
survey_obj = request.registry['survey.survey']
|
survey_obj = request.registry['survey.survey']
|
||||||
result = {'survey':survey, 'page_ids': []}
|
result = {'survey':survey, 'page_ids': []}
|
||||||
for page in survey.page_ids:
|
for page in survey.page_ids:
|
||||||
|
@ -347,8 +348,10 @@ class WebsiteSurvey(http.Controller):
|
||||||
total = ceil(total_record / float(limit))
|
total = ceil(total_record / float(limit))
|
||||||
return range(1, int(total + 1))
|
return range(1, int(total + 1))
|
||||||
|
|
||||||
def get_graph_data(self, question, current_filters=[]):
|
def get_graph_data(self, question, current_filters=None):
|
||||||
'''Returns formatted data required by graph library on basis of filter'''
|
'''Returns formatted data required by graph library on basis of filter'''
|
||||||
|
# TODO refactor this terrible method and merge it with prepare_result_dict
|
||||||
|
current_filters = current_filters if current_filters else []
|
||||||
survey_obj = request.registry['survey.survey']
|
survey_obj = request.registry['survey.survey']
|
||||||
result = []
|
result = []
|
||||||
if question.type == 'multiple_choice':
|
if question.type == 'multiple_choice':
|
||||||
|
@ -360,9 +363,8 @@ class WebsiteSurvey(http.Controller):
|
||||||
data = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)
|
data = survey_obj.prepare_result(request.cr, request.uid, question, current_filters, context=request.context)
|
||||||
for answer in data['answers']:
|
for answer in data['answers']:
|
||||||
values = []
|
values = []
|
||||||
for res in data['result']:
|
for row in data['rows']:
|
||||||
if res[1] == answer:
|
values.append({'text': data['rows'].get(row), 'count': data['result'].get((row, answer))})
|
||||||
values.append({'text': data['rows'][res[0]], 'count': data['result'][res]})
|
|
||||||
result.append({'key': data['answers'].get(answer), 'values': values})
|
result.append({'key': data['answers'].get(answer), 'values': values})
|
||||||
return json.dumps(result)
|
return json.dumps(result)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from openerp.addons.website.models.website import slug
|
||||||
from urlparse import urljoin
|
from urlparse import urljoin
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
@ -289,8 +290,7 @@ class survey_survey(osv.Model):
|
||||||
:param finished: True for completely filled survey,Falser otherwise.
|
:param finished: True for completely filled survey,Falser otherwise.
|
||||||
:returns list of filtered user_input_ids.
|
:returns list of filtered user_input_ids.
|
||||||
'''
|
'''
|
||||||
if context is None:
|
context = context if context else {}
|
||||||
context = {}
|
|
||||||
if filters:
|
if filters:
|
||||||
input_line_obj = self.pool.get('survey.user_input_line')
|
input_line_obj = self.pool.get('survey.user_input_line')
|
||||||
domain_filter, choice, filter_display_data = [], [], []
|
domain_filter, choice, filter_display_data = [], [], []
|
||||||
|
@ -339,22 +339,27 @@ class survey_survey(osv.Model):
|
||||||
filter_display_data.append({'question_text': question.question, 'labels': [label.value for label in labels]})
|
filter_display_data.append({'question_text': question.question, 'labels': [label.value for label in labels]})
|
||||||
return filter_display_data
|
return filter_display_data
|
||||||
|
|
||||||
def prepare_result(self, cr, uid, question, current_filters=[], context=None):
|
def prepare_result(self, cr, uid, question, current_filters=None, context=None):
|
||||||
''' Compute statistical data for questions by counting number of vote per choice on basis of filter '''
|
''' Compute statistical data for questions by counting number of vote per choice on basis of filter '''
|
||||||
if context is None:
|
current_filters = current_filters if current_filters else []
|
||||||
context = {}
|
context = context if context else {}
|
||||||
|
|
||||||
#Calculate and return statistics for choice
|
#Calculate and return statistics for choice
|
||||||
if question.type in ['simple_choice', 'multiple_choice']:
|
if question.type in ['simple_choice', 'multiple_choice']:
|
||||||
result_summary = {}
|
result_summary = []
|
||||||
[result_summary.update({label.id: {'text': label.value, 'count': 0, 'answer_id': label.id}}) for label in question.labels_ids]
|
for label in question.labels_ids:
|
||||||
for input_line in question.user_input_line_ids:
|
count = 0
|
||||||
if input_line.answer_type == 'suggestion' and result_summary.get(input_line.value_suggested.id) and (not(current_filters) or input_line.user_input_id.id in current_filters):
|
for input_line in question.user_input_line_ids:
|
||||||
result_summary[input_line.value_suggested.id]['count'] += 1
|
if input_line.answer_type == 'suggestion' and input_line.value_suggested.id == label.id and (not current_filters or input_line.user_input_id.id in current_filters):
|
||||||
result_summary = result_summary.values()
|
count = count + 1
|
||||||
|
label_summary = {'text': label.value, 'count': count, 'answer_id': label.id}
|
||||||
|
result_summary = result_summary + [label_summary]
|
||||||
|
|
||||||
#Calculate and return statistics for matrix
|
#Calculate and return statistics for matrix
|
||||||
if question.type == 'matrix':
|
if question.type == 'matrix':
|
||||||
rows, answers, res = {}, {}, {}
|
rows = OrderedDict()
|
||||||
|
answers = OrderedDict()
|
||||||
|
res = dict()
|
||||||
[rows.update({label.id: label.value}) for label in question.labels_ids_2]
|
[rows.update({label.id: label.value}) for label in question.labels_ids_2]
|
||||||
[answers.update({label.id: label.value}) for label in question.labels_ids]
|
[answers.update({label.id: label.value}) for label in question.labels_ids]
|
||||||
for cell in product(rows.keys(), answers.keys()):
|
for cell in product(rows.keys(), answers.keys()):
|
||||||
|
@ -386,10 +391,10 @@ class survey_survey(osv.Model):
|
||||||
'most_comman': Counter(all_inputs).most_common(5)})
|
'most_comman': Counter(all_inputs).most_common(5)})
|
||||||
return result_summary
|
return result_summary
|
||||||
|
|
||||||
def get_input_summary(self, cr, uid, question, current_filters=[], context=None):
|
def get_input_summary(self, cr, uid, question, current_filters=None, context=None):
|
||||||
''' Returns overall summary of question e.g. answered, skipped, total_inputs on basis of filter '''
|
''' Returns overall summary of question e.g. answered, skipped, total_inputs on basis of filter '''
|
||||||
if context is None:
|
current_filters = current_filters if current_filters else []
|
||||||
context = {}
|
context = context if context else {}
|
||||||
result = {}
|
result = {}
|
||||||
if question.survey_id.user_input_ids:
|
if question.survey_id.user_input_ids:
|
||||||
total_input_ids = current_filters or [input_id.id for input_id in question.survey_id.user_input_ids if input_id.state != 'new']
|
total_input_ids = current_filters or [input_id.id for input_id in question.survey_id.user_input_ids if input_id.state != 'new']
|
||||||
|
|
|
@ -2325,7 +2325,7 @@
|
||||||
.openerp .oe_form .oe_form_field_image .oe_form_field_image_controls {
|
.openerp .oe_form .oe_form_field_image .oe_form_field_image_controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
padding: 4px 0;
|
padding: 6px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: none;
|
display: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -2647,11 +2647,11 @@
|
||||||
.openerp .oe_list_editable .oe_list_content td.oe_list_field_cell {
|
.openerp .oe_list_editable .oe_list_content td.oe_list_field_cell {
|
||||||
padding: 4px 6px 3px;
|
padding: 4px 6px 3px;
|
||||||
}
|
}
|
||||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) {
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell {
|
||||||
color: transparent;
|
color: transparent;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) * {
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell * {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_drop_down_button {
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_drop_down_button {
|
||||||
|
@ -2667,6 +2667,13 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_list_field_handle {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_readonly {
|
||||||
|
padding: 4px 6px 3px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
|
||||||
height: 27px;
|
height: 27px;
|
||||||
-moz-border-radius: 0;
|
-moz-border-radius: 0;
|
||||||
|
@ -2678,9 +2685,14 @@
|
||||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field select {
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field select {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input {
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float.oe_readonly, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer.oe_readonly {
|
||||||
|
padding: 6px 0px 0px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master {
|
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
@ -3324,9 +3336,9 @@ body.oe_single_form .oe_single_form_container {
|
||||||
.openerp_ie ul.oe_form_status li.oe_active > .arrow span, .openerp_ie ul.oe_form_status_clickable li.oe_active > .arrow span {
|
.openerp_ie ul.oe_form_status li.oe_active > .arrow span, .openerp_ie ul.oe_form_status_clickable li.oe_active > .arrow span {
|
||||||
background-color: #729fcf !important;
|
background-color: #729fcf !important;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.openerp_ie .oe_webclient {
|
.openerp_ie .oe_webclient {
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
.openerp {
|
.openerp {
|
||||||
|
@ -3450,6 +3462,39 @@ input[type="radio"], input[type="checkbox"] {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- EDITOR TOUR ---- {{{ */
|
||||||
|
div.tour-backdrop {
|
||||||
|
z-index: 2009;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover.tour.orphan .arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popover.tour .popover-navigation {
|
||||||
|
padding: 9px 14px;
|
||||||
|
}
|
||||||
|
.popover.tour .popover-navigation *[data-role="end"] {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.popover.tour .popover-navigation *[data-role="next"], .popover.tour .popover-navigation *[data-role="end"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover.fixed {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1100;
|
||||||
|
background-color: black;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1902,7 +1902,7 @@ $sheet-padding: 16px
|
||||||
.oe_form_field_image_controls
|
.oe_form_field_image_controls
|
||||||
position: absolute
|
position: absolute
|
||||||
top: 1px
|
top: 1px
|
||||||
padding: 4px 0
|
padding: 6px 0
|
||||||
width: 100%
|
width: 100%
|
||||||
display: none
|
display: none
|
||||||
text-align: center
|
text-align: center
|
||||||
|
@ -2138,7 +2138,7 @@ $sheet-padding: 16px
|
||||||
.oe_list_editable .oe_list_content td.oe_list_field_cell
|
.oe_list_editable .oe_list_content td.oe_list_field_cell
|
||||||
padding: 4px 6px 3px
|
padding: 4px 6px 3px
|
||||||
.oe_list.oe_list_editable.oe_editing
|
.oe_list.oe_list_editable.oe_editing
|
||||||
.oe_edition .oe_list_field_cell:not(.oe_readonly)
|
.oe_edition .oe_list_field_cell
|
||||||
*
|
*
|
||||||
visibility: hidden
|
visibility: hidden
|
||||||
color: transparent
|
color: transparent
|
||||||
|
@ -2150,6 +2150,11 @@ $sheet-padding: 16px
|
||||||
.oe_input_icon
|
.oe_input_icon
|
||||||
margin-top: 5px
|
margin-top: 5px
|
||||||
.oe_form_field
|
.oe_form_field
|
||||||
|
&.oe_list_field_handle
|
||||||
|
color: transparent
|
||||||
|
&.oe_readonly
|
||||||
|
padding: 4px 6px 3px
|
||||||
|
text-align: left
|
||||||
min-width: 0
|
min-width: 0
|
||||||
max-width: none
|
max-width: none
|
||||||
input, textarea
|
input, textarea
|
||||||
|
@ -2160,9 +2165,13 @@ $sheet-padding: 16px
|
||||||
input, textarea, select
|
input, textarea, select
|
||||||
min-width: 0
|
min-width: 0
|
||||||
&.oe_form_field_float,&.oe_form_view_integer
|
&.oe_form_field_float,&.oe_form_view_integer
|
||||||
input
|
&.oe_readonly
|
||||||
|
padding: 6px 0px 0px
|
||||||
text-align: right
|
text-align: right
|
||||||
|
max-width: 100px
|
||||||
|
input
|
||||||
width: 100% !important
|
width: 100% !important
|
||||||
|
text-align: right
|
||||||
&.oe_form_field_datetime,&.oe_form_field_date
|
&.oe_form_field_datetime,&.oe_form_field_date
|
||||||
input.oe_datepicker_master
|
input.oe_datepicker_master
|
||||||
width: 100% !important
|
width: 100% !important
|
||||||
|
@ -2801,6 +2810,33 @@ input[type="radio"], input[type="checkbox"]
|
||||||
background-color: black
|
background-color: black
|
||||||
opacity: 0.6000000238418579
|
opacity: 0.6000000238418579
|
||||||
|
|
||||||
|
/* ---- EDITOR TOUR ---- {{{ */
|
||||||
|
|
||||||
|
div.tour-backdrop
|
||||||
|
z-index: 2009
|
||||||
|
.popover.tour
|
||||||
|
&.orphan .arrow
|
||||||
|
display: none
|
||||||
|
.popover-navigation
|
||||||
|
padding: 9px 14px
|
||||||
|
*[data-role="end"]
|
||||||
|
float: right
|
||||||
|
*[data-role="next"],*[data-role="end"]
|
||||||
|
cursor: pointer
|
||||||
|
.popover.fixed
|
||||||
|
position: fixed
|
||||||
|
.tour-backdrop
|
||||||
|
position: fixed
|
||||||
|
top: 0
|
||||||
|
right: 0
|
||||||
|
bottom: 0
|
||||||
|
left: 0
|
||||||
|
z-index: 1100
|
||||||
|
background-color: #000
|
||||||
|
opacity: 0.8
|
||||||
|
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
body
|
body
|
||||||
overflow: auto
|
overflow: auto
|
||||||
|
|
|
@ -0,0 +1,545 @@
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// raise an error in test mode if openerp don't exist
|
||||||
|
if (typeof openerp === "undefined") {
|
||||||
|
var error = "openerp is undefined"
|
||||||
|
+ "\nhref: " + window.location.href
|
||||||
|
+ "\nreferrer: " + document.referrer
|
||||||
|
+ "\nlocalStorage: " + window.localStorage.getItem("tour");
|
||||||
|
if (typeof $ !== "undefined") {
|
||||||
|
error += '\n\n' + $("body").html();
|
||||||
|
}
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
var website = openerp.website;
|
||||||
|
|
||||||
|
// don't rewrite T in test mode
|
||||||
|
if (typeof openerp.Tour !== "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/* jQuery selector to match exact text inside an element
|
||||||
|
* :containsExact() - case insensitive
|
||||||
|
* :containsExactCase() - case sensitive
|
||||||
|
* :containsRegex() - set by user ( use: $(el).find(':containsRegex(/(red|blue|yellow)/gi)') )
|
||||||
|
*/
|
||||||
|
$.extend($.expr[':'],{
|
||||||
|
containsExact: function(a,i,m){
|
||||||
|
return $.trim(a.innerHTML.toLowerCase()) === m[3].toLowerCase();
|
||||||
|
},
|
||||||
|
containsExactCase: function(a,i,m){
|
||||||
|
return $.trim(a.innerHTML) === m[3];
|
||||||
|
},
|
||||||
|
// Note all escaped characters need to be double escaped
|
||||||
|
// inside of the containsRegex, so "\(" needs to be "\\("
|
||||||
|
containsRegex: function(a,i,m){
|
||||||
|
var regreg = /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})$/,
|
||||||
|
reg = regreg.exec(m[3]);
|
||||||
|
return reg ? new RegExp(reg[1], reg[2]).test($.trim(a.innerHTML)) : false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend:function(){
|
||||||
|
$.ajaxBusy = ($.ajaxBusy|0) + 1;
|
||||||
|
},
|
||||||
|
complete:function(){
|
||||||
|
$.ajaxBusy--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var localStorage = window.localStorage;
|
||||||
|
|
||||||
|
var Tour = {
|
||||||
|
tours: {},
|
||||||
|
defaultDelay: 50,
|
||||||
|
retryRunningDelay: 1000,
|
||||||
|
errorDelay: 5000,
|
||||||
|
state: null,
|
||||||
|
$element: null,
|
||||||
|
timer: null,
|
||||||
|
testtimer: null,
|
||||||
|
currentTimer: null,
|
||||||
|
register: function (tour) {
|
||||||
|
if (tour.mode !== "test") tour.mode = "tutorial";
|
||||||
|
Tour.tours[tour.id] = tour;
|
||||||
|
},
|
||||||
|
run: function (tour_id, mode) {
|
||||||
|
var tour = Tour.tours[tour_id];
|
||||||
|
if (!tour) {
|
||||||
|
Tour.error(null, "Can't run '"+tour_id+"' (tour undefined)");
|
||||||
|
}
|
||||||
|
this.time = new Date().getTime();
|
||||||
|
if (tour.path && !window.location.href.match(new RegExp("("+Tour.getLang()+")?"+tour.path+"#?$", "i"))) {
|
||||||
|
var href = Tour.getLang()+tour.path;
|
||||||
|
console.log("Tour '"+tour_id+"' Begin from run method (redirection to "+href+")");
|
||||||
|
Tour.saveState(tour.id, mode || tour.mode, -1, 0);
|
||||||
|
$(document).one("ajaxStop", Tour.running);
|
||||||
|
window.location.href = href;
|
||||||
|
} else {
|
||||||
|
console.log("Tour '"+tour_id+"' Begin from run method");
|
||||||
|
Tour.saveState(tour.id, mode || tour.mode, 0, 0);
|
||||||
|
Tour.running();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
registerSteps: function (tour, mode) {
|
||||||
|
if (tour.register) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tour.register = true;
|
||||||
|
|
||||||
|
for (var index=0, len=tour.steps.length; index<len; index++) {
|
||||||
|
var step = tour.steps[index];
|
||||||
|
step.id = index;
|
||||||
|
|
||||||
|
if (!step.waitNot && index > 0 && tour.steps[index-1] &&
|
||||||
|
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
||||||
|
step.waitNot = '.popover.tour.fade.in:visible';
|
||||||
|
}
|
||||||
|
if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) {
|
||||||
|
step.waitFor = '.oe_overlay_options .oe_options:visible';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/);
|
||||||
|
if (snippet) {
|
||||||
|
step.snippet = snippet[1];
|
||||||
|
} else if (step.snippet) {
|
||||||
|
step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!step.element) {
|
||||||
|
step.element = "body";
|
||||||
|
step.orphan = true;
|
||||||
|
step.backdrop = true;
|
||||||
|
} else {
|
||||||
|
step.popover = step.popover || {};
|
||||||
|
step.popover.arrow = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tour.steps[index-1] &&
|
||||||
|
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
||||||
|
var step = {
|
||||||
|
_title: "close popover and finish",
|
||||||
|
id: index,
|
||||||
|
waitNot: '.popover.tour.fade.in:visible'
|
||||||
|
};
|
||||||
|
tour.steps.push(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rendering bootstrap tour and popover
|
||||||
|
if (mode !== "test") {
|
||||||
|
for (var index=0, len=tour.steps.length; index<len; index++) {
|
||||||
|
var step = tour.steps[index];
|
||||||
|
step._title = step._title || step.title;
|
||||||
|
step.title = Tour.popoverTitle(tour, { title: step._title });
|
||||||
|
step.template = step.template || Tour.popover( step.popover );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closePopover: function () {
|
||||||
|
if (Tour.$element) {
|
||||||
|
Tour.$element.popover('destroy');
|
||||||
|
Tour.$element.removeData("tour");
|
||||||
|
Tour.$element.removeData("tour-step");
|
||||||
|
$(".tour-backdrop").remove();
|
||||||
|
$(".popover.tour").remove();
|
||||||
|
Tour.$element = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
autoTogglePopover: function () {
|
||||||
|
var state = Tour.getState();
|
||||||
|
var step = state.step;
|
||||||
|
|
||||||
|
if (Tour.$element &&
|
||||||
|
Tour.$element.is(":visible") &&
|
||||||
|
Tour.$element.data("tour") === state.id &&
|
||||||
|
Tour.$element.data("tour-step") === step.id) {
|
||||||
|
Tour.repositionPopover();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tour.closePopover();
|
||||||
|
|
||||||
|
var $element = $(step.element).first();
|
||||||
|
if (!step.element || !$element.size() || !$element.is(":visible")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Tour.$element = $element;
|
||||||
|
$element.data("tour", state.id);
|
||||||
|
$element.data("tour-step", step.id);
|
||||||
|
$element.popover({
|
||||||
|
placement: step.placement || "auto",
|
||||||
|
animation: true,
|
||||||
|
trigger: "manual",
|
||||||
|
title: step.title,
|
||||||
|
content: step.content,
|
||||||
|
html: true,
|
||||||
|
container: "body",
|
||||||
|
template: step.template,
|
||||||
|
orphan: step.orphan
|
||||||
|
}).popover("show");
|
||||||
|
|
||||||
|
|
||||||
|
var $tip = $element.data("bs.popover").tip();
|
||||||
|
|
||||||
|
|
||||||
|
// add popover style (orphan, static, backdrop)
|
||||||
|
if (step.orphan) {
|
||||||
|
$tip.addClass("orphan");
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = $element[0];
|
||||||
|
var css;
|
||||||
|
do {
|
||||||
|
css = window.getComputedStyle(node);
|
||||||
|
if (!css || css.position == "fixed") {
|
||||||
|
$tip.addClass("fixed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while ((node = node.parentNode) && node !== document);
|
||||||
|
|
||||||
|
if (step.backdrop) {
|
||||||
|
$("body").append('<div class="tour-backdrop"></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.backdrop || $element.parents("#website-top-navbar, .oe_navbar, .modal").size()) {
|
||||||
|
$tip.css("z-index", 2010);
|
||||||
|
}
|
||||||
|
|
||||||
|
// button click event
|
||||||
|
$tip.find("button")
|
||||||
|
.one("click", function () {
|
||||||
|
step.busy = true;
|
||||||
|
if (!$(this).is("[data-role='next']")) {
|
||||||
|
clearTimeout(Tour.timer);
|
||||||
|
Tour.endTour();
|
||||||
|
}
|
||||||
|
Tour.closePopover();
|
||||||
|
});
|
||||||
|
|
||||||
|
Tour.repositionPopover();
|
||||||
|
},
|
||||||
|
repositionPopover: function() {
|
||||||
|
var popover = Tour.$element.data("bs.popover");
|
||||||
|
var $tip = Tour.$element.data("bs.popover").tip();
|
||||||
|
|
||||||
|
if (popover.options.orphan) {
|
||||||
|
return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
|
||||||
|
offsetWidth = $tip[0].offsetWidth;
|
||||||
|
offsetHeight = $tip[0].offsetHeight;
|
||||||
|
tipOffset = $tip.offset();
|
||||||
|
originalLeft = tipOffset.left;
|
||||||
|
originalTop = tipOffset.top;
|
||||||
|
offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
|
||||||
|
if (offsetBottom < 0) {
|
||||||
|
tipOffset.top = tipOffset.top + offsetBottom;
|
||||||
|
}
|
||||||
|
offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
|
||||||
|
if (offsetRight < 0) {
|
||||||
|
tipOffset.left = tipOffset.left + offsetRight;
|
||||||
|
}
|
||||||
|
if (tipOffset.top < 0) {
|
||||||
|
tipOffset.top = 0;
|
||||||
|
}
|
||||||
|
if (tipOffset.left < 0) {
|
||||||
|
tipOffset.left = 0;
|
||||||
|
}
|
||||||
|
$tip.offset(tipOffset);
|
||||||
|
if (popover.options.placement === "bottom" || popover.options.placement === "top") {
|
||||||
|
var left = Tour.$element.offset().left + Tour.$element.outerWidth()/2 - tipOffset.left;
|
||||||
|
$tip.find(".arrow").css("left", left ? left + "px" : "");
|
||||||
|
} else if (popover.options.placement !== "auto") {
|
||||||
|
var top = Tour.$element.offset().top + Tour.$element.outerHeight()/2 - tipOffset.top;
|
||||||
|
$tip.find(".arrow").css("top", top ? top + "px" : "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_load_template: false,
|
||||||
|
load_template: function () {
|
||||||
|
// don't need template to use bootstrap Tour in automatic mode
|
||||||
|
Tour._load_template = true;
|
||||||
|
if (typeof QWeb2 === "undefined") return $.when();
|
||||||
|
var def = $.Deferred();
|
||||||
|
openerp.qweb.add_template('/web/static/src/xml/website.tour.xml', function(err) {
|
||||||
|
if (err) {
|
||||||
|
def.reject(err);
|
||||||
|
} else {
|
||||||
|
def.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
popoverTitle: function (tour, options) {
|
||||||
|
return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover_title', options) : options.title;
|
||||||
|
},
|
||||||
|
popover: function (options) {
|
||||||
|
return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover', options) : options.title;
|
||||||
|
},
|
||||||
|
getLang: function () {
|
||||||
|
return $("html").attr("lang") ? "/" + $("html").attr("lang").replace(/-/, '_') : "";
|
||||||
|
},
|
||||||
|
getState: function () {
|
||||||
|
var state = JSON.parse(localStorage.getItem("tour") || 'false') || {};
|
||||||
|
if (state) { this.time = state.time; }
|
||||||
|
var tour_id,mode,step_id;
|
||||||
|
if (!state.id && window.location.href.indexOf("#tutorial.") > -1) {
|
||||||
|
state = {
|
||||||
|
"id": window.location.href.match(/#tutorial\.(.*)=true/)[1],
|
||||||
|
"mode": "tutorial",
|
||||||
|
"step_id": 0
|
||||||
|
};
|
||||||
|
window.location.hash = "";
|
||||||
|
console.log("Tour '"+state.id+"' Begin from url hash");
|
||||||
|
Tour.saveState(state.id, state.mode, state.step_id, 0);
|
||||||
|
}
|
||||||
|
if (!state.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.tour = Tour.tours[state.id];
|
||||||
|
state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id];
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
error: function (step, message) {
|
||||||
|
var state = Tour.getState();
|
||||||
|
message += '\n tour: ' + state.id
|
||||||
|
+ (step ? '\n step: ' + step.id + ": '" + (step._title || step.title) + "'" : '' )
|
||||||
|
+ '\n href: ' + window.location.href
|
||||||
|
+ '\n referrer: ' + document.referrer
|
||||||
|
+ (step ? '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) : '' )
|
||||||
|
+ (step ? '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) : '' )
|
||||||
|
+ (step ? '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) : '' )
|
||||||
|
+ "\n localStorage: " + JSON.stringify(localStorage)
|
||||||
|
+ '\n\n' + $("body").html();
|
||||||
|
Tour.reset();
|
||||||
|
if (state.mode === "test") {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lists: function () {
|
||||||
|
var tour_ids = [];
|
||||||
|
for (var k in Tour.tours) {
|
||||||
|
tour_ids.push(k);
|
||||||
|
}
|
||||||
|
return tour_ids;
|
||||||
|
},
|
||||||
|
saveState: function (tour_id, mode, step_id, number, wait) {
|
||||||
|
localStorage.setItem("tour", JSON.stringify({
|
||||||
|
"id":tour_id,
|
||||||
|
"mode":mode,
|
||||||
|
"step_id":step_id || 0,
|
||||||
|
"time": this.time,
|
||||||
|
"number": number+1,
|
||||||
|
"wait": wait || 0
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
reset: function () {
|
||||||
|
var state = Tour.getState();
|
||||||
|
if (state && state.tour) {
|
||||||
|
for (var k in state.tour.steps) {
|
||||||
|
state.tour.steps[k].busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStorage.removeItem("tour");
|
||||||
|
clearTimeout(Tour.timer);
|
||||||
|
clearTimeout(Tour.testtimer);
|
||||||
|
Tour.closePopover();
|
||||||
|
},
|
||||||
|
running: function () {
|
||||||
|
var state = Tour.getState();
|
||||||
|
if (!state) return;
|
||||||
|
else if (state.tour) {
|
||||||
|
if (!Tour._load_template) {
|
||||||
|
Tour.load_template().then(Tour.running);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Tour '"+state.id+"' is running");
|
||||||
|
Tour.registerSteps(state.tour, state.mode);
|
||||||
|
Tour.nextStep();
|
||||||
|
} else {
|
||||||
|
if (state.mode === "test" && state.wait >= 10) {
|
||||||
|
Tour.error(state.step, "Tour '"+state.id+"' undefined");
|
||||||
|
}
|
||||||
|
Tour.saveState(state.id, state.mode, state.step_id, state.number-1, state.wait+1);
|
||||||
|
console.log("Tour '"+state.id+"' wait for running (tour undefined)");
|
||||||
|
setTimeout(Tour.running, Tour.retryRunningDelay);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
check: function (step) {
|
||||||
|
return (step &&
|
||||||
|
(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) &&
|
||||||
|
(!step.waitNot || !$(step.waitNot).size()) &&
|
||||||
|
(!step.waitFor || $(step.waitFor).size()));
|
||||||
|
},
|
||||||
|
waitNextStep: function () {
|
||||||
|
var state = Tour.getState();
|
||||||
|
var time = new Date().getTime();
|
||||||
|
var timer;
|
||||||
|
var next = state.tour.steps[state.step.id+1];
|
||||||
|
var overlaps = state.mode === "test" ? Tour.errorDelay : 0;
|
||||||
|
|
||||||
|
window.onbeforeunload = function () {
|
||||||
|
clearTimeout(Tour.timer);
|
||||||
|
clearTimeout(Tour.testtimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkNext () {
|
||||||
|
Tour.autoTogglePopover();
|
||||||
|
|
||||||
|
clearTimeout(Tour.timer);
|
||||||
|
if (Tour.check(next)) {
|
||||||
|
clearTimeout(Tour.currentTimer);
|
||||||
|
// use an other timeout for cke dom loading
|
||||||
|
Tour.saveState(state.id, state.mode, state.step.id, 0);
|
||||||
|
setTimeout(function () {
|
||||||
|
Tour.nextStep(next);
|
||||||
|
}, Tour.defaultDelay);
|
||||||
|
} else if (!overlaps || new Date().getTime() - time < overlaps) {
|
||||||
|
Tour.timer = setTimeout(checkNext, Tour.defaultDelay);
|
||||||
|
} else {
|
||||||
|
Tour.error(next, "Can't reach the next step");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkNext();
|
||||||
|
},
|
||||||
|
nextStep: function (step) {
|
||||||
|
var state = Tour.getState();
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
step = step || state.step;
|
||||||
|
var next = state.tour.steps[step.id+1];
|
||||||
|
|
||||||
|
if (state.mode === "test" && state.number > 3) {
|
||||||
|
Tour.error(next, "Cycling. Can't reach the next step");
|
||||||
|
}
|
||||||
|
|
||||||
|
Tour.saveState(state.id, state.mode, step.id, state.number);
|
||||||
|
|
||||||
|
if (step.id !== state.step_id) {
|
||||||
|
console.log("Tour '"+state.id+"' Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Tour.autoTogglePopover(true);
|
||||||
|
|
||||||
|
if (step.onload) {
|
||||||
|
step.onload();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
setTimeout(function () {
|
||||||
|
if (Tour.getState()) {
|
||||||
|
Tour.waitNextStep();
|
||||||
|
}
|
||||||
|
if (state.mode === "test") {
|
||||||
|
setTimeout(function(){
|
||||||
|
Tour.autoNextStep(state.tour, step);
|
||||||
|
}, Tour.defaultDelay);
|
||||||
|
}
|
||||||
|
}, next.wait || 0);
|
||||||
|
} else {
|
||||||
|
setTimeout(function(){
|
||||||
|
Tour.autoNextStep(state.tour, step);
|
||||||
|
}, Tour.defaultDelay);
|
||||||
|
Tour.endTour();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endTour: function () {
|
||||||
|
var state = Tour.getState();
|
||||||
|
var test = state.step.id >= state.tour.steps.length-1;
|
||||||
|
Tour.reset();
|
||||||
|
if (test) {
|
||||||
|
console.log("Tour '"+state.id+"' finish: ok");
|
||||||
|
console.log('ok');
|
||||||
|
} else {
|
||||||
|
console.log("Tour '"+state.id+"' finish: error");
|
||||||
|
console.log('error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
autoNextStep: function (tour, step) {
|
||||||
|
clearTimeout(Tour.testtimer);
|
||||||
|
|
||||||
|
function autoStep () {
|
||||||
|
if (!step) return;
|
||||||
|
|
||||||
|
if (step.autoComplete) {
|
||||||
|
step.autoComplete(tour);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".popover.tour [data-role='next']").click();
|
||||||
|
|
||||||
|
var $element = $(step.element);
|
||||||
|
if (!$element.size()) return;
|
||||||
|
|
||||||
|
if (step.snippet) {
|
||||||
|
|
||||||
|
Tour.autoDragAndDropSnippet($element);
|
||||||
|
|
||||||
|
} else if ($element.is(":visible")) {
|
||||||
|
|
||||||
|
$element.trigger($.Event("mouseenter", { srcElement: $element[0] }));
|
||||||
|
$element.trigger($.Event("mousedown", { srcElement: $element[0] }));
|
||||||
|
|
||||||
|
var evt = document.createEvent("MouseEvents");
|
||||||
|
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||||
|
$element[0].dispatchEvent(evt);
|
||||||
|
|
||||||
|
// trigger after for step like: mouseenter, next step click on button display with mouseenter
|
||||||
|
setTimeout(function () {
|
||||||
|
$element.trigger($.Event("mouseup", { srcElement: $element[0] }));
|
||||||
|
$element.trigger($.Event("mouseleave", { srcElement: $element[0] }));
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
if (step.sampleText) {
|
||||||
|
|
||||||
|
$element.trigger($.Event("keydown", { srcElement: $element }));
|
||||||
|
if ($element.is("input") ) {
|
||||||
|
$element.val(step.sampleText);
|
||||||
|
} if ($element.is("select")) {
|
||||||
|
$element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true);
|
||||||
|
$element.val(step.sampleText);
|
||||||
|
} else {
|
||||||
|
$element.html(step.sampleText);
|
||||||
|
}
|
||||||
|
setTimeout(function () {
|
||||||
|
$element.trigger($.Event("keyup", { srcElement: $element }));
|
||||||
|
$element.trigger($.Event("change", { srcElement: $element }));
|
||||||
|
}, self.defaultDelay<<1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tour.testtimer = setTimeout(autoStep, 100);
|
||||||
|
},
|
||||||
|
autoDragAndDropSnippet: function (selector) {
|
||||||
|
var $thumbnail = $(selector).first();
|
||||||
|
var thumbnailPosition = $thumbnail.position();
|
||||||
|
$thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top }));
|
||||||
|
$thumbnail.trigger($.Event("mousemove", { which: 1, pageX: document.body.scrollWidth/2, pageY: document.body.scrollHeight/2 }));
|
||||||
|
var $dropZone = $(".oe_drop_zone").first();
|
||||||
|
var dropPosition = $dropZone.position();
|
||||||
|
$dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
openerp.Tour = Tour;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
|
$(document).ready(Tour.running);
|
||||||
|
|
||||||
|
}());
|
|
@ -2810,7 +2810,9 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc
|
||||||
},
|
},
|
||||||
set_dimensions: function (height, width) {
|
set_dimensions: function (height, width) {
|
||||||
this._super(height, width);
|
this._super(height, width);
|
||||||
this.datewidget.$input.css('height', height);
|
if (!this.get("effective_readonly")) {
|
||||||
|
this.datewidget.$input.css('height', height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3510,6 +3512,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
||||||
this.floating = false;
|
this.floating = false;
|
||||||
this.current_display = null;
|
this.current_display = null;
|
||||||
this.is_started = false;
|
this.is_started = false;
|
||||||
|
this.ignore_focusout = false;
|
||||||
},
|
},
|
||||||
reinit_value: function(val) {
|
reinit_value: function(val) {
|
||||||
this.internal_set_value(val);
|
this.internal_set_value(val);
|
||||||
|
@ -3641,6 +3644,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
||||||
var ed_delay = 200;
|
var ed_delay = 200;
|
||||||
var ed_duration = 15000;
|
var ed_duration = 15000;
|
||||||
var anyoneLoosesFocus = function (e) {
|
var anyoneLoosesFocus = function (e) {
|
||||||
|
if (self.ignore_focusout) { return; }
|
||||||
var used = false;
|
var used = false;
|
||||||
if (self.floating) {
|
if (self.floating) {
|
||||||
if (self.last_search.length > 0) {
|
if (self.last_search.length > 0) {
|
||||||
|
@ -3844,11 +3848,17 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
|
||||||
_search_create_popup: function() {
|
_search_create_popup: function() {
|
||||||
this.no_ed = true;
|
this.no_ed = true;
|
||||||
this.ed_def.reject();
|
this.ed_def.reject();
|
||||||
return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments);
|
this.ignore_focusout = true;
|
||||||
|
this.reinit_value(false);
|
||||||
|
var res = instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments);
|
||||||
|
this.ignore_focusout = false;
|
||||||
|
this.no_ed = false;
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
set_dimensions: function (height, width) {
|
set_dimensions: function (height, width) {
|
||||||
this._super(height, width);
|
this._super(height, width);
|
||||||
this.$input.css('height', height);
|
if (!this.get("effective_readonly") && this.$input)
|
||||||
|
this.$input.css('height', height);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5506,9 +5516,13 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
initialize_content: function() {
|
initialize_content: function() {
|
||||||
|
var self= this;
|
||||||
this.$el.find('input.oe_form_binary_file').change(this.on_file_change);
|
this.$el.find('input.oe_form_binary_file').change(this.on_file_change);
|
||||||
this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as);
|
this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as);
|
||||||
this.$el.find('.oe_form_binary_file_clear').click(this.on_clear);
|
this.$el.find('.oe_form_binary_file_clear').click(this.on_clear);
|
||||||
|
this.$el.find('.oe_form_binary_file_edit').click(function(event){
|
||||||
|
self.$el.find('input.oe_form_binary_file').click();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
on_file_change: function(e) {
|
on_file_change: function(e) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -5676,8 +5690,6 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
|
||||||
return;
|
return;
|
||||||
$img.css("max-width", "" + self.options.size[0] + "px");
|
$img.css("max-width", "" + self.options.size[0] + "px");
|
||||||
$img.css("max-height", "" + self.options.size[1] + "px");
|
$img.css("max-height", "" + self.options.size[1] + "px");
|
||||||
$img.css("margin-left", "" + (self.options.size[0] - $img.width()) / 2 + "px");
|
|
||||||
$img.css("margin-top", "" + (self.options.size[1] - $img.height()) / 2 + "px");
|
|
||||||
});
|
});
|
||||||
$img.on('error', function() {
|
$img.on('error', function() {
|
||||||
$img.attr('src', self.placeholder);
|
$img.attr('src', self.placeholder);
|
||||||
|
|
|
@ -285,9 +285,7 @@
|
||||||
if (!this.editor.is_editing()) { return; }
|
if (!this.editor.is_editing()) { return; }
|
||||||
for(var i=0, len=this.fields_for_resize.length; i<len; ++i) {
|
for(var i=0, len=this.fields_for_resize.length; i<len; ++i) {
|
||||||
var item = this.fields_for_resize[i];
|
var item = this.fields_for_resize[i];
|
||||||
if (!item.field.get('effective_invisible')) {
|
this.resize_field(item.field, item.cell);
|
||||||
this.resize_field(item.field, item.cell);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -306,6 +304,11 @@
|
||||||
at: 'left top',
|
at: 'left top',
|
||||||
of: $cell
|
of: $cell
|
||||||
});
|
});
|
||||||
|
if (field.get('effective_readonly')) {
|
||||||
|
field.$el.addClass('oe_readonly');
|
||||||
|
}
|
||||||
|
if(field.widget == "handle")
|
||||||
|
field.$el.addClass('oe_list_field_handle');
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @return {jQuery.Deferred}
|
* @return {jQuery.Deferred}
|
||||||
|
@ -451,13 +454,7 @@
|
||||||
setup_events: function () {
|
setup_events: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
_.each(this.editor.form.fields, function(field, field_name) {
|
_.each(this.editor.form.fields, function(field, field_name) {
|
||||||
var set_invisible = function() {
|
field.on("change:effective_readonly", self, function(){
|
||||||
field.set({'force_invisible': field.get('effective_readonly')});
|
|
||||||
};
|
|
||||||
field.on("change:effective_readonly", self, set_invisible);
|
|
||||||
set_invisible();
|
|
||||||
field.on('change:effective_invisible', self, function () {
|
|
||||||
if (field.get('effective_invisible')) { return; }
|
|
||||||
var item = _(self.fields_for_resize).find(function (item) {
|
var item = _(self.fields_for_resize).find(function (item) {
|
||||||
return item.field === field;
|
return item.field === field;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1310,15 +1310,16 @@
|
||||||
<t t-name="FieldBinaryImage">
|
<t t-name="FieldBinaryImage">
|
||||||
<span class="oe_form_field oe_form_field_image" t-att-style="widget.node.attrs.style">
|
<span class="oe_form_field oe_form_field_image" t-att-style="widget.node.attrs.style">
|
||||||
<div class="oe_form_field_image_controls oe_edit_only">
|
<div class="oe_form_field_image_controls oe_edit_only">
|
||||||
<t t-call="HiddenInputFile">
|
<i class="fa fa-pencil fa-1g pull-left col-md-offset-1 oe_form_binary_file_edit" title="Edit"/>
|
||||||
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
<i class="fa fa-trash-o fa-1g col-md-offset-5 oe_form_binary_file_clear" title="Clear"/>
|
||||||
Edit
|
|
||||||
</t>
|
|
||||||
<div class="oe_form_binary_progress" style="display: none">
|
<div class="oe_form_binary_progress" style="display: none">
|
||||||
<img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/>
|
<img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/>
|
||||||
<b>Uploading ...</b>
|
<b>Uploading ...</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<t t-call="HiddenInputFile">
|
||||||
|
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
||||||
|
</t>
|
||||||
</span>
|
</span>
|
||||||
</t>
|
</t>
|
||||||
<t t-name="FieldBinaryImage-img">
|
<t t-name="FieldBinaryImage-img">
|
||||||
|
@ -2038,4 +2039,5 @@
|
||||||
</t>
|
</t>
|
||||||
<t t-name="StatInfo">
|
<t t-name="StatInfo">
|
||||||
<strong><t t-esc="value"/></strong><br/><t t-esc="text"/></t>
|
<strong><t t-esc="value"/></strong><br/><t t-esc="text"/></t>
|
||||||
|
|
||||||
</templates>
|
</templates>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<templates id="template" xml:space="preserve">
|
<templates id="template" xml:space="preserve">
|
||||||
<t t-name="website.tour_popover">
|
<t t-name="tour.popover">
|
||||||
<div t-attf-class="#{ fixed ? 'popover tour fixed' : 'popover tour' }">
|
<div t-attf-class="#{ fixed ? 'popover tour fixed' : 'popover tour' }">
|
||||||
<div class="arrow"></div>
|
<div class="arrow" t-if="!next"></div>
|
||||||
<h3 class="popover-title"></h3>
|
<h3 class="popover-title"></h3>
|
||||||
<div class="popover-content"></div>
|
<div class="popover-content"></div>
|
||||||
<t t-if="next or end">
|
<t t-if="next or end">
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-name="website.tour_popover_title">
|
<t t-name="tour.popover_title">
|
||||||
<t t-esc="title"/><button title="End This Tutorial" type="button" class="close" data-role="end">×</button>
|
<t t-esc="title"/><button title="End This Tutorial" type="button" class="close" data-role="end">×</button>
|
||||||
</t>
|
</t>
|
||||||
</templates>
|
</templates>
|
|
@ -51,6 +51,7 @@
|
||||||
<script src="/web/static/src/js/view_list_editable.js" type="text/javascript"></script>
|
<script src="/web/static/src/js/view_list_editable.js" type="text/javascript"></script>
|
||||||
<script src="/web/static/src/js/view_tree.js" type="text/javascript"></script>
|
<script src="/web/static/src/js/view_tree.js" type="text/javascript"></script>
|
||||||
<script src="/base/static/src/js/apps.js" type="text/javascript"></script>
|
<script src="/base/static/src/js/apps.js" type="text/javascript"></script>
|
||||||
|
<script src="/web/static/src/js/tour.js" type="text/javascript"></script>
|
||||||
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
|
<link href="/web/static/lib/fontawesome/css/font-awesome.css" rel="stylesheet"/>
|
||||||
<link href="/web/static/lib/cleditor/jquery.cleditor.css" rel="stylesheet"/>
|
<link href="/web/static/lib/cleditor/jquery.cleditor.css" rel="stylesheet"/>
|
||||||
<link href="/web/static/lib/jquery.textext/jquery.textext.css" rel="stylesheet"/>
|
<link href="/web/static/lib/jquery.textext/jquery.textext.css" rel="stylesheet"/>
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
|
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
|
||||||
|
<script type="text/javascript" src="/web/static/src/js/tour.js"></script>
|
||||||
<script type="text/javascript" charset="utf-8">
|
<script type="text/javascript" charset="utf-8">
|
||||||
openerp._modules = <t t-raw="modules"/>;
|
openerp._modules = <t t-raw="modules"/>;
|
||||||
</script>
|
</script>
|
||||||
|
@ -246,6 +247,7 @@
|
||||||
<t t-call="web.assets_backend"/>
|
<t t-call="web.assets_backend"/>
|
||||||
|
|
||||||
<script type="text/javascript" id="qunit_config">
|
<script type="text/javascript" id="qunit_config">
|
||||||
|
localStorage.clear();
|
||||||
QUnit.config.testTimeout = 5 * 60 * 1000;
|
QUnit.config.testTimeout = 5 * 60 * 1000;
|
||||||
QUnit.moduleDone(function(result) {
|
QUnit.moduleDone(function(result) {
|
||||||
console.log(result.name + " (" + result.passed + "/" + result.total + " passed tests)");
|
console.log(result.name + " (" + result.passed + "/" + result.total + " passed tests)");
|
||||||
|
@ -253,6 +255,8 @@
|
||||||
QUnit.done(function(result) {
|
QUnit.done(function(result) {
|
||||||
if (result.failed === 0) {
|
if (result.failed === 0) {
|
||||||
console.log('ok');
|
console.log('ok');
|
||||||
|
} else {
|
||||||
|
console.log('error');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
openerp.web.qweb.add_template("/web/webclient/qweb");
|
openerp.web.qweb.add_template("/web/webclient/qweb");
|
||||||
|
|
|
@ -8,6 +8,7 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
import unicodedata
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import urlparse
|
import urlparse
|
||||||
|
@ -28,6 +29,7 @@ except ImportError:
|
||||||
import openerp
|
import openerp
|
||||||
from openerp.osv import orm, osv, fields
|
from openerp.osv import orm, osv, fields
|
||||||
from openerp.tools import html_escape as escape
|
from openerp.tools import html_escape as escape
|
||||||
|
from openerp.tools import ustr as ustr
|
||||||
from openerp.tools.safe_eval import safe_eval
|
from openerp.tools.safe_eval import safe_eval
|
||||||
from openerp.addons.web.http import request
|
from openerp.addons.web.http import request
|
||||||
|
|
||||||
|
@ -85,15 +87,29 @@ def is_multilang_url(local_url, langs=None):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def slugify(s, max_length=None):
|
def slugify(s, max_length=None):
|
||||||
|
""" Transform a string to a slug that can be used in a url path.
|
||||||
|
|
||||||
|
This method will first try to do the job with python-slugify if present.
|
||||||
|
Otherwise it will process string by stripping leading and ending spaces,
|
||||||
|
converting unicode chars to ascii, lowering all chars and replacing spaces
|
||||||
|
and underscore with hyphen "-".
|
||||||
|
|
||||||
|
:param s: str
|
||||||
|
:param max_length: int
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
s = ustr(s)
|
||||||
if slugify_lib:
|
if slugify_lib:
|
||||||
# There are 2 different libraries only python-slugify is supported
|
# There are 2 different libraries only python-slugify is supported
|
||||||
try:
|
try:
|
||||||
return slugify_lib.slugify(s, max_length=max_length)
|
return slugify_lib.slugify(s, max_length=max_length)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
spaceless = re.sub(r'\s+', '-', s)
|
uni = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii')
|
||||||
specialless = re.sub(r'[^-_A-Za-z0-9]', '', spaceless)
|
slug = re.sub('[\W_]', ' ', uni).strip().lower()
|
||||||
return specialless[:max_length]
|
slug = re.sub('[-\s]+', '-', slug)
|
||||||
|
|
||||||
|
return slug[:max_length]
|
||||||
|
|
||||||
def slug(value):
|
def slug(value):
|
||||||
if isinstance(value, orm.browse_record):
|
if isinstance(value, orm.browse_record):
|
||||||
|
@ -147,7 +163,7 @@ class website(osv.osv):
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'company_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID, 'base.public_user'),
|
'company_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID, 'base.public_user'),
|
||||||
}
|
}
|
||||||
|
|
||||||
# cf. Wizard hack in website_views.xml
|
# cf. Wizard hack in website_views.xml
|
||||||
def noop(self, *args, **kwargs):
|
def noop(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -516,36 +516,3 @@ ul.oe_menu_editor .disclose {
|
||||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
|
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- EDITOR TOUR ---- {{{ */
|
|
||||||
div.tour-backdrop {
|
|
||||||
z-index: 2009;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover.tour.orphan .arrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.popover.tour .popover-navigation {
|
|
||||||
padding: 9px 14px;
|
|
||||||
}
|
|
||||||
.popover.tour .popover-navigation *[data-role="end"] {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.popover.tour .popover-navigation *[data-role="next"], .popover.tour .popover-navigation *[data-role="end"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover.fixed {
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tour-backdrop {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1100;
|
|
||||||
background-color: black;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
|
@ -450,32 +450,4 @@ $infobar_height: 20px
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
/* ---- EDITOR TOUR ---- {{{ */
|
|
||||||
|
|
||||||
div.tour-backdrop
|
|
||||||
z-index: 2009
|
|
||||||
.popover.tour
|
|
||||||
&.orphan .arrow
|
|
||||||
display: none
|
|
||||||
.popover-navigation
|
|
||||||
padding: 9px 14px
|
|
||||||
*[data-role="end"]
|
|
||||||
float: right
|
|
||||||
*[data-role="next"],*[data-role="end"]
|
|
||||||
cursor: pointer
|
|
||||||
.popover.fixed
|
|
||||||
position: fixed
|
|
||||||
.tour-backdrop
|
|
||||||
position: fixed
|
|
||||||
top: 0
|
|
||||||
right: 0
|
|
||||||
bottom: 0
|
|
||||||
left: 0
|
|
||||||
z-index: 1100
|
|
||||||
background-color: #000
|
|
||||||
opacity: 0.8
|
|
||||||
|
|
||||||
|
|
||||||
// }}}
|
|
||||||
|
|
||||||
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:
|
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var website = openerp.website;
|
|
||||||
var _t = openerp._t;
|
var _t = openerp._t;
|
||||||
|
|
||||||
website.Tour.register({
|
openerp.Tour.register({
|
||||||
id: 'banner',
|
id: 'banner',
|
||||||
name: _t("Build a page"),
|
name: _t("Build a page"),
|
||||||
path: '/page/website.homepage',
|
path: '/page/website.homepage',
|
||||||
|
|
|
@ -1,535 +1,24 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// raise an error in test mode if openerp don't exist
|
window.openerp.website.EditorBar.include({
|
||||||
if (typeof openerp === "undefined") {
|
tours: [],
|
||||||
var error = "openerp is undefined"
|
start: function () {
|
||||||
+ "\nhref: " + window.location.href
|
var self = this;
|
||||||
+ "\nreferrer: " + document.referrer
|
var menu = $('#help-menu');
|
||||||
+ "\nlocalStorage: " + window.localStorage.getItem("tour");
|
_.each(window.openerp.Tour.tours, function (tour) {
|
||||||
if (typeof $ !== "undefined") {
|
if (tour.mode === "test") {
|
||||||
error += '\n\n' + $("body").html();
|
return;
|
||||||
}
|
}
|
||||||
throw new Error(error);
|
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
||||||
}
|
$menuItem.click(function () {
|
||||||
|
T.reset();
|
||||||
var website = window.openerp.website;
|
T.run(tour.id);
|
||||||
|
|
||||||
// don't rewrite T in test mode
|
|
||||||
if (typeof website.Tour !== "undefined") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't need template to use bootstrap Tour in automatic mode
|
|
||||||
if (typeof QWeb2 !== "undefined") {
|
|
||||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (website.EditorBar) {
|
|
||||||
website.EditorBar.include({
|
|
||||||
tours: [],
|
|
||||||
start: function () {
|
|
||||||
var self = this;
|
|
||||||
var menu = $('#help-menu');
|
|
||||||
_.each(T.tours, function (tour) {
|
|
||||||
if (tour.mode === "test") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
|
|
||||||
$menuItem.click(function () {
|
|
||||||
T.reset();
|
|
||||||
T.run(tour.id);
|
|
||||||
});
|
|
||||||
menu.append($menuItem);
|
|
||||||
});
|
});
|
||||||
return this._super();
|
menu.append($menuItem);
|
||||||
}
|
});
|
||||||
});
|
return this._super();
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
/* jQuery selector to match exact text inside an element
|
|
||||||
* :containsExact() - case insensitive
|
|
||||||
* :containsExactCase() - case sensitive
|
|
||||||
* :containsRegex() - set by user ( use: $(el).find(':containsRegex(/(red|blue|yellow)/gi)') )
|
|
||||||
*/
|
|
||||||
$.extend($.expr[':'],{
|
|
||||||
containsExact: function(a,i,m){
|
|
||||||
return $.trim(a.innerHTML.toLowerCase()) === m[3].toLowerCase();
|
|
||||||
},
|
|
||||||
containsExactCase: function(a,i,m){
|
|
||||||
return $.trim(a.innerHTML) === m[3];
|
|
||||||
},
|
|
||||||
// Note all escaped characters need to be double escaped
|
|
||||||
// inside of the containsRegex, so "\(" needs to be "\\("
|
|
||||||
containsRegex: function(a,i,m){
|
|
||||||
var regreg = /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})$/,
|
|
||||||
reg = regreg.exec(m[3]);
|
|
||||||
return reg ? new RegExp(reg[1], reg[2]).test($.trim(a.innerHTML)) : false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$.ajaxSetup({
|
|
||||||
beforeSend:function(){
|
|
||||||
$.ajaxBusy = ($.ajaxBusy|0) + 1;
|
|
||||||
},
|
|
||||||
complete:function(){
|
|
||||||
$.ajaxBusy--;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
var localStorage = window.localStorage;
|
|
||||||
|
|
||||||
var T = website.Tour = {
|
|
||||||
tours: {},
|
|
||||||
defaultDelay: 50,
|
|
||||||
retryRunningDelay: 1000,
|
|
||||||
errorDelay: 5000,
|
|
||||||
state: null,
|
|
||||||
$element: null,
|
|
||||||
timer: null,
|
|
||||||
testtimer: null,
|
|
||||||
currentTimer: null,
|
|
||||||
register: function (tour) {
|
|
||||||
if (tour.mode !== "test") tour.mode = "tutorial";
|
|
||||||
T.tours[tour.id] = tour;
|
|
||||||
},
|
|
||||||
run: function (tour_id, mode) {
|
|
||||||
var tour = T.tours[tour_id];
|
|
||||||
this.time = new Date().getTime();
|
|
||||||
if (tour.path && !window.location.href.match(new RegExp("("+T.getLang()+")?"+tour.path+"#?$", "i"))) {
|
|
||||||
var href = "/"+T.getLang()+tour.path;
|
|
||||||
console.log("Tour Begin from run method (redirection to "+href+")");
|
|
||||||
T.saveState(tour.id, mode || tour.mode, -1, 0);
|
|
||||||
window.location.href = href;
|
|
||||||
} else {
|
|
||||||
console.log("Tour Begin from run method");
|
|
||||||
T.saveState(tour.id, mode || tour.mode, 0, 0);
|
|
||||||
T.running();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
registerSteps: function (tour) {
|
|
||||||
if (tour.register) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tour.register = true;
|
|
||||||
|
|
||||||
for (var index=0, len=tour.steps.length; index<len; index++) {
|
|
||||||
var step = tour.steps[index];
|
|
||||||
step.id = index;
|
|
||||||
|
|
||||||
if (!step.waitNot && index > 0 && tour.steps[index-1] &&
|
|
||||||
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
|
||||||
step.waitNot = '.popover.tour.fade.in:visible';
|
|
||||||
}
|
|
||||||
if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) {
|
|
||||||
step.waitFor = '.oe_overlay_options .oe_options:visible';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/);
|
|
||||||
if (snippet) {
|
|
||||||
step.snippet = snippet[1];
|
|
||||||
} else if (step.snippet) {
|
|
||||||
step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!step.element) {
|
|
||||||
step.element = "body";
|
|
||||||
step.orphan = true;
|
|
||||||
step.backdrop = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tour.steps[index-1] &&
|
|
||||||
tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
|
|
||||||
var step = {
|
|
||||||
_title: "",
|
|
||||||
id: index,
|
|
||||||
waitNot: '.popover.tour.fade.in:visible'
|
|
||||||
};
|
|
||||||
tour.steps.push(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
// rendering bootstrap tour and popover
|
|
||||||
if (tour.mode !== "test") {
|
|
||||||
for (var index=0, len=tour.steps.length; index<len; index++) {
|
|
||||||
var step = tour.steps[index];
|
|
||||||
step._title = step._title || step.title;
|
|
||||||
step.title = T.popoverTitle(tour, { title: step._title });
|
|
||||||
step.template = step.template || T.popover( step.popover );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closePopover: function () {
|
|
||||||
if (T.$element) {
|
|
||||||
T.$element.popover('destroy');
|
|
||||||
T.$element.removeData("tour");
|
|
||||||
T.$element.removeData("tour-step");
|
|
||||||
$(".tour-backdrop").remove();
|
|
||||||
$(".popover.tour").remove();
|
|
||||||
T.$element = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autoTogglePopover: function () {
|
|
||||||
var state = T.getState();
|
|
||||||
var step = state.step;
|
|
||||||
|
|
||||||
if (T.$element &&
|
|
||||||
T.$element.is(":visible") &&
|
|
||||||
T.$element.data("tour") === state.id &&
|
|
||||||
T.$element.data("tour-step") === step.id) {
|
|
||||||
T.repositionPopover();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step.busy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
T.closePopover();
|
|
||||||
|
|
||||||
var $element = $(step.element).first();
|
|
||||||
if (!step.element || !$element.size() || !$element.is(":visible")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
T.$element = $element;
|
|
||||||
$element.data("tour", state.id);
|
|
||||||
$element.data("tour-step", step.id);
|
|
||||||
$element.popover({
|
|
||||||
placement: step.placement || "auto",
|
|
||||||
animation: true,
|
|
||||||
trigger: "manual",
|
|
||||||
title: step.title,
|
|
||||||
content: step.content,
|
|
||||||
html: true,
|
|
||||||
container: "body",
|
|
||||||
template: step.template,
|
|
||||||
orphan: step.orphan
|
|
||||||
}).popover("show");
|
|
||||||
|
|
||||||
|
|
||||||
var $tip = $element.data("bs.popover").tip();
|
|
||||||
|
|
||||||
|
|
||||||
// add popover style (orphan, static, backdrop)
|
|
||||||
if (step.orphan) {
|
|
||||||
$tip.addClass("orphan");
|
|
||||||
}
|
|
||||||
|
|
||||||
var node = $element[0];
|
|
||||||
var css;
|
|
||||||
do {
|
|
||||||
css = window.getComputedStyle(node);
|
|
||||||
if (!css || css.position == "fixed") {
|
|
||||||
$tip.addClass("fixed");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while ((node = node.parentNode) && node !== document);
|
|
||||||
|
|
||||||
if (step.backdrop) {
|
|
||||||
$("body").append('<div class="tour-backdrop"></div>');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step.backdrop || $element.parents("#website-top-navbar, .modal").size()) {
|
|
||||||
$tip.css("z-index", 2010);
|
|
||||||
}
|
|
||||||
|
|
||||||
// button click event
|
|
||||||
$tip.find("button")
|
|
||||||
.one("click", function () {
|
|
||||||
step.busy = true;
|
|
||||||
if (!$(this).is("[data-role='next']")) {
|
|
||||||
clearTimeout(T.timer);
|
|
||||||
T.endTour();
|
|
||||||
}
|
|
||||||
T.closePopover();
|
|
||||||
});
|
|
||||||
|
|
||||||
T.repositionPopover();
|
|
||||||
},
|
|
||||||
repositionPopover: function() {
|
|
||||||
var popover = T.$element.data("bs.popover");
|
|
||||||
var $tip = T.$element.data("bs.popover").tip();
|
|
||||||
|
|
||||||
if (popover.options.orphan) {
|
|
||||||
return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
|
|
||||||
offsetWidth = $tip[0].offsetWidth;
|
|
||||||
offsetHeight = $tip[0].offsetHeight;
|
|
||||||
tipOffset = $tip.offset();
|
|
||||||
originalLeft = tipOffset.left;
|
|
||||||
originalTop = tipOffset.top;
|
|
||||||
offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
|
|
||||||
if (offsetBottom < 0) {
|
|
||||||
tipOffset.top = tipOffset.top + offsetBottom;
|
|
||||||
}
|
|
||||||
offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
|
|
||||||
if (offsetRight < 0) {
|
|
||||||
tipOffset.left = tipOffset.left + offsetRight;
|
|
||||||
}
|
|
||||||
if (tipOffset.top < 0) {
|
|
||||||
tipOffset.top = 0;
|
|
||||||
}
|
|
||||||
if (tipOffset.left < 0) {
|
|
||||||
tipOffset.left = 0;
|
|
||||||
}
|
|
||||||
$tip.offset(tipOffset);
|
|
||||||
if (popover.options.placement === "bottom" || popover.options.placement === "top") {
|
|
||||||
var left = T.$element.offset().left + T.$element.outerWidth()/2 - tipOffset.left;
|
|
||||||
$tip.find(".arrow").css("left", left ? left + "px" : "");
|
|
||||||
} else if (popover.options.placement !== "auto") {
|
|
||||||
var top = T.$element.offset().top + T.$element.outerHeight()/2 - tipOffset.top;
|
|
||||||
$tip.find(".arrow").css("top", top ? top + "px" : "");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
popoverTitle: function (tour, options) {
|
|
||||||
return openerp.qweb ? openerp.qweb.render('website.tour_popover_title', options) : options.title;
|
|
||||||
},
|
|
||||||
popover: function (options) {
|
|
||||||
return openerp.qweb ? openerp.qweb.render('website.tour_popover', options) : options.title;
|
|
||||||
},
|
|
||||||
getLang: function () {
|
|
||||||
return $("html").attr("lang").replace(/-/, '_');
|
|
||||||
},
|
|
||||||
getState: function () {
|
|
||||||
var state = JSON.parse(localStorage.getItem("tour") || 'false') || {};
|
|
||||||
if (state) { this.time = state.time; }
|
|
||||||
var tour_id,mode,step_id;
|
|
||||||
if (!state.id && window.location.href.indexOf("#tutorial.") > -1) {
|
|
||||||
state = {
|
|
||||||
"id": window.location.href.match(/#tutorial\.(.*)=true/)[1],
|
|
||||||
"mode": "tutorial",
|
|
||||||
"step_id": 0
|
|
||||||
};
|
|
||||||
window.location.hash = "";
|
|
||||||
console.log("Tour Begin from url hash");
|
|
||||||
T.saveState(state.id, state.mode, state.step_id, 0);
|
|
||||||
}
|
|
||||||
if (!state.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.tour = T.tours[state.id];
|
|
||||||
state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id];
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
error: function (step, message) {
|
|
||||||
var state = T.getState();
|
|
||||||
message += '\n tour: ' + state.id
|
|
||||||
+ '\n step: ' + step.id + ": '" + (step._title || step.title) + "'"
|
|
||||||
+ '\n href: ' + window.location.href
|
|
||||||
+ '\n referrer: ' + document.referrer
|
|
||||||
+ '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden")))
|
|
||||||
+ '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size())
|
|
||||||
+ '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size())
|
|
||||||
+ "\n localStorage: " + JSON.stringify(localStorage)
|
|
||||||
+ '\n\n' + $("body").html();
|
|
||||||
T.reset();
|
|
||||||
throw new Error(message);
|
|
||||||
},
|
|
||||||
lists: function () {
|
|
||||||
var tour_ids = [];
|
|
||||||
for (var k in T.tours) {
|
|
||||||
tour_ids.push(k);
|
|
||||||
}
|
|
||||||
return tour_ids;
|
|
||||||
},
|
|
||||||
saveState: function (tour_id, mode, step_id, number) {
|
|
||||||
localStorage.setItem("tour", JSON.stringify({"id":tour_id, "mode":mode, "step_id":step_id || 0, "time": this.time, "number": number+1}));
|
|
||||||
},
|
|
||||||
reset: function () {
|
|
||||||
var state = T.getState();
|
|
||||||
if (state) {
|
|
||||||
for (var k in state.tour.steps) {
|
|
||||||
state.tour.steps[k].busy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
localStorage.removeItem("tour");
|
|
||||||
clearTimeout(T.timer);
|
|
||||||
clearTimeout(T.testtimer);
|
|
||||||
T.closePopover();
|
|
||||||
},
|
|
||||||
running: function () {
|
|
||||||
function run () {
|
|
||||||
var state = T.getState();
|
|
||||||
if (!state) return;
|
|
||||||
if (state.tour) {
|
|
||||||
console.log("Tour '"+state.id+"' is running");
|
|
||||||
T.registerSteps(state.tour);
|
|
||||||
T.nextStep();
|
|
||||||
} else {
|
|
||||||
console.log("Tour '"+state.id+"' wait for running (tour undefined)");
|
|
||||||
setTimeout(T.running, state.mode === "test" ? T.defaultDelay : T.retryRunningDelay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTimeout(function () {
|
|
||||||
if ($.ajaxBusy) {
|
|
||||||
$(document).ajaxStop(run);
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
},0);
|
|
||||||
},
|
|
||||||
check: function (step) {
|
|
||||||
return (step &&
|
|
||||||
(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) &&
|
|
||||||
(!step.waitNot || !$(step.waitNot).size()) &&
|
|
||||||
(!step.waitFor || $(step.waitFor).size()));
|
|
||||||
},
|
|
||||||
waitNextStep: function () {
|
|
||||||
var state = T.getState();
|
|
||||||
var time = new Date().getTime();
|
|
||||||
var timer;
|
|
||||||
var next = state.tour.steps[state.step.id+1];
|
|
||||||
var overlaps = state.mode === "test" ? T.errorDelay : 0;
|
|
||||||
|
|
||||||
window.onbeforeunload = function () {
|
|
||||||
clearTimeout(T.timer);
|
|
||||||
clearTimeout(T.testtimer);
|
|
||||||
};
|
|
||||||
|
|
||||||
function checkNext () {
|
|
||||||
T.autoTogglePopover();
|
|
||||||
|
|
||||||
clearTimeout(T.timer);
|
|
||||||
if (T.check(next)) {
|
|
||||||
clearTimeout(T.currentTimer);
|
|
||||||
// use an other timeout for cke dom loading
|
|
||||||
T.saveState(state.id, state.mode, state.step.id, 0);
|
|
||||||
setTimeout(function () {
|
|
||||||
T.nextStep(next);
|
|
||||||
}, T.defaultDelay);
|
|
||||||
} else if (!overlaps || new Date().getTime() - time < overlaps) {
|
|
||||||
T.timer = setTimeout(checkNext, T.defaultDelay);
|
|
||||||
} else {
|
|
||||||
T.error(next, "Can't reach the next step");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkNext();
|
|
||||||
},
|
|
||||||
nextStep: function (step) {
|
|
||||||
var state = T.getState();
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
step = step || state.step;
|
|
||||||
var next = state.tour.steps[step.id+1];
|
|
||||||
|
|
||||||
if (state.number > 3) {
|
|
||||||
T.error(next, "Cycling. Can't reach the next step");
|
|
||||||
}
|
|
||||||
|
|
||||||
T.saveState(state.id, state.mode, step.id, state.number);
|
|
||||||
|
|
||||||
if (step.id !== state.step_id) {
|
|
||||||
console.log("Tour Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)");
|
|
||||||
}
|
|
||||||
|
|
||||||
T.autoTogglePopover(true);
|
|
||||||
|
|
||||||
if (step.onload) {
|
|
||||||
step.onload();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next) {
|
|
||||||
setTimeout(function () {
|
|
||||||
T.waitNextStep();
|
|
||||||
if (state.mode === "test") {
|
|
||||||
setTimeout(function(){
|
|
||||||
T.autoNextStep(state.tour, step);
|
|
||||||
}, T.defaultDelay);
|
|
||||||
}
|
|
||||||
}, next.wait || 0);
|
|
||||||
} else {
|
|
||||||
T.endTour();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
endTour: function () {
|
|
||||||
var state = T.getState();
|
|
||||||
var test = state.step.id >= state.tour.steps.length-1;
|
|
||||||
T.reset();
|
|
||||||
if (test) {
|
|
||||||
console.log('ok');
|
|
||||||
} else {
|
|
||||||
console.log('error');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autoNextStep: function (tour, step) {
|
|
||||||
clearTimeout(T.testtimer);
|
|
||||||
|
|
||||||
function autoStep () {
|
|
||||||
if (!step) return;
|
|
||||||
|
|
||||||
if (step.autoComplete) {
|
|
||||||
step.autoComplete(tour);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(".popover.tour [data-role='next']").click();
|
|
||||||
|
|
||||||
var $element = $(step.element);
|
|
||||||
if (!$element.size()) return;
|
|
||||||
|
|
||||||
if (step.snippet) {
|
|
||||||
|
|
||||||
T.autoDragAndDropSnippet($element);
|
|
||||||
|
|
||||||
} else if ($element.is(":visible")) {
|
|
||||||
|
|
||||||
$element.trigger($.Event("mouseenter", { srcElement: $element[0] }));
|
|
||||||
$element.trigger($.Event("mousedown", { srcElement: $element[0] }));
|
|
||||||
|
|
||||||
var evt = document.createEvent("MouseEvents");
|
|
||||||
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
|
||||||
$element[0].dispatchEvent(evt);
|
|
||||||
|
|
||||||
// trigger after for step like: mouseenter, next step click on button display with mouseenter
|
|
||||||
setTimeout(function () {
|
|
||||||
$element.trigger($.Event("mouseup", { srcElement: $element[0] }));
|
|
||||||
$element.trigger($.Event("mouseleave", { srcElement: $element[0] }));
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
if (step.sampleText) {
|
|
||||||
|
|
||||||
$element.trigger($.Event("keydown", { srcElement: $element }));
|
|
||||||
if ($element.is("input") ) {
|
|
||||||
$element.val(step.sampleText);
|
|
||||||
} if ($element.is("select")) {
|
|
||||||
$element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true);
|
|
||||||
$element.val(step.sampleText);
|
|
||||||
} else {
|
|
||||||
$element.html(step.sampleText);
|
|
||||||
}
|
|
||||||
setTimeout(function () {
|
|
||||||
$element.trigger($.Event("keyup", { srcElement: $element }));
|
|
||||||
$element.trigger($.Event("change", { srcElement: $element }));
|
|
||||||
}, self.defaultDelay<<1);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
T.testtimer = setTimeout(autoStep, 100);
|
|
||||||
},
|
|
||||||
autoDragAndDropSnippet: function (selector) {
|
|
||||||
var $thumbnail = $(selector).first();
|
|
||||||
var thumbnailPosition = $thumbnail.position();
|
|
||||||
$thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top }));
|
|
||||||
$thumbnail.trigger($.Event("mousemove", { which: 1, pageX: document.body.scrollWidth/2, pageY: document.body.scrollHeight/2 }));
|
|
||||||
var $dropZone = $(".oe_drop_zone").first();
|
|
||||||
var dropPosition = $dropZone.position();
|
|
||||||
$dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//$(document).ready(T.running);
|
|
||||||
website.ready().then(T.running);
|
|
||||||
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -9,6 +9,7 @@ from lxml.builder import E
|
||||||
from openerp.tests import common
|
from openerp.tests import common
|
||||||
from openerp.addons.base.ir import ir_qweb
|
from openerp.addons.base.ir import ir_qweb
|
||||||
from openerp.addons.website.models.ir_qweb import html_to_text
|
from openerp.addons.website.models.ir_qweb import html_to_text
|
||||||
|
from openerp.addons.website.models.website import slugify
|
||||||
|
|
||||||
impl = getDOMImplementation()
|
impl = getDOMImplementation()
|
||||||
document = impl.createDocument(None, None, None)
|
document = impl.createDocument(None, None, None)
|
||||||
|
@ -238,3 +239,56 @@ class TestConvertBack(common.TransactionCase):
|
||||||
"New content",
|
"New content",
|
||||||
"element edition should have been written directly to the m2o record"
|
"element edition should have been written directly to the m2o record"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class TestTitleToSlug(unittest2.TestCase):
|
||||||
|
"""
|
||||||
|
Those tests should pass with or without python-slugify
|
||||||
|
See website/models/website.py slugify method
|
||||||
|
"""
|
||||||
|
def test_spaces(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"spaces",
|
||||||
|
slugify(u" spaces ")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unicode(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"heterogeneite",
|
||||||
|
slugify(u"hétérogénéité")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_underscore(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"one-two",
|
||||||
|
slugify(u"one_two")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_caps(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"camelcase",
|
||||||
|
slugify(u"CamelCase")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_special_chars(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"o-d-o-o",
|
||||||
|
slugify(u"o!#d{|\o/@~o&%^?")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_str_to_unicode(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"espana",
|
||||||
|
slugify("España")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_numbers(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"article-1",
|
||||||
|
slugify(u"Article 1")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_all(self):
|
||||||
|
self.assertEqual(
|
||||||
|
"do-you-know-martine-a-la-plage",
|
||||||
|
slugify(u"Do YOU know 'Martine à la plage' ?")
|
||||||
|
)
|
||||||
|
|
|
@ -8,6 +8,6 @@ class TestUi(openerp.tests.HttpCase):
|
||||||
self.phantom_js("/", "console.log('ok')", "openerp.website.editor", login='admin')
|
self.phantom_js("/", "console.log('ok')", "openerp.website.editor", login='admin')
|
||||||
|
|
||||||
def test_04_admin_tour_banner(self):
|
def test_04_admin_tour_banner(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('banner', 'test')", "openerp.website.Tour.tours.banner", login='admin')
|
self.phantom_js("/", "openerp.Tour.run('banner', 'test')", "openerp.Tour.tours.banner", login='admin')
|
||||||
|
|
||||||
# vim:et:
|
# vim:et:
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var website = openerp.website;
|
|
||||||
var _t = openerp._t;
|
var _t = openerp._t;
|
||||||
|
|
||||||
website.Tour.register({
|
openerp.Tour.register({
|
||||||
id: 'blog',
|
id: 'blog',
|
||||||
name: _t("Create a blog post"),
|
name: _t("Create a blog post"),
|
||||||
steps: [
|
steps: [
|
||||||
|
|
|
@ -2,5 +2,5 @@ import openerp.tests
|
||||||
|
|
||||||
class TestUi(openerp.tests.HttpCase):
|
class TestUi(openerp.tests.HttpCase):
|
||||||
def test_admin(self):
|
def test_admin(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('blog', 'test')", "openerp.website.Tour.tours.blog")
|
self.phantom_js("/", "openerp.Tour.run('blog', 'test')", "openerp.Tour.tours.blog")
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var website = openerp.website;
|
|
||||||
var _t = openerp._t;
|
var _t = openerp._t;
|
||||||
|
|
||||||
website.Tour.register({
|
openerp.Tour.register({
|
||||||
id: 'event',
|
id: 'event',
|
||||||
name: _t("Create an event"),
|
name: _t("Create an event"),
|
||||||
steps: [
|
steps: [
|
||||||
|
|
|
@ -2,5 +2,5 @@ import openerp.tests
|
||||||
|
|
||||||
class TestUi(openerp.tests.HttpCase):
|
class TestUi(openerp.tests.HttpCase):
|
||||||
def test_admin(self):
|
def test_admin(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('event', 'test')", "openerp.website.Tour.tours.event")
|
self.phantom_js("/", "openerp.Tour.run('event', 'test')", "openerp.Tour.tours.event")
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var website = openerp.website;
|
openerp.Tour.register({
|
||||||
|
|
||||||
website.Tour.register({
|
|
||||||
id: 'event_buy_tickets',
|
id: 'event_buy_tickets',
|
||||||
name: "Try to buy tickets for event",
|
name: "Try to buy tickets for event",
|
||||||
path: '/event',
|
path: '/event',
|
||||||
|
|
|
@ -3,19 +3,19 @@ import os
|
||||||
import openerp.tests
|
import openerp.tests
|
||||||
|
|
||||||
inject = [
|
inject = [
|
||||||
("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
|
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')),
|
||||||
("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")),
|
("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")),
|
||||||
]
|
]
|
||||||
|
|
||||||
@openerp.tests.common.at_install(False)
|
@openerp.tests.common.at_install(False)
|
||||||
@openerp.tests.common.post_install(True)
|
@openerp.tests.common.post_install(True)
|
||||||
class TestUi(openerp.tests.HttpCase):
|
class TestUi(openerp.tests.HttpCase):
|
||||||
def test_admin(self):
|
def test_admin(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", inject=inject)
|
self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", inject=inject)
|
||||||
|
|
||||||
def test_demo(self):
|
def test_demo(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject);
|
self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject);
|
||||||
|
|
||||||
def test_public(self):
|
def test_public(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login=None, inject=inject);
|
self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", login=None, inject=inject);
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,6 @@
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script> <!-- groups="base.group_website_designer" -->
|
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script> <!-- groups="base.group_website_designer" -->
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
|
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
|
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
|
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.tour.banner.js"></script> <!-- groups="base.group_website_designer" -->
|
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.snippets.editor.js"></script>
|
<script type="text/javascript" src="/website/static/src/js/website.snippets.editor.js"></script>
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
|
<script type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
|
||||||
<script type="text/javascript" src="/website/static/src/js/website.translator.js"></script>
|
<script type="text/javascript" src="/website/static/src/js/website.translator.js"></script>
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
openerp.Tour.register({
|
||||||
var website = openerp.website;
|
|
||||||
|
|
||||||
website.Tour.register({
|
|
||||||
id: 'shop_customize',
|
id: 'shop_customize',
|
||||||
name: "Customize the page and search a product",
|
name: "Customize the page and search a product",
|
||||||
path: '/shop',
|
path: '/shop',
|
||||||
|
@ -37,7 +34,7 @@
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
website.Tour.register({
|
openerp.Tour.register({
|
||||||
id: 'shop_buy_product',
|
id: 'shop_buy_product',
|
||||||
name: "Try to buy products",
|
name: "Try to buy products",
|
||||||
path: '/shop',
|
path: '/shop',
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var website = openerp.website;
|
|
||||||
var _t = openerp._t;
|
var _t = openerp._t;
|
||||||
|
|
||||||
website.Tour.register({
|
openerp.Tour.register({
|
||||||
id: 'shop',
|
id: 'shop',
|
||||||
name: _t("Create a product"),
|
name: _t("Create a product"),
|
||||||
steps: [
|
steps: [
|
||||||
|
|
|
@ -3,22 +3,22 @@ import os
|
||||||
import openerp.tests
|
import openerp.tests
|
||||||
|
|
||||||
inject = [
|
inject = [
|
||||||
("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
|
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')),
|
||||||
("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")),
|
("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")),
|
||||||
]
|
]
|
||||||
|
|
||||||
@openerp.tests.common.at_install(False)
|
@openerp.tests.common.at_install(False)
|
||||||
@openerp.tests.common.post_install(True)
|
@openerp.tests.common.post_install(True)
|
||||||
class TestUi(openerp.tests.HttpCase):
|
class TestUi(openerp.tests.HttpCase):
|
||||||
def test_01_admin_shop_tour(self):
|
def test_01_admin_shop_tour(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('shop', 'test')", "openerp.website.Tour.tours.shop", login="admin")
|
self.phantom_js("/", "openerp.Tour.run('shop', 'test')", "openerp.Tour.tours.shop", login="admin")
|
||||||
|
|
||||||
def test_02_admin_checkout(self):
|
def test_02_admin_checkout(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('shop_customize', 'test')", "openerp.website.Tour.tours.shop_customize", login="admin", inject=inject)
|
self.phantom_js("/", "openerp.Tour.run('shop_customize', 'test')", "openerp.Tour.tours.shop_customize", login="admin", inject=inject)
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="admin", inject=inject)
|
self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", login="admin", inject=inject)
|
||||||
|
|
||||||
def test_03_demo_checkout(self):
|
def test_03_demo_checkout(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="demo", inject=inject)
|
self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", login="demo", inject=inject)
|
||||||
|
|
||||||
def test_04_public_checkout(self):
|
def test_04_public_checkout(self):
|
||||||
self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", inject=inject)
|
self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", inject=inject)
|
||||||
|
|
|
@ -76,17 +76,23 @@ class ir_http(osv.AbstractModel):
|
||||||
request.uid = request.session.uid
|
request.uid = request.session.uid
|
||||||
|
|
||||||
def _authenticate(self, auth_method='user'):
|
def _authenticate(self, auth_method='user'):
|
||||||
if request.session.uid:
|
try:
|
||||||
try:
|
if request.session.uid:
|
||||||
request.session.check_security()
|
try:
|
||||||
# what if error in security.check()
|
request.session.check_security()
|
||||||
# -> res_users.check()
|
# what if error in security.check()
|
||||||
# -> res_users.check_credentials()
|
# -> res_users.check()
|
||||||
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
|
# -> res_users.check_credentials()
|
||||||
# All other exceptions mean undetermined status (e.g. connection pool full),
|
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
|
||||||
# let them bubble up
|
# All other exceptions mean undetermined status (e.g. connection pool full),
|
||||||
request.session.logout()
|
# let them bubble up
|
||||||
getattr(self, "_auth_method_%s" % auth_method)()
|
request.session.logout()
|
||||||
|
getattr(self, "_auth_method_%s" % auth_method)()
|
||||||
|
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
_logger.exception("Exception during request Authentication.")
|
||||||
|
raise openerp.exceptions.AccessDenied()
|
||||||
return auth_method
|
return auth_method
|
||||||
|
|
||||||
def _handle_exception(self, exception):
|
def _handle_exception(self, exception):
|
||||||
|
|
|
@ -496,6 +496,7 @@ class module(osv.osv):
|
||||||
'params': {'menu_id': menu_ids and menu_ids[0] or False}
|
'params': {'menu_id': menu_ids and menu_ids[0] or False}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#TODO remove me in master, not called anymore
|
||||||
def button_immediate_uninstall(self, cr, uid, ids, context=None):
|
def button_immediate_uninstall(self, cr, uid, ids, context=None):
|
||||||
"""
|
"""
|
||||||
Uninstall the selected module(s) immediately and fully,
|
Uninstall the selected module(s) immediately and fully,
|
||||||
|
|
|
@ -82,8 +82,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button name="button_immediate_install" states="uninstalled" string="Install" type="object" class="oe_highlight"/>
|
<button name="button_immediate_install" states="uninstalled" string="Install" type="object" class="oe_highlight"/>
|
||||||
<button name="button_immediate_upgrade" states="installed" string="Upgrade" type="object" class="oe_highlight"/>
|
<button name="button_immediate_upgrade" states="installed" string="Upgrade" type="object" class="oe_highlight"/>
|
||||||
<button name="button_immediate_uninstall" states="installed" string="Uninstall" type="object"
|
<button name="button_uninstall" states="installed" string="Uninstall" type="object"/>
|
||||||
confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/>
|
|
||||||
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" type="object"/>
|
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" type="object"/>
|
||||||
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" type="object"/>
|
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" type="object"/>
|
||||||
<button name="button_install_cancel" states="to install" string="Cancel Install" type="object"/>
|
<button name="button_install_cancel" states="to install" string="Cancel Install" type="object"/>
|
||||||
|
|
|
@ -68,6 +68,20 @@ class base_module_upgrade(osv.osv_memory):
|
||||||
res = mod_obj.read(cr, uid, ids, ['name','state'], context)
|
res = mod_obj.read(cr, uid, ids, ['name','state'], context)
|
||||||
return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
|
return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
|
||||||
|
|
||||||
|
def upgrade_module_cancel(self, cr, uid, ids, context=None):
|
||||||
|
mod_obj = self.pool.get('ir.module.module')
|
||||||
|
to_installed_ids = mod_obj.search(cr, uid, [
|
||||||
|
('state', 'in', ['to upgrade', 'to remove'])])
|
||||||
|
if to_installed_ids:
|
||||||
|
mod_obj.write(cr, uid, to_installed_ids, {'state': 'installed'}, context=context)
|
||||||
|
|
||||||
|
to_uninstalled_ids = mod_obj.search(cr, uid, [
|
||||||
|
('state', '=', 'to install')])
|
||||||
|
if to_uninstalled_ids:
|
||||||
|
mod_obj.write(cr, uid, to_uninstalled_ids, {'state': 'uninstalled'}, context=context)
|
||||||
|
|
||||||
|
return {'type': 'ir.actions.act_window_close'}
|
||||||
|
|
||||||
def upgrade_module(self, cr, uid, ids, context=None):
|
def upgrade_module(self, cr, uid, ids, context=None):
|
||||||
ir_module = self.pool.get('ir.module.module')
|
ir_module = self.pool.get('ir.module.module')
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,15 @@
|
||||||
<field name="model">base.module.upgrade</field>
|
<field name="model">base.module.upgrade</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="System Update">
|
<form string="System Update">
|
||||||
<div><label string="Your system will be updated."/></div>
|
<p>This module will trigger the uninstallation of below modules.</p>
|
||||||
<div><label string="Note that this operation might take a few minutes."/></div>
|
<p><strong>This operation will permanently erase all data currently stored by the modules!</strong></p>
|
||||||
<separator string="Modules to Update"/>
|
<p>If you wish to cancel the process, press the cancel button below</p>
|
||||||
|
<separator string="Impacted Modules"/>
|
||||||
<field name="module_info"/>
|
<field name="module_info"/>
|
||||||
<footer>
|
<footer>
|
||||||
<button name="upgrade_module" string="Update" type="object" class="oe_highlight"/>
|
<button name="upgrade_module" string="Update" type="object" class="oe_highlight"/>
|
||||||
or
|
or
|
||||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
<button string="Cancel" class="oe_link" name="upgrade_module_cancel" type="object"/>
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
@ -128,8 +128,7 @@ class res_partner_bank(osv.osv):
|
||||||
change_default=True, domain="[('country_id','=',country_id)]"),
|
change_default=True, domain="[('country_id','=',country_id)]"),
|
||||||
'company_id': fields.many2one('res.company', 'Company',
|
'company_id': fields.many2one('res.company', 'Company',
|
||||||
ondelete='cascade', help="Only if this bank account belong to your company"),
|
ondelete='cascade', help="Only if this bank account belong to your company"),
|
||||||
'partner_id': fields.many2one('res.partner', 'Account Owner', required=True,
|
'partner_id': fields.many2one('res.partner', 'Account Owner', ondelete='cascade', select=True),
|
||||||
ondelete='cascade', select=True),
|
|
||||||
'state': fields.selection(_bank_type_get, 'Bank Account Type', required=True,
|
'state': fields.selection(_bank_type_get, 'Bank Account Type', required=True,
|
||||||
change_default=True),
|
change_default=True),
|
||||||
'sequence': fields.integer('Sequence'),
|
'sequence': fields.integer('Sequence'),
|
||||||
|
|
|
@ -28,6 +28,7 @@ from openerp.osv import osv, fields
|
||||||
from openerp.tools import ustr
|
from openerp.tools import ustr
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
from openerp import exceptions
|
from openerp import exceptions
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -434,6 +435,43 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||||
def copy(self, cr, uid, id, values, context=None):
|
def copy(self, cr, uid, id, values, context=None):
|
||||||
raise osv.except_osv(_("Cannot duplicate configuration!"), "")
|
raise osv.except_osv(_("Cannot duplicate configuration!"), "")
|
||||||
|
|
||||||
|
def fields_view_get(self, cr, user, view_id=None, view_type='form',
|
||||||
|
context=None, toolbar=False, submenu=False):
|
||||||
|
ret_val = super(res_config_settings, self).fields_view_get(
|
||||||
|
cr, user, view_id=view_id, view_type=view_type, context=context,
|
||||||
|
toolbar=toolbar, submenu=submenu)
|
||||||
|
|
||||||
|
doc = etree.XML(ret_val['arch'])
|
||||||
|
|
||||||
|
for field in ret_val['fields']:
|
||||||
|
if not field.startswith("module_"):
|
||||||
|
continue
|
||||||
|
for node in doc.xpath("//field[@name='%s']" % field):
|
||||||
|
if 'on_change' not in node.attrib:
|
||||||
|
node.set("on_change",
|
||||||
|
"onchange_module(%s, '%s')" % (field, field))
|
||||||
|
|
||||||
|
ret_val['arch'] = etree.tostring(doc)
|
||||||
|
return ret_val
|
||||||
|
|
||||||
|
def onchange_module(self, cr, uid, ids, field_value, module_name, context={}):
|
||||||
|
module_pool = self.pool.get('ir.module.module')
|
||||||
|
module_ids = module_pool.search(
|
||||||
|
cr, uid, [('name', '=', module_name.replace("module_", '')),
|
||||||
|
('state','in', ['to install', 'installed', 'to upgrade'])],
|
||||||
|
context=context)
|
||||||
|
|
||||||
|
if module_ids and not field_value:
|
||||||
|
dep_ids = module_pool.downstream_dependencies(cr, uid, module_ids, context=context)
|
||||||
|
dep_name = [x.shortdesc for x in module_pool.browse(
|
||||||
|
cr, uid, dep_ids + module_ids, context=context)]
|
||||||
|
message = '\n'.join(dep_name)
|
||||||
|
return {'warning': {'title': _('Warning!'),
|
||||||
|
'message':
|
||||||
|
_('Disabling this option will also uninstall the following modules \n%s' % message)
|
||||||
|
}}
|
||||||
|
return {}
|
||||||
|
|
||||||
def _get_classified_fields(self, cr, uid, context=None):
|
def _get_classified_fields(self, cr, uid, context=None):
|
||||||
""" return a dictionary with the fields classified by category::
|
""" return a dictionary with the fields classified by category::
|
||||||
|
|
||||||
|
|
|
@ -270,8 +270,6 @@ class WebRequest(object):
|
||||||
to abitrary responses. Anything returned (except None) will
|
to abitrary responses. Anything returned (except None) will
|
||||||
be used as response."""
|
be used as response."""
|
||||||
self._failed = exception # prevent tx commit
|
self._failed = exception # prevent tx commit
|
||||||
if isinstance(exception, werkzeug.exceptions.HTTPException):
|
|
||||||
return exception
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def _call_function(self, *args, **kwargs):
|
def _call_function(self, *args, **kwargs):
|
||||||
|
@ -538,6 +536,15 @@ class HttpRequest(WebRequest):
|
||||||
params.pop('session_id', None)
|
params.pop('session_id', None)
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|
||||||
|
def _handle_exception(self, exception):
|
||||||
|
"""Called within an except block to allow converting exceptions
|
||||||
|
to abitrary responses. Anything returned (except None) will
|
||||||
|
be used as response."""
|
||||||
|
try:
|
||||||
|
return super(HttpRequest, self)._handle_exception(exception)
|
||||||
|
except werkzeug.exceptions.HTTPException, e:
|
||||||
|
return e
|
||||||
|
|
||||||
def dispatch(self):
|
def dispatch(self):
|
||||||
if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
|
if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
|
||||||
headers = {
|
headers = {
|
||||||
|
|
|
@ -78,7 +78,7 @@ class Graph(dict):
|
||||||
)
|
)
|
||||||
|
|
||||||
## and we update the default values with values from the database
|
## and we update the default values with values from the database
|
||||||
additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
|
additional_data.update((x['name'], x) for x in cr.dictfetchall())
|
||||||
|
|
||||||
for package in self.values():
|
for package in self.values():
|
||||||
for k, v in additional_data[package.name].items():
|
for k, v in additional_data[package.name].items():
|
||||||
|
|
|
@ -384,11 +384,12 @@ class TestStream(object):
|
||||||
if self.r.match(s):
|
if self.r.match(s):
|
||||||
return
|
return
|
||||||
first = True
|
first = True
|
||||||
for c in s.split('\n'):
|
level = logging.ERROR if s.startswith(('ERROR', 'FAIL', 'Traceback')) else logging.INFO
|
||||||
|
for c in s.splitlines():
|
||||||
if not first:
|
if not first:
|
||||||
c = '` ' + c
|
c = '` ' + c
|
||||||
first = False
|
first = False
|
||||||
self.logger.info(c)
|
self.logger.log(level, c)
|
||||||
|
|
||||||
current_test = None
|
current_test = None
|
||||||
|
|
||||||
|
|
|
@ -212,9 +212,9 @@ PSEUDOCONFIG_MAPPER = {
|
||||||
'debug': ['openerp:DEBUG'],
|
'debug': ['openerp:DEBUG'],
|
||||||
'debug_sql': ['openerp.sql_db:DEBUG'],
|
'debug_sql': ['openerp.sql_db:DEBUG'],
|
||||||
'info': [],
|
'info': [],
|
||||||
'warn': ['openerp:WARNING'],
|
'warn': ['openerp:WARNING', 'werkzeug:WARNING'],
|
||||||
'error': ['openerp:ERROR'],
|
'error': ['openerp:ERROR', 'werkzeug:ERROR'],
|
||||||
'critical': ['openerp:CRITICAL'],
|
'critical': ['openerp:CRITICAL', 'werkzeug:CRITICAL'],
|
||||||
}
|
}
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -46,8 +46,15 @@ SLEEP_INTERVAL = 60 # 1 min
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
# Werkzeug WSGI servers patched
|
# Werkzeug WSGI servers patched
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
|
class LoggingBaseWSGIServerMixIn(object):
|
||||||
|
def handle_error(self, request, client_address):
|
||||||
|
t, e, _ = sys.exc_info()
|
||||||
|
if t == socket.error and e.errno == errno.EPIPE:
|
||||||
|
# broken pipe, ignore error
|
||||||
|
return
|
||||||
|
_logger.exception('Exception happened during processing of request from %s', client_address)
|
||||||
|
|
||||||
class BaseWSGIServerNoBind(werkzeug.serving.BaseWSGIServer):
|
class BaseWSGIServerNoBind(LoggingBaseWSGIServerMixIn, werkzeug.serving.BaseWSGIServer):
|
||||||
""" werkzeug Base WSGI Server patched to skip socket binding. PreforkServer
|
""" werkzeug Base WSGI Server patched to skip socket binding. PreforkServer
|
||||||
use this class, sets the socket and calls the process_request() manually
|
use this class, sets the socket and calls the process_request() manually
|
||||||
"""
|
"""
|
||||||
|
@ -74,7 +81,7 @@ class RequestHandler(werkzeug.serving.WSGIRequestHandler):
|
||||||
# should also work with systemd socket activation. This is currently untested
|
# should also work with systemd socket activation. This is currently untested
|
||||||
# and not yet used.
|
# and not yet used.
|
||||||
|
|
||||||
class ThreadedWSGIServerReloadable(werkzeug.serving.ThreadedWSGIServer):
|
class ThreadedWSGIServerReloadable(LoggingBaseWSGIServerMixIn, werkzeug.serving.ThreadedWSGIServer):
|
||||||
""" werkzeug Threaded WSGI Server patched to allow reusing a listen socket
|
""" werkzeug Threaded WSGI Server patched to allow reusing a listen socket
|
||||||
given by the environement, this is used by autoreload to keep the listen
|
given by the environement, this is used by autoreload to keep the listen
|
||||||
socket open when a reload happens.
|
socket open when a reload happens.
|
||||||
|
|
|
@ -30,12 +30,11 @@ the ORM does, in fact.
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import uuid
|
import uuid
|
||||||
|
import psycopg2.extras
|
||||||
import psycopg2.extensions
|
import psycopg2.extensions
|
||||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ
|
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ
|
||||||
from psycopg2.pool import PoolError
|
from psycopg2.pool import PoolError
|
||||||
from psycopg2.psycopg1 import cursor as psycopg1cursor
|
|
||||||
|
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||||
|
|
||||||
|
@ -76,7 +75,7 @@ sql_counter = 0
|
||||||
class Cursor(object):
|
class Cursor(object):
|
||||||
"""Represents an open transaction to the PostgreSQL DB backend,
|
"""Represents an open transaction to the PostgreSQL DB backend,
|
||||||
acting as a lightweight wrapper around psycopg2's
|
acting as a lightweight wrapper around psycopg2's
|
||||||
``psycopg1cursor`` objects.
|
``cursor`` objects.
|
||||||
|
|
||||||
``Cursor`` is the object behind the ``cr`` variable used all
|
``Cursor`` is the object behind the ``cr`` variable used all
|
||||||
over the OpenERP code.
|
over the OpenERP code.
|
||||||
|
@ -175,7 +174,7 @@ class Cursor(object):
|
||||||
self._serialized = serialized
|
self._serialized = serialized
|
||||||
|
|
||||||
self._cnx = pool.borrow(dsn(dbname))
|
self._cnx = pool.borrow(dsn(dbname))
|
||||||
self._obj = self._cnx.cursor(cursor_factory=psycopg1cursor)
|
self._obj = self._cnx.cursor()
|
||||||
if self.sql_log:
|
if self.sql_log:
|
||||||
self.__caller = frame_codeinfo(currentframe(),2)
|
self.__caller = frame_codeinfo(currentframe(),2)
|
||||||
else:
|
else:
|
||||||
|
@ -188,6 +187,16 @@ class Cursor(object):
|
||||||
|
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
|
|
||||||
|
def __build_dict(self, row):
|
||||||
|
return { d.name: row[i] for i, d in enumerate(self._obj.description) }
|
||||||
|
def dictfetchone(self):
|
||||||
|
row = self._obj.fetchone()
|
||||||
|
return row and self.__build_dict(row)
|
||||||
|
def dictfetchmany(self, size):
|
||||||
|
return map(self.__build_dict, self._obj.fetchmany(size))
|
||||||
|
def dictfetchall(self):
|
||||||
|
return map(self.__build_dict, self._obj.fetchall())
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if not self._closed and not self._cnx.closed:
|
if not self._closed and not self._cnx.closed:
|
||||||
# Oops. 'self' has not been closed explicitly.
|
# Oops. 'self' has not been closed explicitly.
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
// Phantomjs openerp helper
|
// Phantomjs odoo helper
|
||||||
|
// jshint evil: true, loopfunc: true
|
||||||
|
|
||||||
function waitFor (ready, callback, timeout, timeoutMessageCallback) {
|
function waitFor (condition, callback, timeout, timeoutMessageCallback) {
|
||||||
timeout = timeout || 10000;
|
timeout = timeout || 10000;
|
||||||
var start = new Date;
|
var start = new Date();
|
||||||
|
|
||||||
(function waitLoop() {
|
(function waitLoop() {
|
||||||
if(new Date - start > timeout) {
|
if(new Date() - start > timeout) {
|
||||||
console.log('error', timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms");
|
console.log('error', timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms");
|
||||||
phantom.exit(1);
|
phantom.exit(1);
|
||||||
} else if (ready()) {
|
} else if (condition()) {
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
setTimeout(waitLoop, 250);
|
setTimeout(waitLoop, 250);
|
||||||
|
@ -44,7 +45,7 @@ function PhantomTest() {
|
||||||
}
|
}
|
||||||
return result.join('');
|
return result.join('');
|
||||||
}));
|
}));
|
||||||
msg.push('(leaf frame on top)')
|
msg.push('(leaf frame on top)');
|
||||||
}
|
}
|
||||||
console.log('error', JSON.stringify(msg.join('\n')));
|
console.log('error', JSON.stringify(msg.join('\n')));
|
||||||
phantom.exit(1);
|
phantom.exit(1);
|
||||||
|
@ -86,9 +87,9 @@ function PhantomTest() {
|
||||||
};
|
};
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
self.page.evaluate(function () {
|
self.page.evaluate(function () {
|
||||||
var message = ("Timeout\nhref: " + window.location.href
|
var message = ("Timeout\nhref: " + window.location.href +
|
||||||
+ "\nreferrer: " + document.referrer
|
"\nreferrer: " + document.referrer +
|
||||||
+ "\n\n" + (document.body && document.body.innerHTML)).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*");
|
"\n\n" + (document.body && document.body.innerHTML)).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*");
|
||||||
console.log('error', message);
|
console.log('error', message);
|
||||||
phantom.exit(1);
|
phantom.exit(1);
|
||||||
});
|
});
|
||||||
|
@ -107,6 +108,8 @@ function PhantomTest() {
|
||||||
url_path = "/login?" + qp.join('&');
|
url_path = "/login?" + qp.join('&');
|
||||||
}
|
}
|
||||||
var url = self.origin + url_path;
|
var url = self.origin + url_path;
|
||||||
|
code = code || "true";
|
||||||
|
ready = ready || "true";
|
||||||
self.page.open(url, function(status) {
|
self.page.open(url, function(status) {
|
||||||
if (status !== 'success') {
|
if (status !== 'success') {
|
||||||
console.log('error', "failed to load " + url);
|
console.log('error', "failed to load " + url);
|
||||||
|
@ -115,7 +118,7 @@ function PhantomTest() {
|
||||||
console.log('loaded', url, status);
|
console.log('loaded', url, status);
|
||||||
// process ready
|
// process ready
|
||||||
waitFor(function() {
|
waitFor(function() {
|
||||||
console.log("PhantomTest.run: wait for condition: " + ready);
|
console.log("PhantomTest.run: wait for condition:", ready);
|
||||||
return self.page.evaluate(function (ready) {
|
return self.page.evaluate(function (ready) {
|
||||||
var r = false;
|
var r = false;
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue