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):
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:
for idx, line in enumerate(vals['line_ids']):
line[2]['sequence'] = idx + 1
@ -65,17 +68,14 @@ class account_bank_statement(osv.osv):
return periods[0]
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:
context = {}
obj_seq = self.pool.get('ir.sequence')
default_journal_id = self._default_journal_id(cr, uid, context=context)
if default_journal_id != False:
period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context)
context['fiscalyear_id'] = period.fiscalyear_id.id
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)
period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context)
context['fiscalyear_id'] = period.fiscalyear_id.id
journal = self.pool.get('account.journal').browse(cr, uid, journal_id, None)
return obj_seq.next_by_id(cr, uid, journal.sequence_id.id, context=context)
def _currency(self, cursor, user, ids, name, args, context=None):
res = {}
@ -150,7 +150,7 @@ class account_bank_statement(osv.osv):
}
_defaults = {
'name': _compute_default_statement_name,
'name': '/',
'date': fields.date.context_today,
'state': 'draft',
'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):
"""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
create the move from.
:param browse_record st_line: account.bank.statement.line record to create the move from.
:return: int/long of the account.account to use as counterpart
"""
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):
"""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
create the move from.
:param browse_record st_line: account.bank.statement.line record to create the move from.
:return: int/long of the res.partner to use as counterpart
"""
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):
"""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
overridden to implement custom move generation (making sure to call super() to
establish a clean extension chain).
statement line by calling the _prepare_move_line_vals.
:param browse_record st_line: account.bank.statement.line record to
create the move from.
:param browse_record st_line: account.bank.statement.line record to create the move from.
:param int/long move_id: ID of the account.move to link 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
: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:
amt_cur = st_line.amount
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:
amt_cur = st_line.amount_currency
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,
amount_currency=False, account_id=False, partner_id=False, context=None):
"""Prepare the dict of values to create the move line from a
statement line. All non-mandatory args will replace the default computed one.
This method may be overridden to implement custom move generation (making sure to
call super() to establish a clean extension chain).
statement line.
:param browse_record st_line: account.bank.statement.line record to
create the move from.
@ -342,21 +329,29 @@ class account_bank_statement(osv.osv):
move_ids.append(st_line.journal_entry_id.id)
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.link_bank_to_partner(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
def button_cancel(self, cr, uid, ids, context=None):
done = []
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):
if st.state=='draft':
continue
move_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.unlink(cr, uid, move_ids, context)
done.append(st.id)
return self.write(cr, uid, done, {'state':'draft'}, context=context)
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
def _compute_balance_end_real(self, cr, uid, journal_id, context=None):
res = False
@ -417,18 +412,35 @@ class account_bank_statement(osv.osv):
def number_of_lines_reconciled(self, cr, uid, id, context=None):
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):
""" 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
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
if currency_obj.position == 'after':
return "return amount.toFixed("+str(digits)+") + ' "+currency_obj.symbol+"';"
elif currency_obj.position == 'before':
return "return '"+currency_obj.symbol+" ' + amount.toFixed("+str(digits)+");"
function = ""
done_currencies = []
for st_line in st.line_ids:
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):
@ -444,35 +456,40 @@ class account_bank_statement_line(osv.osv):
}
for mv_line in reconciliation_data['reconciliation_proposition']:
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
for reconciliation_data in ret:
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:
reconciliation_data['st_line']['no_match'] = True
#for reconciliation_data in ret:
# if not reconciliation_data['st_line']['has_no_partner']:
# st_line = self.browse(cr, uid, reconciliation_data['st_line']['id'], context=context)
# 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
def get_statement_line_for_reconciliation(self, cr, uid, id, context=None):
""" Returns the data required by the bank statement reconciliation use case """
line = self.browse(cr, uid, id, context=context)
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)
amount_str = line.amount > 0 and line.amount or -line.amount
amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency)
amount_currency_str = ""
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,
'ref': line.ref,
'note': line.note or "",
'name': line.name,
'date': line.date,
'amount': line.amount,
'amount': amount,
'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,
'statement_id': line.statement_id.id,
'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,
}
if line.partner_id.id:
if line.amount > 0:
dict['open_balance_account_id'] = line.partner_id.property_account_receivable.id
else:
dict['open_balance_account_id'] = line.partner_id.property_account_payable.id
return dict
data['open_balance_account_id'] = line.partner_id.property_account_payable.id
if amount > 0:
data['open_balance_account_id'] = line.partner_id.property_account_receivable.id
return data
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):
""" Returns move lines that constitute the best guess to reconcile a statement line. """
st_line = self.browse(cr, uid, id, context=context)
company_currency = st_line.journal_id.company_id.currency_id.id
statement_currency = st_line.journal_id.currency.id or company_currency
# either use the unsigned debit/credit fields or the signed amount_currency field
sign = 1
if statement_currency == company_currency:
amount_field = 'credit'
if st_line.amount > 0:
amount_field = 'debit'
else:
amount_field = 'credit'
else:
amount_field = 'amount_currency'
if st_line.amount < 0:
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
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:
return exact_match_id
# select oldest move lines
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:
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 = []
total = 0
# get_move_lines_counterparts inverts debit and credit
amount_field = 'debit' if amount_field == 'credit' else 'credit'
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)
total += line[amount_field]
else:
if total >= abs(st_line.amount):
break
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
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 string str: string to filter lines
:param string filter_str: string to filter lines
:param integer offset: offset of the request
:param integer limit: number of lines to fetch
:param boolean count: just return the number of records
: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')
currency_obj = self.pool.get('res.currency')
domain = additional_domain + [
('partner_id', '=', st_line.partner_id.id),
('reconcile_id', '=', False),
('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
('state', '=', 'valid'),
]
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:
domain.append(('id', 'not in', excluded_ids))
if str:
domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)]
if filter_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
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:
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:
continue
nb_lines += 1
@ -577,7 +617,7 @@ class account_bank_statement_line(osv.osv):
return nb_lines
else:
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:
continue
amount_currency_str = ""
@ -595,8 +635,12 @@ class account_bank_statement_line(osv.osv):
'period_name': line.period_id.name,
'journal_name': line.journal_id.name,
'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:
ret_line['debit'] = 0
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
ctx = context.copy()
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['credit'] = currency_obj.compute(cr, uid, statement_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['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=statement_currency)
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, st_line_currency.id, company_currency.id, ret_line['credit'], context=ctx)
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=st_line_currency)
ret.append(ret_line)
if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id)
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):
""" 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 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)
company_currency = st_line.journal_id.company_id.currency_id
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')
# 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.'))
for mv_line_dict in mv_line_dicts:
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)
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)
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
st_line_statement_id = st_line.statement_id.id
st_line_journal_id = st_line.journal_id.id
st_line_partner_id = st_line.partner_id.id
st_line_company_id = st_line.company_id.id
st_line_period_id = st_line.statement_id.period_id.id
st_line_currency = st_line.currency_id or statement_currency
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
to_create = []
for mv_line_dict in mv_line_dicts:
mv_line_dict['ref'] = move_name
mv_line_dict['move_id'] = move_id
mv_line_dict['period_id'] = st_line_period_id
mv_line_dict['journal_id'] = st_line_journal_id
mv_line_dict['partner_id'] = st_line_partner_id
mv_line_dict['company_id'] = st_line_company_id
mv_line_dict['statement_id'] = st_line_statement_id
mv_line_dict['period_id'] = st_line.statement_id.period_id.id
mv_line_dict['journal_id'] = st_line.journal_id.id
mv_line_dict['company_id'] = st_line.company_id.id
mv_line_dict['statement_id'] = st_line.statement_id.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_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['currency_id'] = statement_currency.id
mv_line_dict['debit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['debit'])
mv_line_dict['credit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['credit'])
elif st_line_currency and 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)
mv_line_dict['currency_id'] = st_line_currency
mv_line_dict['currency_id'] = st_line_currency.id
if st_line.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
debit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['debit'] / 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)
else:
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
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
if mv_line_dict.get('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'),
'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),
'ref': fields.char('Reference', size=32),
'ref': fields.char('Structured Communication'),
'note': fields.text('Notes'),
'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),

