class InvoiceConditionText(Model): """add info condition in the invoice""" _name = "account.condition_text" _description = "Invoices conditions" _columns = { 'name': fields.char('Condition summary', required=True, size=128), 'type': fields.selection([('header', 'Top condition'), ('footer', 'Bottom condition')], 'type', required=True), 'text': fields.html('Condition', translate=True, required=True)}
class AccountInvoice(Model): """Inherit account.invoice in order to add bvr printing functionnalites. BVR is a Swiss payment vector""" _inherit = "account.invoice" _compile_get_ref = re.compile('[^0-9]') def _get_reference_type(self, cursor, user, context=None): """Function use by the function field reference_type in order to initalise available BVR Reference Types""" res = super(AccountInvoice, self)._get_reference_type(cursor, user, context=context) res.append(('bvr', 'BVR')) return res def _compute_full_bvr_name(self, cursor, uid, ids, field_names, arg, context=None): res = {} move_line_obj = self.pool.get('account.move.line') account_obj = self.pool.get('account.account') tier_account_id = account_obj.search( cursor, uid, [('type', 'in', ['receivable', 'payable'])]) for inv in self.browse(cursor, uid, ids, context=context): move_lines = move_line_obj.search( cursor, uid, [('move_id', '=', inv.move_id.id), ('account_id', 'in', tier_account_id)]) if move_lines: if len(move_lines) == 1: res[inv.id] = self._space(inv.get_bvr_ref()) else: refs = [] for move_line in move_line_obj.browse(cursor, uid, move_lines, context=context): refs.append(self._space(move_line.get_bvr_ref())) res[inv.id] = ' ; '.join(refs) return res _columns = { ### BVR reference type BVR or FREE 'reference_type': fields.selection(_get_reference_type, 'Reference Type', required=True), ### Partner bank link between bank and partner id 'partner_bank_id': fields.many2one( 'res.partner.bank', 'Bank Account', help= 'The partner bank account to pay\nKeep empty to use the default'), 'bvr_reference': fields.function(_compute_full_bvr_name, type="char", size=512, string="BVR REF.", store=True, readonly=True) } def get_bvr_ref(self, cursor, uid, inv_id, context=None): """Retrieve ESR/BVR reference form invoice in order to print it""" res = '' if isinstance(inv_id, list): inv_id = inv_id[0] inv = self.browse(cursor, uid, inv_id, context=context) ## We check if the type is bvr, if not we return false if inv.partner_bank_id.state != 'bvr': return '' ## if inv.partner_bank_id.bvr_adherent_num: res = inv.partner_bank_id.bvr_adherent_num invoice_number = '' if inv.number: invoice_number = self._compile_get_ref.sub('', inv.number) return mod10r(res + invoice_number.rjust(26 - len(res), '0')) def _space(self, nbr, nbrspc=5): """Spaces * 5. Example: self._space('123456789012345') '12 34567 89012 345' """ return ''.join([' '[(i - 2) % nbrspc:] + c for i, c in enumerate(nbr)]) def _update_ref_on_account_analytic_line(self, cr, uid, ref, move_id, context=None): cr.execute( 'UPDATE account_analytic_line SET ref=%s' ' FROM account_move_line ' ' WHERE account_move_line.move_id = %s ' ' AND account_analytic_line.move_id = account_move_line.id', (ref, move_id)) return True def action_number(self, cr, uid, ids, context=None): res = super(AccountInvoice, self).action_number(cr, uid, ids, context=context) move_line_obj = self.pool.get('account.move.line') account_obj = self.pool.get('account.account') tier_account_id = account_obj.search( cr, uid, [('type', 'in', ['receivable', 'payable'])]) for inv in self.browse(cr, uid, ids, context=context): if inv.type != 'out_invoice' and inv.partner_bank_id.state != 'bvr': continue move_lines = move_line_obj.search( cr, uid, [('move_id', '=', inv.move_id.id), ('account_id', 'in', tier_account_id)]) # We keep this branch for compatibility with single BVR report. # This should be cleaned when porting to V8 if move_lines: if len(move_lines) == 1: ref = inv.get_bvr_ref() move_id = inv.move_id if move_id: cr.execute( 'UPDATE account_move_line SET transaction_ref=%s' ' WHERE move_id=%s', (ref, move_id.id)) self._update_ref_on_account_analytic_line( cr, uid, ref, move_id.id) else: for move_line in move_line_obj.browse(cr, uid, move_lines, context=context): ref = move_line.get_bvr_ref() if ref: cr.execute( 'UPDATE account_move_line SET transaction_ref=%s' ' WHERE id=%s', (ref, move_line.id)) self._update_ref_on_account_analytic_line( cr, uid, ref, move_line.move_id.id) return res def copy(self, cursor, uid, inv_id, default=None, context=None): default = default or {} default.update({'reference': False}) return super(AccountInvoice, self).copy(cursor, uid, inv_id, default, context)
class CreditControlPolicyLevel(Model): """Define a policy level. A level allows to determine if a move line is due and the level of overdue of the line""" _name = "credit.control.policy.level" _order = 'level' _description = """A credit control policy level""" _columns = { 'policy_id': fields.many2one('credit.control.policy', 'Related Policy', required=True), 'name': fields.char('Name', size=128, required=True, translate=True), 'level': fields.integer('Level', required=True), 'computation_mode': fields.selection([('net_days', 'Due Date'), ('end_of_month', 'Due Date, End Of Month'), ('previous_date', 'Previous Reminder')], 'Compute Mode', required=True), 'delay_days': fields.integer('Delay (in days)', required='True'), 'email_template_id': fields.many2one('email.template', 'Email Template', required=True), 'channel': fields.selection([('letter', 'Letter'), ('email', 'Email')], 'Channel', required=True), 'custom_text': fields.text('Custom Message', required=True, translate=True), 'custom_mail_text': fields.text('Custom Mail Message', required=True, translate=True), } def _check_level_mode(self, cr, uid, rids, context=None): """ The smallest level of a policy cannot be computed on the "previous_date". Return False if this happens. """ if isinstance(rids, (int, long)): rids = [rids] for level in self.browse(cr, uid, rids, context): smallest_level_id = self.search( cr, uid, [('policy_id', '=', level.policy_id.id)], order='level asc', limit=1, context=context) smallest_level = self.browse(cr, uid, smallest_level_id[0], context) if smallest_level.computation_mode == 'previous_date': return False return True _sql_constraint = [('unique level', 'UNIQUE (policy_id, level)', 'Level must be unique per policy')] _constraints = [ (_check_level_mode, 'The smallest level can not be of type Previous Reminder', ['level']) ] def _previous_level(self, cr, uid, policy_level, context=None): """ For one policy level, returns the id of the previous level If there is no previous level, it returns None, it means that's the first policy level :param browse_record policy_level: policy level :return: previous level id or None if there is no previous level """ previous_level_ids = self.search( cr, uid, [('policy_id', '=', policy_level.policy_id.id), ('level', '<', policy_level.level)], order='level desc', limit=1, context=context) return previous_level_ids[0] if previous_level_ids else None # ----- sql time related methods --------- def _net_days_get_boundary(self): return " (mv_line.date_maturity + %(delay)s)::date <= date(%(controlling_date)s)" def _end_of_month_get_boundary(self): return ( "(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+INTERVAL '1 MONTH - 1 day')::date" "<= date(%(controlling_date)s)") def _previous_date_get_boundary(self): return "(cr_line.date + %(delay)s)::date <= date(%(controlling_date)s)" def _get_sql_date_boundary_for_computation_mode(self, cr, uid, level, controlling_date, context=None): """Return a where clauses statement for the given controlling date and computation mode of the level""" fname = "_%s_get_boundary" % (level.computation_mode, ) if hasattr(self, fname): fnc = getattr(self, fname) return fnc() else: raise NotImplementedError( _('Can not get function for computation mode: ' '%s is not implemented') % (fname, )) # ----------------------------------------- def _get_first_level_move_line_ids(self, cr, uid, level, controlling_date, lines, context=None): """Retrieve all the move lines that are linked to a first level. We use Raw SQL for performance. Security rule where applied in policy object when the first set of lines were retrieved""" level_lines = set() if not lines: return level_lines sql = ( "SELECT DISTINCT mv_line.id\n" " FROM account_move_line mv_line\n" " WHERE mv_line.id in %(line_ids)s\n" " AND NOT EXISTS (SELECT id\n" " FROM credit_control_line\n" " WHERE move_line_id = mv_line.id\n" # lines from a previous level with a draft or ignored state # have to be generated again for the previous level " AND state not in ('draft', 'ignored'))") sql += " AND" sql += self._get_sql_date_boundary_for_computation_mode( cr, uid, level, controlling_date, context) data_dict = { 'controlling_date': controlling_date, 'line_ids': tuple(lines), 'delay': level.delay_days } cr.execute(sql, data_dict) res = cr.fetchall() if res: level_lines.update([x[0] for x in res]) return level_lines def _get_other_level_move_line_ids(self, cr, uid, level, controlling_date, lines, context=None): """ Retrieve the move lines for other levels than first level. """ level_lines = set() if not lines: return level_lines sql = ( "SELECT mv_line.id\n" " FROM account_move_line mv_line\n" " JOIN credit_control_line cr_line\n" " ON (mv_line.id = cr_line.move_line_id)\n" " WHERE cr_line.id = (SELECT credit_control_line.id FROM credit_control_line\n" " WHERE credit_control_line.move_line_id = mv_line.id\n" " AND state != 'ignored'" " ORDER BY credit_control_line.level desc limit 1)\n" " AND cr_line.level = %(previous_level)s\n" # lines from a previous level with a draft or ignored state # have to be generated again for the previous level " AND cr_line.state not in ('draft', 'ignored')\n" " AND mv_line.id in %(line_ids)s\n") sql += " AND " sql += self._get_sql_date_boundary_for_computation_mode( cr, uid, level, controlling_date, context) previous_level_id = self._previous_level(cr, uid, level, context=context) previous_level = self.browse(cr, uid, previous_level_id, context=context) data_dict = { 'controlling_date': controlling_date, 'line_ids': tuple(lines), 'delay': level.delay_days, 'previous_level': previous_level.level } # print cr.mogrify(sql, data_dict) cr.execute(sql, data_dict) res = cr.fetchall() if res: level_lines.update([x[0] for x in res]) return level_lines def get_level_lines(self, cr, uid, level_id, controlling_date, lines, context=None): """get all move lines in entry lines that match the current level""" assert not (isinstance(level_id, list) and len(level_id) > 1), \ "level_id: only one id expected" if isinstance(level_id, list): level_id = level_id[0] matching_lines = set() level = self.browse(cr, uid, level_id, context=context) if self._previous_level(cr, uid, level, context=context) is None: method = self._get_first_level_move_line_ids else: method = self._get_other_level_move_line_ids matching_lines.update( method(cr, uid, level, controlling_date, lines, context=context)) return matching_lines
class scan_bvr(TransientModel): _name = "scan.bvr" _description = "BVR/ESR Scanning Wizard" _columns = { 'journal_id': fields.many2one('account.journal', string="Invoice journal"), 'bvr_string': fields.char(size=128, string='BVR String'), 'partner_id': fields.many2one('res.partner', string="Partner"), 'bank_account_id': fields.many2one('res.partner.bank', string="Partner Bank Account"), 'state': fields.selection( [ ('new', 'New'), ('valid', 'valid'), ('need_extra_info', 'Need extra information'), ], 'State' ), } def _default_journal(self, cr, uid, context=None): pool = pooler.get_pool(cr.dbname) user = pool.get('res.users').browse(cr, uid, uid, context=context) if user.company_id: # We will get purchase journal linked with this company journal_ids = pool.get('account.journal').search( cr, uid, [('type', '=', 'purchase'), ('company_id', '=', user.company_id.id)], context=context ) if len(journal_ids) == 1: return journal_ids[0] else: return False else: return False _defaults = { 'state': 'new', 'journal_id': _default_journal, } def _check_number(self, part_validation): nTab = [0, 9, 4, 6, 8, 2, 7, 1, 3, 5] resultnumber = 0 for number in part_validation: resultnumber = nTab[(resultnumber + int(number) - 0) % 10] return (10 - resultnumber) % 10 def _construct_bvrplus_in_chf(self, bvr_string): if len(bvr_string) != 43: raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in first part') ) elif self._check_number(bvr_string[0:2]) != int(bvr_string[2]): raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in second part') ) elif self._check_number(bvr_string[4:30]) != int(bvr_string[30]): raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in third part') ) elif self._check_number(bvr_string[33:41]) != int(bvr_string[41]): raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in fourth part') ) else: bvr_struct = { 'type': bvr_string[0:2], 'amount': 0.0, 'reference': bvr_string[4:31], 'bvrnumber': bvr_string[4:10], 'beneficiaire': self._create_bvr_account( bvr_string[33:42] ), 'domain': '', 'currency': '' } return bvr_struct def _construct_bvr_in_chf(self, bvr_string): if len(bvr_string) != 53: raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in first part') ) elif self._check_number(bvr_string[0:12]) != int(bvr_string[12]): raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in second part') ) elif self._check_number(bvr_string[14:40]) != int(bvr_string[40]): raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in third part') ) elif self._check_number(bvr_string[43:51]) != int(bvr_string[51]): raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in fourth part') ) else: bvr_struct = { 'type': bvr_string[0:2], 'amount': float(bvr_string[2:12]) / 100, 'reference': bvr_string[14:41], 'bvrnumber': bvr_string[14:20], 'beneficiaire': self._create_bvr_account( bvr_string[43:52] ), 'domain': '', 'currency': '' } return bvr_struct def _construct_bvr_postal_in_chf(self, bvr_string): if len(bvr_string) != 42: raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in first part') ) else: bvr_struct = { 'type': bvr_string[0:2], 'amount': float(bvr_string[2:12]) / 100, 'reference': bvr_string[14:30], 'bvrnumber': '', 'beneficiaire': self._create_bvr_account( bvr_string[32:41] ), 'domain': '', 'currency': '' } return bvr_struct def _construct_bvr_postal_other_in_chf(self, bvr_string): if len(bvr_string) != 41: raise orm.except_orm( _('Validation Error'), _('BVR CheckSum Error in first part') ) else: bvr_struct = { 'type': bvr_string[0:2], 'amount': float(bvr_string[7:16]) / 100, 'reference': bvr_string[18:33], 'bvrnumber': '000000', 'beneficiaire': self._create_bvr_account( bvr_string[34:40] ), 'domain': '', 'currency': '' } return bvr_struct def _create_invoice_line(self, cr, uid, ids, data, context): invoice_line_ids = False pool = pooler.get_pool(cr.dbname) invoice_line_obj = pool.get('account.invoice.line') # First we write partner_id self.write(cr, uid, ids, {'partner_id': data['partner_id']}) # We check that this partner have a default product accounts_data = pool.get('res.partner').read( cr, uid, data['partner_id'], ['supplier_invoice_default_product'], context=context ) if accounts_data['supplier_invoice_default_product']: product_onchange_result = invoice_line_obj.product_id_change( cr, uid, ids, accounts_data['supplier_invoice_default_product'][0], uom_id=False, qty=0, name='', type='in_invoice', partner_id=data['partner_id'], fposition_id=False, price_unit=False, currency_id=False, context=context, company_id=None ) # We will check that the tax specified # on the product is price include or amount is 0 if product_onchange_result['value']['invoice_line_tax_id']: taxes = pool.get('account.tax').browse( cr, uid, product_onchange_result['value']['invoice_line_tax_id'] ) for taxe in taxes: if not taxe.price_include and taxe.amount != 0.0: raise orm.except_orm( _('Error !'), _('The default product in this partner has ' 'wrong taxes configuration') ) prod = accounts_data['supplier_invoice_default_product'][0] account = product_onchange_result['value']['account_id'] taxes = product_onchange_result['value']['invoice_line_tax_id'] invoice_line_vals = { 'product_id': prod, 'account_id': account, 'name': product_onchange_result['value']['name'], 'uos_id': product_onchange_result['value']['uos_id'], 'price_unit': data['bvr_struct']['amount'], 'invoice_id': data['invoice_id'], 'invoice_line_tax_id': [(6, 0, taxes)], } invoice_line_ids = invoice_line_obj.create( cr, uid, invoice_line_vals, context=context) return invoice_line_ids def _create_direct_invoice(self, cr, uid, ids, data, context): pool = pooler.get_pool(cr.dbname) # We will call the function, that create invoice line account_invoice_obj = pool.get('account.invoice') account_invoice_tax_obj = pool.get('account.invoice.tax') if data['bank_account']: account_info = pool.get('res.partner.bank').browse( cr, uid, data['bank_account'], context=context ) # We will now search the currency_id currency_search = pool.get('res.currency').search( cr, uid, [('name', '=', data['bvr_struct']['currency'])], context=context ) currency_id = pool.get('res.currency').browse(cr, uid, currency_search[0], context=context) # Account Modification if data['bvr_struct']['domain'] == 'name': pool.get('res.partner.bank').write( cr, uid, data['bank_account'], {'post_number': data['bvr_struct']['beneficiaire']}, context=context ) else: pool.get('res.partner.bank').write( cr, uid, data['bank_account'], {'bvr_adherent_num': data['bvr_struct']['bvrnumber'], 'bvr_number': data['bvr_struct']['beneficiaire']}, context=context ) date_due = time.strftime('%Y-%m-%d') # We will now compute the due date and fixe the payment term payment_term_id = (account_info.partner_id.property_payment_term and account_info.partner_id.property_payment_term.id or False) if payment_term_id: # We Calculate due_date inv_mod = pool.get('account.invoice') res = inv_mod.onchange_payment_term_date_invoice( cr, uid, [], payment_term_id, time.strftime('%Y-%m-%d') ) date_due = res['value']['date_due'] curr_invoice = { 'name': time.strftime('%Y-%m-%d'), 'partner_id': account_info.partner_id.id, 'account_id': account_info.partner_id.property_account_payable.id, 'date_due': date_due, 'date_invoice': time.strftime('%Y-%m-%d'), 'payment_term': payment_term_id, 'reference_type': 'bvr', 'reference': data['bvr_struct']['reference'], 'amount_total': data['bvr_struct']['amount'], 'check_total': data['bvr_struct']['amount'], 'partner_bank_id': account_info.id, 'comment': '', 'currency_id': currency_id.id, 'journal_id': data['journal_id'], 'type': 'in_invoice', } last_invoice = account_invoice_obj.create( cr, uid, curr_invoice, context=context ) data['invoice_id'] = last_invoice self._create_invoice_line(cr, uid, ids, data, context) # Now we create taxes lines computed_tax = account_invoice_tax_obj.compute(cr, uid, last_invoice, context=context) inv = account_invoice_obj.browse(cr, uid, last_invoice, context=context) account_invoice_obj.check_tax_lines(cr, uid, inv, computed_tax, account_invoice_tax_obj) action = { 'domain': "[('id','=', " + str(last_invoice) + ")]", 'name': 'Invoices', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'account.invoice', 'view_id': False, 'context': "{'type':'out_invoice'}", 'type': 'ir.actions.act_window', 'res_id': last_invoice } return action def _create_bvr_account(self, account_unformated): acc_len = len(account_unformated) account_formated = "%s-%s-%s" % ( account_unformated[0:2], str(int(account_unformated[2:acc_len - 1])), account_unformated[acc_len - 1:acc_len] ) return account_formated def _get_bvr_structurated(self, bvr_string): if bvr_string is not False: # We will get the 2 frist digit of the BVr string in order # to now the BVR type of this account bvr_type = bvr_string[0:2] if bvr_type == '01' and len(bvr_string) == 42: # This BVR is the type of BVR in CHF # WE will call the function and Call bvr_struct = self._construct_bvr_postal_in_chf(bvr_string) # We will test if the BVR have an Adherent Number if not we # will make the search of the account base on # his name non base on the BVR adherent number if (bvr_struct['bvrnumber'] == '000000'): bvr_struct['domain'] = 'name' else: bvr_struct['domain'] = 'bvr_adherent_num' # We will set the currency , in this case it's allways CHF bvr_struct['currency'] = 'CHF' elif bvr_type == '01': # This BVR is the type of BVR in CHF # We will call the function and Call bvr_struct = self._construct_bvr_in_chf(bvr_string) # We will test if the BVR have an Adherent Number if not # we will make the search of the account base on # his name non base on the BVR adherent number if (bvr_struct['bvrnumber'] == '000000'): bvr_struct['domain'] = 'name' else: bvr_struct['domain'] = 'bvr_adherent_num' # We will set the currency , in this case it's allways CHF bvr_struct['currency'] = 'CHF' elif bvr_type == '03': # It will be (At this time) the same work # as for a standard BVR with 01 code bvr_struct = self._construct_bvr_postal_in_chf(bvr_string) # We will test if the BVR have an Adherent Number # if not we will make the search of the account base on # his name non base on the BVR adherent number if (bvr_struct['bvrnumber'] == '000000'): bvr_struct['domain'] = 'name' else: bvr_struct['domain'] = 'bvr_adherent_num' # We will set the currency , in this case it's allways CHF bvr_struct['currency'] = 'CHF' elif bvr_type == '04': # It the BVR postal in CHF bvr_struct = self._construct_bvrplus_in_chf(bvr_string) # We will test if the BVR have an Adherent Number # if not we will make the search of the account base on # his name non base on the BVR adherent number if (bvr_struct['bvrnumber'] == '000000'): bvr_struct['domain'] = 'name' else: bvr_struct['domain'] = 'bvr_adherent_num' # We will set the currency , in this case it's allways CHF bvr_struct['currency'] = 'CHF' elif bvr_type == '21': # It for a BVR in Euro bvr_struct = self._construct_bvr_in_chf(bvr_string) # We will test if the BVR have an Adherent Number if # not we will make the search of the account base on # his name non base on the BVR adherent number if (bvr_struct['bvrnumber'] == '000000'): bvr_struct['domain'] = 'name' else: bvr_struct['domain'] = 'bvr_adherent_num' # We will set the currency , in this case it's allways CHF bvr_struct['currency'] = 'EUR' ## elif bvr_type == '31': # It the BVR postal in CHF bvr_struct = self._construct_bvrplus_in_chf(bvr_string) # We will test if the BVR have an Adherent Number if not # we will make the search of the account base on # his name non base on the BVR adherent number if (bvr_struct['bvrnumber'] == '000000'): bvr_struct['domain'] = 'name' else: bvr_struct['domain'] = 'bvr_adherent_num' # We will set the currency , in this case it's allways CHF bvr_struct['currency'] = 'EUR' elif bvr_type[0:1] == '<' and len(bvr_string) == 41: # It the BVR postal in CHF bvr_struct = self._construct_bvr_postal_other_in_chf( bvr_string) # We will test if the BVR have an Adherent Number # if not we will make the search of the account base on # his name non base on the BVR adherent number if (bvr_struct['bvrnumber'] == '000000'): bvr_struct['domain'] = 'name' else: bvr_struct['domain'] = 'bvr_adherent_num' # We will set the currency , in this case it's allways CHF bvr_struct['currency'] = 'CHF' else: raise orm.except_orm(_('BVR Type error'), _('This kind of BVR is not supported ' 'at this time')) return bvr_struct def validate_bvr_string(self, cr, uid, ids, context): # We will now retrive result bvr_data = self.browse(cr, uid, ids, context)[0] # BVR Standrard # 0100003949753>120000000000234478943216899+ 010001628> # BVR without BVr Reference # 0100000229509>000000013052001000111870316+ 010618955> # BVR + In CHF # 042>904370000000000000007078109+ 010037882> # BVR In euro # 2100000440001>961116900000006600000009284+ 030001625> # <060001000313795> 110880150449186+ 43435> # <010001000165865> 951050156515104+ 43435> # <010001000060190> 052550152684006+ 43435> # # Explode and check the BVR Number and structurate it # data = {} data['bvr_struct'] = self._get_bvr_structurated( bvr_data.bvr_string) # We will now search the account linked with this BVR if data['bvr_struct']['domain'] == 'name': domain = [('acc_number', '=', data['bvr_struct']['beneficiaire'])] partner_bank_search = self.pool.get('res.partner.bank').search( cr, uid, domain, context=context ) else: domain = [ ('bvr_adherent_num', '=', data['bvr_struct']['bvrnumber']) ] partner_bank_search = self.pool.get('res.partner.bank').search( cr, uid, domain, context=context ) # We will need to know if we need to create invoice line if partner_bank_search: # We have found the account corresponding to the # bvr_adhreent_number # so we can directly create the account partner_bank_result = self.pool.get('res.partner.bank').browse( cr, uid, partner_bank_search[0], context=context ) data['id'] = bvr_data.id data['partner_id'] = partner_bank_result.partner_id.id data['bank_account'] = partner_bank_result.id data['journal_id'] = bvr_data.journal_id.id action = self._create_direct_invoice(cr, uid, ids, data, context) return action elif bvr_data.bank_account_id: data['id'] = bvr_data.id data['partner_id'] = bvr_data.partner_id.id data['journal_id'] = bvr_data.journal_id.id data['bank_account'] = bvr_data.bank_account_id.id action = self._create_direct_invoice(cr, uid, ids, data, context) return action else: # we haven't found a valid bvr_adherent_number # we will need to create or update a bank account self.write(cr, uid, ids, {'state': 'need_extra_info'}) return { 'type': 'ir.actions.act_window', 'res_model': 'scan.bvr', 'view_mode': 'form', 'view_type': 'form', 'res_id': bvr_data.id, 'views': [(False, 'form')], 'target': 'new', }
class CreditControlMarker(TransientModel): """Change the state of lines in mass""" _name = 'credit.control.marker' _description = 'Mass marker' def _get_line_ids(self, cr, uid, context=None): if context is None: context = {} res = False if (context.get('active_model') == 'credit.control.line' and context.get('active_ids')): res = self._filter_line_ids(cr, uid, context['active_ids'], context=context) return res _columns = { 'name': fields.selection([('ignored', 'Ignored'), ('to_be_sent', 'Ready To Send'), ('sent', 'Done')], 'Mark as', required=True), 'line_ids': fields.many2many('credit.control.line', string='Credit Control Lines', domain="[('state', '!=', 'sent')]"), } _defaults = { 'name': 'to_be_sent', 'line_ids': _get_line_ids, } def _filter_line_ids(self, cr, uid, active_ids, context=None): """get line to be marked filter done lines""" line_obj = self.pool.get('credit.control.line') domain = [('state', '!=', 'sent'), ('id', 'in', active_ids)] return line_obj.search(cr, uid, domain, context=context) def _mark_lines(self, cr, uid, filtered_ids, state, context=None): """write hook""" line_obj = self.pool.get('credit.control.line') if not state: raise ValueError(_('state can not be empty')) line_obj.write(cr, uid, filtered_ids, {'state': state}, context=context) return filtered_ids def mark_lines(self, cr, uid, wiz_id, context=None): """Write state of selected credit lines to the one in entry done credit line will be ignored""" assert not (isinstance(wiz_id, list) and len(wiz_id) > 1), \ "wiz_id: only one id expected" if isinstance(wiz_id, list): wiz_id = wiz_id[0] form = self.browse(cr, uid, wiz_id, context) if not form.line_ids: raise except_osv(_('Error'), _('No credit control lines selected.')) line_ids = [l.id for l in form.line_ids] filtered_ids = self._filter_line_ids(cr, uid, line_ids, context) if not filtered_ids: raise except_osv( _('Information'), _('No lines will be changed. All the selected lines are already done.' )) self._mark_lines(cr, uid, filtered_ids, form.name, context) return { 'domain': unicode([('id', 'in', filtered_ids)]), 'view_type': 'form', 'view_mode': 'tree,form', 'view_id': False, 'res_model': 'credit.control.line', 'type': 'ir.actions.act_window' }
class AccountInvoice(Model): """Inherit account.invoice in order to add bvr printing functionnalites. BVR is a Swiss payment vector""" _inherit = "account.invoice" _compile_get_ref = re.compile('[^0-9]') def _get_reference_type(self, cr, user, context=None): """Function used by the function field 'reference_type' in order to initalise available BVR Reference Types """ res = super(AccountInvoice, self)._get_reference_type(cr, user, context=context) res.append(('bvr', 'BVR')) return res def _compute_full_bvr_name(self, cr, uid, ids, field_names, arg, context=None): res = {} move_line_obj = self.pool.get('account.move.line') account_obj = self.pool.get('account.account') tier_account_id = account_obj.search( cr, uid, [('type', 'in', ['receivable', 'payable'])], context=context) for inv in self.browse(cr, uid, ids, context=context): move_lines = move_line_obj.search( cr, uid, [('move_id', '=', inv.move_id.id), ('account_id', 'in', tier_account_id)], context=context) if move_lines: refs = [] for move_line in move_line_obj.browse(cr, uid, move_lines, context=context): refs.append(AccountInvoice._space(move_line.get_bvr_ref())) res[inv.id] = ' ; '.join(refs) return res _columns = { # BVR reference type BVR or FREE 'reference_type': fields.selection(_get_reference_type, 'Reference Type', required=True), # Partner bank link between bank and partner id 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account', help='The partner bank account to pay\n' 'Keep empty to use the default'), 'bvr_reference': fields.function(_compute_full_bvr_name, type="char", size=512, string="BVR REF.", store=True, readonly=True) } @staticmethod def _space(nbr, nbrspc=5): """Spaces * 5. Example: AccountInvoice._space('123456789012345') '12 34567 89012 345' """ return ''.join([' '[(i - 2) % nbrspc:] + c for i, c in enumerate(nbr)]) def _update_ref_on_account_analytic_line(self, cr, uid, ref, move_id, context=None): """Propagate reference on analytic line""" cr.execute( 'UPDATE account_analytic_line SET ref=%s' ' FROM account_move_line ' ' WHERE account_move_line.move_id = %s ' ' AND account_analytic_line.move_id = account_move_line.id', (ref, move_id)) return True def _action_bvr_number_move_line(self, cr, uid, invoice, move_line, ref, context=None): """Propagate reference on move lines and analytic lines""" if not ref: return cr.execute( 'UPDATE account_move_line SET transaction_ref=%s' ' WHERE id=%s', (ref, move_line.id)) self._update_ref_on_account_analytic_line(cr, uid, ref, move_line.move_id.id) def action_number(self, cr, uid, ids, context=None): """ Copy the BVR/ESR reference in the transaction_ref of move lines. For customers invoices: the BVR reference is computed using ``get_bvr_ref()`` on the invoice or move lines. For suppliers invoices: the BVR reference is stored in the reference field of the invoice. """ res = super(AccountInvoice, self).action_number(cr, uid, ids, context=context) move_line_obj = self.pool.get('account.move.line') for inv in self.browse(cr, uid, ids, context=context): move_line_ids = move_line_obj.search( cr, uid, [('move_id', '=', inv.move_id.id), ('account_id', '=', inv.account_id.id)], context=context) if not move_line_ids: continue move_lines = move_line_obj.browse(cr, uid, move_line_ids, context=context) for move_line in move_lines: if inv.type in ('out_invoice', 'out_refund'): ref = move_line.get_bvr_ref() elif inv.reference_type == 'bvr' and inv.reference: ref = inv.reference else: ref = False self._action_bvr_number_move_line(cr, uid, inv, move_line, ref, context=context) return res def copy(self, cr, uid, inv_id, default=None, context=None): default = default or {} default.update({'reference': False}) return super(AccountInvoice, self).copy(cr, uid, inv_id, default, context)
class CreditControlRun(Model): """Credit Control run generate all credit control lines and reject""" _name = "credit.control.run" _rec_name = 'date' _description = """Credit control line generator""" _columns = { 'date': fields.date('Controlling Date', required=True), 'policy_ids': fields.many2many('credit.control.policy', rel="credit_run_policy_rel", id1='run_id', id2='policy_id', string='Policies', readonly=True, states={'draft': [('readonly', False)]}), 'report': fields.text('Report', readonly=True), 'state': fields.selection([ ('draft', 'Draft'), ('done', 'Done'), ], string='State', required=True, readonly=True), 'manual_ids': fields.many2many( 'account.move.line', rel="credit_runreject_rel", string='Lines to handle manually', help=('If a credit control line has been generated on a policy ' 'and the policy has been changed meantime, ' 'it has to be handled manually'), readonly=True), } def _get_policies(self, cr, uid, context=None): return self.pool.get('credit.control.policy').\ search(cr, uid, [], context=context) _defaults = { 'state': 'draft', 'policy_ids': _get_policies, } def _check_run_date(self, cr, uid, ids, controlling_date, context=None): """Ensure that there is no credit line in the future using controlling_date""" line_obj = self.pool.get('credit.control.line') lines = line_obj.search(cr, uid, [('date', '>', controlling_date)], order='date DESC', limit=1, context=context) if lines: line = line_obj.browse(cr, uid, lines[0], context=context) raise except_osv( _('Error'), _('A run has already been executed more recently than %s') % (line.date)) return True def _generate_credit_lines(self, cr, uid, run_id, context=None): """ Generate credit control lines. """ cr_line_obj = self.pool.get('credit.control.line') assert not (isinstance(run_id, list) and len(run_id) > 1), \ "run_id: only one id expected" if isinstance(run_id, list): run_id = run_id[0] run = self.browse(cr, uid, run_id, context=context) manually_managed_lines = set() # line who changed policy credit_line_ids = [] # generated lines run._check_run_date(run.date, context=context) policies = run.policy_ids if not policies: raise except_osv(_('Error'), _('Please select a policy')) report = '' for policy in policies: if policy.do_nothing: continue lines = policy._get_move_lines_to_process(run.date, context=context) manual_lines = policy._lines_different_policy(lines, context=context) lines.difference_update(manual_lines) manually_managed_lines.update(manual_lines) policy_generated_ids = [] if lines: # policy levels are sorted by level so iteration is in the correct order for level in reversed(policy.level_ids): level_lines = level.get_level_lines(run.date, lines, context=context) policy_generated_ids += cr_line_obj.create_or_update_from_mv_lines( cr, uid, [], list(level_lines), level.id, run.date, context=context) if policy_generated_ids: report += _("Policy \"%s\" has generated %d Credit Control Lines.\n") % \ (policy.name, len(policy_generated_ids)) credit_line_ids += policy_generated_ids else: report += _( "Policy \"%s\" has not generated any Credit Control Lines.\n" % policy.name) vals = { 'state': 'done', 'report': report, 'manual_ids': [(6, 0, manually_managed_lines)] } run.write(vals, context=context) def generate_credit_lines(self, cr, uid, run_id, context=None): """Generate credit control lines Lock the ``credit_control_run`` Postgres table to avoid concurrent calls of this method. """ try: cr.execute('SELECT id FROM credit_control_run' ' LIMIT 1 FOR UPDATE NOWAIT') except Exception, exc: # in case of exception openerp will do a rollback for us and free the lock raise except_osv( _('Error'), _('A credit control run is already running' ' in background, please try later.'), str(exc)) self._generate_credit_lines(cr, uid, run_id, context) return True
class CreditControlLine(Model): """A credit control line describes an amount due by a customer for a due date. A line is created once the due date of the payment is exceeded. It is created in "draft" and some actions are available (send by email, print, ...) """ _name = "credit.control.line" _description = "A credit control line" _rec_name = "id" _columns = { 'date': fields.date('Controlling date', required=True), # maturity date of related move line we do not use a related field in order to # allow manual changes 'date_due': fields.date('Due date', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'date_sent': fields.date('Sent date', readonly=True, states={'draft': [('readonly', False)]}), 'state': fields.selection([('draft', 'Draft'), ('ignored', 'Ignored'), ('to_be_sent', 'Ready To Send'), ('sent', 'Done'), ('error', 'Error'), ('email_error', 'Emailing Error')], 'State', required=True, readonly=True, help=("Draft lines need to be triaged.\n" "Ignored lines are lines for which we do " "not want to send something.\n" "Draft and ignored lines will be " "generated again on the next run.")), 'channel': fields.selection([('letter', 'Letter'), ('email', 'Email')], 'Channel', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True), 'partner_id': fields.many2one('res.partner', "Partner", required=True), 'amount_due': fields.float('Due Amount Tax incl.', required=True, readonly=True), 'balance_due': fields.float('Due balance', required=True, readonly=True), 'mail_message_id': fields.many2one('mail.message', 'Sent Email', readonly=True), 'move_line_id': fields.many2one('account.move.line', 'Move line', required=True, readonly=True), 'account_id': fields.related('move_line_id', 'account_id', type='many2one', relation='account.account', string='Account', store=True, readonly=True), 'currency_id': fields.related('move_line_id', 'currency_id', type='many2one', relation='res.currency', string='Currency', store=True, readonly=True), 'company_id': fields.related('move_line_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True), # we can allow a manual change of policy in draft state 'policy_level_id': fields.many2one('credit.control.policy.level', 'Overdue Level', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'policy_id': fields.related('policy_level_id', 'policy_id', type='many2one', relation='credit.control.policy', string='Policy', store=True, readonly=True), 'level': fields.related('policy_level_id', 'level', type='integer', relation='credit.control.policy', string='Level', store=True, readonly=True), } _defaults = {'state': 'draft'} def _prepare_from_move_line(self, cr, uid, move_line, level, controlling_date, open_amount, context=None): """Create credit control line""" data = {} data['date'] = controlling_date data['date_due'] = move_line.date_maturity data['state'] = 'draft' data['channel'] = level.channel data[ 'invoice_id'] = move_line.invoice_id.id if move_line.invoice_id else False data['partner_id'] = move_line.partner_id.id data['amount_due'] = (move_line.amount_currency or move_line.debit or move_line.credit) data['balance_due'] = open_amount data['policy_level_id'] = level.id data['company_id'] = move_line.company_id.id data['move_line_id'] = move_line.id return data def create_or_update_from_mv_lines(self, cr, uid, ids, lines, level_id, controlling_date, context=None): """Create or update line based on levels""" currency_obj = self.pool.get('res.currency') level_obj = self.pool.get('credit.control.policy.level') ml_obj = self.pool.get('account.move.line') user = self.pool.get('res.users').browse(cr, uid, uid) currency_ids = currency_obj.search(cr, uid, [], context=context) tolerance = {} tolerance_base = user.company_id.credit_control_tolerance for c_id in currency_ids: tolerance[c_id] = currency_obj.compute( cr, uid, c_id, user.company_id.currency_id.id, tolerance_base, context=context) level = level_obj.browse(cr, uid, level_id, context) line_ids = [] for line in ml_obj.browse(cr, uid, lines, context): open_amount = line.amount_residual_currency if open_amount > tolerance.get(line.currency_id.id, tolerance_base): vals = self._prepare_from_move_line(cr, uid, line, level, controlling_date, open_amount, context=context) line_id = self.create(cr, uid, vals, context=context) line_ids.append(line_id) # when we have lines generated earlier in draft, # on the same level, it means that we have left # them, so they are to be considered as ignored previous_draft_ids = self.search( cr, uid, [('move_line_id', '=', line.id), ('level', '=', level.id), ('state', '=', 'draft'), ('id', '!=', line_id)], context=context) if previous_draft_ids: self.write(cr, uid, previous_draft_ids, {'state': 'ignored'}, context=context) return line_ids def unlink(self, cr, uid, ids, context=None, check=True): for line in self.browse(cr, uid, ids, context=context): if line.state != 'draft': raise osv.except_osv( _('Error !'), _('You are not allowed to delete a credit control line that ' 'is not in draft state.')) return super(CreditControlLine, self).unlink(cr, uid, ids, context=context)