Merge remote-tracking branch 'odoo/master' into master-wmsstagingmig-jco

This commit is contained in:
Josse Colpaert 2014-06-23 10:06:42 +02:00
commit cf48492570
191 changed files with 1757 additions and 1382 deletions

View File

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

View File

@ -26,6 +26,9 @@ from openerp.report import report_sxw
class account_bank_statement(osv.osv): class account_bank_statement(osv.osv):
def create(self, cr, uid, vals, context=None): def create(self, cr, uid, vals, context=None):
if vals.get('name', '/') == '/':
journal_id = vals.get('journal_id', self._default_journal_id(cr, uid, context=context))
vals['name'] = self._compute_default_statement_name(cr, uid, journal_id, context=context)
if 'line_ids' in vals: if 'line_ids' in vals:
for idx, line in enumerate(vals['line_ids']): for idx, line in enumerate(vals['line_ids']):
line[2]['sequence'] = idx + 1 line[2]['sequence'] = idx + 1
@ -65,17 +68,14 @@ class account_bank_statement(osv.osv):
return periods[0] return periods[0]
return False return False
def _compute_default_statement_name(self, cr, uid, context=None): def _compute_default_statement_name(self, cr, uid, journal_id, context=None):
if context is None: if context is None:
context = {} context = {}
obj_seq = self.pool.get('ir.sequence') obj_seq = self.pool.get('ir.sequence')
default_journal_id = self._default_journal_id(cr, uid, context=context) period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context)
if default_journal_id != False: context['fiscalyear_id'] = period.fiscalyear_id.id
period = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, context=context), context=context) journal = self.pool.get('account.journal').browse(cr, uid, journal_id, None)
context['fiscalyear_id'] = period.fiscalyear_id.id return obj_seq.next_by_id(cr, uid, journal.sequence_id.id, context=context)
journal = self.pool.get('account.journal').browse(cr, uid, default_journal_id, None)
return obj_seq.next_by_id(cr, uid, journal.sequence_id.id, context=context)
return obj_seq.next_by_code(cr, uid, 'account.bank.statement', context=context)
def _currency(self, cursor, user, ids, name, args, context=None): def _currency(self, cursor, user, ids, name, args, context=None):
res = {} res = {}
@ -150,7 +150,7 @@ class account_bank_statement(osv.osv):
} }
_defaults = { _defaults = {
'name': _compute_default_statement_name, 'name': '/',
'date': fields.date.context_today, 'date': fields.date.context_today,
'state': 'draft', 'state': 'draft',
'journal_id': _default_journal_id, 'journal_id': _default_journal_id,
@ -213,11 +213,8 @@ class account_bank_statement(osv.osv):
def _get_counter_part_account(sefl, cr, uid, st_line, context=None): def _get_counter_part_account(sefl, cr, uid, st_line, context=None):
"""Retrieve the account to use in the counterpart move. """Retrieve the account to use in the counterpart move.
This method may be overridden to implement custom move generation (making sure to
call super() to establish a clean extension chain).
:param browse_record st_line: account.bank.statement.line record to :param browse_record st_line: account.bank.statement.line record to create the move from.
create the move from.
:return: int/long of the account.account to use as counterpart :return: int/long of the account.account to use as counterpart
""" """
if st_line.amount >= 0: if st_line.amount >= 0:
@ -226,26 +223,19 @@ class account_bank_statement(osv.osv):
def _get_counter_part_partner(sefl, cr, uid, st_line, context=None): def _get_counter_part_partner(sefl, cr, uid, st_line, context=None):
"""Retrieve the partner to use in the counterpart move. """Retrieve the partner to use in the counterpart move.
This method may be overridden to implement custom move generation (making sure to
call super() to establish a clean extension chain).
:param browse_record st_line: account.bank.statement.line record to :param browse_record st_line: account.bank.statement.line record to create the move from.
create the move from.
:return: int/long of the res.partner to use as counterpart :return: int/long of the res.partner to use as counterpart
""" """
return st_line.partner_id and st_line.partner_id.id or False return st_line.partner_id and st_line.partner_id.id or False
def _prepare_bank_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, context=None): def _prepare_bank_move_line(self, cr, uid, st_line, move_id, amount, company_currency_id, context=None):
"""Compute the args to build the dict of values to create the counter part move line from a """Compute the args to build the dict of values to create the counter part move line from a
statement line by calling the _prepare_move_line_vals. This method may be statement line by calling the _prepare_move_line_vals.
overridden to implement custom move generation (making sure to call super() to
establish a clean extension chain).
:param browse_record st_line: account.bank.statement.line record to :param browse_record st_line: account.bank.statement.line record to create the move from.
create the move from.
:param int/long move_id: ID of the account.move to link the move line :param int/long move_id: ID of the account.move to link the move line
:param float amount: amount of the move line :param float amount: amount of the move line
:param int/long account_id: ID of account to use as counter part
:param int/long company_currency_id: ID of currency of the concerned company :param int/long company_currency_id: ID of currency of the concerned company
:return: dict of value to create() the bank account.move.line :return: dict of value to create() the bank account.move.line
""" """
@ -258,7 +248,6 @@ class account_bank_statement(osv.osv):
if st_line.statement_id.currency.id != company_currency_id: if st_line.statement_id.currency.id != company_currency_id:
amt_cur = st_line.amount amt_cur = st_line.amount
cur_id = st_line.currency_id or st_line.statement_id.currency.id cur_id = st_line.currency_id or st_line.statement_id.currency.id
# TODO : FIXME the amount should be in the journal currency
if st_line.currency_id and st_line.amount_currency: if st_line.currency_id and st_line.amount_currency:
amt_cur = st_line.amount_currency amt_cur = st_line.amount_currency
cur_id = st_line.currency_id.id cur_id = st_line.currency_id.id
@ -269,9 +258,7 @@ class account_bank_statement(osv.osv):
def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id=False, def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id=False,
amount_currency=False, account_id=False, partner_id=False, context=None): amount_currency=False, account_id=False, partner_id=False, context=None):
"""Prepare the dict of values to create the move line from a """Prepare the dict of values to create the move line from a
statement line. All non-mandatory args will replace the default computed one. statement line.
This method may be overridden to implement custom move generation (making sure to
call super() to establish a clean extension chain).
:param browse_record st_line: account.bank.statement.line record to :param browse_record st_line: account.bank.statement.line record to
create the move from. create the move from.
@ -342,21 +329,29 @@ class account_bank_statement(osv.osv):
move_ids.append(st_line.journal_entry_id.id) move_ids.append(st_line.journal_entry_id.id)
self.pool.get('account.move').post(cr, uid, move_ids, context=context) self.pool.get('account.move').post(cr, uid, move_ids, context=context)
self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st.name,), context=context) self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st.name,), context=context)
self.link_bank_to_partner(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state':'confirm'}, context=context) return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
def button_cancel(self, cr, uid, ids, context=None): def button_cancel(self, cr, uid, ids, context=None):
done = []
account_move_obj = self.pool.get('account.move') account_move_obj = self.pool.get('account.move')
reconcile_pool = self.pool.get('account.move.reconcile')
move_line_pool = self.pool.get('account.move.line')
move_ids = []
for st in self.browse(cr, uid, ids, context=context): for st in self.browse(cr, uid, ids, context=context):
if st.state=='draft':
continue
move_ids = []
for line in st.line_ids: for line in st.line_ids:
move_ids += [x.id for x in line.move_ids] if line.journal_entry_id:
move_ids.append(line.journal_entry_id.id)
for aml in line.journal_entry_id.line_id:
if aml.reconcile_id:
move_lines = [l.id for l in aml.reconcile_id.line_id]
move_lines.remove(aml.id)
reconcile_pool.unlink(cr, uid, [aml.reconcile_id.id], context=context)
if len(move_lines) >= 2:
move_line_pool.reconcile_partial(cr, uid, move_lines, 'auto', context=context)
if move_ids:
account_move_obj.button_cancel(cr, uid, move_ids, context=context) account_move_obj.button_cancel(cr, uid, move_ids, context=context)
account_move_obj.unlink(cr, uid, move_ids, context) account_move_obj.unlink(cr, uid, move_ids, context)
done.append(st.id) return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
return self.write(cr, uid, done, {'state':'draft'}, context=context)
def _compute_balance_end_real(self, cr, uid, journal_id, context=None): def _compute_balance_end_real(self, cr, uid, journal_id, context=None):
res = False res = False
@ -417,18 +412,35 @@ class account_bank_statement(osv.osv):
def number_of_lines_reconciled(self, cr, uid, id, context=None): def number_of_lines_reconciled(self, cr, uid, id, context=None):
bsl_obj = self.pool.get('account.bank.statement.line') bsl_obj = self.pool.get('account.bank.statement.line')
return bsl_obj.search_count(cr, uid, [('statement_id','=',id), ('journal_entry_id','!=',False)], context=context) return bsl_obj.search_count(cr, uid, [('statement_id', '=', id), ('journal_entry_id', '!=', False)], context=context)
def get_format_currency_js_function(self, cr, uid, id, context=None): def get_format_currency_js_function(self, cr, uid, id, context=None):
""" Returns a string that can be used to instanciate a javascript function. """ Returns a string that can be used to instanciate a javascript function.
That function formats a number according to the statement's journal currency """ That function formats a number according to the statement line's currency or the statement currency"""
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
currency_obj = id and self.browse(cr, uid, id, context=context).journal_id.currency or company_currency st = id and self.browse(cr, uid, id, context=context)
if not st:
return
statement_currency = st.journal_id.currency or company_currency
digits = 2 # TODO : from currency_obj digits = 2 # TODO : from currency_obj
if currency_obj.position == 'after': function = ""
return "return amount.toFixed("+str(digits)+") + ' "+currency_obj.symbol+"';" done_currencies = []
elif currency_obj.position == 'before': for st_line in st.line_ids:
return "return '"+currency_obj.symbol+" ' + amount.toFixed("+str(digits)+");" st_line_currency = st_line.currency_id or statement_currency
if st_line_currency.id not in done_currencies:
if st_line_currency.position == 'after':
return_str = "return amount.toFixed(" + str(digits) + ") + ' " + st_line_currency.symbol + "';"
else:
return_str = "return '" + st_line_currency.symbol + " ' + amount.toFixed(" + str(digits) + ");"
function += "if (currency_id === " + str(st_line_currency.id) + "){ " + return_str + " }"
done_currencies.append(st_line_currency.id)
return function
def link_bank_to_partner(self, cr, uid, ids, context=None):
for statement in self.browse(cr, uid, ids, context=context):
for st_line in statement.line_ids:
if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id.id != st_line.partner_id.id:
self.pool.get('res.partner.bank').write(cr, uid, [st_line.bank_account_id.id], {'partner_id': st_line.partner_id.id}, context=context)
class account_bank_statement_line(osv.osv): class account_bank_statement_line(osv.osv):
@ -444,35 +456,40 @@ class account_bank_statement_line(osv.osv):
} }
for mv_line in reconciliation_data['reconciliation_proposition']: for mv_line in reconciliation_data['reconciliation_proposition']:
mv_line_ids_selected.append(mv_line['id']) mv_line_ids_selected.append(mv_line['id'])
ret.append(reconciliation_data); ret.append(reconciliation_data)
# Check if, now that 'candidate' move lines were selected, there are moves left for statement lines # Check if, now that 'candidate' move lines were selected, there are moves left for statement lines
for reconciliation_data in ret: #for reconciliation_data in ret:
if not reconciliation_data['st_line']['has_no_partner']: # if not reconciliation_data['st_line']['has_no_partner']:
if self.get_move_lines_counterparts(cr, uid, reconciliation_data['st_line']['id'], excluded_ids=mv_line_ids_selected, count=True, context=context) == 0: # st_line = self.browse(cr, uid, reconciliation_data['st_line']['id'], context=context)
reconciliation_data['st_line']['no_match'] = True # if not self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=mv_line_ids_selected, count=True, context=context):
# reconciliation_data['st_line']['no_match'] = True
return ret return ret
def get_statement_line_for_reconciliation(self, cr, uid, id, context=None): def get_statement_line_for_reconciliation(self, cr, uid, id, context=None):
""" Returns the data required by the bank statement reconciliation use case """ """ Returns the data required by the bank statement reconciliation use case """
line = self.browse(cr, uid, id, context=context) line = self.browse(cr, uid, id, context=context)
statement_currency = line.journal_id.currency or line.journal_id.company_id.currency_id statement_currency = line.journal_id.currency or line.journal_id.company_id.currency_id
amount = line.amount
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_widget', context=context) rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_widget', context=context)
amount_str = line.amount > 0 and line.amount or -line.amount amount_str = line.amount > 0 and line.amount or -line.amount
amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency) amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency)
amount_currency_str = "" amount_currency_str = ""
if line.amount_currency and line.currency_id: if line.amount_currency and line.currency_id:
amount_currency_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id) amount_currency_str = amount_str
amount_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
amount = line.amount_currency
dict = { data = {
'id': line.id, 'id': line.id,
'ref': line.ref, 'ref': line.ref,
'note': line.note or "", 'note': line.note or "",
'name': line.name, 'name': line.name,
'date': line.date, 'date': line.date,
'amount': line.amount, 'amount': amount,
'amount_str': amount_str, 'amount_str': amount_str,
'no_match': self.get_move_lines_counterparts(cr, uid, id, count=True, context=context) == 0 and line.partner_id.id, 'currency_id': line.currency_id.id or statement_currency.id,
'no_match': self.get_move_lines_counterparts(cr, uid, line, count=True, context=context) == 0,
'partner_id': line.partner_id.id, 'partner_id': line.partner_id.id,
'statement_id': line.statement_id.id, 'statement_id': line.statement_id.id,
'account_code': line.journal_id.default_debit_account_id.code, 'account_code': line.journal_id.default_debit_account_id.code,
@ -482,93 +499,116 @@ class account_bank_statement_line(osv.osv):
'has_no_partner': not line.partner_id.id, 'has_no_partner': not line.partner_id.id,
} }
if line.partner_id.id: if line.partner_id.id:
if line.amount > 0: data['open_balance_account_id'] = line.partner_id.property_account_payable.id
dict['open_balance_account_id'] = line.partner_id.property_account_receivable.id if amount > 0:
else: data['open_balance_account_id'] = line.partner_id.property_account_receivable.id
dict['open_balance_account_id'] = line.partner_id.property_account_payable.id return data
return dict
def search_structured_com(self, cr, uid, st_line, context=None):
if not st_line.ref:
return
domain = [('ref', '=', st_line.ref)]
if st_line.partner_id:
domain += [('partner_id', '=', st_line.partner_id.id)]
ids = self.pool.get('account.move.line').search(cr, uid, domain, limit=1, context=context)
return ids and ids[0] or False
def get_reconciliation_proposition(self, cr, uid, id, excluded_ids=[], context=None): def get_reconciliation_proposition(self, cr, uid, id, excluded_ids=[], context=None):
""" Returns move lines that constitute the best guess to reconcile a statement line. """ """ Returns move lines that constitute the best guess to reconcile a statement line. """
st_line = self.browse(cr, uid, id, context=context) st_line = self.browse(cr, uid, id, context=context)
company_currency = st_line.journal_id.company_id.currency_id.id company_currency = st_line.journal_id.company_id.currency_id.id
statement_currency = st_line.journal_id.currency.id or company_currency statement_currency = st_line.journal_id.currency.id or company_currency
# either use the unsigned debit/credit fields or the signed amount_currency field # either use the unsigned debit/credit fields or the signed amount_currency field
sign = 1 sign = 1
if statement_currency == company_currency: if statement_currency == company_currency:
amount_field = 'credit'
if st_line.amount > 0: if st_line.amount > 0:
amount_field = 'debit' amount_field = 'debit'
else:
amount_field = 'credit'
else: else:
amount_field = 'amount_currency' amount_field = 'amount_currency'
if st_line.amount < 0: if st_line.amount < 0:
sign = -1 sign = -1
# look for structured communication
exact_match_id = self.search_structured_com(cr, uid, st_line, context=context)
if exact_match_id:
return self.make_counter_part_lines(cr, uid, st_line, [exact_match_id], count=False, context=context)
#we don't propose anything if there is no partner detected
if not st_line.partner_id.id:
return []
# look for exact match # look for exact match
exact_match_id = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=1, additional_domain=[(amount_field,'=',(sign*st_line.amount))]) exact_match_id = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=1, additional_domain=[(amount_field, '=', (sign * st_line.amount))])
if exact_match_id: if exact_match_id:
return exact_match_id return exact_match_id
# select oldest move lines # select oldest move lines
if sign == -1: if sign == -1:
mv_lines = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field,'<',0)]) mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '<', 0)])
else: else:
mv_lines = self.get_move_lines_counterparts(cr, uid, id, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field,'>',0)]) mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '>', 0)])
ret = [] ret = []
total = 0 total = 0
# get_move_lines_counterparts inverts debit and credit # get_move_lines_counterparts inverts debit and credit
amount_field = 'debit' if amount_field == 'credit' else 'credit' amount_field = 'debit' if amount_field == 'credit' else 'credit'
for line in mv_lines: for line in mv_lines:
if total + line[amount_field] <= st_line.amount: if total + line[amount_field] <= abs(st_line.amount):
ret.append(line) ret.append(line)
total += line[amount_field] total += line[amount_field]
else: if total >= abs(st_line.amount):
break break
return ret return ret
def get_move_lines_counterparts(self, cr, uid, id, excluded_ids=[], str="", offset=0, limit=None, count=False, additional_domain=[], context=None): def get_move_lines_counterparts_id(self, cr, uid, st_line_id, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None):
st_line = self.browse(cr, uid, st_line_id, context=context)
return self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids, filter_str, offset, limit, count, additional_domain, context=context)
def get_move_lines_counterparts(self, cr, uid, st_line, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None):
""" Find the move lines that could be used to reconcile a statement line and returns the counterpart that could be created to reconcile them """ Find the move lines that could be used to reconcile a statement line and returns the counterpart that could be created to reconcile them
If count is true, only returns the count. If count is true, only returns the count.
:param integer id: the id of the statement line :param st_line: the browse record of the statement line
:param integers list excluded_ids: ids of move lines that should not be fetched :param integers list excluded_ids: ids of move lines that should not be fetched
:param string str: string to filter lines :param string filter_str: string to filter lines
:param integer offset: offset of the request :param integer offset: offset of the request
:param integer limit: number of lines to fetch :param integer limit: number of lines to fetch
:param boolean count: just return the number of records :param boolean count: just return the number of records
:param tuples list domain: additional domain restrictions :param tuples list domain: additional domain restrictions
""" """
if context is None:
context = {}
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_counterpart_widget', context=context)
st_line = self.browse(cr, uid, id, context=context)
company_currency = st_line.journal_id.company_id.currency_id
statement_currency = st_line.journal_id.currency or company_currency
mv_line_pool = self.pool.get('account.move.line') mv_line_pool = self.pool.get('account.move.line')
currency_obj = self.pool.get('res.currency')
domain = additional_domain + [ domain = additional_domain + [
('partner_id', '=', st_line.partner_id.id),
('reconcile_id', '=', False), ('reconcile_id', '=', False),
('state','=','valid'), ('state', '=', 'valid'),
'|',('account_id.type', '=', 'receivable'),
('account_id.type', '=', 'payable'), #Let the front-end warn the user if he tries to mix payable and receivable in the same reconciliation
] ]
if st_line.partner_id.id:
domain += [('partner_id', '=', st_line.partner_id.id),
'|', ('account_id.type', '=', 'receivable'),
('account_id.type', '=', 'payable')]
else:
domain += [('account_id.reconcile', '=', True)]
#domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')]
if excluded_ids: if excluded_ids:
domain.append(('id', 'not in', excluded_ids)) domain.append(('id', 'not in', excluded_ids))
if str: if filter_str:
domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)] if not st_line.partner_id:
domain += [ '|', ('partner_id.name', 'ilike', filter_str)]
domain += ['|', ('move_id.name', 'ilike', filter_str), ('move_id.ref', 'ilike', filter_str)]
line_ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context)
return self.make_counter_part_lines(cr, uid, st_line, line_ids, count=count, context=context)
def make_counter_part_lines(self, cr, uid, st_line, line_ids, count=False, context=None):
if context is None:
context = {}
mv_line_pool = self.pool.get('account.move.line')
currency_obj = self.pool.get('res.currency')
company_currency = st_line.journal_id.company_id.currency_id
statement_currency = st_line.journal_id.currency or company_currency
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_counterpart_widget', context=context)
#partially reconciled lines can be displayed only once #partially reconciled lines can be displayed only once
reconcile_partial_ids = [] reconcile_partial_ids = []
ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context)
if count: if count:
nb_lines = 0 nb_lines = 0
for line in mv_line_pool.browse(cr, uid, ids, context=context): for line in mv_line_pool.browse(cr, uid, line_ids, context=context):
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
continue continue
nb_lines += 1 nb_lines += 1
@ -577,7 +617,7 @@ class account_bank_statement_line(osv.osv):
return nb_lines return nb_lines
else: else:
ret = [] ret = []
for line in mv_line_pool.browse(cr, uid, ids, context=context): for line in mv_line_pool.browse(cr, uid, line_ids, context=context):
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
continue continue
amount_currency_str = "" amount_currency_str = ""
@ -595,8 +635,12 @@ class account_bank_statement_line(osv.osv):
'period_name': line.period_id.name, 'period_name': line.period_id.name,
'journal_name': line.journal_id.name, 'journal_name': line.journal_id.name,
'amount_currency_str': amount_currency_str, 'amount_currency_str': amount_currency_str,
'partner_id': line.partner_id.id,
'partner_name': line.partner_id.name,
'has_no_partner': not bool(st_line.partner_id.id),
} }
if statement_currency.id != company_currency.id and line.currency_id and line.currency_id.id == statement_currency.id: st_line_currency = st_line.currency_id or statement_currency
if st_line.currency_id and line.currency_id and line.currency_id.id == st_line.currency_id.id:
if line.amount_residual_currency < 0: if line.amount_residual_currency < 0:
ret_line['debit'] = 0 ret_line['debit'] = 0
ret_line['credit'] = -line.amount_residual_currency ret_line['credit'] = -line.amount_residual_currency
@ -613,21 +657,46 @@ class account_bank_statement_line(osv.osv):
ret_line['credit'] = line.amount_residual if line.debit != 0 else 0 ret_line['credit'] = line.amount_residual if line.debit != 0 else 0
ctx = context.copy() ctx = context.copy()
ctx.update({'date': st_line.date}) ctx.update({'date': st_line.date})
ret_line['debit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, ret_line['debit'], context=ctx) ret_line['debit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['debit'], context=ctx)
ret_line['credit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, ret_line['credit'], context=ctx) ret_line['credit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['credit'], context=ctx)
ret_line['debit_str'] = rml_parser.formatLang(ret_line['debit'], currency_obj=statement_currency) ret_line['debit_str'] = rml_parser.formatLang(ret_line['debit'], currency_obj=st_line_currency)
ret_line['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=statement_currency) ret_line['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=st_line_currency)
ret.append(ret_line) ret.append(ret_line)
if line.reconcile_partial_id: if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id) reconcile_partial_ids.append(line.reconcile_partial_id.id)
return ret return ret
def get_currency_rate_line(self, cr, uid, st_line, currency_diff, move_id, context=None):
if currency_diff < 0:
account_id = st_line.company_id.expense_currency_exchange_account_id.id
if not account_id:
raise osv.except_osv(_('Insufficient Configuration!'), _("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
else:
account_id = st_line.company_id.income_currency_exchange_account_id.id
if not account_id:
raise osv.except_osv(_('Insufficient Configuration!'), _("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
return {
'move_id': move_id,
'name': _('change') + ': ' + (st_line.name or '/'),
'period_id': st_line.statement_id.period_id.id,
'journal_id': st_line.journal_id.id,
'partner_id': st_line.partner_id.id,
'company_id': st_line.company_id.id,
'statement_id': st_line.statement_id.id,
'debit': currency_diff < 0 and -currency_diff or 0,
'credit': currency_diff > 0 and currency_diff or 0,
'date': st_line.date,
'account_id': account_id
}
def process_reconciliation(self, cr, uid, id, mv_line_dicts, context=None): def process_reconciliation(self, cr, uid, id, mv_line_dicts, context=None):
""" Creates a move line for each item of mv_line_dicts and for the statement line. Reconcile a new move line with its counterpart_move_line_id if specified. Finally, mark the statement line as reconciled by putting the newly created move id in the column journal_entry_id. """ Creates a move line for each item of mv_line_dicts and for the statement line. Reconcile a new move line with its counterpart_move_line_id if specified. Finally, mark the statement line as reconciled by putting the newly created move id in the column journal_entry_id.
:param int id: id of the bank statement line :param int id: id of the bank statement line
:param list of dicts mv_line_dicts: move lines to create. If counterpart_move_line_id is specified, reconcile with it :param list of dicts mv_line_dicts: move lines to create. If counterpart_move_line_id is specified, reconcile with it
""" """
if context is None:
context = {}
st_line = self.browse(cr, uid, id, context=context) st_line = self.browse(cr, uid, id, context=context)
company_currency = st_line.journal_id.company_id.currency_id company_currency = st_line.journal_id.company_id.currency_id
statement_currency = st_line.journal_id.currency or company_currency statement_currency = st_line.journal_id.currency or company_currency
@ -637,7 +706,7 @@ class account_bank_statement_line(osv.osv):
currency_obj = self.pool.get('res.currency') currency_obj = self.pool.get('res.currency')
# Checks # Checks
if st_line.journal_entry_id.id != False: if st_line.journal_entry_id.id:
raise osv.except_osv(_('Error!'), _('The bank statement line was already reconciled.')) raise osv.except_osv(_('Error!'), _('The bank statement line was already reconciled.'))
for mv_line_dict in mv_line_dicts: for mv_line_dict in mv_line_dicts:
for field in ['debit', 'credit', 'amount_currency']: for field in ['debit', 'credit', 'amount_currency']:
@ -657,37 +726,50 @@ class account_bank_statement_line(osv.osv):
amount = currency_obj.compute(cr, uid, st_line.statement_id.currency.id, company_currency.id, st_line.amount, context=context) amount = currency_obj.compute(cr, uid, st_line.statement_id.currency.id, company_currency.id, st_line.amount, context=context)
bank_st_move_vals = bs_obj._prepare_bank_move_line(cr, uid, st_line, move_id, amount, company_currency.id, context=context) bank_st_move_vals = bs_obj._prepare_bank_move_line(cr, uid, st_line, move_id, amount, company_currency.id, context=context)
aml_obj.create(cr, uid, bank_st_move_vals, context=context) aml_obj.create(cr, uid, bank_st_move_vals, context=context)
st_line_currency_rate = bank_st_move_vals['amount_currency'] and statement_currency.id == company_currency.id and (bank_st_move_vals['amount_currency'] / st_line.amount) or False
st_line_currency = bank_st_move_vals['currency_id']
# Complete the dicts # Complete the dicts
st_line_statement_id = st_line.statement_id.id st_line_currency = st_line.currency_id or statement_currency
st_line_journal_id = st_line.journal_id.id st_line_currency_rate = st_line.currency_id and statement_currency.id == company_currency.id and (st_line.amount_currency / st_line.amount) or False
st_line_partner_id = st_line.partner_id.id to_create = []
st_line_company_id = st_line.company_id.id
st_line_period_id = st_line.statement_id.period_id.id
for mv_line_dict in mv_line_dicts: for mv_line_dict in mv_line_dicts:
mv_line_dict['ref'] = move_name mv_line_dict['ref'] = move_name
mv_line_dict['move_id'] = move_id mv_line_dict['move_id'] = move_id
mv_line_dict['period_id'] = st_line_period_id mv_line_dict['period_id'] = st_line.statement_id.period_id.id
mv_line_dict['journal_id'] = st_line_journal_id mv_line_dict['journal_id'] = st_line.journal_id.id
mv_line_dict['partner_id'] = st_line_partner_id mv_line_dict['company_id'] = st_line.company_id.id
mv_line_dict['company_id'] = st_line_company_id mv_line_dict['statement_id'] = st_line.statement_id.id
mv_line_dict['statement_id'] = st_line_statement_id
if mv_line_dict.get('counterpart_move_line_id'): if mv_line_dict.get('counterpart_move_line_id'):
mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context) mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context)
mv_line_dict['account_id'] = mv_line.account_id.id mv_line_dict['account_id'] = mv_line.account_id.id
if statement_currency.id != company_currency.id: if st_line_currency.id != company_currency.id:
mv_line_dict['amount_currency'] = mv_line_dict['debit'] - mv_line_dict['credit'] mv_line_dict['amount_currency'] = mv_line_dict['debit'] - mv_line_dict['credit']
mv_line_dict['currency_id'] = statement_currency.id mv_line_dict['currency_id'] = st_line_currency.id
mv_line_dict['debit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['debit']) if st_line.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
mv_line_dict['credit'] = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['credit']) debit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['debit'] / st_line_currency_rate)
elif st_line_currency and st_line_currency_rate: credit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['credit'] / st_line_currency_rate)
mv_line_dict['amount_currency'] = self.pool.get('res.currency').round(cr, uid, st_line.currency_id, (mv_line_dict['debit'] - mv_line_dict['credit']) * st_line_currency_rate) else:
mv_line_dict['currency_id'] = st_line_currency debit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=context)
credit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=context)
if mv_line_dict.get('counterpart_move_line_id'):
#post an account line that use the same currency rate than the counterpart (to balance the account) and post the difference in another line
ctx = context.copy()
ctx['date'] = mv_line.date
debit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=ctx)
credit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx)
mv_line_dict['credit'] = credit_at_old_rate
mv_line_dict['debit'] = debit_at_old_rate
if debit_at_old_rate - debit_at_current_rate:
currency_diff = debit_at_current_rate - debit_at_old_rate
to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context))
if credit_at_old_rate - credit_at_current_rate:
currency_diff = credit_at_current_rate - credit_at_old_rate
to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context))
else:
mv_line_dict['debit'] = debit_at_current_rate
mv_line_dict['credit'] = credit_at_current_rate
to_create.append(mv_line_dict)
# Create move lines # Create move lines
move_line_pairs_to_reconcile = [] move_line_pairs_to_reconcile = []
for mv_line_dict in mv_line_dicts: for mv_line_dict in to_create:
counterpart_move_line_id = None # NB : this attribute is irrelevant for aml_obj.create() and needs to be removed from the dict counterpart_move_line_id = None # NB : this attribute is irrelevant for aml_obj.create() and needs to be removed from the dict
if mv_line_dict.get('counterpart_move_line_id'): if mv_line_dict.get('counterpart_move_line_id'):
counterpart_move_line_id = mv_line_dict['counterpart_move_line_id'] counterpart_move_line_id = mv_line_dict['counterpart_move_line_id']
@ -723,7 +805,7 @@ class account_bank_statement_line(osv.osv):
'bank_account_id': fields.many2one('res.partner.bank','Bank Account'), 'bank_account_id': fields.many2one('res.partner.bank','Bank Account'),
'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='cascade'), 'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='cascade'),
'journal_id': fields.related('statement_id', 'journal_id', type='many2one', relation='account.journal', string='Journal', store=True, readonly=True), 'journal_id': fields.related('statement_id', 'journal_id', type='many2one', relation='account.journal', string='Journal', store=True, readonly=True),
'ref': fields.char('Reference', size=32), 'ref': fields.char('Structured Communication'),
'note': fields.text('Notes'), 'note': fields.text('Notes'),
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of bank statement lines."), 'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of bank statement lines."),
'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True), 'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),

View File

@ -295,7 +295,8 @@ class account_invoice(osv.osv):
}, },
multi='all'), multi='all'),
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}), 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]},
domain="[('type', 'in', {'out_invoice': ['sale'], 'out_refund': ['sale_refund'], 'in_refund': ['purchase_refund'], 'in_invoice': ['purchase']}.get(type, [])), ('company_id', '=', company_id)]"),
'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}), 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}), 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean', 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean',

View File

@ -127,8 +127,8 @@ class account_move_line(osv.osv):
if move_line.reconcile_id: if move_line.reconcile_id:
continue continue
if not move_line.account_id.type in ('payable', 'receivable'): if not move_line.account_id.reconcile:
#this function does not suport to be used on move lines not related to payable or receivable accounts #this function does not suport to be used on move lines not related to a reconcilable account
continue continue
if move_line.currency_id: if move_line.currency_id:
@ -741,6 +741,8 @@ class account_move_line(osv.osv):
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
if context is None: if context is None:
context = {} context = {}
if context.get('fiscalyear'):
args.append(('period_id.fiscalyear_id', '=', context.get('fiscalyear', False)))
if context and context.get('next_partner_only', False): if context and context.get('next_partner_only', False):
if not context.get('partner_id', False): if not context.get('partner_id', False):
partner = self.list_partners_to_reconcile(cr, uid, context=context) partner = self.list_partners_to_reconcile(cr, uid, context=context)
@ -823,7 +825,7 @@ class account_move_line(osv.osv):
'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge) 'line_partial_ids': map(lambda x: (4,x,False), merges+unmerge)
}, context=context) }, context=context)
move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context) move_rec_obj.reconcile_partial_check(cr, uid, [r_id] + merges_rec, context=context)
return True return r_id
def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context=None): def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context=None):
account_obj = self.pool.get('account.account') account_obj = self.pool.get('account.account')

View File

@ -351,7 +351,7 @@
<act_window <act_window
id="action_account_items" id="action_account_items"
name="Journal Items" name="Journal Items"
context="{'search_default_account_id': [active_id]}" context="{'search_default_account_id': [active_id], 'fiscalyear': context.get('fiscalyear')}"
res_model="account.move.line" res_model="account.move.line"
src_model="account.account" src_model="account.account"
key2="tree_but_open"/> key2="tree_but_open"/>

View File

@ -15,7 +15,7 @@
<field name="balance_end_real" eval="3707.58"/> <field name="balance_end_real" eval="3707.58"/>
</record> </record>
<record id="demo_bank_statement_line_1" model="account.bank.statement.line"> <record id="demo_bank_statement_line_1" model="account.bank.statement.line">
<field name="ref">001</field> <field name="ref"></field>
<field name="statement_id" ref="demo_bank_statement_1"/> <field name="statement_id" ref="demo_bank_statement_1"/>
<field name="sequence" eval="1"/> <field name="sequence" eval="1"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>
@ -26,7 +26,7 @@
<field name="partner_id" ref="base.res_partner_9"/> <field name="partner_id" ref="base.res_partner_9"/>
</record> </record>
<record id="demo_bank_statement_line_2" model="account.bank.statement.line"> <record id="demo_bank_statement_line_2" model="account.bank.statement.line">
<field name="ref">002</field> <field name="ref">SAJ2014002</field>
<field name="statement_id" ref="demo_bank_statement_1"/> <field name="statement_id" ref="demo_bank_statement_1"/>
<field name="sequence" eval="2"/> <field name="sequence" eval="2"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>
@ -37,7 +37,7 @@
<field name="partner_id" ref="base.res_partner_9"/> <field name="partner_id" ref="base.res_partner_9"/>
</record> </record>
<record id="demo_bank_statement_line_3" model="account.bank.statement.line"> <record id="demo_bank_statement_line_3" model="account.bank.statement.line">
<field name="ref">003</field> <field name="ref"></field>
<field name="statement_id" ref="demo_bank_statement_1"/> <field name="statement_id" ref="demo_bank_statement_1"/>
<field name="sequence" eval="3"/> <field name="sequence" eval="3"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>
@ -47,7 +47,7 @@
<field name="date" eval="time.strftime('%Y')+'-01-01'"/> <field name="date" eval="time.strftime('%Y')+'-01-01'"/>
</record> </record>
<record id="demo_bank_statement_line_4" model="account.bank.statement.line"> <record id="demo_bank_statement_line_4" model="account.bank.statement.line">
<field name="ref">004</field> <field name="ref"></field>
<field name="statement_id" ref="demo_bank_statement_1"/> <field name="statement_id" ref="demo_bank_statement_1"/>
<field name="sequence" eval="4"/> <field name="sequence" eval="4"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>

View File

@ -120,8 +120,6 @@
cursor: pointer; } cursor: pointer; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_match:not(.no_partner) .toggle_match { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_match:not(.no_partner) .toggle_match {
visibility: hidden !important; } visibility: hidden !important; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_partner .partner_name, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line.no_partner .line_open_balance {
display: none !important; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line > table > tbody > tr:nth-child(1) > td table { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line > table > tbody > tr:nth-child(1) > td table {
margin-bottom: 10px; } margin-bottom: 10px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line table.details td:first-child { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line table.details td:first-child {
@ -202,10 +200,6 @@
cursor: pointer; } cursor: pointer; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6) {
border-left: 1px solid black; } border-left: 1px solid black; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.initial_line > td:nth-child(5) {
border-top: 1px solid black; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.initial_line > td:nth-child(6) {
border-top: 1px solid black; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls {
padding: 0 0 5px 18px; } padding: 0 0 5px 18px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls .filter { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls .filter {

View File

@ -194,12 +194,6 @@ $initialLineBackground: #f0f0f0;
} }
} }
&.no_partner {
.partner_name, .line_open_balance {
display: none !important;
}
}
/* gap between accounting_view and action view */ /* gap between accounting_view and action view */
> table > tbody > tr:nth-child(1) > td table { > table > tbody > tr:nth-child(1) > td table {
margin-bottom: 10px; margin-bottom: 10px;
@ -341,10 +335,6 @@ $initialLineBackground: #f0f0f0;
// accounting "T" // accounting "T"
td:nth-child(6) { border-left: $accountingBorder; } td:nth-child(6) { border-left: $accountingBorder; }
tr.initial_line > td {
&:nth-child(5) { border-top: $accountingBorder; }
&:nth-child(6) { border-top: $accountingBorder; }
}
} }

View File

@ -171,7 +171,7 @@ openerp.account = function (instance) {
deferred_promises.push(self.model_bank_statement deferred_promises.push(self.model_bank_statement
.call("get_format_currency_js_function", [self.statement_id]) .call("get_format_currency_js_function", [self.statement_id])
.then(function(data){ .then(function(data){
self.formatCurrency = new Function("amount", data); self.formatCurrency = new Function("amount, currency_id", data);
}) })
); );
@ -245,7 +245,7 @@ openerp.account = function (instance) {
keyboardShortcutsHandler: function(e) { keyboardShortcutsHandler: function(e) {
var self = this; var self = this;
if (e.which === 13 && (e.ctrlKey || e.metaKey)) { if ((e.which === 13 || e.which === 10) && (e.ctrlKey || e.metaKey)) {
$.each(self.getChildren(), function(i, o){ $.each(self.getChildren(), function(i, o){
if (o.is_valid && o.persistAndDestroy()) { if (o.is_valid && o.persistAndDestroy()) {
self.lines_reconciled_with_ctrl_enter++; self.lines_reconciled_with_ctrl_enter++;
@ -789,6 +789,9 @@ openerp.account = function (instance) {
line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : ""); line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : "");
line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line}); line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line});
line.q_label = line.name; line.q_label = line.name;
if (line.has_no_partner){
line.q_label = line.partner_name + ': ' +line.q_label;
}
// WARNING : pretty much of a ugly hack // WARNING : pretty much of a ugly hack
// The value of account_move.ref is either the move's communication or it's name without the slashes // The value of account_move.ref is either the move's communication or it's name without the slashes
@ -981,6 +984,7 @@ openerp.account = function (instance) {
lineOpenBalanceClickHandler: function() { lineOpenBalanceClickHandler: function() {
var self = this; var self = this;
if (self.get("mode") === "create") { if (self.get("mode") === "create") {
self.addLineBeingEdited();
self.set("mode", "match"); self.set("mode", "match");
} else { } else {
self.set("mode", "create"); self.set("mode", "create");
@ -1038,7 +1042,8 @@ openerp.account = function (instance) {
var slice_start = self.get("pager_index") * self.max_move_lines_displayed; var slice_start = self.get("pager_index") * self.max_move_lines_displayed;
var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed; var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed;
_( _.filter(self.mv_lines_deselected, function(o){ _( _.filter(self.mv_lines_deselected, function(o){
return o.name.indexOf(self.filter) !== -1 || o.ref.indexOf(self.filter) !== -1 }) return o.q_label.indexOf(self.filter) !== -1 || (o.ref && o.ref.indexOf(self.filter) !== -1)
})
.slice(slice_start, slice_end)).each(function(line){ .slice(slice_start, slice_end)).each(function(line){
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false})); var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
self.bindPopoverTo($line.find(".line_info_button")); self.bindPopoverTo($line.find(".line_info_button"));
@ -1057,7 +1062,6 @@ openerp.account = function (instance) {
updatePagerControls: function() { updatePagerControls: function() {
var self = this; var self = this;
if (self.get("pager_index") === 0) if (self.get("pager_index") === 0)
self.$(".pager_control_left").addClass("disabled"); self.$(".pager_control_left").addClass("disabled");
else else
@ -1075,7 +1079,7 @@ openerp.account = function (instance) {
balanceChanged: function() { balanceChanged: function() {
var self = this; var self = this;
var balance = self.get("balance"); var balance = self.get("balance");
self.$(".tbody_open_balance").empty();
// Special case hack : no identified partner // Special case hack : no identified partner
if (self.st_line.has_no_partner) { if (self.st_line.has_no_partner) {
if (Math.abs(balance).toFixed(3) === "0.000") { if (Math.abs(balance).toFixed(3) === "0.000") {
@ -1088,19 +1092,23 @@ openerp.account = function (instance) {
self.$(".button_ok").attr("disabled", "disabled"); self.$(".button_ok").attr("disabled", "disabled");
self.$(".button_ok").text("OK"); self.$(".button_ok").text("OK");
self.is_valid = false; self.is_valid = false;
var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : "");
var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : "");
var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]}));
$line.find('.js_open_balance')[0].innerHTML = "Choose counterpart";
self.$(".tbody_open_balance").append($line);
} }
return; return;
} }
self.$(".tbody_open_balance").empty();
if (Math.abs(balance).toFixed(3) === "0.000") { if (Math.abs(balance).toFixed(3) === "0.000") {
self.$(".button_ok").addClass("oe_highlight"); self.$(".button_ok").addClass("oe_highlight");
self.$(".button_ok").text("OK"); self.$(".button_ok").text("OK");
} else { } else {
self.$(".button_ok").removeClass("oe_highlight"); self.$(".button_ok").removeClass("oe_highlight");
self.$(".button_ok").text("Keep open"); self.$(".button_ok").text("Keep open");
var debit = (balance > 0 ? self.formatCurrency(balance) : ""); var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : "");
var credit = (balance < 0 ? self.formatCurrency(-1*balance) : ""); var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : "");
var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]})); var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]}));
self.$(".tbody_open_balance").append($line); self.$(".tbody_open_balance").append($line);
} }
@ -1111,21 +1119,15 @@ openerp.account = function (instance) {
self.$(".action_pane.active").removeClass("active"); self.$(".action_pane.active").removeClass("active");
// Special case hack : if no_partner, either inactive or create // Special case hack : if no_partner and mode == inactive
if (self.st_line.has_no_partner) { if (self.st_line.has_no_partner) {
if (self.get("mode") === "inactive") { if (self.get("mode") === "inactive") {
self.$(".match").slideUp(self.animation_speed); self.$(".match").slideUp(self.animation_speed);
self.$(".create").slideUp(self.animation_speed); self.$(".create").slideUp(self.animation_speed);
self.$(".toggle_match").removeClass("visible_toggle"); self.$(".toggle_match").removeClass("visible_toggle");
self.el.dataset.mode = "inactive"; self.el.dataset.mode = "inactive";
} else { return;
self.initializeCreateForm(); }
self.$(".match").slideUp(self.animation_speed);
self.$(".create").slideDown(self.animation_speed);
self.$(".toggle_match").addClass("visible_toggle");
self.el.dataset.mode = "create";
}
return;
} }
if (self.get("mode") === "inactive") { if (self.get("mode") === "inactive") {
@ -1198,6 +1200,8 @@ openerp.account = function (instance) {
var self = this; var self = this;
var line_created_being_edited = self.get("line_created_being_edited"); var line_created_being_edited = self.get("line_created_being_edited");
line_created_being_edited[0][elt.corresponding_property] = val.newValue; line_created_being_edited[0][elt.corresponding_property] = val.newValue;
line_created_being_edited[0].currency_id = self.st_line.currency_id;
// Specific cases // Specific cases
if (elt === self.account_id_field) if (elt === self.account_id_field)
@ -1215,7 +1219,7 @@ openerp.account = function (instance) {
var tax = data.taxes[0]; var tax = data.taxes[0];
var tax_account_id = (amount > 0 ? tax.account_collected_id : tax.account_paid_id) var tax_account_id = (amount > 0 ? tax.account_collected_id : tax.account_paid_id)
line_created_being_edited[0].amount = (data.total.toFixed(3) === amount.toFixed(3) ? amount : data.total); line_created_being_edited[0].amount = (data.total.toFixed(3) === amount.toFixed(3) ? amount : data.total);
line_created_being_edited[1] = {id: line_created_being_edited[0].id, account_id: tax_account_id, account_num: self.map_account_id_code[tax_account_id], label: tax.name, amount: tax.amount, no_remove_action: true}; line_created_being_edited[1] = {id: line_created_being_edited[0].id, account_id: tax_account_id, account_num: self.map_account_id_code[tax_account_id], label: tax.name, amount: tax.amount, no_remove_action: true, currency_id: self.st_line.currency_id};
} }
); );
} else { } else {
@ -1228,10 +1232,9 @@ openerp.account = function (instance) {
$.when(deferred_tax).then(function(){ $.when(deferred_tax).then(function(){
// Format amounts // Format amounts
if (line_created_being_edited[0].amount) if (line_created_being_edited[0].amount)
line_created_being_edited[0].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[0].amount)); line_created_being_edited[0].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[0].amount), line_created_being_edited[0].currency_id);
if (line_created_being_edited[1] && line_created_being_edited[1].amount) if (line_created_being_edited[1] && line_created_being_edited[1].amount)
line_created_being_edited[1].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[1].amount)); line_created_being_edited[1].amount_str = self.formatCurrency(Math.abs(line_created_being_edited[1].amount), line_created_being_edited[0].currency_id);
self.set("line_created_being_edited", line_created_being_edited); self.set("line_created_being_edited", line_created_being_edited);
self.createdLinesChanged(); // TODO For some reason, previous line doesn't trigger change handler self.createdLinesChanged(); // TODO For some reason, previous line doesn't trigger change handler
}); });
@ -1268,10 +1271,10 @@ openerp.account = function (instance) {
line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit; line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit;
if (balance < 0) { if (balance < 0) {
line.debit -= balance; line.debit -= balance;
line.debit_str = self.formatCurrency(line.debit); line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id);
} else { } else {
line.credit -= balance; line.credit -= balance;
line.credit_str = self.formatCurrency(line.credit); line.credit_str = self.formatCurrency(line.credit, self.st_line.currency_id);
} }
line.propose_partial_reconcile = false; line.propose_partial_reconcile = false;
line.partial_reconcile = true; line.partial_reconcile = true;
@ -1291,12 +1294,13 @@ openerp.account = function (instance) {
}, },
unpartialReconcileLine: function(line) { unpartialReconcileLine: function(line) {
var self = this;
if (line.initial_amount > 0) { if (line.initial_amount > 0) {
line.debit = line.initial_amount; line.debit = line.initial_amount;
line.debit_str = this.formatCurrency(line.debit); line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id);
} else { } else {
line.credit = -1 * line.initial_amount; line.credit = -1 * line.initial_amount;
line.credit_str = this.formatCurrency(line.credit); line.credit_str = self.formatCurrency(line.credit, self.st_line.currency_id);
} }
line.propose_partial_reconcile = true; line.propose_partial_reconcile = true;
line.partial_reconcile = false; line.partial_reconcile = false;
@ -1359,16 +1363,15 @@ openerp.account = function (instance) {
if (limit > 0) { if (limit > 0) {
// Load move lines // Load move lines
deferred_move_lines = self.model_bank_statement_line deferred_move_lines = self.model_bank_statement_line
.call("get_move_lines_counterparts", [self.st_line.id, excluded_ids, self.filter, offset, limit]) .call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, offset, limit])
.then(function (lines) { .then(function (lines) {
_(lines).each(self.decorateMoveLine.bind(self)); _(lines).each(self.decorateMoveLine.bind(self));
move_lines = lines; move_lines = lines;
}); });
} }
// Fetch the number of move lines corresponding to this statement line and this filter // Fetch the number of move lines corresponding to this statement line and this filter
var deferred_total_move_lines_num = self.model_bank_statement_line var deferred_total_move_lines_num = self.model_bank_statement_line
.call("get_move_lines_counterparts", [self.st_line.id, excluded_ids, self.filter, offset, limit, true]) .call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, 0, undefined, true])
.then(function(num){ .then(function(num){
move_lines_num = num; move_lines_num = num;
}); });

View File

@ -185,7 +185,7 @@
<td><span class="toggle_create glyphicon glyphicon-play"></span></td> <td><span class="toggle_create glyphicon glyphicon-play"></span></td>
<td><t t-esc="account_code"/></td> <td><t t-esc="account_code"/></td>
<td></td> <td></td>
<td>Open balance</td> <td class="js_open_balance">Open balance</td>
<td><t t-esc="debit"/></td> <td><t t-esc="debit"/></td>
<td><t t-esc="credit"/></td> <td><t t-esc="credit"/></td>
<td></td> <td></td>

View File

@ -99,7 +99,7 @@
</tr> </tr>
<tr t-foreach="get_lines_with_out_partner(data['form'])" t-as="not_partner"> <tr t-foreach="get_lines_with_out_partner(data['form'])" t-as="not_partner">
<td> <td>
<span t-esc="partner['name']"/> <span t-esc="not_partner['name']"/>
</td> </td>
<td class="text-right"> <td class="text-right">
<span t-esc="formatLang(not_partner['direction'], currency_obj=res_company.currency_id)"/> <span t-esc="formatLang(not_partner['direction'], currency_obj=res_company.currency_id)"/>

View File

@ -62,9 +62,10 @@ class account_chart(osv.osv_memory):
ORDER BY p.date_stop DESC ORDER BY p.date_stop DESC
LIMIT 1) AS period_stop''', (fiscalyear_id, fiscalyear_id)) LIMIT 1) AS period_stop''', (fiscalyear_id, fiscalyear_id))
periods = [i[0] for i in cr.fetchall()] periods = [i[0] for i in cr.fetchall()]
if periods and len(periods) > 1: if periods:
start_period = periods[0] start_period = periods[0]
end_period = periods[1] if len(periods) > 1:
end_period = periods[1]
res['value'] = {'period_from': start_period, 'period_to': end_period} res['value'] = {'period_from': start_period, 'period_to': end_period}
else: else:
res['value'] = {'period_from': False, 'period_to': False} res['value'] = {'period_from': False, 'period_to': False}

View File

@ -22,7 +22,6 @@ from dateutil.relativedelta import relativedelta
import datetime import datetime
import logging import logging
import time import time
import traceback
from openerp.osv import osv, fields from openerp.osv import osv, fields
from openerp.osv.orm import intersect, except_orm from openerp.osv.orm import intersect, except_orm
@ -73,6 +72,7 @@ class account_analytic_invoice_line(osv.osv):
result = {} result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=local_context) res = self.pool.get('product.product').browse(cr, uid, product, context=local_context)
price = False
if price_unit is not False: if price_unit is not False:
price = price_unit price = price_unit
elif pricelist_id: elif pricelist_id:
@ -746,29 +746,32 @@ class account_analytic_account(osv.osv):
contract_ids = ids contract_ids = ids
else: else:
contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')]) contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True), ('type', '=', 'contract')])
for contract in self.browse(cr, uid, contract_ids, context=context): if contract_ids:
try: cr.execute('SELECT company_id, array_agg(id) as ids FROM account_analytic_account WHERE id IN %s GROUP BY company_id', (tuple(contract_ids),))
invoice_values = self._prepare_invoice(cr, uid, contract, context=context) for company_id, ids in cr.fetchall():
invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context)) for contract in self.browse(cr, uid, ids, context=dict(context, company_id=company_id, force_company=company_id)):
next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d") try:
interval = contract.recurring_interval invoice_values = self._prepare_invoice(cr, uid, contract, context=context)
if contract.recurring_rule_type == 'daily': invoice_ids.append(self.pool['account.invoice'].create(cr, uid, invoice_values, context=context))
new_date = next_date+relativedelta(days=+interval) next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d")
elif contract.recurring_rule_type == 'weekly': interval = contract.recurring_interval
new_date = next_date+relativedelta(weeks=+interval) if contract.recurring_rule_type == 'daily':
elif contract.recurring_rule_type == 'monthly': new_date = next_date+relativedelta(days=+interval)
new_date = next_date+relativedelta(months=+interval) elif contract.recurring_rule_type == 'weekly':
else: new_date = next_date+relativedelta(weeks=+interval)
new_date = next_date+relativedelta(years=+interval) elif contract.recurring_rule_type == 'monthly':
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context) new_date = next_date+relativedelta(months=+interval)
if automatic: else:
cr.commit() new_date = next_date+relativedelta(years=+interval)
except Exception: self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
if automatic: if automatic:
cr.rollback() cr.commit()
_logger.error(traceback.format_exc()) except Exception:
else: if automatic:
raise cr.rollback()
_logger.exception('Fail to create recurring invoice for contract %s', contract.code)
else:
raise
return invoice_ids return invoice_ids
class account_analytic_account_summary_user(osv.osv): class account_analytic_account_summary_user(osv.osv):

View File

@ -248,7 +248,7 @@
<page string="Bill Information"> <page string="Bill Information">
<field name="line_dr_ids" on_change="onchange_price(line_dr_ids, tax_id, partner_id)" context="{'journal_id':journal_id,'partner_id':partner_id}"> <field name="line_dr_ids" on_change="onchange_price(line_dr_ids, tax_id, partner_id)" context="{'journal_id':journal_id,'partner_id':partner_id}">
<tree string="Expense Lines" editable="bottom"> <tree string="Expense Lines" editable="bottom">
<field name="account_id" widget="selection" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]"/> <field name="account_id" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]"/>
<field name="name"/> <field name="name"/>
<field name="amount"/> <field name="amount"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/> <field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>

View File

@ -1,137 +1,41 @@
#
# Implements encrypting functions.
#
# Copyright (c) 2008, F S 3 Consulting Inc.
#
# Maintainer:
# Alec Joseph Rivera (agi<at>fs3.ph)
# refactored by Antony Lesuisse <al<at>openerp.com>
#
import hashlib
import hmac
import logging import logging
from random import sample
from string import ascii_letters, digits from passlib.context import CryptContext
import openerp import openerp
from openerp.osv import fields, osv from openerp.osv import fields, osv
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
magic_md5 = '$1$' default_crypt_context = CryptContext(
magic_sha256 = '$5$' # kdf which can be verified by the context. The default encryption kdf is
# the first of the list
def gen_salt(length=8, symbols=None): ['pbkdf2_sha512', 'md5_crypt'],
if symbols is None: # deprecated algorithms are still verified as usual, but ``needs_update``
symbols = ascii_letters + digits # will indicate that the stored hash should be replaced by a more recent
return ''.join(sample(symbols, length)) # algorithm. Passlib 1.6 supports an `auto` value which deprecates any
# algorithm but the default, but Debian only provides 1.5 so...
def md5crypt( raw_pw, salt, magic=magic_md5 ): deprecated=['md5_crypt'],
""" md5crypt FreeBSD crypt(3) based on but different from md5 )
The md5crypt is based on Mark Johnson's md5crypt.py, which in turn is
based on FreeBSD src/lib/libcrypt/crypt.c (1.2) by Poul-Henning Kamp.
Mark's port can be found in ActiveState ASPN Python Cookbook. Kudos to
Poul and Mark. -agi
Original license:
* "THE BEER-WARE LICENSE" (Revision 42):
*
* <phk@login.dknet.dk> wrote this file. As long as you retain this
* notice you can do whatever you want with this stuff. If we meet some
* day, and you think this stuff is worth it, you can buy me a beer in
* return.
*
* Poul-Henning Kamp
"""
raw_pw = raw_pw.encode('utf-8')
salt = salt.encode('utf-8')
hash = hashlib.md5()
hash.update( raw_pw + magic + salt )
st = hashlib.md5()
st.update( raw_pw + salt + raw_pw)
stretch = st.digest()
for i in range( 0, len( raw_pw ) ):
hash.update( stretch[i % 16] )
i = len( raw_pw )
while i:
if i & 1:
hash.update('\x00')
else:
hash.update( raw_pw[0] )
i >>= 1
saltedmd5 = hash.digest()
for i in range( 1000 ):
hash = hashlib.md5()
if i & 1:
hash.update( raw_pw )
else:
hash.update( saltedmd5 )
if i % 3:
hash.update( salt )
if i % 7:
hash.update( raw_pw )
if i & 1:
hash.update( saltedmd5 )
else:
hash.update( raw_pw )
saltedmd5 = hash.digest()
itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
rearranged = ''
for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)):
v = ord( saltedmd5[a] ) << 16 | ord( saltedmd5[b] ) << 8 | ord( saltedmd5[c] )
for i in range(4):
rearranged += itoa64[v & 0x3f]
v >>= 6
v = ord( saltedmd5[11] )
for i in range( 2 ):
rearranged += itoa64[v & 0x3f]
v >>= 6
return magic + salt + '$' + rearranged
def sh256crypt(cls, password, salt, magic=magic_sha256):
iterations = 1000
# see http://en.wikipedia.org/wiki/PBKDF2
result = password.encode('utf8')
for i in xrange(cls.iterations):
result = hmac.HMAC(result, salt, hashlib.sha256).digest() # uses HMAC (RFC 2104) to apply salt
result = result.encode('base64') # doesnt seem to be crypt(3) compatible
return '%s%s$%s' % (magic_sha256, salt, result)
class res_users(osv.osv): class res_users(osv.osv):
_inherit = "res.users" _inherit = "res.users"
def init(self, cr):
_logger.info("Hashing passwords, may be slow for databases with many users...")
cr.execute("SELECT id, password FROM res_users"
" WHERE password IS NOT NULL"
" AND password != ''")
for uid, pwd in cr.fetchall():
self._set_password(cr, openerp.SUPERUSER_ID, uid, pwd)
def set_pw(self, cr, uid, id, name, value, args, context): def set_pw(self, cr, uid, id, name, value, args, context):
if value: if value:
encrypted = md5crypt(value, gen_salt()) self._set_password(cr, uid, id, value, context=context)
cr.execute("update res_users set password='', password_crypt=%s where id=%s", (encrypted, id))
del value
def get_pw( self, cr, uid, ids, name, args, context ): def get_pw( self, cr, uid, ids, name, args, context ):
cr.execute('select id, password from res_users where id in %s', (tuple(map(int, ids)),)) cr.execute('select id, password from res_users where id in %s', (tuple(map(int, ids)),))
stored_pws = cr.fetchall() return dict(cr.fetchall())
res = {}
for id, stored_pw in stored_pws:
res[id] = stored_pw
return res
_columns = { _columns = {
'password': fields.function(get_pw, fnct_inv=set_pw, type='char', string='Password', invisible=True, store=True), 'password': fields.function(get_pw, fnct_inv=set_pw, type='char', string='Password', invisible=True, store=True),
@ -141,27 +45,51 @@ class res_users(osv.osv):
def check_credentials(self, cr, uid, password): def check_credentials(self, cr, uid, password):
# convert to base_crypt if needed # convert to base_crypt if needed
cr.execute('SELECT password, password_crypt FROM res_users WHERE id=%s AND active', (uid,)) cr.execute('SELECT password, password_crypt FROM res_users WHERE id=%s AND active', (uid,))
encrypted = None
if cr.rowcount: if cr.rowcount:
stored_password, stored_password_crypt = cr.fetchone() stored, encrypted = cr.fetchone()
if stored_password and not stored_password_crypt: if stored and not encrypted:
salt = gen_salt() self._set_password(cr, uid, uid, stored)
stored_password_crypt = md5crypt(stored_password, salt)
cr.execute("UPDATE res_users SET password='', password_crypt=%s WHERE id=%s", (stored_password_crypt, uid))
try: try:
return super(res_users, self).check_credentials(cr, uid, password) return super(res_users, self).check_credentials(cr, uid, password)
except openerp.exceptions.AccessDenied: except openerp.exceptions.AccessDenied:
# check md5crypt if encrypted:
if stored_password_crypt: valid_pass, replacement = self._crypt_context(cr, uid, uid)\
if stored_password_crypt[:len(magic_md5)] == magic_md5: .verify_and_update(password, encrypted)
salt = stored_password_crypt[len(magic_md5):11] if replacement is not None:
if stored_password_crypt == md5crypt(password, salt): self._set_encrypted_password(cr, uid, uid, replacement)
return if valid_pass:
elif stored_password_crypt[:len(magic_md5)] == magic_sha256: return
salt = stored_password_crypt[len(magic_md5):11]
if stored_password_crypt == md5crypt(password, salt):
return
# Reraise password incorrect
raise raise
def _set_password(self, cr, uid, id, password, context=None):
""" Encrypts then stores the provided plaintext password for the user
``id``
"""
encrypted = self._crypt_context(cr, uid, id, context=context).encrypt(password)
self._set_encrypted_password(cr, uid, id, encrypted, context=context)
def _set_encrypted_password(self, cr, uid, id, encrypted, context=None):
""" Store the provided encrypted password to the database, and clears
any plaintext password
:param uid: id of the current user
:param id: id of the user on which the password should be set
"""
cr.execute(
"UPDATE res_users SET password='', password_crypt=%s WHERE id=%s",
(encrypted, id))
def _crypt_context(self, cr, uid, id, context=None):
""" Passlib CryptContext instance used to encrypt and verify
passwords. Can be overridden if technical, legal or political matters
require different kdfs than the provided default.
Requires a CryptContext as deprecation and upgrade notices are used
internally
"""
return default_crypt_context
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -74,7 +74,7 @@ class OAuthLogin(Home):
state = dict( state = dict(
d=request.session.db, d=request.session.db,
p=provider['id'], p=provider['id'],
r=redirect, r=werkzeug.url_quote_plus(redirect),
) )
token = request.params.get('token') token = request.params.get('token')
if token: if token:
@ -143,7 +143,7 @@ class OAuthController(http.Controller):
cr.commit() cr.commit()
action = state.get('a') action = state.get('a')
menu = state.get('m') menu = state.get('m')
redirect = state.get('r') redirect = werkzeug.url_unquote_plus(state['r']) if state.get('r') else False
url = '/web' url = '/web'
if redirect: if redirect:
url = redirect url = redirect

View File

@ -102,7 +102,7 @@ openerp.calendar = function(instance) {
var self = this; var self = this;
var action_url = ''; var action_url = '';
action_url = _.str.sprintf('/?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id); action_url = _.str.sprintf('/web?db=%s#id=%s&view_type=form&model=calendar.event', db, meeting_id);
var reload_page = function(){ var reload_page = function(){
return location.replace(action_url); return location.replace(action_url);

View File

@ -45,13 +45,10 @@ class crm_claim_stage(osv.osv):
help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."), help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."),
'case_default': fields.boolean('Common to All Teams', 'case_default': fields.boolean('Common to All Teams',
help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."), help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."),
'fold': fields.boolean('Hide in Views when Empty',
help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
} }
_defaults = { _defaults = {
'sequence': lambda *args: 1, 'sequence': lambda *args: 1,
'fold': False,
} }
class crm_claim(osv.osv): class crm_claim(osv.osv):

View File

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

View File

@ -51,7 +51,6 @@
<field name="name"/> <field name="name"/>
<field name="case_default"/> <field name="case_default"/>
<field name="sequence"/> <field name="sequence"/>
<field name="fold"/>
</group> </group>
</form> </form>
</field> </field>

View File

@ -32,7 +32,7 @@ class report_event_registration(osv.osv):
'draft_state': fields.integer(' # No of Draft Registrations', size=20), 'draft_state': fields.integer(' # No of Draft Registrations', size=20),
'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20), 'confirm_state': fields.integer(' # No of Confirmed Registrations', size=20),
'seats_max': fields.integer('Max Seats'), 'seats_max': fields.integer('Max Seats'),
'nbevent': fields.integer('Number Of Events'), 'nbevent': fields.integer('Number of Registrations'),
'event_type': fields.many2one('event.type', 'Event Type'), 'event_type': fields.many2one('event.type', 'Event Type'),
'registration_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True), 'registration_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Attended'), ('cancel', 'Cancelled')], 'Registration State', readonly=True, required=True),
'event_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True), 'event_state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Event State', readonly=True, required=True),
@ -59,7 +59,7 @@ class report_event_registration(osv.osv):
r.name AS name_registration, r.name AS name_registration,
e.company_id AS company_id, e.company_id AS company_id,
e.date_begin AS event_date, e.date_begin AS event_date,
count(e.id) AS nbevent, count(r.id) AS nbevent,
CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state, CASE WHEN r.state IN ('draft') THEN r.nb_register ELSE 0 END AS draft_state,
CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state, CASE WHEN r.state IN ('open','done') THEN r.nb_register ELSE 0 END AS confirm_state,
e.type AS event_type, e.type AS event_type,

View File

@ -1,7 +1,7 @@
.oe_event_date{ .oe_event_date{
border-top-left-radius:3px; border-top-left-radius:3px;
border-top-right-radius:3px; border-top-right-radius:3px;
font-size: 48px; font-size: 36px;
height: auto; height: auto;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;

View File

@ -245,7 +245,8 @@ class event_ticket(osv.osv):
] ]
def onchange_product_id(self, cr, uid, ids, product_id=False, context=None): def onchange_product_id(self, cr, uid, ids, product_id=False, context=None):
return {'value': {'price': self.pool.get("product.product").browse(cr, uid, product_id).list_price or 0}} price = self.pool.get("product.product").browse(cr, uid, product_id).list_price if product_id else 0
return {'value': {'price': price}}
class event_registration(osv.osv): class event_registration(osv.osv):

View File

@ -58,7 +58,7 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date
end_date = default_end_date end_date = default_end_date
if start_date and end_date: if start_date and end_date:
return (start_date.strftime(DF), end_date.strftime(DF)) return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF))
else: else:
return (start_date, end_date) return (start_date, end_date)

View File

@ -699,7 +699,7 @@ class google_calendar(osv.AbstractModel):
for att in att_obj.browse(cr, uid, my_att_ids, context=context): for att in att_obj.browse(cr, uid, my_att_ids, context=context):
event = att.event_id event = att.event_id
base_event_id = att.google_internal_event_id.split('_')[0] base_event_id = att.google_internal_event_id.rsplit('_', 1)[0]
if base_event_id not in event_to_synchronize: if base_event_id not in event_to_synchronize:
event_to_synchronize[base_event_id] = {} event_to_synchronize[base_event_id] = {}
@ -721,7 +721,7 @@ class google_calendar(osv.AbstractModel):
for event in all_event_from_google.values(): for event in all_event_from_google.values():
event_id = event.get('id') event_id = event.get('id')
base_event_id = event_id.split('_')[0] base_event_id = event_id.rsplit('_', 1)[0]
if base_event_id not in event_to_synchronize: if base_event_id not in event_to_synchronize:
event_to_synchronize[base_event_id] = {} event_to_synchronize[base_event_id] = {}
@ -786,7 +786,7 @@ class google_calendar(osv.AbstractModel):
if actSrc == 'OE': if actSrc == 'OE':
self.delete_an_event(cr, uid, current_event[0], context=context) self.delete_an_event(cr, uid, current_event[0], context=context)
elif actSrc == 'GG': elif actSrc == 'GG':
new_google_event_id = event.GG.event['id'].split('_')[1] new_google_event_id = event.GG.event['id'].rsplit('_', 1)[1]
if 'T' in new_google_event_id: if 'T' in new_google_event_id:
new_google_event_id = new_google_event_id.replace('T', '')[:-1] new_google_event_id = new_google_event_id.replace('T', '')[:-1]
else: else:
@ -795,7 +795,8 @@ class google_calendar(osv.AbstractModel):
if event.GG.status: if event.GG.status:
parent_event = {} parent_event = {}
if not event_to_synchronize[base_event][0][1].OE.event_id: if not event_to_synchronize[base_event][0][1].OE.event_id:
event_to_synchronize[base_event][0][1].OE.event_id = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].split('_')[0])], ['event_id'], context=context_novirtual)[0].get('event_id')[0] main_ev = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].rsplit('_', 1)[0])], fields=['event_id'], context=context_novirtual)
event_to_synchronize[base_event][0][1].OE.event_id = main_ev[0].get('event_id')[0]
parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id) parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id)
res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context) res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context)

View File

@ -225,7 +225,7 @@ class hr_employee(osv.osv):
"resized as a 128x128px image, with aspect ratio preserved. "\ "resized as a 128x128px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views."), "Use this field in form views or some kanban views."),
'image_small': fields.function(_get_image, fnct_inv=_set_image, 'image_small': fields.function(_get_image, fnct_inv=_set_image,
string="Smal-sized photo", type="binary", multi="_get_image", string="Small-sized photo", type="binary", multi="_get_image",
store = { store = {
'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), 'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
}, },

View File

@ -40,7 +40,9 @@
<page string="Public Information"> <page string="Public Information">
<group> <group>
<group string="Contact Information"> <group string="Contact Information">
<field name="address_id" on_change="onchange_address_id(address_id)" context="{'show_address': 1}" options='{"always_reload": True, "highlight_first_line": True}'/> <field name="address_id" on_change="onchange_address_id(address_id)"
context="{'show_address': 1, 'default_customer': False}"
options='{"always_reload": True, "highlight_first_line": True}'/>
<field name="mobile_phone"/> <field name="mobile_phone"/>
<field name="work_location"/> <field name="work_location"/>
</group> </group>
@ -68,7 +70,9 @@
<field name="otherid" groups="base.group_hr_user"/> <field name="otherid" groups="base.group_hr_user"/>
</group> </group>
<group string="Contact Information"> <group string="Contact Information">
<field name="address_home_id" context="{'show_address': 1}" options='{"always_reload": True, "highlight_first_line": True}'/> <field name="address_home_id"
context="{'show_address': 1, 'default_customer': False}"
options='{"always_reload": True, "highlight_first_line": True}'/>
</group> </group>
<group string="Status"> <group string="Status">
<field name="gender"/> <field name="gender"/>

View File

@ -99,7 +99,7 @@ class hr_holidays_status(osv.osv):
for record in self.browse(cr, uid, ids, context=context): for record in self.browse(cr, uid, ids, context=context):
name = record.name name = record.name
if not record.limit: if not record.limit:
name = name + (' (%d/%d)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0)) name = name + (' (%g/%g)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0))
res.append((record.id, name)) res.append((record.id, name))
return res return res

View File

@ -59,25 +59,7 @@ A removal of one object in the CODA processing results in the removal of the
associated objects. The removal of a CODA File containing multiple Bank associated objects. The removal of a CODA File containing multiple Bank
Statements will also remove those associated statements. Statements will also remove those associated statements.
The following reconciliation logic has been implemented in the CODA processing: Instead of a manual adjustment of the generated Bank Statements, you can also
-------------------------------------------------------------------------------
1) The Company's Bank Account Number of the CODA statement is compared against
the Bank Account Number field of the Company's CODA Bank Account
configuration records (whereby bank accounts defined in type='info'
configuration records are ignored). If this is the case an 'internal transfer'
transaction is generated using the 'Internal Transfer Account' field of the
CODA File Import wizard.
2) As a second step the 'Structured Communication' field of the CODA transaction
line is matched against the reference field of in- and outgoing invoices
(supported : Belgian Structured Communication Type).
3) When the previous step doesn't find a match, the transaction counterparty is
located via the Bank Account Number configured on the OpenERP Customer and
Supplier records.
4) In case the previous steps are not successful, the transaction is generated
by using the 'Default Account for Unrecognized Movement' field of the CODA
File Import wizard in order to allow further manual processing.
In stead of a manual adjustment of the generated Bank Statements, you can also
re-import the CODA after updating the OpenERP database with the information that re-import the CODA after updating the OpenERP database with the information that
was missing to allow automatic reconciliation. was missing to allow automatic reconciliation.

View File

@ -28,46 +28,4 @@ class account_bank_statement(osv.osv):
} }
class account_bank_statement_line(osv.osv):
_inherit = 'account.bank.statement.line'
_columns = {
'coda_account_number': fields.char('Account Number', help="The Counter Party Account Number")
}
def create(self, cr, uid, data, context=None):
"""
This function creates a Bank Account Number if, for a bank statement line,
the partner_id field and the coda_account_number field are set,
and the account number does not exist in the database
"""
if 'partner_id' in data and data['partner_id'] and 'coda_account_number' in data and data['coda_account_number']:
acc_number_ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', data['coda_account_number'])])
if len(acc_number_ids) == 0:
try:
type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': data['coda_account_number'], 'partner_id': data['partner_id'], 'state': type_id.code}, context=context)
except ValueError:
pass
return super(account_bank_statement_line, self).create(cr, uid, data, context=context)
def write(self, cr, uid, ids, vals, context=None):
super(account_bank_statement_line, self).write(cr, uid, ids, vals, context)
"""
Same as create function above, but for write function
"""
if 'partner_id' in vals:
for line in self.pool.get('account.bank.statement.line').browse(cr, uid, ids, context=context):
if line.coda_account_number:
acc_number_ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', line.coda_account_number)])
if len(acc_number_ids) == 0:
try:
type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': line.coda_account_number, 'partner_id': vals['partner_id'], 'state': type_id.code}, context=context)
except ValueError:
pass
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -32,5 +32,27 @@
<field eval="'2011-01-31'" name="date_stop"/> <field eval="'2011-01-31'" name="date_stop"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>
</record> </record>
<!-- invoice with BBA -->
<record id="coda_demo_invoice_1" model="account.invoice">
<field name="currency_id" ref="base.EUR"/>
<field name="company_id" ref="base.main_company"/>
<field name="journal_id" ref="account.sales_journal"/>
<field name="period_id" ref="period_1_2011"/>
<field name="state">draft</field>
<field name="type">out_invoice</field>
<field name="account_id" ref="account.a_recv"/>
<field name="partner_id" ref="base.res_partner_9"/>
<field name="reference_type">bba</field>
<field name="reference">+++240/2838/42818+++</field>
</record>
<record id="invoice_1_line_1" model="account.invoice.line">
<field name="name">Otpez Laptop without OS</field>
<field name="invoice_id" ref="coda_demo_invoice_1"/>
<field name="price_unit">608.89</field>
<field name="quantity">10</field>
<field name="account_id" ref="account.a_sale"/>
</record>
<workflow action="invoice_open" model="account.invoice" ref="coda_demo_invoice_1"/>
</data> </data>
</openerp> </openerp>

View File

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

View File

@ -291,79 +291,38 @@ class account_coda_import(osv.osv_memory):
if 'counterpartyAddress' in line and line['counterpartyAddress'] != '': if 'counterpartyAddress' in line and line['counterpartyAddress'] != '':
note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress']) note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress'])
line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']])) line['name'] = "\n".join(filter(None, [line['counterpartyName'], line['communication']]))
partner = None
partner_id = None partner_id = None
invoice = False structured_com = ""
bank_account_id = False
if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101': if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101':
ids = self.pool.get('account.invoice').search(cr, uid, [('reference', '=', line['communication']), ('reference_type', '=', 'bba')]) structured_com = line['communication']
# Gère les communications structurées
# TODO : à faire primer sur resolution_proposition : si la communication indique une facture, on la sélectionne
# if ids:
# invoice = self.pool.get('account.invoice').browse(cr, uid, ids[0])
# partner = invoice.partner_id
# partner_id = partner.id
# if invoice.type in ['in_invoice', 'in_refund'] and line['debit'] == '1':
# line['transaction_type'] = 'supplier'
# elif invoice.type in ['out_invoice', 'out_refund'] and line['debit'] == '0':
# line['transaction_type'] = 'customer'
# line['account'] = invoice.account_id.id
# line['reconcile'] = False
# if invoice.type in ['in_invoice', 'out_invoice']:
# iml_ids = self.pool.get('account.move.line').search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)])
# if iml_ids:
# line['reconcile'] = iml_ids[0]
# if line['reconcile']:
# voucher_vals = {
# 'type': line['transaction_type'] == 'supplier' and 'payment' or 'receipt',
# 'name': line['name'],
# 'partner_id': partner_id,
# 'journal_id': statement['journal_id'].id,
# 'account_id': statement['journal_id'].default_credit_account_id.id,
# 'company_id': statement['journal_id'].company_id.id,
# 'currency_id': statement['journal_id'].company_id.currency_id.id,
# 'date': line['entryDate'],
# 'amount': abs(line['amount']),
# 'period_id': statement['period_id'],
# 'invoice_id': invoice.id,
# }
# context['invoice_id'] = invoice.id
# voucher_vals.update(self.pool.get('account.voucher').onchange_partner_id(cr, uid, [],
# partner_id=partner_id,
# journal_id=statement['journal_id'].id,
# amount=abs(line['amount']),
# currency_id=statement['journal_id'].company_id.currency_id.id,
# ttype=line['transaction_type'] == 'supplier' and 'payment' or 'receipt',
# date=line['transactionDate'],
# context=context
# )['value'])
# line_drs = []
# for line_dr in voucher_vals['line_dr_ids']:
# line_drs.append((0, 0, line_dr))
# voucher_vals['line_dr_ids'] = line_drs
# line_crs = []
# for line_cr in voucher_vals['line_cr_ids']:
# line_crs.append((0, 0, line_cr))
# voucher_vals['line_cr_ids'] = line_crs
# line['voucher_id'] = self.pool.get('account.voucher').create(cr, uid, voucher_vals, context=context)
if 'counterpartyNumber' in line and line['counterpartyNumber']: if 'counterpartyNumber' in line and line['counterpartyNumber']:
ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))]) ids = self.pool.get('res.partner.bank').search(cr, uid, [('acc_number', '=', str(line['counterpartyNumber']))])
if ids and len(ids) > 0: if ids:
partner = self.pool.get('res.partner.bank').browse(cr, uid, ids[0], context=context).partner_id bank_account_id = ids[0]
partner_id = partner.id partner_id = self.pool.get('res.partner.bank').browse(cr, uid, bank_account_id, context=context).partner_id.id
else:
#create the bank account, not linked to any partner. The reconciliation will link the partner manually
#chosen at the bank statement final confirmation time.
try:
type_model, type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'bank_normal')
type_id = self.pool.get('res.partner.bank.type').browse(cr, uid, type_id, context=context)
bank_code = type_id.code
except ValueError:
bank_code = 'bank'
bank_account_id = self.pool.get('res.partner.bank').create(cr, uid, {'acc_number': str(line['counterpartyNumber']), 'state': bank_code}, context=context)
if 'communication' in line and line['communication'] != '': if 'communication' in line and line['communication'] != '':
note.append(_('Communication') + ': ' + line['communication']) note.append(_('Communication') + ': ' + line['communication'])
data = { data = {
'name': line['name'], 'name': line['name'],
'note': "\n".join(note), 'note': "\n".join(note),
'date': line['entryDate'], 'date': line['entryDate'],
'amount': line['amount'], 'amount': line['amount'],
'partner_id': partner_id, 'partner_id': partner_id,
'statement_id': statement['id'], 'statement_id': statement['id'],
'ref': line['ref'], 'ref': structured_com,
'sequence': line['sequence'], 'sequence': line['sequence'],
'coda_account_number': line['counterpartyNumber'], 'bank_account_id': bank_account_id,
} }
self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context) self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context)
if statement['coda_note'] != '': if statement['coda_note'] != '':

View File

@ -141,7 +141,7 @@ class account_invoice(osv.osv):
elif algorithm == 'random': elif algorithm == 'random':
if not self.check_bbacomm(reference): if not self.check_bbacomm(reference):
base = random.randint(1, 9999999999) base = random.randint(1, 9999999999)
bbacomm = str(base).rjust(7, '0') bbacomm = str(base).rjust(10, '0')
base = int(bbacomm) base = int(bbacomm)
mod = base % 97 or 97 mod = base % 97 or 97
mod = str(mod).rjust(2, '0') mod = str(mod).rjust(2, '0')

View File

@ -25,7 +25,7 @@
<record id="impuestos_plantilla_isv_por_cobrar" model="account.tax.template"> <record id="impuestos_plantilla_isv_por_cobrar" model="account.tax.template">
<field name="chart_template_id" ref="cuentas_plantilla"/> <field name="chart_template_id" ref="cuentas_plantilla"/>
<field name="name">ISV por Cobrar</field> <field name="name">ISV por Cobrar</field>
<field name="amount" eval="0.12"/> <field name="amount" eval="0.15"/>
<field name="type">percent</field> <field name="type">percent</field>
<field name="account_collected_id" ref="cta110301"/> <field name="account_collected_id" ref="cta110301"/>
<field name="account_paid_id" ref="cta110301"/> <field name="account_paid_id" ref="cta110301"/>
@ -42,7 +42,7 @@
<record id="impuestos_plantilla_isv_por_pagar" model="account.tax.template"> <record id="impuestos_plantilla_isv_por_pagar" model="account.tax.template">
<field name="chart_template_id" ref="cuentas_plantilla"/> <field name="chart_template_id" ref="cuentas_plantilla"/>
<field name="name">ISV por Pagar</field> <field name="name">ISV por Pagar</field>
<field name="amount" eval="0.12"/> <field name="amount" eval="0.15"/>
<field name="type">percent</field> <field name="type">percent</field>
<field name="account_collected_id" ref="cta210201"/> <field name="account_collected_id" ref="cta210201"/>
<field name="account_paid_id" ref="cta210201"/> <field name="account_paid_id" ref="cta210201"/>

View File

@ -32,7 +32,7 @@ This is the latest UK OpenERP localisation necessary to run OpenERP accounting f
- a few other adaptations""", - a few other adaptations""",
'author': 'SmartMode LTD', 'author': 'SmartMode LTD',
'website': 'http://www.smartmode.co.uk', 'website': 'http://www.smartmode.co.uk',
'depends': ['base_iban', 'base_vat', 'account_chart'], 'depends': ['base_iban', 'base_vat', 'account_chart', 'account_anglo_saxon'],
'data': [ 'data': [
'data/account.account.type.csv', 'data/account.account.type.csv',
'data/account.account.template.csv', 'data/account.account.template.csv',

View File

@ -28,7 +28,7 @@ United States - Chart of accounts.
================================== ==================================
""", """,
'website': 'http://www.openerp.com', 'website': 'http://www.openerp.com',
'depends': ['account_chart'], 'depends': ['account_chart', 'account_anglo_saxon'],
'data': [ 'data': [
'l10n_us_account_type.xml', 'l10n_us_account_type.xml',
'account_chart_template.xml', 'account_chart_template.xml',

View File

@ -176,7 +176,7 @@ class mail_notification(osv.Model):
references = message.parent_id.message_id if message.parent_id else False references = message.parent_id.message_id if message.parent_id else False
# create email values # create email values
max_recipients = 100 max_recipients = 50
chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)] chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)]
email_ids = [] email_ids = []
for chunk in chunks: for chunk in chunks:
@ -188,7 +188,7 @@ class mail_notification(osv.Model):
'references': references, 'references': references,
} }
email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)) email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context))
if force_send and len(chunks) < 6: # for more than 500 followers, use the queue system if force_send and len(chunks) < 2: # for more than 50 followers, use the queue system
self.pool.get('mail.mail').send(cr, uid, email_ids, context=context) self.pool.get('mail.mail').send(cr, uid, email_ids, context=context)
return True return True

View File

@ -211,3 +211,16 @@ class mail_group(osv.Model):
return [] return []
else: else:
return super(mail_group, self).get_suggested_thread(cr, uid, removed_suggested_threads, context) return super(mail_group, self).get_suggested_thread(cr, uid, removed_suggested_threads, context)
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
res = super(mail_group, self).message_get_email_values(cr, uid, id, notif_mail=notif_mail, context=context)
group = self.browse(cr, uid, id, context=context)
res.update({
'headers': {
'Precedence': 'list',
}
})
if group.alias_domain:
res['headers']['List-Id'] = '%s.%s' % (group.alias_name, group.alias_domain)
res['headers']['List-Post'] = '<mailto:%s@%s>' % (group.alias_name, group.alias_domain)
return res

View File

@ -204,12 +204,15 @@ class mail_mail(osv.Model):
""" """
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context) body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
body_alternative = tools.html2plaintext(body) body_alternative = tools.html2plaintext(body)
return { res = {
'body': body, 'body': body,
'body_alternative': body_alternative, 'body_alternative': body_alternative,
'subject': self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context), 'subject': self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context),
'email_to': self.send_get_mail_to(cr, uid, mail, partner=partner, context=context), 'email_to': self.send_get_mail_to(cr, uid, mail, partner=partner, context=context),
} }
if mail.model and mail.res_id and self.pool.get(mail.model) and hasattr(self.pool[mail.model], 'message_get_email_values'):
res.update(self.pool[mail.model].message_get_email_values(cr, uid, mail.res_id, mail, context=context))
return res
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
""" Sends the selected emails immediately, ignoring their current """ Sends the selected emails immediately, ignoring their current
@ -268,6 +271,9 @@ class mail_mail(osv.Model):
# build an RFC2822 email.message.Message object and send it without queuing # build an RFC2822 email.message.Message object and send it without queuing
res = None res = None
for email in email_list: for email in email_list:
email_headers = dict(headers)
if email.get('headers'):
email_headers.update(email['headers'])
msg = ir_mail_server.build_email( msg = ir_mail_server.build_email(
email_from=mail.email_from, email_from=mail.email_from,
email_to=email.get('email_to'), email_to=email.get('email_to'),
@ -282,7 +288,7 @@ class mail_mail(osv.Model):
object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
subtype='html', subtype='html',
subtype_alternative='plain', subtype_alternative='plain',
headers=headers) headers=email_headers)
res = ir_mail_server.send_email(cr, uid, msg, res = ir_mail_server.send_email(cr, uid, msg,
mail_server_id=mail.mail_server_id.id, mail_server_id=mail.mail_server_id.id,
context=context) context=context)

View File

@ -34,6 +34,7 @@ import pytz
import socket import socket
import time import time
import xmlrpclib import xmlrpclib
import re
from email.message import Message from email.message import Message
from urllib import urlencode from urllib import urlencode
@ -48,6 +49,8 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
mail_header_msgid_re = re.compile('<[^<>]+>')
def decode_header(message, header, separator=' '): def decode_header(message, header, separator=' '):
return separator.join(map(decode, filter(None, message.get_all(header, [])))) return separator.join(map(decode, filter(None, message.get_all(header, []))))
@ -694,6 +697,16 @@ class mail_thread(osv.AbstractModel):
if record.alias_domain and record.alias_name else False if record.alias_domain and record.alias_name else False
for record in self.browse(cr, SUPERUSER_ID, ids, context=context)] for record in self.browse(cr, SUPERUSER_ID, ids, context=context)]
def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
""" Temporary method to create custom notification email values for a given
model and document. This should be better to have a headers field on
the mail.mail model, computed when creating the notification email, but
this cannot be done in a stable version.
TDE FIXME: rethink this ulgy thing. """
res = dict()
return res
#------------------------------------------------------ #------------------------------------------------------
# Mail gateway # Mail gateway
#------------------------------------------------------ #------------------------------------------------------
@ -1301,13 +1314,13 @@ class mail_thread(osv.AbstractModel):
msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
if message.get('In-Reply-To'): if message.get('In-Reply-To'):
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))]) parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To'].strip()))])
if parent_ids: if parent_ids:
msg_dict['parent_id'] = parent_ids[0] msg_dict['parent_id'] = parent_ids[0]
if message.get('References') and 'parent_id' not in msg_dict: if message.get('References') and 'parent_id' not in msg_dict:
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', msg_list = mail_header_msgid_re.findall(decode(message['References']))
[x.strip() for x in decode(message['References']).split()])]) parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in', [x.strip() for x in msg_list])])
if parent_ids: if parent_ids:
msg_dict['parent_id'] = parent_ids[0] msg_dict['parent_id'] = parent_ids[0]

View File

@ -267,10 +267,7 @@ class mail_compose_message(osv.TransientModel):
# mass mailing: rendering override wizard static values # mass mailing: rendering override wizard static values
if mass_mail_mode and wizard.model: if mass_mail_mode and wizard.model:
# always keep a copy, reset record name (avoid browsing records) # always keep a copy, reset record name (avoid browsing records)
mail_values.update(notification=True, record_name=False) mail_values.update(notification=True, model=wizard.model, res_id=res_id, record_name=False)
if hasattr(self.pool[wizard.model], 'message_new'):
mail_values['model'] = wizard.model
mail_values['res_id'] = res_id
# auto deletion of mail_mail # auto deletion of mail_mail
if 'mail_auto_delete' in context: if 'mail_auto_delete' in context:
mail_values['auto_delete'] = context.get('mail_auto_delete') mail_values['auto_delete'] = context.get('mail_auto_delete')

View File

@ -84,7 +84,8 @@ class MailMail(osv.Model):
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None): def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context) res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context)
if mail.mailing_id and res.get('body') and res.get('email_to'): if mail.mailing_id and res.get('body') and res.get('email_to'):
email_to = tools.email_split(res.get('email_to')[0]) emails = tools.email_split(res.get('email_to')[0])
email_to = emails and emails[0] or False
unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context) unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context)
if unsubscribe_url: if unsubscribe_url:
res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p') res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p')

View File

@ -591,6 +591,7 @@
<field name="mail_mail_id"/> <field name="mail_mail_id"/>
<field name="message_id"/> <field name="message_id"/>
<field name="sent"/> <field name="sent"/>
<field name="exception"/>
<field name="opened"/> <field name="opened"/>
<field name="replied"/> <field name="replied"/>
<field name="bounced"/> <field name="bounced"/>

View File

@ -1071,8 +1071,8 @@ class mrp_production(osv.osv):
return False return False
# Take routing location as a Source Location. # Take routing location as a Source Location.
source_location_id = production.location_src_id.id source_location_id = production.location_src_id.id
if production.bom_id.routing_id and production.bom_id.routing_id.location_id: if production.routing_id and production.routing_id.location_id:
source_location_id = production.bom_id.routing_id.location_id.id source_location_id = production.routing_id.location_id.id
destination_location_id = production.product_id.property_stock_production.id destination_location_id = production.product_id.property_stock_production.id
if not source_location_id: if not source_location_id:

View File

@ -678,6 +678,17 @@ class product_template(osv.osv):
if not context or "create_product_product" not in context: if not context or "create_product_product" not in context:
self.create_variant_ids(cr, uid, [product_template_id], context=context) self.create_variant_ids(cr, uid, [product_template_id], context=context)
self._set_standard_price(cr, uid, product_template_id, vals.get('standard_price', 0.0), context=context) self._set_standard_price(cr, uid, product_template_id, vals.get('standard_price', 0.0), context=context)
# TODO: this is needed to set given values to first variant after creation
# these fields should be moved to product as lead to confusion
related_vals = {}
if vals.get('ean13'):
related_vals['ean13'] = vals['ean13']
if vals.get('default_code'):
related_vals['default_code'] = vals['default_code']
if related_vals:
self.write(cr, uid, product_template_id, related_vals, context=context)
return product_template_id return product_template_id
def write(self, cr, uid, ids, vals, context=None): def write(self, cr, uid, ids, vals, context=None):

View File

@ -27,7 +27,7 @@ class account_invoice(osv.Model):
template_values = Composer.onchange_template_id( template_values = Composer.onchange_template_id(
cr, uid, composer_id, line.product_id.email_template_id.id, 'comment', 'account.invoice', invoice.id cr, uid, composer_id, line.product_id.email_template_id.id, 'comment', 'account.invoice', invoice.id
)['value'] )['value']
template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', '[]')] template_values['attachment_ids'] = [(4, id) for id in template_values.get('attachment_ids', [])]
Composer.write(cr, uid, [composer_id], template_values, context=context) Composer.write(cr, uid, [composer_id], template_values, context=context)
Composer.send_mail(cr, uid, [composer_id], context=context) Composer.send_mail(cr, uid, [composer_id], context=context)
return True return True

View File

@ -50,8 +50,8 @@
<field name="user_id" ref="base.user_demo"/> <field name="user_id" ref="base.user_demo"/>
<field name="alias_model">project.task</field> <field name="alias_model">project.task</field>
<field name="message_follower_ids" eval="[(6, 0, [ <field name="message_follower_ids" eval="[(6, 0, [
ref('base.user_root'), ref('base.partner_root'),
ref('base.user_demo')])]"/> ref('base.partner_demo')])]"/>
</record> </record>
<!-- We assign after so that default values applies --> <!-- We assign after so that default values applies -->

View File

@ -27,11 +27,11 @@ class project_configuration(osv.osv_memory):
_inherit = 'res.config.settings' _inherit = 'res.config.settings'
_columns = { _columns = {
'module_project_mrp': fields.boolean('Generate tasks from sale orders', 'module_sale_service': fields.boolean('Generate tasks from sale orders',
help='This feature automatically creates project tasks from service products in sale orders. ' help='This feature automatically creates project tasks from service products in sale orders. '
'More precisely, tasks are created for procurement lines with product of type \'Service\', ' 'More precisely, tasks are created for procurement lines with product of type \'Service\', '
'procurement method \'Make to Order\', and supply method \'Manufacture\'.\n' 'procurement method \'Make to Order\', and supply method \'Manufacture\'.\n'
'-This installs the module project_mrp.'), '-This installs the module sale_service.'),
'module_pad': fields.boolean("Use integrated collaborative note pads on task", 'module_pad': fields.boolean("Use integrated collaborative note pads on task",
help='Lets the company customize which Pad installation should be used to link to new pads ' help='Lets the company customize which Pad installation should be used to link to new pads '
'(for example: http://ietherpad.com/).\n' '(for example: http://ietherpad.com/).\n'

View File

@ -33,8 +33,8 @@
<label for="module_project_timesheet"/> <label for="module_project_timesheet"/>
</div> </div>
<div> <div>
<field name="module_project_mrp" class="oe_inline"/> <field name="module_sale_service" class="oe_inline"/>
<label for="module_project_mrp"/> <label for="module_sale_service"/>
</div> </div>
<div> <div>
<field name="module_pad" class="oe_inline"/> <field name="module_pad" class="oe_inline"/>

View File

@ -137,7 +137,7 @@ class project_work(osv.osv):
amount = vals_line['unit_amount'] amount = vals_line['unit_amount']
prod_id = vals_line['product_id'] prod_id = vals_line['product_id']
unit = False unit = False
timeline_id = timesheet_obj.create(cr, uid, vals=vals_line, context=context) timeline_id = timesheet_obj.create(cr, uid, vals_line, context=context)
# Compute based on pricetype # Compute based on pricetype
amount_unit = timesheet_obj.on_change_unit_amount(cr, uid, timeline_id, amount_unit = timesheet_obj.on_change_unit_amount(cr, uid, timeline_id,

View File

@ -1310,6 +1310,7 @@ class procurement_order(osv.osv):
if available_draft_po_ids: if available_draft_po_ids:
po_id = available_draft_po_ids[0] po_id = available_draft_po_ids[0]
po_rec = po_obj.browse(cr, uid, po_id, context=context) 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: 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) po_obj.write(cr, uid, [po_id], {'date_order': purchase_date}, context=context)
#look for any other PO line in the selected PO with same product and UoM to sum quantities instead of creating a new po line #look for any other PO line in the selected PO with same product and UoM to sum quantities instead of creating a new po line
@ -1369,8 +1370,11 @@ class product_template(osv.Model):
_inherit = 'product.template' _inherit = 'product.template'
def _get_buy_route(self, cr, uid, context=None): 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] buy_route = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'purchase.route_warehouse0_buy')
if buy_route:
return [buy_route]
return []
def _purchase_count(self, cr, uid, ids, field_name, arg, context=None): def _purchase_count(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, 0) res = dict.fromkeys(ids, 0)

View File

@ -79,9 +79,7 @@ class stock_move(osv.osv):
if move.purchase_line_id: if move.purchase_line_id:
purchase_line = 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['invoice_line_tax_id'] = [(6, 0, [x.id for x in purchase_line.taxes_id])]
#res['account_analytic_id'] = purc_line.order_id.project_id and sale_line.order_id.project_id.id or False
res['price_unit'] = purchase_line.price_unit res['price_unit'] = purchase_line.price_unit
#res['discount'] = sale_line.discount
return res return res
class stock_picking(osv.osv): class stock_picking(osv.osv):

View File

@ -134,14 +134,15 @@ class Report(osv.Model):
website = None website = None
if request and hasattr(request, 'website'): if request and hasattr(request, 'website'):
website = request.website website = request.website
values.update({ values.update(
'time': time, time=time,
'translate_doc': translate_doc, translate_doc=translate_doc,
'editable': True, # Will active inherit_branding editable=True, # Will active inherit_branding
'user': user, user=user,
'res_company': user.company_id, res_company=user.company_id,
'website': website, website=website,
}) editable_no_editor=True,
)
return view_obj.render(cr, uid, template, values, context=context) return view_obj.render(cr, uid, template, values, context=context)
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------

View File

@ -122,7 +122,7 @@ Example: 10% for retailers, promotion of 5 EUR on this product, etc."""),
def onchange_task_work(self, cr, uid, ids, task_work, context=None): def onchange_task_work(self, cr, uid, ids, task_work, context=None):
return {'value': { return {'value': {
'module_project_timesheet': task_work, 'module_project_timesheet': task_work,
'module_project_mrp': task_work, 'module_sale_service': task_work,
}} }}
def onchange_timesheet(self, cr, uid, ids, timesheet, context=None): def onchange_timesheet(self, cr, uid, ids, timesheet, context=None):