View File

@ -127,8 +127,8 @@ class account_move_line(osv.osv):
if move_line.reconcile_id:
continue
if not move_line.account_id.type in ('payable', 'receivable'):
#this function does not suport to be used on move lines not related to payable or receivable accounts
if not move_line.account_id.reconcile:
#this function does not suport to be used on move lines not related to a reconcilable account
continue
if move_line.currency_id:

View File

@ -15,7 +15,7 @@
<field name="balance_end_real" eval="3707.58"/>
</record>
<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="sequence" eval="1"/>
<field name="company_id" ref="base.main_company"/>
@ -26,7 +26,7 @@
<field name="partner_id" ref="base.res_partner_9"/>
</record>
<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="sequence" eval="2"/>
<field name="company_id" ref="base.main_company"/>
@ -37,7 +37,7 @@
<field name="partner_id" ref="base.res_partner_9"/>
</record>
<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="sequence" eval="3"/>
<field name="company_id" ref="base.main_company"/>
@ -47,7 +47,7 @@
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
</record>
<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="sequence" eval="4"/>
<field name="company_id" ref="base.main_company"/>

View File

@ -120,8 +120,6 @@
cursor: pointer; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_match:not(.no_partner) .toggle_match {
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 {
margin-bottom: 10px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line table.details td:first-child {
@ -202,10 +200,6 @@
cursor: pointer; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6) {
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 {
padding: 0 0 5px 18px; }
.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 */
> table > tbody > tr:nth-child(1) > td table {
margin-bottom: 10px;
@ -341,10 +335,6 @@ $initialLineBackground: #f0f0f0;
// accounting "T"
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
.call("get_format_currency_js_function", [self.statement_id])
.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) {
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){
if (o.is_valid && o.persistAndDestroy()) {
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_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line});
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
// 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() {
var self = this;
if (self.get("mode") === "create") {
self.addLineBeingEdited();
self.set("mode", "match");
} else {
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_end = (self.get("pager_index")+1) * self.max_move_lines_displayed;
_( _.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){
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
self.bindPopoverTo($line.find(".line_info_button"));
@ -1057,7 +1062,6 @@ openerp.account = function (instance) {
updatePagerControls: function() {
var self = this;
if (self.get("pager_index") === 0)
self.$(".pager_control_left").addClass("disabled");
else
@ -1075,7 +1079,7 @@ openerp.account = function (instance) {
balanceChanged: function() {
var self = this;
var balance = self.get("balance");
self.$(".tbody_open_balance").empty();
// Special case hack : no identified partner
if (self.st_line.has_no_partner) {
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").text("OK");
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;
}
self.$(".tbody_open_balance").empty();
if (Math.abs(balance).toFixed(3) === "0.000") {
self.$(".button_ok").addClass("oe_highlight");
self.$(".button_ok").text("OK");
} else {
self.$(".button_ok").removeClass("oe_highlight");
self.$(".button_ok").text("Keep open");
var debit = (balance > 0 ? self.formatCurrency(balance) : "");
var credit = (balance < 0 ? self.formatCurrency(-1*balance) : "");
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]}));
self.$(".tbody_open_balance").append($line);
}
@ -1111,21 +1119,15 @@ openerp.account = function (instance) {
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.get("mode") === "inactive") {
self.$(".match").slideUp(self.animation_speed);
self.$(".create").slideUp(self.animation_speed);
self.$(".toggle_match").removeClass("visible_toggle");
self.el.dataset.mode = "inactive";
} else {
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;
return;
}
}
if (self.get("mode") === "inactive") {
@ -1198,6 +1200,8 @@ openerp.account = function (instance) {
var self = this;
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].currency_id = self.st_line.currency_id;
// Specific cases
if (elt === self.account_id_field)
@ -1215,7 +1219,7 @@ openerp.account = function (instance) {
var tax = data.taxes[0];
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[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 {
@ -1228,10 +1232,9 @@ openerp.account = function (instance) {
$.when(deferred_tax).then(function(){
// Format amounts
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)
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.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;
if (balance < 0) {
line.debit -= balance;
line.debit_str = self.formatCurrency(line.debit);
line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id);
} else {
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.partial_reconcile = true;
@ -1291,12 +1294,13 @@ openerp.account = function (instance) {
},
unpartialReconcileLine: function(line) {
var self = this;
if (line.initial_amount > 0) {
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 {
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.partial_reconcile = false;
@ -1359,16 +1363,15 @@ openerp.account = function (instance) {
if (limit > 0) {
// Load move lines
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) {
_(lines).each(self.decorateMoveLine.bind(self));
move_lines = lines;
});
}
// 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
.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){
move_lines_num = num;
});

View File

@ -185,7 +185,7 @@
<td><span class="toggle_create glyphicon glyphicon-play"></span></td>
<td><t t-esc="account_code"/></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="credit"/></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."),
'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."),
'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 = {
'sequence': lambda *args: 1,
'fold': False,
}
class crm_claim(osv.osv):

View File

@ -61,7 +61,6 @@
<field name="name">Rejected</field>
<field name="sequence">29</field>
<field name="case_default" eval="True"/>
<field name="fold" eval="True"/>
</record>

View File

@ -51,7 +51,6 @@
<field name="name"/>
<field name="case_default"/>
<field name="sequence"/>
<field name="fold"/>
</group>
</form>
</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
Statements will also remove those associated statements.
The following reconciliation logic has been implemented in the CODA processing:
-------------------------------------------------------------------------------
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
Instead 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
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:

View File

@ -32,5 +32,27 @@
<field eval="'2011-01-31'" name="date_stop"/>
<field name="company_id" ref="base.main_company"/>
</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>
</openerp>

View File

@ -4,7 +4,7 @@
2200010000 GKCCBEBB 1 0
2300010000BE41063012345610 PARTNER 1 0 1
3100010001OL44483FW SCTOFBIONLO001010001001PARTNER 1 0 0
2100020000OL4414AC8BOVSOVSOVERS00000000030444501101110015000002010237 11011113501 0
2100020000OL4414AC8BOVSOVSOVERS0000000003044450110111001500001101240283842818 11011113501 0
2200020000 BBRUBEBB 1 0
2300020000BE61310126985517 PARTNER 2 0 1
3100020001OL4414AC8BOVSOVSOVERS001500001001PARTNER 2 1 0

View File

@ -291,79 +291,38 @@ class account_coda_import(osv.osv_memory):
if 'counterpartyAddress' in line and line['counterpartyAddress'] != '':
note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress'])
line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']]))
partner = 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':
ids = self.pool.get('account.invoice').search(cr, uid, [('reference', '=', line['communication']), ('reference_type', '=', 'bba')])
# 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)
structured_com = line['communication']
if 'counterpartyNumber' in line and line['counterpartyNumber']:
ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))])
if ids and len(ids) > 0:
partner = self.pool.get('res.partner.bank').browse(cr, uid, ids[0], context=context).partner_id
partner_id = partner.id
if ids:
bank_account_id = ids[0]
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'] != '':
note.append(_('Communication') + ': ' + line['communication'])
data = {
'name': line['name'],
'note': "\n".join(note),
'note': "\n".join(note),
'date': line['entryDate'],
'amount': line['amount'],
'partner_id': partner_id,
'statement_id': statement['id'],
'ref': line['ref'],
'ref': structured_com,
'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)
if statement['coda_note'] != '':

View File

@ -25,7 +25,7 @@
<record id="impuestos_plantilla_isv_por_cobrar" model="account.tax.template">
<field name="chart_template_id" ref="cuentas_plantilla"/>
<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="account_collected_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">
<field name="chart_template_id" ref="cuentas_plantilla"/>
<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="account_collected_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}">
<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="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_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
<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">
<field name="product_id" 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="location_dest_id" readonly="1" string="Destination Loc." widget="selection" groups="stock.group_locations"/>
<field name="scrapped" invisible="1"/>

View File

@ -18,16 +18,16 @@
<field name="lot_id" domain="[('product_id', '=', product_id)]"
context="{'default_product_id':product_id}"
attrs="{'required': [('track_production', '=', True), ('mode', '=', 'consume_produce')]}"
groups="stock.group_tracking_lot"/>
groups="stock.group_production_lot"/>
</group>
<group string="To Consume">
<field name="consume_lines">
<field name="consume_lines" nolabel="1">
<tree string="Consume Lines" editable="top">
<field name="product_id"/>
<field name="product_qty"/>
<field name="lot_id" domain="[('product_id', '=', product_id)]"
context="{'default_product_id':product_id}"
groups="stock.group_tracking_lot"/>
groups="stock.group_production_lot"/>
</tree>
</field>
</group>

View File

@ -15,7 +15,7 @@
<field name="product_qty" class="oe_inline"/>
<field name="product_uom" class="oe_inline" readonly="1" groups="product.group_uom"/>
</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}"/>
<field name="location_id" groups="stock.group_locations"/>
</group>

View File

@ -30,7 +30,7 @@
<h2>
<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"/>
</h2>
@ -41,7 +41,9 @@
</div>
<div class="col-xs-3" groups="stock.group_production_lot">
<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 t-if="o.guarantee_limit" class="col-xs-3">
<strong>Guarantee Limit:</strong>

View File

@ -64,7 +64,7 @@ class procurement_group(osv.osv):
}
_defaults = {
'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):
@ -267,7 +267,7 @@ class procurement_order(osv.osv):
#
# 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
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()
# Run confirmed procurements
dom = [('state', '=', 'confirmed')]
if company_id:
dom += [('company_id', '=', company_id)]
while True:
ids = self.search(cr, SUPERUSER_ID, [('state', '=', 'confirmed')], context=context)
ids = self.search(cr, SUPERUSER_ID, dom, context=context)
if not ids:
break
self.run(cr, SUPERUSER_ID, ids, context=context)
@ -298,8 +301,11 @@ class procurement_order(osv.osv):
# Check if running procurements are done
offset = 0
dom = [('state', '=', 'running')]
if company_id:
dom += [('company_id', '=', company_id)]
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:
break
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')
#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()
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
new_cr.close()
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):
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 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,
'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,
'invoice_state': order.invoice_method == 'picking' and '2binvoiced' or 'none'
}
diff_quantity = order_line.product_qty
@ -709,9 +710,10 @@ class purchase_order(osv.osv):
tmp.update({
'product_uom_qty': min(procurement_qty, diff_quantity),
'product_uos_qty': min(procurement_qty, diff_quantity),
'move_dest_id': procurement.move_dest_id.id, # blabla
'group_id': procurement.group_id.id or group_id, # blabla to check ca devrait etre bon et groupé dans le meme picking qd meme
'move_dest_id': procurement.move_dest_id.id, #move destination is same as procurement destination
'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,
'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)
res.append(tmp)
@ -1299,6 +1301,7 @@ class procurement_order(osv.osv):
res[procurement.id] = False
else:
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)
#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, [
@ -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)
if available_draft_po_ids:
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
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:
@ -1318,7 +1325,6 @@ class procurement_order(osv.osv):
po_line_id = po_line_obj.create(cr, SUPERUSER_ID, line_vals, context=context)
linked_po_ids.append(procurement.id)
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
po_vals = {
'name': name,
@ -1363,6 +1369,13 @@ class product_template(osv.Model):
_name = '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):
res = dict.fromkeys(ids, 0)
for template in self.browse(cr, uid, ids, context=context):
@ -1374,6 +1387,7 @@ class product_template(osv.Model):
}
_defaults = {
'purchase_ok': 1,
'route_ids': _get_buy_route,
}
class product_product(osv.Model):
@ -1451,15 +1465,9 @@ class account_invoice_line(osv.Model):
readonly=True),
}
class product_product(osv.osv):
_inherit = "product.product"
class product_template(osv.osv):
_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:

View File

@ -55,6 +55,33 @@ class stock_move(osv.osv):
})
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):
_inherit = 'stock.picking'

