Last active
December 9, 2020 11:04
-
-
Save jev-odoo/cfc902ec0e36d3f518d92aa96fc180cf to your computer and use it in GitHub Desktop.
POS - Balance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # FOR VERSION 13 | |
| # Set sessions ids between the brackets, separated by a coma | |
| # I.E. SESSION_ID = [12, 18, 132] or SESSION_ID = [4] | |
| # Set DEFAULT_ACCOUNT account_id as simple integer (not required) | |
| SESSION_IDS = [] | |
| MODE = 'check' | |
| DEFAULT_ACCOUNT = 0 | |
| # DO NOT MODIFY | |
| VALID_VERSIONS = (13,) | |
| VALID_SESSION_STATES = ('opened', 'closing_control') | |
| VALID_MODES = ('check', 'process') | |
| LOG_LINE_ACTION_NAME = 'POS - Balance Session' | |
| LOG_LINE_NAME = 'Support Intervention' | |
| ON_SUCCESS_LOG_NOTE = 'This session was unbalanced and has been force closed by Odoo Support. An account_move_line has been posted to counter the difference.' | |
| SAVE_LOGS = True # Save logs in ir.logging table accessible from technical menu | |
| RAISE_LOGS = True # Show logs in a popup after the process | |
| PARTIAL_LOGS = [] | |
| FULL_LOGS = [] | |
| INFO = 'Info' | |
| WARNING = 'Warning' | |
| ERROR = 'Error' | |
| # -----===== HELPERS =====------ | |
| def _add_log_line(level, log_message, log_details: list = None): | |
| if log_details: | |
| details = [] | |
| for detail in log_details: | |
| for key, value in detail.items(): | |
| if key == 'counter': | |
| details.append('{}/{}'.format(value[0], value[1])) | |
| else: | |
| details.append('{}: {}'.format(key.title(), value)) | |
| log_message = '{} ({})'.format(log_message, ' - '.join(details)) | |
| PARTIAL_LOGS.append((log_message, level)) | |
| def check_mode(): | |
| if MODE not in VALID_MODES: | |
| raise Warning('Invalid mode. (Actual mode: {}, Valid modes: {})'.format(MODE, iter_to_string(VALID_MODES))) | |
| def check_version(env, versions: float = None): | |
| latest_version = env['ir.module.module'].search([('name', '=', 'base')]).latest_version.replace('~', '-') | |
| actual_version = 0 | |
| versions_to_check = [versions] if versions else VALID_VERSIONS | |
| for version in versions_to_check: | |
| if latest_version.startswith(str(version)) or latest_version.startswith('saas-%s' % str(version)): | |
| actual_version = version | |
| if not versions and not actual_version: | |
| raise Warning('Invalid version. (Actual version: {} - Valid versions: {})'.format(latest_version, iter_to_string(VALID_VERSIONS))) | |
| return actual_version | |
| def commit(env, session): | |
| if MODE == 'process': | |
| commit_message = 'commiting' | |
| post_note(env, session) | |
| env.cr.commit() | |
| else: | |
| commit_message = 'rollbacking' | |
| env.cr.rollback() | |
| log_message = '{} mode detected, {} changes.'.format(MODE, commit_message) | |
| log_details = [{'session_id': session.id}] | |
| _add_log_line(INFO, log_message, log_details) | |
| def format_logs(logs): | |
| lines = ['{} - {}'.format(x[1], x[0]) for x in logs] | |
| return '\n'.join(lines) | |
| def get_session(env, additional_domains: list = None, raise_exception=True): | |
| sessions = env['pos.session'] | |
| domain = [] | |
| if SESSION_IDS: | |
| domain.append(('id', 'in', SESSION_IDS)) | |
| else: | |
| if raise_exception: | |
| raise Warning('Please define the session id.') | |
| if additional_domains: | |
| domain += additional_domains | |
| sessions = sessions.search(domain, order='id ASC') | |
| if not sessions: | |
| raise Warning('No session found. ids: {}'.format(iter_to_string(SESSION_IDS))) | |
| return sessions | |
| def is_time_between(begin_time, end_time): | |
| check_time = datetime.datetime.utcnow().time() | |
| if begin_time < end_time: | |
| return begin_time <= check_time <= end_time | |
| else: # crosses midnight | |
| return check_time >= begin_time or check_time <= end_time | |
| def iter_to_string(iterable): | |
| return ', '.join(str(x) for x in iterable) | |
| def post_note(env, session): | |
| if not check_version(env, 12.0) and ON_SUCCESS_LOG_NOTE: | |
| message = '{}'.format(ON_SUCCESS_LOG_NOTE) | |
| session.message_post(body=message) | |
| def raise_logs(): | |
| if RAISE_LOGS: | |
| lines = format_logs(FULL_LOGS) | |
| raise Warning(lines) | |
| def save_log_lines(env): | |
| for line in PARTIAL_LOGS: | |
| FULL_LOGS.append(line) | |
| if SAVE_LOGS: | |
| lines = format_logs(PARTIAL_LOGS) | |
| data = ({ | |
| 'create_date': datetime.datetime.now(), | |
| 'create_uid': env.uid, | |
| 'type': 'server', | |
| 'dbname': env.cr.dbname, | |
| 'name': LOG_LINE_NAME, | |
| 'level': 'info', | |
| 'message': lines, | |
| 'path': 'action', | |
| 'line': 0, | |
| 'func': LOG_LINE_ACTION_NAME, | |
| }) | |
| env['ir.logging'].create(data) | |
| env.cr.commit() | |
| PARTIAL_LOGS.clear() | |
| # -----===== SPECIFIC FUNCTIONS =====------ | |
| def _action_pos_session_closing_control(session): | |
| session._check_pos_session_balance() | |
| session.write({'state': 'closing_control', 'stop_at': datetime.datetime.now()}) | |
| def _get_account(env, session): | |
| if DEFAULT_ACCOUNT: | |
| account = env['account.account'].browse(DEFAULT_ACCOUNT).exists() | |
| else: | |
| account = session.company_id.account_default_pos_receivable_account_id or env['ir.property'].get('property_account_receivable_id', 'res.partner') | |
| if not account: | |
| raise Warning('Receivable Account not found.') | |
| return account | |
| def balance(env, session): | |
| _action_pos_session_closing_control(session) | |
| return _action_pos_session_close(env, session) | |
| def _action_pos_session_close(env, session): | |
| if session.cash_register_id and session.cash_control and abs(session.cash_register_difference) > session.config_id.amount_authorized_diff: | |
| log_message = 'Session cash register is not balanced. Please balance before running this action. Skipping.' | |
| log_details = [{'session_id': session.id}] | |
| _add_log_line(ERROR, log_message, log_details) | |
| return | |
| return _validate_session(env, session) | |
| def _create_account_move(env, session): | |
| journal = session.config_id.journal_id | |
| account_move = session.env['account.move'].with_context(default_journal_id=journal.id).create({ | |
| 'journal_id': journal.id, | |
| 'date': datetime.datetime.now(), | |
| 'ref': session.name, | |
| }) | |
| session.write({'move_id': account_move.id}) | |
| data = {} | |
| data = session._accumulate_amounts(data) | |
| data = session._create_non_reconciliable_move_lines(data) | |
| data = session._create_cash_statement_lines_and_cash_move_lines(data) | |
| data = session._create_invoice_receivable_lines(data) | |
| data = session._create_stock_output_lines(data) | |
| # The balance is done here | |
| data = _create_extra_move_lines(env, session, data) | |
| if not data: | |
| return | |
| data = session._reconcile_account_move_lines(data) | |
| return True | |
| def _create_extra_move_lines(env, session, data): | |
| MoveLine = data.get('MoveLine') | |
| extra_move_lines = _get_extra_move_lines_vals(env, session) | |
| if not extra_move_lines: | |
| return | |
| MoveLine.create(extra_move_lines) | |
| return data | |
| def _get_extra_move_lines_vals(env, session): | |
| res = session._get_extra_move_lines_vals() | |
| debit = sum([amount.get('debit', 0) for amount in res]) or 0 | |
| credit = sum([amount.get('credit', 0) for amount in res]) or 0 | |
| rounding_difference = {'amount': 0.0, 'amount_converted': 0.0} | |
| rounding_vals = [] | |
| rounding_difference['amount'] = sum(session.move_id.line_ids.mapped('debit')) - sum(session.move_id.line_ids.mapped('credit')) | |
| rounding_difference['amount'] += (debit - credit) | |
| rounding_difference['amount_converted'] = rounding_difference['amount'] | |
| if not session.company_id.currency_id.is_zero(rounding_difference['amount_converted']): | |
| value = _get_rounding_difference_vals(env, session, rounding_difference['amount'], rounding_difference['amount_converted']) | |
| if not value: | |
| return | |
| rounding_vals += [value] | |
| amount = value['credit'] or value['debit'] | |
| log_message = 'Session balanced.' | |
| log_details = [{'session_id': session.id}, | |
| {'amount': amount}] | |
| _add_log_line(INFO, log_message, log_details) | |
| return res + rounding_vals | |
| def _get_rounding_difference_vals(env, session, amount, amount_converted): | |
| partial_args = { | |
| 'name': 'Rounding error balancing line', | |
| 'move_id': session.move_id.id, | |
| } | |
| account = _get_account(env, session) | |
| partial_args['account_id'] = account.id | |
| if amount > 0: # loss | |
| return session._credit_amounts(partial_args, amount, amount_converted) | |
| else: # profit | |
| return session._debit_amounts(partial_args, -amount, -amount_converted) | |
| def _validate_session(env, session): | |
| session.ensure_one() | |
| session._check_if_no_draft_orders() | |
| create_am = _create_account_move(env, session) | |
| if not create_am: | |
| return | |
| if session.move_id.line_ids: | |
| session.move_id.post() | |
| # Set the uninvoiced orders' state to 'done' | |
| session.env['pos.order'].search([('session_id', '=', session.id), ('state', '=', 'paid')]).write({'state': 'done'}) | |
| else: | |
| # The cash register needs to be confirmed for cash diffs | |
| # made thru cash in/out when sesion is in cash_control. | |
| if session.config_id.cash_control: | |
| session.cash_register_id.button_confirm_bank() | |
| session.move_id.unlink() | |
| session.write({'state': 'closed'}) | |
| log_message = 'Session successfully closed.' | |
| log_details = [{'session_id': session.id}] | |
| _add_log_line(INFO, log_message, log_details) | |
| return True | |
| # -----===== MAIN PROCESS =====------ | |
| def process(env): | |
| check_mode() | |
| check_version(env) | |
| additional_domain = [] | |
| sessions = get_session(env, additional_domain) | |
| for session in sessions: | |
| if session.state not in VALID_SESSION_STATES: | |
| log_message = 'Invalid session state, skipping.' | |
| log_details = [{'session_id': session.id}, | |
| {'actual state': session.state}, | |
| {'valid states': iter_to_string(VALID_SESSION_STATES)}] | |
| _add_log_line(WARNING, log_message, log_details) | |
| continue | |
| try: | |
| if balance(env, session): | |
| commit(env, session) | |
| else: | |
| env.cr.rollback() | |
| except Exception as e: | |
| log_message = 'Unexpected Error occured.' | |
| log_details = [{'session_id': session.id}, | |
| {'error': str(e)}] | |
| _add_log_line(ERROR, log_message, log_details) | |
| env.cr.rollback() | |
| save_log_lines(env) | |
| raise_logs() | |
| process(env) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment