Merge remote-tracking branch 'odoo/master' into master-sass-in-bundles-fme

This commit is contained in:
Fabien Meghazi 2014-06-23 10:00:34 +02:00
commit 0e9aca1013
91 changed files with 1526 additions and 1183 deletions

View File

@ -1,2 +0,0 @@
.*
**/node_modules

View File

@ -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),

View File

@ -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:

View File

@ -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"/>

View File

@ -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 {

View File

@ -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; }
}
} }

View File

@ -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;
}); });

View File

@ -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>

View File

@ -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):

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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:

View File

@ -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>

View File

@ -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

View File

@ -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'] != '':

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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 {}

View File

@ -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)

View File

@ -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:

View File

@ -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'

View File

@ -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 + \

View File

@ -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], {

View File

@ -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:

View File

@ -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'

View File

@ -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>

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
60 access_stock_location_path_sale_manager stock.location.path partner salemanager model_stock_location_path base.group_sale_manager 1 1 1 1
61 access_stock_location_path_stock_user stock.location.path stock user model_stock_location_path stock.group_stock_user 1 1 1 1
62 access_stock_location_path stock.location.path model_stock_location_path base.group_sale_salesman 1 0 0 0
63 access_stock_location_route access_stock_location_route_stock_manager stock.location.route model_stock_location_route stock.group_stock_manager 1 1 1 1
64 access_stock_location_route stock.location.route model_stock_location_route base.group_user 1 0 0 0
65 access_procurement_rule procurement.rule.flow model_procurement_rule base.group_sale_salesman 1 0 0 0
66 access_procurement_rule_internal procurement.rule.flow internal model_procurement_rule base.group_user 1 0 0 0

View File

@ -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"/>

View File

@ -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

View File

@ -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>

View File

@ -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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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>

View File

@ -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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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&amp;value=%s&amp;width=%s&amp;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"/>

View File

@ -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 {}

View File

@ -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',

View File

@ -30,11 +30,7 @@
<field name="property_stock_account_input" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/> <field name="property_stock_account_input" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
<field name="property_stock_account_output" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/> <field name="property_stock_account_output" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','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>

View File

@ -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()

View File

@ -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','&lt;&gt;','done'),('invoice_state','&lt;&gt;','2binvoiced')]}" type="action" class="oe_highlight" groups="base.group_user"/> <button name="%(action_stock_invoice_onshipping)d" string="Create Invoice" attrs="{'invisible': ['|',('state','&lt;&gt;','done'),('invoice_state','&lt;&gt;','2binvoiced')]}" type="action" class="oe_highlight" groups="base.group_user"/>
<button name="%(action_stock_invoice_onshipping)d" string="Refund Invoice" attrs="{'invisible': ['|',('state','&lt;&gt;','done'),('invoice_state','&lt;&gt;','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"/>

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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:

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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']

View File

@ -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;
} }

View File

@ -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

View File

@ -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);
}());

View File

@ -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);

View File

@ -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;
}); });

View File

@ -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>

View File

@ -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>

View File

@ -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"/>

View File

@ -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");

View File

@ -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

View File

@ -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;
}

View File

@ -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:

View File

@ -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',

View File

@ -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);
}()); }());

View File

@ -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' ?")
)

View File

@ -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:

View File

@ -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: [

View File

@ -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")

View File

@ -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: [

View File

@ -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")

View File

@ -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',

View File

@ -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);

View File

@ -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>

View File

@ -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',

View File

@ -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: [

View File

@ -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)

View File

@ -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):

View File

@ -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,

View File

@ -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"/>

View File

@ -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')

View File

@ -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>

View File

@ -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'),

View File

@ -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::

View File

@ -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 = {

View File

@ -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():

View File

@ -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

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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 {