View File

@ -128,13 +128,12 @@ class sale_order(osv.osv):
sale_clause = ''
no_invoiced = False
for arg in args:
if arg[1] == '=':
if arg[2]:
clause += 'AND inv.state = \'paid\''
else:
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 '
no_invoiced = True
if (arg[1] == '=' and arg[2]) or (arg[1] == '!=' and not arg[2]):
clause += 'AND inv.state = \'paid\''
else:
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 '
no_invoiced = True
cursor.execute('SELECT rel.order_id ' \
'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)
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:
sale_line = move.procurement_id.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))) + "])]"
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
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')
#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
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],
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
@ -341,14 +340,15 @@ class procurement_order(osv.osv):
if context is None:
context = {}
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')
procurement_obj = self.pool.get('procurement.order')
offset = 0
ids = [1]
dom = company_id and [('company_id', '=', company_id)] or []
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):
prods = self._product_virtual_get(cr, uid, op)
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):
res = {}
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
_columns = {
'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'),
'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',
type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
string='Quantity On Hand',
@ -277,6 +277,12 @@ class product_product(osv.osv):
res['fields']['qty_available']['string'] = _('Produced Qty')
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):
_name = 'product.template'
_inherit = 'product.template'

View File

@ -193,7 +193,7 @@
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<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_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)]}"/>
@ -202,13 +202,13 @@
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_stat_button"
name="%(product_open_quants)d"
icon="fa-bank"
icon="fa-building-o"
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 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"
attrs="{'invisible':[('type', '=', 'service')]}" icon="fa-pinterest" string="Reordering Rules"/>
attrs="{'invisible':[('type', '=', 'service')]}" icon="fa-refresh" string="Reordering Rules"/>
</xpath>
</field>
</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_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_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_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

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>
</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="model_id" search="[('model','=','stock.picking.type')]" model="ir.model"/>
<field name="global" eval="True"/>