View File

@ -128,13 +128,12 @@ class sale_order(osv.osv):
sale_clause = '' sale_clause = ''
no_invoiced = False no_invoiced = False
for arg in args: for arg in args:
if arg[1] == '=': if (arg[1] == '=' and arg[2]) or (arg[1] == '!=' and not arg[2]):
if arg[2]: clause += 'AND inv.state = \'paid\''
clause += 'AND inv.state = \'paid\'' else:
else: clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id '
clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id ' sale_clause = ', sale_order AS sale '
sale_clause = ', sale_order AS sale ' no_invoiced = True
no_invoiced = True
cursor.execute('SELECT rel.order_id ' \ cursor.execute('SELECT rel.order_id ' \
'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \ 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \
@ -686,7 +685,7 @@ class sale_order(osv.osv):
def procurement_needed(self, cr, uid, ids, context=None): def procurement_needed(self, cr, uid, ids, context=None):
#when sale is installed only, there is no need to create procurements, that's only #when sale is installed only, there is no need to create procurements, that's only
#further installed modules (project_mrp, sale_stock) that will change this. #further installed modules (sale_service, sale_stock) that will change this.
sale_line_obj = self.pool.get('sale.order.line') sale_line_obj = self.pool.get('sale.order.line')
res = [] res = []
for order in self.browse(cr, uid, ids, context=context): for order in self.browse(cr, uid, ids, context=context):
@ -839,7 +838,7 @@ class sale_order_line(osv.osv):
def need_procurement(self, cr, uid, ids, context=None): def need_procurement(self, cr, uid, ids, context=None):
#when sale is installed only, there is no need to create procurements, that's only #when sale is installed only, there is no need to create procurements, that's only
#further installed modules (project_mrp, sale_stock) that will change this. #further installed modules (sale_service, sale_stock) that will change this.
return False return False
def _amount_line(self, cr, uid, ids, field_name, arg, context=None): def _amount_line(self, cr, uid, ids, field_name, arg, context=None):

View File

@ -19,6 +19,6 @@
# #
############################################################################## ##############################################################################
import project_mrp import models
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -50,8 +50,8 @@ completed.
'website': 'http://www.openerp.com', 'website': 'http://www.openerp.com',
'images': ['images/product.jpeg', 'images/task_from_SO.jpeg'], 'images': ['images/product.jpeg', 'images/task_from_SO.jpeg'],
'depends': ['project', 'procurement', 'sale', 'procurement_jit'], 'depends': ['project', 'procurement', 'sale', 'procurement_jit'],
'data': ['project_mrp_view.xml'], #'process/project_mrp_process.xml' 'data': ['views/sale_service_view.xml'],
'demo': ['project_mrp_demo.xml'], 'demo': ['demo/sale_service_demo.xml'],
'test': ['test/project_task_procurement.yml'], 'test': ['test/project_task_procurement.yml'],
'installable': True, 'installable': True,
'auto_install': False, 'auto_install': False,

Some files were not shown because too many files have changed in this diff Show More