class account_invoice(Model): ''' Inherit account.invoice in order to change BVR ref field type ''' _inherit = "account.invoice" _columns = { 'bvr_reference': fields.char( "BVR REF.", size=32, ) } def _check_bvr_ref(self, cr, uid, ids, context=None): record = self.browse(cr, uid, ids, context=context) for data in record: if not data.bvr_reference: return True # No check if no reference clean_ref = data.bvr_reference.replace(' ', '') if not clean_ref.isdigit() or len(clean_ref) > 27: return False clean_ref = clean_ref.rjust(27, '0') # Add zeros to the left if not clean_ref == mod10r(clean_ref[0:26]): return False return True _constraints = [ (_check_bvr_ref, 'Error: BVR ref should only contain number (max. 27) and spaces.', ['bvr_reference']) ]
class AccountMoveLine(Model): _inherit = "account.move.line" _compile_get_ref = re.compile('[^0-9]') _columns = { 'transaction_ref': fields.char('Transaction Ref.', size=128), } def init(self, cr): cr.execute('UPDATE account_move_line SET transaction_ref = ref' ' WHERE transaction_ref IS NULL' ' AND ref IS NOT NULL') return True def get_bvr_ref(self, cursor, uid, move_line_id, context=None): """Retrieve ESR/BVR reference from move line in order to print it""" res = '' if isinstance(move_line_id, (tuple, list)): assert len(move_line_id) == 1, "Only 1 ID expected" move_line_id = move_line_id[0] move_line = self.browse(cursor, uid, move_line_id, context=context) ## We check if the type is bvr, if not we return false if move_line.invoice.partner_bank_id.state != 'bvr': return '' ## if move_line.invoice.partner_bank_id.bvr_adherent_num: res = move_line.invoice.partner_bank_id.bvr_adherent_num move_number = '' if move_line.invoice.number: move_number = self._compile_get_ref.sub( '', str(move_line.invoice.number) + str(move_line_id)) return mod10r(res + move_number.rjust(26 - len(res), '0'))
class AccountTaxCode(Model): """Inherit account tax code in order to add a Case code""" _name = 'account.tax.code' _inherit = "account.tax.code" _columns = { 'code': fields.char('Case Code', size=512), }
class AccountMoveLine(Model): _inherit = "account.move.line" _compile_get_ref = re.compile('[^0-9]') _columns = { 'transaction_ref': fields.char('Transaction Ref.', size=128), } def _get_bvr_amount(self, cr, uid, move, rtype=None): """Hook to get amount in CHF for BVR""" return move.debit def get_bvr_ref(self, cr, uid, move_line_id, context=None): """Retrieve ESR/BVR reference from move line in order to print it Returns False when no BVR reference should be generated. No reference is generated when a transaction reference already exists for the line (likely been generated by a payment service). """ if isinstance(move_line_id, (tuple, list)): assert len(move_line_id) == 1, "Only 1 ID expected" move_line_id = move_line_id[0] move_line = self.browse(cr, uid, move_line_id, context=context) if not self._is_generate_bvr(cr, uid, move_line.invoice, context): return '' reference = self._compute_bvr_ref(cr, uid, move_line, context) if (move_line.transaction_ref and move_line.transaction_ref != reference): # the line has already a transaction id and it is not # a BVR reference return '' return reference def _is_generate_bvr(self, cr, uid, invoice, context=None): ''' Determine if BVR should be generated or not. ''' # We check if the type is bvr, if not we return false return (invoice.partner_bank_id and invoice.partner_bank_id.state == 'bvr') def _compute_bvr_ref(self, cr, uid, move_line, context=None): ''' Default BVR reference computation ''' res = '' if move_line.invoice.partner_bank_id.bvr_adherent_num: res = move_line.invoice.partner_bank_id.bvr_adherent_num move_number = '' if move_line.invoice.number: compound = str(move_line.invoice.number) + str(move_line.id) move_number = self._compile_get_ref.sub('', compound) reference = mod10r(res + move_number.rjust(26 - len(res), '0')) return reference
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 AccountMoveLine(Model): _inherit = "account.move.line" _compile_get_ref = re.compile('[^0-9]') _columns = { 'transaction_ref': fields.char('Transaction Ref.', size=128), } def get_bvr_ref(self, cursor, uid, move_line_id, context=None): """Retrieve ESR/BVR reference from move line in order to print it Returns False when no BVR reference should be generated. No reference is generated when a transaction reference already exists for the line (likely been generated by a payment service). """ res = '' if isinstance(move_line_id, (tuple, list)): assert len(move_line_id) == 1, "Only 1 ID expected" move_line_id = move_line_id[0] move_line = self.browse(cursor, uid, move_line_id, context=context) ## We check if the type is bvr, if not we return false if move_line.invoice.partner_bank_id.state != 'bvr': return '' ## if move_line.invoice.partner_bank_id.bvr_adherent_num: res = move_line.invoice.partner_bank_id.bvr_adherent_num move_number = '' if move_line.invoice.number: move_number = self._compile_get_ref.sub( '', str(move_line.invoice.number) + str(move_line_id)) reference = mod10r(res + move_number.rjust(26 - len(res), '0')) if (move_line.transaction_ref and move_line.transaction_ref != reference): # the line has already a transaction id and it is not # a BVR reference return '' return reference
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 CreditControlPolicy(Model): """Define a policy of reminder""" _name = "credit.control.policy" _description = """Define a reminder policy""" _columns = { 'name': fields.char('Name', required=True, size=128), 'level_ids': fields.one2many('credit.control.policy.level', 'policy_id', 'Policy Levels'), 'do_nothing': fields.boolean('Do nothing', help='For policies which should not ' 'generate lines or are obsolete'), 'company_id': fields.many2one('res.company', 'Company'), 'account_ids': fields.many2many('account.account', string='Accounts', required=True, domain="[('reconcile', '=', True)]", help="This policy will be active only" " for the selected accounts"), 'active': fields.boolean('Active'), } _defaults = { 'active': True, } def _move_lines_domain(self, cr, uid, policy, controlling_date, context=None): """Build the default domain for searching move lines""" account_ids = [a.id for a in policy.account_ids] return [('account_id', 'in', account_ids), ('date_maturity', '<=', controlling_date), ('reconcile_id', '=', False), ('partner_id', '!=', False)] def _due_move_lines(self, cr, uid, policy, controlling_date, context=None): """ Get the due move lines for the policy of the company. The set of ids will be reduced and extended according to the specific policies defined on partners and invoices. Do not use direct SQL in order to respect security rules. Assume that only the receivable lines have a maturity date and that accounts used in the policy are reconcilable. """ move_l_obj = self.pool.get('account.move.line') user = self.pool.get('res.users').browse(cr, uid, uid, context=context) if user.company_id.credit_policy_id.id != policy.id: return set() domain_line = self._move_lines_domain(cr, uid, policy, controlling_date, context=context) return set(move_l_obj.search(cr, uid, domain_line, context=context)) def _move_lines_subset(self, cr, uid, policy, controlling_date, model, move_relation_field, context=None): """ Get the move lines related to one model for a policy. Do not use direct SQL in order to respect security rules. Assume that only the receivable lines have a maturity date and that accounts used in the policy are reconcilable. The policy relation field must be named credit_policy_id. :param browse_record policy: policy :param str controlling_date: date of credit control :param str model: name of the model where is defined a credit_policy_id :param str move_relation_field: name of the field in account.move.line which is a many2one to `model` :return: set of ids to add in the process, set of ids to remove from the process """ # MARK possible place for a good optimisation my_obj = self.pool.get(model) move_l_obj = self.pool.get('account.move.line') default_domain = self._move_lines_domain(cr, uid, policy, controlling_date, context=context) to_add_ids = set() to_remove_ids = set() # The lines which are linked to this policy have to be included in the # run for this policy. # If another object override the credit_policy_id (ie. invoice after add_obj_ids = my_obj.search(cr, uid, [('credit_policy_id', '=', policy.id)], context=context) if add_obj_ids: domain = list(default_domain) domain.append((move_relation_field, 'in', add_obj_ids)) to_add_ids = set( move_l_obj.search(cr, uid, domain, context=context)) # The lines which are linked to another policy do not have to be # included in the run for this policy. neg_obj_ids = my_obj.search(cr, uid, [('credit_policy_id', '!=', policy.id), ('credit_policy_id', '!=', False)], context=context) if neg_obj_ids: domain = list(default_domain) domain.append((move_relation_field, 'in', neg_obj_ids)) to_remove_ids = set( move_l_obj.search(cr, uid, domain, context=context)) return to_add_ids, to_remove_ids def _get_partner_related_lines(self, cr, uid, policy, controlling_date, context=None): """ Get the move lines for a policy related to a partner. :param browse_record policy: policy :param str controlling_date: date of credit control :param str model: name of the model where is defined a credit_policy_id :param str move_relation_field: name of the field in account.move.line which is a many2one to `model` :return: set of ids to add in the process, set of ids to remove from the process """ return self._move_lines_subset(cr, uid, policy, controlling_date, 'res.partner', 'partner_id', context=context) def _get_invoice_related_lines(self, cr, uid, policy, controlling_date, context=None): """ Get the move lines for a policy related to an invoice. :param browse_record policy: policy :param str controlling_date: date of credit control :param str model: name of the model where is defined a credit_policy_id :param str move_relation_field: name of the field in account.move.line which is a many2one to `model` :return: set of ids to add in the process, set of ids to remove from the process """ return self._move_lines_subset(cr, uid, policy, controlling_date, 'account.invoice', 'invoice', context=context) def _get_move_lines_to_process(self, cr, uid, policy_id, controlling_date, context=None): """Build a list of move lines ids to include in a run for a policy at a given date. :param int/long policy: id of the policy :param str controlling_date: date of credit control :return: set of ids to include in the run """ assert not (isinstance(policy_id, list) and len(policy_id) > 1), \ "policy_id: only one id expected" if isinstance(policy_id, list): policy_id = policy_id[0] policy = self.browse(cr, uid, policy_id, context=context) # there is a priority between the lines, depicted by the calls below # warning, side effect method called on lines lines = self._due_move_lines(cr, uid, policy, controlling_date, context=context) add_ids, remove_ids = self._get_partner_related_lines(cr, uid, policy, controlling_date, context=context) lines = lines.union(add_ids).difference(remove_ids) add_ids, remove_ids = self._get_invoice_related_lines(cr, uid, policy, controlling_date, context=context) lines = lines.union(add_ids).difference(remove_ids) return lines def _lines_different_policy(self, cr, uid, policy_id, lines, context=None): """ Return a set of move lines ids for which there is an existing credit line but with a different policy. """ different_lines = set() if not lines: return different_lines assert not (isinstance(policy_id, list) and len(policy_id) > 1), \ "policy_id: only one id expected" if isinstance(policy_id, list): policy_id = policy_id[0] cr.execute( "SELECT move_line_id FROM credit_control_line" " WHERE policy_id != %s and move_line_id in %s", (policy_id, tuple(lines))) res = cr.fetchall() if res: different_lines.update([x[0] for x in res]) return different_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 CreditControlPrinter(TransientModel): """Print lines""" _name = "credit.control.printer" _rec_name = 'id' _description = 'Mass printer' 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 = context['active_ids'] return res _columns = { 'mark_as_sent': fields.boolean('Mark letter lines as sent', help="Only letter lines will be marked."), 'report_file': fields.binary('Generated Report', readonly=True), 'state': fields.char('state', size=32), 'line_ids': fields.many2many( 'credit.control.line', string='Credit Control Lines'), } _defaults = { 'mark_as_sent': True, 'line_ids': _get_line_ids, } def _filter_line_ids(self, cr, uid, active_ids, context=None): """filter lines to use in the wizard""" line_obj = self.pool.get('credit.control.line') domain = [('state', '=', 'to_be_sent'), ('id', 'in', active_ids), ('channel', '=', 'letter')] return line_obj.search(cr, uid, domain, context=context) def print_lines(self, cr, uid, wiz_id, context=None): assert not (isinstance(wiz_id, list) and len(wiz_id) > 1), \ "wiz_id: only one id expected" comm_obj = self.pool.get('credit.control.communication') if isinstance(wiz_id, list): wiz_id = wiz_id[0] form = self.browse(cr, uid, wiz_id, context) if not form.line_ids and not form.print_all: raise except_osv(_('Error'), _('No credit control lines selected.')) line_ids = [l.id for l in form.line_ids] comms = comm_obj._generate_comm_from_credit_line_ids( cr, uid, line_ids, context=context) report_file = comm_obj._generate_report(cr, uid, comms, context=context) form.write({'report_file': base64.b64encode(report_file), 'state': 'done'}) if form.mark_as_sent: filtered_ids = self._filter_line_ids(cr, uid, line_ids, context) comm_obj._mark_credit_line_as_sent(cr, uid, comms, context=context) return False # do not close the window, we need it to download the report
class ResPartner(Model): """Adds lastname and firstname, name become a stored function field""" _inherit = 'res.partner' def init(self, cursor): cursor.execute( 'SELECT id FROM res_partner WHERE lastname IS NOT NULL Limit 1') if not cursor.fetchone(): cursor.execute( 'UPDATE res_partner set lastname = name WHERE name IS NOT NULL' ) # Create Sql constraint if table is not empty cursor.execute('SELECT id FROM res_partner Limit 1') if cursor.fetchone(): cursor.execute( 'ALTER TABLE res_partner ALTER COLUMN lastname SET NOT NULL' ) def _prepare_name_custom(self, cursor, uid, partner, context=None): """ This function is designed to be inherited in a custom module """ names = (partner.lastname, partner.firstname) fullname = " ".join([s for s in names if s]) return fullname def _compute_name_custom(self, cursor, uid, ids, fname, arg, context=None): res = {} for partner in self.browse(cursor, uid, ids, context=context): res[partner.id] = self._prepare_name_custom(cursor, uid, partner, context=context) return res def _write_name(self, cursor, uid, partner_id, field_name, field_value, arg, context=None): """ Try to reverse the effect of _compute_name_custom: * if the partner is not a company and the firstname does not change in the new name then firstname remains untouched and lastname is updated accordingly * otherwise lastname=new name and firstname=False In addition an heuristic avoids to keep a firstname without a non-blank lastname """ field_value = field_value and not field_value.isspace( ) and field_value or False vals = {'lastname': field_value, 'firstname': False} if field_value: flds = self.read(cursor, uid, [partner_id], ['firstname', 'is_company'], context=context)[0] if not flds['is_company']: to_check = ' %s' % flds['firstname'] if field_value.endswith(to_check): ln = field_value[:-len(to_check)].strip() if ln: vals['lastname'] = ln del (vals['firstname']) else: # If the lastname is deleted from the new name # then the firstname becomes the lastname vals['lastname'] = flds['firstname'] return self.write(cursor, uid, partner_id, vals, context=context) def copy_data(self, cr, uid, _id, default=None, context=None): """ Avoid to replicate the firstname into the name when duplicating a partner """ default = default or {} if not default.get('lastname'): default = default.copy() default['lastname'] = _('%s (copy)') % self.read( cr, uid, [_id], ['lastname'], context=context)[0]['lastname'] if default.get('name'): del (default['name']) return super(ResPartner, self).copy_data(cr, uid, _id, default, context=context) def create(self, cursor, uid, vals, context=None): """ To support data backward compatibility we have to keep this overwrite even if we use fnct_inv: otherwise we can't create entry because lastname is mandatory and module will not install if there is demo data """ to_use = vals if 'name' in vals: corr_vals = vals.copy() corr_vals['lastname'] = corr_vals['name'] del (corr_vals['name']) to_use = corr_vals return super(ResPartner, self).create(cursor, uid, to_use, context=context) _columns = { 'name': fields.function(_compute_name_custom, string="Name", type="char", store=True, select=True, readonly=True, fnct_inv=_write_name), 'firstname': fields.char("Firstname"), 'lastname': fields.char("Lastname", required=True) }