View File

@ -272,27 +272,28 @@ class stock_quant(osv.osv):
_columns = {
'name': fields.function(_get_quant_name, type='char', string='Identifier'),
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete="restrict"),
'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="restrict"),
'qty': fields.float('Quantity', required=True, help="Quantity of products in this quant, in the default unit of measure of the product"),
'package_id': fields.many2one('stock.quant.package', string='Package', help="The package containing this quant"),
'packaging_type_id': fields.related('package_id', 'packaging_id', type='many2one', relation='product.packaging', string='Type of packaging', store=True),
'reservation_id': fields.many2one('stock.move', 'Reserved for Move', help="The move the quant is reserved for"),
'lot_id': fields.many2one('stock.production.lot', 'Lot'),
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete="restrict", readonly=True),
'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", readonly=True),
'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', readonly=True, store=True),
'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', readonly=True),
'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'),
'in_date': fields.datetime('Incoming Date'),
'create_date': fields.datetime('Creation Date', readonly=True),
'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'),
'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),
# 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'),
'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_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"),
'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.', readonly=True),
'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 = {
@ -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))
return True
#----------------------------------------------------------
# Stock Picking
#----------------------------------------------------------
@ -1248,6 +1248,7 @@ class stock_picking(osv.osv):
'''
move_obj = self.pool.get('stock.move')
operation_obj = self.pool.get('stock.pack.operation')
moves = []
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():
if remaining_qty > 0:
@ -1260,9 +1261,12 @@ class stock_picking(osv.osv):
'product_uom': product.uom_id.id,
'product_uom_qty': remaining_qty,
'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):
""" Unreserve quants then try to reassign quants."""
@ -1289,11 +1293,13 @@ class stock_picking(osv.osv):
else:
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)
todo_move_ids = []
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()
#split move lines eventually
todo_move_ids = []
toassign_move_ids = []
for move in picking.move_lines:
remaining_qty = move.remaining_qty
@ -1371,7 +1377,7 @@ class stock_picking(osv.osv):
op = operation
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)
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)
pack_operation_ids.append(op.id)
for record in op.linked_move_operation_ids:
@ -1783,7 +1789,7 @@ class stock_move(osv.osv):
'move_dest_id': move.id,
'group_id': group_id,
'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,
}
@ -3521,7 +3527,6 @@ class stock_location_path(osv.osv):
# -------------------------
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):
"""
@ -3818,8 +3823,8 @@ class stock_pack_operation(osv.osv):
if pack_op.qty_done < pack_op.product_qty:
# 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)
self.write(cr, uid, ids, {'product_qty': pack_op.product_qty - pack_op.qty_done, 'qty_done': 0}, context=context)
processed_ids.append(op)
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)
self.write(cr, uid, processed_ids, {'processed': 'true'}, context=context)
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)
product_id = obj.product_id.id
val = {'product_id': product_id}
new_lot_id = False
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})
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)
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)
else:
#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 = {
'picking_id': picking_id,
'product_qty': 0,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
'qty_done': 1,
}
}
for key in domain:
var_name, dummy, value = key
uom_id = False

View File

@ -355,7 +355,7 @@
<record model="ir.actions.act_window" id="location_open_quants">
<field name="context">{'search_default_productgroup': 1}</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>
</record>
@ -1356,6 +1356,7 @@
<field name="model">stock.picking.type</field>
<field name="arch" type="xml">
<tree string="Picking Types">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="warehouse_id"/>
<field name="sequence_id"/>
@ -1794,7 +1795,7 @@
</record>
<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="name">Quants</field>
<field name="name">Current Stock</field>
<field name="res_model">stock.quant</field>
</record>

View File

@ -7,18 +7,15 @@
<t>
<div class="page">
<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="col-xs-6 mt6">
<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="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 class="text-center" t-if="o.loc_barcode" t-field="o.loc_barcode"></p>
<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>

View File

@ -7,15 +7,15 @@
<t t-call="report.external_layout">
<div class="page">
<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 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>
</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>
</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>
</div>
<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>
<td>
<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>
</td>
<t t-if="o.picking_type_id.code != 'outgoing'"><td><span t-field="move.location_dest_id"/></td></t>
@ -104,13 +104,13 @@
</t>
<td>
<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 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 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>
</td>
<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')
#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()
for proc in self.browse(new_cr, uid, ids, context=context):
proc_obj._procure_orderpoint_confirm(new_cr, uid, use_new_cursor=new_cr.dbname, context=context)
user_obj = self.pool.get('res.users')
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
new_cr.close()
return {}

View File

@ -53,6 +53,7 @@ Dashboard / Reports for Warehouse Management includes:
'wizard/stock_change_standard_price_view.xml',
'wizard/stock_invoice_onshipping_view.xml',
'wizard/stock_valuation_history_view.xml',
'wizard/stock_return_picking_view.xml',
'product_data.xml',
'product_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_output" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
</group>
</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>
</xpath>
</field>
</record>

View File

@ -27,11 +27,10 @@ class stock_location_path(osv.osv):
'invoice_state': fields.selection([
("invoiced", "Invoiced"),
("2binvoiced", "To Be Invoiced"),
("none", "Not Applicable")], "Invoice Status",
required=True,),
("none", "Not Applicable")], "Invoice Status",),
}
_defaults = {
'invoice_state': 'none',
'invoice_state': '',
}
#----------------------------------------------------------
@ -43,11 +42,10 @@ class procurement_rule(osv.osv):
'invoice_state': fields.selection([
("invoiced", "Invoiced"),
("2binvoiced", "To Be Invoiced"),
("none", "Not Applicable")], "Invoice Status",
required=True),
("none", "Not Applicable")], "Invoice Status",),
}
_defaults = {
'invoice_state': 'none',
'invoice_state': '',
}
#----------------------------------------------------------
@ -61,16 +59,16 @@ class procurement_order(osv.osv):
'invoice_state': fields.selection([("invoiced", "Invoiced"),
("2binvoiced", "To Be Invoiced"),
("none", "Not Applicable")
], "Invoice Control", required=True),
], "Invoice Control"),
}
def _run_move_create(self, cr, uid, procurement, context=None):
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
_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):
''' 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
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):
key = group and picking.id or True
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:
todo.setdefault(key, [])
todo[key].append(move)
@ -259,12 +257,7 @@ class stock_picking(osv.osv):
invoice_line_vals['origin'] = origin
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)
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')))
return invoices.values()

