class UnrealizedCurrencyReportPrinter(TransientModel): _name = "unrealized.report.printer" _columns = { 'chart_account_id': fields.many2one('account.account', 'Chart root', domain=[('parent_id', '=', False)]), 'period_id': fields.many2one('account.period', 'Period to use', required=True) } def print_report(self, cursor, uid, wid, data, context=None): context = context or {} # we update form with display account value if isinstance(wid, list): wid = wid[0] current = self.browse(cursor, uid, wid, context) form = {} form['period_id'] = current.period_id.id form['period_name'] = current.period_id.name form['account_ids'] = [current.chart_account_id.id] data['form'] = form print data return { 'type': 'ir.actions.report.xml', 'report_name': 'currency_unrealized', 'datas': data }
class StockLevelConfig(Model): _name = 'stock.level.configuration' _columns = { 'stock_location_id': fields.many2one('stock.location', 'Location to use', required=True), 'product_field': fields.many2one('ir.model.fields', 'Fields to use', required=True, help="Field of product form to sum") }
class OrderpointTemplate(BaseProductConfigTemplate, Model): """ Template for orderpoints """ _name = 'stock.warehouse.orderpoint.template' _inherit = 'stock.warehouse.orderpoint' _table = 'stock_warehouse_orderpoint_template' _clean_mode = 'deactivate' _columns = { 'product_id': fields.many2one('product.product', 'Product', required=False, ondelete='cascade', domain=[('type', '=', 'product')]), } def _get_ids_2_clean(self, cursor, uid, template_br, product_ids, context=None): """ hook to select model specific objects to clean return must return a list of id""" model_obj = self._get_model() ids_to_del = model_obj.search(cursor, uid, [('product_id', 'in', product_ids)]) return ids_to_del
class AccountInvoice(Model): """Add a link to a credit control policy on account.account""" _inherit = "account.invoice" _columns = { 'credit_policy_id': fields.many2one('credit.control.policy', 'Credit Control Policy', help=("The Credit Control Policy " "used for this invoice. " "If nothing is defined, " "it will use the account " "setting or the partner " "setting.")), 'credit_control_line_ids': fields.one2many('credit.control.line', 'invoice_id', string='Credit Lines', readonly=True) } def action_move_create(self, cr, uid, ids, context=None): """ Write the id of the invoice in the generated moves. """ res = super(AccountInvoice, self).action_move_create(cr, uid, ids, context=context) for inv in self.browse(cr, uid, ids, context=context): if inv.move_id: for line in inv.move_id.line_id: line.write({'invoice_id': inv.id}) return res
class AccountInvoice(Model): """ Add account.condition_text to invoice""" _inherit = "account.invoice" def _set_condition(self, cr, uid, inv_id, commentid, key, partner_id=False): """Set the text of the notes in invoices""" if not commentid: return {} if not partner_id: raise osv.except_osv( _('No Customer Defined !'), _('Before choosing condition text select a customer.')) lang = self.pool.get('res.partner').browse(cr, uid, partner_id).lang or 'en_US' cond = self.pool.get('account.condition_text').browse( cr, uid, commentid, {'lang': lang}) return {'value': {key: cond.text}} def set_header(self, cursor, uid, inv_id, commentid, partner_id=False): return self._set_condition(cursor, uid, inv_id, commentid, 'note1', partner_id) def set_footer(self, cursor, uid, inv_id, commentid, partner_id=False): return self._set_condition(cursor, uid, inv_id, commentid, 'note2', partner_id) _columns = { 'text_condition1': fields.many2one('account.condition_text', 'Header condition', domain=[('type', '=', 'header')]), 'text_condition2': fields.many2one('account.condition_text', 'Footer condition', domain=[('type', '=', 'footer')]), 'note1': fields.html('Header'), 'note2': fields.html('Footer'), }
class ResPartner(Model): _inherit = 'res.partner' _columns = { 'supplier_invoice_default_product': fields.many2one( 'product.product', 'Default product supplier invoice', help="Use by the scan BVR wizard, if completed, it'll generate " "a line with the proper amount and this specified product"), }
class AccountInvoice(Model): """ Add account.condition_text to invoice""" _inherit = "account.invoice" def _set_condition(self, cr, uid, inv_id, commentid, key): """Set the text of the notes in invoices""" if not commentid: return {} try: lang = self.browse(cr, uid, inv_id)[0].partner_id.lang except: lang = 'en_US' cond = self.pool.get('account.condition_text').browse( cr, uid, commentid, {'lang': lang}) return {'value': {key: cond.text}} def set_header(self, cr, uid, inv_id, commentid): return self._set_condition(cr, uid, inv_id, commentid, 'note1') def set_footer(self, cr, uid, inv_id, commentid): return self._set_condition(cr, uid, inv_id, commentid, 'note2') _columns = { 'text_condition1': fields.many2one('account.condition_text', 'Header condition', domain=[('type', '=', 'header')]), 'text_condition2': fields.many2one('account.condition_text', 'Footer condition', domain=[('type', '=', 'footer')]), 'note1': fields.html('Header'), 'note2': fields.html('Footer'), }
class ResCompany(Model): _inherit = 'res.company' _columns = { 'credit_control_tolerance': fields.float('Credit Control Tolerance'), # This is not a property on the partner because we cannot search # on fields.property (subclass fields.function). 'credit_policy_id': fields.many2one( 'credit.control.policy', 'Credit Control Policy', help=("The Credit Control Policy used on partners by default. This " "setting can be overriden on partners or invoices.")), } _defaults = {"credit_control_tolerance": 0.1}
class ResPartner(Model): """Add a settings on the credit control policy to use on the partners, and links to the credit control lines.""" _inherit = "res.partner" _columns = { 'credit_policy_id': fields.many2one('credit.control.policy', 'Credit Control Policy', help=("The Credit Control Policy" "used for this partner. This " "setting can be forced on the " "invoice. If nothing is defined, " "it will use the company " "setting.")), 'credit_control_line_ids': fields.one2many('credit.control.line', 'invoice_id', string='Credit Control Lines', readonly=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 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 CreditCommunication(TransientModel): """Shell class used to provide a base model to email template and reporting. Il use this approche in version 7 a browse record will exist even if not saved""" _name = "credit.control.communication" _description = "credit control communication" _rec_name = 'partner_id' _columns = { 'partner_id': fields.many2one('res.partner', 'Partner', required=True), 'current_policy_level': fields.many2one('credit.control.policy.level', 'Level', required=True), 'credit_control_line_ids': fields.many2many('credit.control.line', rel='comm_credit_rel', string='Credit Lines'), 'company_id': fields.many2one('res.company', 'Company', required=True), 'user_id': fields.many2one('res.users', 'User') } _defaults = { 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get( cr, uid, 'credit.control.policy', context=c), 'user_id': lambda s, cr, uid, c: uid } def get_email(self, cr, uid, com_id, context=None): """Return a valid email for customer""" if isinstance(com_id, list): assert len( com_id) == 1, "get_email only support one id as parameter" com_id = com_id[0] form = self.browse(cr, uid, com_id, context=context) contact = form.get_contact_address() return contact.email def get_contact_address(self, cr, uid, com_id, context=None): pmod = self.pool['res.partner'] if isinstance(com_id, list): com_id = com_id[0] form = self.browse(cr, uid, com_id, context=context) part = form.partner_id add_ids = part.address_get(adr_pref=['invoice']) or {} add_id = add_ids.get('invoice', add_ids.get('default', False)) return pmod.browse(cr, uid, add_id, context) def _get_credit_lines(self, cr, uid, line_ids, partner_id, level_id, context=None): """Return credit lines related to a partner and a policy level""" cr_line_obj = self.pool.get('credit.control.line') cr_l_ids = cr_line_obj.search(cr, uid, [('id', 'in', line_ids), ('partner_id', '=', partner_id), ('policy_level_id', '=', level_id)], context=context) return cr_l_ids def _generate_comm_from_credit_line_ids(self, cr, uid, line_ids, context=None): if not line_ids: return [] comms = [] sql = ( "SELECT distinct partner_id, policy_level_id, credit_control_policy_level.level" " FROM credit_control_line JOIN credit_control_policy_level " " ON (credit_control_line.policy_level_id = credit_control_policy_level.id)" " WHERE credit_control_line.id in %s" " ORDER by credit_control_policy_level.level") cr.execute(sql, (tuple(line_ids), )) res = cr.dictfetchall() for level_assoc in res: data = {} data['credit_control_line_ids'] = \ [(6, 0, self._get_credit_lines(cr, uid, line_ids, level_assoc['partner_id'], level_assoc['policy_level_id'], context=context))] data['partner_id'] = level_assoc['partner_id'] data['current_policy_level'] = level_assoc['policy_level_id'] comm_id = self.create(cr, uid, data, context=context) comms.append(self.browse(cr, uid, comm_id, context=context)) return comms def _generate_emails(self, cr, uid, comms, context=None): """Generate email message using template related to level""" cr_line_obj = self.pool.get('credit.control.line') email_temp_obj = self.pool.get('email.template') email_message_obj = self.pool.get('mail.mail') att_obj = self.pool.get('ir.attachment') email_ids = [] essential_fields = ['subject', 'body_html', 'email_from', 'email_to'] for comm in comms: # we want to use a local cr in order to send the maximum # of email template = comm.current_policy_level.email_template_id.id email_values = {} cl_ids = [cl.id for cl in comm.credit_control_line_ids] email_values = email_temp_obj.generate_email(cr, uid, template, comm.id, context=context) email_values['body_html'] = email_values['body'] email_values['type'] = 'email' email_id = email_message_obj.create(cr, uid, email_values, context=context) state = 'sent' # The mail will not be send, however it will be in the pool, in an # error state. So we create it, link it with the credit control line # and put this latter in a `email_error` state we not that we have a # problem with the email if any(not email_values.get(field) for field in essential_fields): state = 'email_error' cr_line_obj.write(cr, uid, cl_ids, { 'mail_message_id': email_id, 'state': state }, context=context) att_ids = [] for att in email_values.get('attachments', []): attach_fname = att[0] attach_datas = att[1] data_attach = { 'name': attach_fname, 'datas': attach_datas, 'datas_fname': attach_fname, 'res_model': 'mail.mail', 'res_id': email_id, 'type': 'binary', } att_ids.append( att_obj.create(cr, uid, data_attach, context=context)) email_message_obj.write(cr, uid, [email_id], {'attachment_ids': [(6, 0, att_ids)]}, context=context) email_ids.append(email_id) return email_ids def _generate_report(self, cr, uid, comms, context=None): """Will generate a report by inserting mako template of related policy template""" service = netsvc.LocalService('report.credit_control_summary') ids = [x.id for x in comms] result, format = service.create(cr, uid, ids, {}, {}) return result def _mark_credit_line_as_sent(self, cr, uid, comms, context=None): line_ids = [] for comm in comms: line_ids += [x.id for x in comm.credit_control_line_ids] l_obj = self.pool.get('credit.control.line') l_obj.write(cr, uid, line_ids, {'state': 'sent'}, context=context) return line_ids
class AccountMoveLine(Model): _inherit = "account.move.line" _columns = {'invoice_id': fields.many2one('account.invoice', 'Invoice')}
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)
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)