View File

@ -33,7 +33,6 @@
<field name="arch" type="xml">
<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="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 expr="//field[@name='move_type']" position="after">
<field name="invoice_state" groups="account.group_account_invoice"/>

View File

@ -22,3 +22,4 @@
import stock_change_standard_price
import stock_invoice_onshipping
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):
def _get_journal(self, cr, uid, context=None):
res = self._get_journal_id(cr, uid, context=context)
if res:
return res[0][0]
return False
def _get_journal_id(self, cr, uid, context=None):
journal_obj = self.pool.get('account.journal')
journal_type = self._get_journal_type(cr, uid, context=context)
journals = journal_obj.search(cr, uid, [('type', '=', journal_type)])
return journals and journals[0] or False
def _get_journal_type(self, cr, uid, context=None):
if context is None:
context = {}
journal_obj = self.pool.get('account.journal')
value = journal_obj.search(cr, uid, [('type', 'in',('sale','sale_Refund'))])
res_ids = context and context.get('active_ids', [])
pick_obj = self.pool.get('stock.picking')
pickings = pick_obj.browse(cr, uid, res_ids, context=context)
vals = []
for jr_type in journal_obj.browse(cr, uid, value, context=context):
t1 = jr_type.id,jr_type.name
if t1 not in vals:
vals.append(t1)
return vals
pick = pickings and pickings[0]
if not pick or not pick.move_lines:
return 'sale'
src_usage = pick.move_lines[0].location_id.usage
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"
_description = "Stock Invoice Onshipping"
_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"),
'inv_type': fields.selection([('out_invoice','Create Invoice'),('out_refund','Refund Invoice')], "Invoice Type"),
'invoice_date': fields.date('Invoice Date'),
}
_defaults = {
'journal_type': _get_journal_type,
'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):
@ -71,24 +85,30 @@ class stock_invoice_onshipping(osv.osv_memory):
def open_invoice(self, cr, uid, ids, context=None):
if context is None:
context = {}
invoice_ids = self.create_invoice(cr, uid, ids, context=context)
if not invoice_ids:
raise osv.except_osv(_('Error!'), _('No invoice created!'))
onshipdata_obj = self.read(cr, uid, ids, ['journal_id', 'group', 'invoice_date', 'inv_type'])
inv_type = onshipdata_obj[0]['inv_type']
data = self.browse(cr, uid, ids[0], context=context)
action_model = False
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')
if inv_type == "out_refund":
action_model,action_id = data_pool.get_object_reference(cr, uid, 'account', "action_invoice_tree3")
elif inv_type == "out_invoice":
action_model,action_id = data_pool.get_object_reference(cr, uid, 'account', "action_invoice_tree1")
if inv_type == "out_invoice":
action_id = data_pool.xmlid_to_res_id(cr, uid, 'account.action_invoice_tree1')
elif inv_type == "in_invoice":
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:
action_pool = self.pool[action_model]
if action_id:
action_pool = self.pool['ir.actions.act_window']
action = action_pool.read(cr, uid, action_id, context=context)
action['domain'] = "[('id','in', ["+','.join(map(str,invoice_ids))+"])]"
return action
@ -97,18 +117,17 @@ class stock_invoice_onshipping(osv.osv_memory):
def create_invoice(self, cr, uid, ids, context=None):
context = context or {}
picking_pool = self.pool.get('stock.picking')
onshipdata_obj = self.read(cr, uid, ids, ['journal_id', 'group', 'invoice_date', 'inv_type'])
context['date_inv'] = onshipdata_obj[0]['invoice_date']
inv_type = onshipdata_obj[0]['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'] = data.invoice_date
acc_journal = self.pool.get("account.journal")
inv_type = journal2type.get(data.journal_type) or 'out_invoice'
context['inv_type'] = inv_type
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,
journal_id = onshipdata_obj[0]['journal_id'],
group = onshipdata_obj[0]['group'],
journal_id = data.journal_id.id,
group = data.group,
type = inv_type,
context=context)
return res

View File

@ -7,10 +7,10 @@
<field name="arch" type="xml">
<form string="Create invoice">
<h1>
<field name="inv_type" readonly="1"/>
<field name="journal_type" readonly="1"/>
</h1>
<group>
<field name="journal_id"/>
<field name="journal_id" domain="[('type','=',journal_type)]"/>
<field name="group"/>
<field name="invoice_date" />
</group>
@ -23,17 +23,6 @@
</field>
</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"
res_model="stock.invoice.onshipping"
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")
lines_rec = self.browse(cr, uid, lines, context=context)
for line_rec in lines_rec:
if not line_rec.product_id.id in prod_dict:
if line_rec.product_id.cost_method == 'real':
prod_dict[line_rec.product_id.id] = line_rec.price_unit_on_quant
else:
if line_rec.product_id.cost_method == 'real':
price = line_rec.price_unit_on_quant
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)
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
return res

View File

@ -14,7 +14,7 @@
<record id="picking_type_dropship" model="stock.picking.type">
<field name="name">Dropship</field>
<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_dest_id" ref="stock.stock_location_customers"/>
</record>
@ -37,7 +37,6 @@
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="route_drop_shipping"/>
<field name="picking_type_id" ref="picking_type_dropship"/>
</record>
</record>
</data>
</openerp>

View File

@ -318,8 +318,9 @@ class WebsiteSurvey(http.Controller):
'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"""
current_filters = current_filters if current_filters else []
survey_obj = request.registry['survey.survey']
result = {'survey':survey, 'page_ids': []}
for page in survey.page_ids:
@ -347,8 +348,10 @@ class WebsiteSurvey(http.Controller):
total = ceil(total_record / float(limit))
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'''
# 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']
result = []
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)
for answer in data['answers']:
values = []
for res in data['result']:
if res[1] == answer:
values.append({'text': data['rows'][res[0]], 'count': data['result'][res]})
for row in data['rows']:
values.append({'text': data['rows'].get(row), 'count': data['result'].get((row, answer))})
result.append({'key': data['answers'].get(answer), 'values': values})
return json.dumps(result)

View File

@ -26,6 +26,7 @@ from openerp.addons.website.models.website import slug
from urlparse import urljoin
from itertools import product
from collections import Counter
from collections import OrderedDict
import datetime
import logging
@ -289,8 +290,7 @@ class survey_survey(osv.Model):
:param finished: True for completely filled survey,Falser otherwise.
:returns list of filtered user_input_ids.
'''
if context is None:
context = {}
context = context if context else {}
if filters:
input_line_obj = self.pool.get('survey.user_input_line')
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]})
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 '''
if context is None:
context = {}
current_filters = current_filters if current_filters else []
context = context if context else {}
#Calculate and return statistics for choice
if question.type in ['simple_choice', 'multiple_choice']:
result_summary = {}
[result_summary.update({label.id: {'text': label.value, 'count': 0, 'answer_id': label.id}}) for label in question.labels_ids]
for input_line in question.user_input_line_ids:
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):
result_summary[input_line.value_suggested.id]['count'] += 1
result_summary = result_summary.values()
result_summary = []
for label in question.labels_ids:
count = 0
for input_line in question.user_input_line_ids:
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):
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
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]
[answers.update({label.id: label.value}) for label in question.labels_ids]
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)})
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 '''
if context is None:
context = {}
current_filters = current_filters if current_filters else []
context = context if context else {}
result = {}
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']

View File

@ -2325,7 +2325,7 @@
.openerp .oe_form .oe_form_field_image .oe_form_field_image_controls {
position: absolute;
top: 1px;
padding: 4px 0;
padding: 6px 0;
width: 100%;
display: none;
text-align: center;
@ -2647,11 +2647,11 @@
.openerp .oe_list_editable .oe_list_content td.oe_list_field_cell {
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;
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;
}
.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_drop_down_button {
@ -2667,6 +2667,13 @@
min-width: 0;
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 {
height: 27px;
-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 {
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;
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;
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 {
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 {
background-color: #729fcf !important;
}
}
.openerp_ie .oe_webclient {
height: auto !important;
}
@media print {
.openerp {
@ -3450,6 +3462,39 @@ input[type="radio"], input[type="checkbox"] {
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 {
overflow: auto;
}

View File

@ -1902,7 +1902,7 @@ $sheet-padding: 16px
.oe_form_field_image_controls
position: absolute
top: 1px
padding: 4px 0
padding: 6px 0
width: 100%
display: none
text-align: center
@ -2138,7 +2138,7 @@ $sheet-padding: 16px
.oe_list_editable .oe_list_content td.oe_list_field_cell
padding: 4px 6px 3px
.oe_list.oe_list_editable.oe_editing
.oe_edition .oe_list_field_cell:not(.oe_readonly)
.oe_edition .oe_list_field_cell
*
visibility: hidden
color: transparent
@ -2150,6 +2150,11 @@ $sheet-padding: 16px
.oe_input_icon
margin-top: 5px
.oe_form_field
&.oe_list_field_handle
color: transparent
&.oe_readonly
padding: 4px 6px 3px
text-align: left
min-width: 0
max-width: none
input, textarea
@ -2160,9 +2165,13 @@ $sheet-padding: 16px
input, textarea, select
min-width: 0
&.oe_form_field_float,&.oe_form_view_integer
input
&.oe_readonly
padding: 6px 0px 0px
text-align: right
max-width: 100px
input
width: 100% !important
text-align: right
&.oe_form_field_datetime,&.oe_form_field_date
input.oe_datepicker_master
width: 100% !important
@ -2801,6 +2810,33 @@ input[type="radio"], input[type="checkbox"]
background-color: black
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
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) {
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.current_display = null;
this.is_started = false;
this.ignore_focusout = false;
},
reinit_value: function(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_duration = 15000;
var anyoneLoosesFocus = function (e) {
if (self.ignore_focusout) { return; }
var used = false;
if (self.floating) {
if (self.last_search.length > 0) {
@ -3844,11 +3848,17 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
_search_create_popup: function() {
this.no_ed = true;
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) {
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);
},
initialize_content: function() {
var self= this;
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('.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) {
var self = this;
@ -5676,8 +5690,6 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
return;
$img.css("max-width", "" + self.options.size[0] + "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.attr('src', self.placeholder);

View File

@ -285,9 +285,7 @@
if (!this.editor.is_editing()) { return; }
for(var i=0, len=this.fields_for_resize.length; i<len; ++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',
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}
@ -451,13 +454,7 @@
setup_events: function () {
var self = this;
_.each(this.editor.form.fields, function(field, field_name) {
var set_invisible = 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; }
field.on("change:effective_readonly", self, function(){
var item = _(self.fields_for_resize).find(function (item) {
return item.field === field;
});

View File

@ -1310,15 +1310,16 @@
<t t-name="FieldBinaryImage">
<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">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
Edit
</t>
<i class="fa fa-pencil fa-1g pull-left col-md-offset-1 oe_form_binary_file_edit" title="Edit"/>
<i class="fa fa-trash-o fa-1g col-md-offset-5 oe_form_binary_file_clear" title="Clear"/>
<div class="oe_form_binary_progress" style="display: none">
<img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/>
<b>Uploading ...</b>
</div>
</div>
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
</t>
</span>
</t>
<t t-name="FieldBinaryImage-img">
@ -2038,4 +2039,5 @@
</t>
<t t-name="StatInfo">
<strong><t t-esc="value"/></strong><br/><t t-esc="text"/></t>
</templates>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<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 class="arrow"></div>
<div class="arrow" t-if="!next"></div>
<h3 class="popover-title"></h3>
<div class="popover-content"></div>
<t t-if="next or end">
@ -21,7 +21,7 @@
</t>
</div>
</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>
</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_tree.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/cleditor/jquery.cleditor.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/src/js/openerpframework.js"></script>
<script type="text/javascript" src="/web/static/src/js/tour.js"></script>
<script type="text/javascript" charset="utf-8">
openerp._modules = <t t-raw="modules"/>;
</script>
@ -246,6 +247,7 @@
<t t-call="web.assets_backend"/>
<script type="text/javascript" id="qunit_config">
localStorage.clear();
QUnit.config.testTimeout = 5 * 60 * 1000;
QUnit.moduleDone(function(result) {
console.log(result.name + " (" + result.passed + "/" + result.total + " passed tests)");
@ -253,6 +255,8 @@
QUnit.done(function(result) {
if (result.failed === 0) {
console.log('ok');
} else {
console.log('error');
}
});
openerp.web.qweb.add_template("/web/webclient/qweb");

View File

@ -8,6 +8,7 @@ import itertools
import logging
import math
import mimetypes
import unicodedata
import os
import re
import urlparse
@ -28,6 +29,7 @@ except ImportError:
import openerp
from openerp.osv import orm, osv, fields
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.addons.web.http import request
@ -85,15 +87,29 @@ def is_multilang_url(local_url, langs=None):
return False
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:
# There are 2 different libraries only python-slugify is supported
try:
return slugify_lib.slugify(s, max_length=max_length)
except TypeError:
pass
spaceless = re.sub(r'\s+', '-', s)
specialless = re.sub(r'[^-_A-Za-z0-9]', '', spaceless)
return specialless[:max_length]
uni = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii')
slug = re.sub('[\W_]', ' ', uni).strip().lower()
slug = re.sub('[-\s]+', '-', slug)
return slug[:max_length]
def slug(value):
if isinstance(value, orm.browse_record):
@ -147,7 +163,7 @@ class website(osv.osv):
_defaults = {
'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
def noop(self, *args, **kwargs):
pass

View File

@ -516,36 +516,3 @@ ul.oe_menu_editor .disclose {
filter: progid:DXImageTransform.Microsoft.Alpha(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:

View File

@ -1,10 +1,9 @@
(function () {
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.Tour.register({
openerp.Tour.register({
id: 'banner',
name: _t("Build a page"),
path: '/page/website.homepage',

View File

@ -1,535 +1,24 @@
(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 = window.openerp.website;
// 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);
window.openerp.website.EditorBar.include({
tours: [],
start: function () {
var self = this;
var menu = $('#help-menu');
_.each(window.openerp.Tour.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);
});
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;
menu.append($menuItem);
});
return this._super();
}
});
$.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.addons.base.ir import ir_qweb
from openerp.addons.website.models.ir_qweb import html_to_text
from openerp.addons.website.models.website import slugify
impl = getDOMImplementation()
document = impl.createDocument(None, None, None)
@ -238,3 +239,56 @@ class TestConvertBack(common.TransactionCase):
"New content",
"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')
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:

View File

@ -1,10 +1,9 @@
(function () {
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.Tour.register({
openerp.Tour.register({
id: 'blog',
name: _t("Create a blog post"),
steps: [

View File

@ -2,5 +2,5 @@ import openerp.tests
class TestUi(openerp.tests.HttpCase):
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 () {
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.Tour.register({
openerp.Tour.register({
id: 'event',
name: _t("Create an event"),
steps: [

View File

@ -2,5 +2,5 @@ import openerp.tests
class TestUi(openerp.tests.HttpCase):
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 () {
'use strict';
var website = openerp.website;
website.Tour.register({
openerp.Tour.register({
id: 'event_buy_tickets',
name: "Try to buy tickets for event",
path: '/event',

View File

@ -3,19 +3,19 @@ import os
import openerp.tests
inject = [
("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")),
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.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.post_install(True)
class TestUi(openerp.tests.HttpCase):
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):
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):
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.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.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.ace.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.translator.js"></script>

View File

@ -1,9 +1,6 @@
(function () {
'use strict';
var website = openerp.website;
website.Tour.register({
openerp.Tour.register({
id: 'shop_customize',
name: "Customize the page and search a product",
path: '/shop',
@ -37,7 +34,7 @@
]
});
website.Tour.register({
openerp.Tour.register({
id: 'shop_buy_product',
name: "Try to buy products",
path: '/shop',

View File

@ -1,10 +1,9 @@
(function () {
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.Tour.register({
openerp.Tour.register({
id: 'shop',
name: _t("Create a product"),
steps: [

View File

@ -3,22 +3,22 @@ import os
import openerp.tests
inject = [
("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")),
("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.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.post_install(True)
class TestUi(openerp.tests.HttpCase):
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):
self.phantom_js("/", "openerp.website.Tour.run('shop_customize', 'test')", "openerp.website.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_customize', 'test')", "openerp.Tour.tours.shop_customize", 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):
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):
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
def _authenticate(self, auth_method='user'):
if request.session.uid:
try:
request.session.check_security()
# what if error in security.check()
# -> res_users.check()
# -> res_users.check_credentials()
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
# All other exceptions mean undetermined status (e.g. connection pool full),
# let them bubble up
request.session.logout()
getattr(self, "_auth_method_%s" % auth_method)()
try:
if request.session.uid:
try:
request.session.check_security()
# what if error in security.check()
# -> res_users.check()
# -> res_users.check_credentials()
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
# All other exceptions mean undetermined status (e.g. connection pool full),
# let them bubble up
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
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}
}
#TODO remove me in master, not called anymore
def button_immediate_uninstall(self, cr, uid, ids, context=None):
"""
Uninstall the selected module(s) immediately and fully,

View File

@ -82,8 +82,7 @@
<div>
<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_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" states="installed" string="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_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)
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):
ir_module = self.pool.get('ir.module.module')

View File

@ -7,14 +7,15 @@
<field name="model">base.module.upgrade</field>
<field name="arch" type="xml">
<form string="System Update">
<div><label string="Your system will be updated."/></div>
<div><label string="Note that this operation might take a few minutes."/></div>
<separator string="Modules to Update"/>
<p>This module will trigger the uninstallation of below modules.</p>
<p><strong>This operation will permanently erase all data currently stored by the modules!</strong></p>
<p>If you wish to cancel the process, press the cancel button below</p>
<separator string="Impacted Modules"/>
<field name="module_info"/>
<footer>
<button name="upgrade_module" string="Update" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
<button string="Cancel" class="oe_link" name="upgrade_module_cancel" type="object"/>
</footer>
</form>
</field>

View File

@ -128,8 +128,7 @@ class res_partner_bank(osv.osv):
change_default=True, domain="[('country_id','=',country_id)]"),
'company_id': fields.many2one('res.company', 'Company',
ondelete='cascade', help="Only if this bank account belong to your company"),
'partner_id': fields.many2one('res.partner', 'Account Owner', required=True,
ondelete='cascade', select=True),
'partner_id': fields.many2one('res.partner', 'Account Owner', ondelete='cascade', select=True),
'state': fields.selection(_bank_type_get, 'Bank Account Type', required=True,
change_default=True),
'sequence': fields.integer('Sequence'),

View File

@ -28,6 +28,7 @@ from openerp.osv import osv, fields
from openerp.tools import ustr
from openerp.tools.translate import _
from openerp import exceptions
from lxml import etree
_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):
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):
""" 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
be used as response."""
self._failed = exception # prevent tx commit
if isinstance(exception, werkzeug.exceptions.HTTPException):
return exception
raise
def _call_function(self, *args, **kwargs):
@ -538,6 +536,15 @@ class HttpRequest(WebRequest):
params.pop('session_id', None)
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):
if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
headers = {

View File

@ -78,7 +78,7 @@ class Graph(dict):
)
## 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 k, v in additional_data[package.name].items():

View File

@ -384,11 +384,12 @@ class TestStream(object):
if self.r.match(s):
return
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:
c = '` ' + c
first = False
self.logger.info(c)
self.logger.log(level, c)
current_test = None

View File

@ -212,9 +212,9 @@ PSEUDOCONFIG_MAPPER = {
'debug': ['openerp:DEBUG'],
'debug_sql': ['openerp.sql_db:DEBUG'],
'info': [],
'warn': ['openerp:WARNING'],
'error': ['openerp:ERROR'],
'critical': ['openerp:CRITICAL'],
'warn': ['openerp:WARNING', 'werkzeug:WARNING'],
'error': ['openerp:ERROR', 'werkzeug:ERROR'],
'critical': ['openerp:CRITICAL', 'werkzeug:CRITICAL'],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -46,8 +46,15 @@ SLEEP_INTERVAL = 60 # 1 min
#----------------------------------------------------------
# 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
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
# 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
given by the environement, this is used by autoreload to keep the listen
socket open when a reload happens.

View File

@ -30,12 +30,11 @@ the ORM does, in fact.
from contextlib import contextmanager
from functools import wraps
import logging
import time
import uuid
import psycopg2.extras
import psycopg2.extensions
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_REPEATABLE_READ
from psycopg2.pool import PoolError
from psycopg2.psycopg1 import cursor as psycopg1cursor
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
@ -76,7 +75,7 @@ sql_counter = 0
class Cursor(object):
"""Represents an open transaction to the PostgreSQL DB backend,
acting as a lightweight wrapper around psycopg2's
``psycopg1cursor`` objects.
``cursor`` objects.
``Cursor`` is the object behind the ``cr`` variable used all
over the OpenERP code.
@ -175,7 +174,7 @@ class Cursor(object):
self._serialized = serialized
self._cnx = pool.borrow(dsn(dbname))
self._obj = self._cnx.cursor(cursor_factory=psycopg1cursor)
self._obj = self._cnx.cursor()
if self.sql_log:
self.__caller = frame_codeinfo(currentframe(),2)
else:
@ -188,6 +187,16 @@ class Cursor(object):
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):
if not self._closed and not self._cnx.closed:
# 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;
var start = new Date;
var start = new Date();
(function waitLoop() {
if(new Date - start > timeout) {
if(new Date() - start > timeout) {
console.log('error', timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms");
phantom.exit(1);
} else if (ready()) {
} else if (condition()) {
callback();
} else {
setTimeout(waitLoop, 250);
@ -44,7 +45,7 @@ function PhantomTest() {
}
return result.join('');
}));
msg.push('(leaf frame on top)')
msg.push('(leaf frame on top)');
}
console.log('error', JSON.stringify(msg.join('\n')));
phantom.exit(1);
@ -86,9 +87,9 @@ function PhantomTest() {
};
setTimeout(function () {
self.page.evaluate(function () {
var message = ("Timeout\nhref: " + window.location.href
+ "\nreferrer: " + document.referrer
+ "\n\n" + (document.body && document.body.innerHTML)).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*");
var message = ("Timeout\nhref: " + window.location.href +
"\nreferrer: " + document.referrer +
"\n\n" + (document.body && document.body.innerHTML)).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*");
console.log('error', message);
phantom.exit(1);
});
@ -107,6 +108,8 @@ function PhantomTest() {
url_path = "/login?" + qp.join('&');
}
var url = self.origin + url_path;
code = code || "true";
ready = ready || "true";
self.page.open(url, function(status) {
if (status !== 'success') {
console.log('error', "failed to load " + url);
@ -115,7 +118,7 @@ function PhantomTest() {
console.log('loaded', url, status);
// process ready
waitFor(function() {
console.log("PhantomTest.run: wait for condition: " + ready);
console.log("PhantomTest.run: wait for condition:", ready);
return self.page.evaluate(function (ready) {
var r = false;
try {