2018-01-16 06:58:15 +01:00
# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
2018-01-16 06:58:15 +01:00
2018-01-16 11:34:37 +01:00
from flectra import api , fields , models , SUPERUSER_ID , _
from flectra . exceptions import UserError , ValidationError
2018-01-16 06:58:15 +01:00
class PosSession ( models . Model ) :
_name = ' pos.session '
_order = ' id desc '
POS_SESSION_STATE = [
( ' opening_control ' , ' Opening Control ' ) , # method action_pos_session_open
( ' opened ' , ' In Progress ' ) , # method action_pos_session_closing_control
( ' closing_control ' , ' Closing Control ' ) , # method action_pos_session_close
( ' closed ' , ' Closed & Posted ' ) ,
]
def _confirm_orders ( self ) :
for session in self :
company_id = session . config_id . journal_id . company_id . id
orders = session . order_ids . filtered ( lambda order : order . state == ' paid ' )
journal_id = self . env [ ' ir.config_parameter ' ] . sudo ( ) . get_param (
' pos.closing.journal_id_ %s ' % company_id , default = session . config_id . journal_id . id )
if not journal_id :
raise UserError ( _ ( " You have to set a Sale Journal for the POS: %s " ) % ( session . config_id . name , ) )
move = self . env [ ' pos.order ' ] . with_context ( force_company = company_id ) . _create_account_move ( session . start_at , session . name , int ( journal_id ) , company_id )
orders . with_context ( force_company = company_id ) . _create_account_move_line ( session , move )
for order in session . order_ids . filtered ( lambda o : o . state not in [ ' done ' , ' invoiced ' ] ) :
if order . state not in ( ' paid ' ) :
raise UserError (
_ ( " You cannot confirm all orders of this session, because they have not the ' paid ' status. \n "
" {reference} is in state {state} , total amount: {total} , paid: {paid} " ) . format (
reference = order . pos_reference or order . name ,
state = order . state ,
total = order . amount_total ,
paid = order . amount_paid ,
) )
order . action_pos_order_done ( )
orders = session . order_ids . filtered ( lambda order : order . state in [ ' invoiced ' , ' done ' ] )
orders . sudo ( ) . _reconcile_payments ( )
config_id = fields . Many2one (
' pos.config ' , string = ' Point of Sale ' ,
help = " The physical point of sale you will use. " ,
required = True ,
index = True )
name = fields . Char ( string = ' Session ID ' , required = True , readonly = True , default = ' / ' )
user_id = fields . Many2one (
' res.users ' , string = ' Responsible ' ,
required = True ,
index = True ,
readonly = True ,
states = { ' opening_control ' : [ ( ' readonly ' , False ) ] } ,
default = lambda self : self . env . uid )
currency_id = fields . Many2one ( ' res.currency ' , related = ' config_id.currency_id ' , string = " Currency " )
start_at = fields . Datetime ( string = ' Opening Date ' , readonly = True )
stop_at = fields . Datetime ( string = ' Closing Date ' , readonly = True , copy = False )
state = fields . Selection (
POS_SESSION_STATE , string = ' Status ' ,
required = True , readonly = True ,
index = True , copy = False , default = ' opening_control ' )
sequence_number = fields . Integer ( string = ' Order Sequence Number ' , help = ' A sequence number that is incremented with each order ' , default = 1 )
login_number = fields . Integer ( string = ' Login Sequence Number ' , help = ' A sequence number that is incremented each time a user resumes the pos session ' , default = 0 )
cash_control = fields . Boolean ( compute = ' _compute_cash_all ' , string = ' Has Cash Control ' )
cash_journal_id = fields . Many2one ( ' account.journal ' , compute = ' _compute_cash_all ' , string = ' Cash Journal ' , store = True )
cash_register_id = fields . Many2one ( ' account.bank.statement ' , compute = ' _compute_cash_all ' , string = ' Cash Register ' , store = True )
cash_register_balance_end_real = fields . Monetary (
related = ' cash_register_id.balance_end_real ' ,
string = " Ending Balance " ,
help = " Total of closing cash control lines. " ,
readonly = True )
cash_register_balance_start = fields . Monetary (
related = ' cash_register_id.balance_start ' ,
string = " Starting Balance " ,
help = " Total of opening cash control lines. " ,
readonly = True )
cash_register_total_entry_encoding = fields . Monetary (
related = ' cash_register_id.total_entry_encoding ' ,
string = ' Total Cash Transaction ' ,
readonly = True ,
help = " Total of all paid sales orders " )
cash_register_balance_end = fields . Monetary (
related = ' cash_register_id.balance_end ' ,
digits = 0 ,
string = " Theoretical Closing Balance " ,
help = " Sum of opening balance and transactions. " ,
readonly = True )
cash_register_difference = fields . Monetary (
related = ' cash_register_id.difference ' ,
string = ' Difference ' ,
help = " Difference between the theoretical closing balance and the real closing balance. " ,
readonly = True )
journal_ids = fields . Many2many (
' account.journal ' ,
related = ' config_id.journal_ids ' ,
readonly = True ,
string = ' Available Payment Methods ' )
order_ids = fields . One2many ( ' pos.order ' , ' session_id ' , string = ' Orders ' )
statement_ids = fields . One2many ( ' account.bank.statement ' , ' pos_session_id ' , string = ' Bank Statement ' , readonly = True )
picking_count = fields . Integer ( compute = ' _compute_picking_count ' )
rescue = fields . Boolean ( string = ' Recovery Session ' ,
help = " Auto-generated session for orphan orders, ignored in constraints " ,
readonly = True ,
copy = False )
_sql_constraints = [ ( ' uniq_name ' , ' unique(name) ' , " The name of this POS Session must be unique ! " ) ]
@api.multi
def _compute_picking_count ( self ) :
for pos in self :
pickings = pos . order_ids . mapped ( ' picking_id ' ) . filtered ( lambda x : x . state != ' done ' )
pos . picking_count = len ( pickings . ids )
@api.multi
def action_stock_picking ( self ) :
pickings = self . order_ids . mapped ( ' picking_id ' ) . filtered ( lambda x : x . state != ' done ' )
action_picking = self . env . ref ( ' stock.action_picking_tree_ready ' )
action = action_picking . read ( ) [ 0 ]
action [ ' context ' ] = { }
action [ ' domain ' ] = [ ( ' id ' , ' in ' , pickings . ids ) ]
return action
@api.depends ( ' config_id ' , ' statement_ids ' )
def _compute_cash_all ( self ) :
for session in self :
session . cash_journal_id = session . cash_register_id = session . cash_control = False
if session . config_id . cash_control :
for statement in session . statement_ids :
if statement . journal_id . type == ' cash ' :
session . cash_control = True
session . cash_journal_id = statement . journal_id . id
session . cash_register_id = statement . id
if not session . cash_control and session . state != ' closed ' :
raise UserError ( _ ( " Cash control can only be applied to cash journals. " ) )
@api.constrains ( ' user_id ' , ' state ' )
def _check_unicity ( self ) :
# open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
if self . search_count ( [
( ' state ' , ' not in ' , ( ' closed ' , ' closing_control ' ) ) ,
( ' user_id ' , ' = ' , self . user_id . id ) ,
( ' rescue ' , ' = ' , False )
] ) > 1 :
raise ValidationError ( _ ( " You cannot create two active sessions with the same responsible! " ) )
@api.constrains ( ' config_id ' )
def _check_pos_config ( self ) :
if self . search_count ( [
( ' state ' , ' != ' , ' closed ' ) ,
( ' config_id ' , ' = ' , self . config_id . id ) ,
( ' rescue ' , ' = ' , False )
] ) > 1 :
raise ValidationError ( _ ( " Another session is already opened for this point of sale. " ) )
@api.model
def create ( self , values ) :
config_id = values . get ( ' config_id ' ) or self . env . context . get ( ' default_config_id ' )
if not config_id :
raise UserError ( _ ( " You should assign a Point of Sale to your session. " ) )
# journal_id is not required on the pos_config because it does not
# exists at the installation. If nothing is configured at the
# installation we do the minimal configuration. Impossible to do in
# the .xml files as the CoA is not yet installed.
pos_config = self . env [ ' pos.config ' ] . browse ( config_id )
ctx = dict ( self . env . context , company_id = pos_config . company_id . id )
if not pos_config . journal_id :
default_journals = pos_config . with_context ( ctx ) . default_get ( [ ' journal_id ' , ' invoice_journal_id ' ] )
if ( not default_journals . get ( ' journal_id ' ) or
not default_journals . get ( ' invoice_journal_id ' ) ) :
raise UserError ( _ ( " Unable to open the session. You have to assign a sales journal to your point of sale. " ) )
pos_config . with_context ( ctx ) . sudo ( ) . write ( {
' journal_id ' : default_journals [ ' journal_id ' ] ,
' invoice_journal_id ' : default_journals [ ' invoice_journal_id ' ] } )
# define some cash journal if no payment method exists
if not pos_config . journal_ids :
Journal = self . env [ ' account.journal ' ]
journals = Journal . with_context ( ctx ) . search ( [ ( ' journal_user ' , ' = ' , True ) , ( ' type ' , ' = ' , ' cash ' ) ] )
if not journals :
journals = Journal . with_context ( ctx ) . search ( [ ( ' type ' , ' = ' , ' cash ' ) ] )
if not journals :
journals = Journal . with_context ( ctx ) . search ( [ ( ' journal_user ' , ' = ' , True ) ] )
journals . sudo ( ) . write ( { ' journal_user ' : True } )
pos_config . sudo ( ) . write ( { ' journal_ids ' : [ ( 6 , 0 , journals . ids ) ] } )
pos_name = self . env [ ' ir.sequence ' ] . with_context ( ctx ) . next_by_code ( ' pos.session ' )
if values . get ( ' name ' ) :
pos_name + = ' ' + values [ ' name ' ]
statements = [ ]
ABS = self . env [ ' account.bank.statement ' ]
uid = SUPERUSER_ID if self . env . user . has_group ( ' point_of_sale.group_pos_user ' ) else self . env . user . id
for journal in pos_config . journal_ids :
# set the journal_id which should be used by
# account.bank.statement to set the opening balance of the
# newly created bank statement
ctx [ ' journal_id ' ] = journal . id if pos_config . cash_control and journal . type == ' cash ' else False
st_values = {
' journal_id ' : journal . id ,
' user_id ' : self . env . user . id ,
' name ' : pos_name
}
statements . append ( ABS . with_context ( ctx ) . sudo ( uid ) . create ( st_values ) . id )
values . update ( {
' name ' : pos_name ,
' statement_ids ' : [ ( 6 , 0 , statements ) ] ,
' config_id ' : config_id
} )
res = super ( PosSession , self . with_context ( ctx ) . sudo ( uid ) ) . create ( values )
if not pos_config . cash_control :
res . action_pos_session_open ( )
return res
@api.multi
def unlink ( self ) :
for session in self . filtered ( lambda s : s . statement_ids ) :
session . statement_ids . unlink ( )
return super ( PosSession , self ) . unlink ( )
@api.multi
def login ( self ) :
self . ensure_one ( )
self . write ( {
' login_number ' : self . login_number + 1 ,
} )
@api.multi
def action_pos_session_open ( self ) :
# second browse because we need to refetch the data from the DB for cash_register_id
# we only open sessions that haven't already been opened
for session in self . filtered ( lambda session : session . state == ' opening_control ' ) :
values = { }
if not session . start_at :
values [ ' start_at ' ] = fields . Datetime . now ( )
values [ ' state ' ] = ' opened '
session . write ( values )
session . statement_ids . button_open ( )
return True
@api.multi
def action_pos_session_closing_control ( self ) :
self . _check_pos_session_balance ( )
for session in self :
session . write ( { ' state ' : ' closing_control ' , ' stop_at ' : fields . Datetime . now ( ) } )
if not session . config_id . cash_control :
session . action_pos_session_close ( )
@api.multi
def _check_pos_session_balance ( self ) :
for session in self :
for statement in session . statement_ids :
if ( statement != session . cash_register_id ) and ( statement . balance_end != statement . balance_end_real ) :
statement . write ( { ' balance_end_real ' : statement . balance_end } )
@api.multi
def action_pos_session_validate ( self ) :
self . _check_pos_session_balance ( )
self . action_pos_session_close ( )
@api.multi
def action_pos_session_close ( self ) :
# Close CashBox
for session in self :
company_id = session . config_id . company_id . id
ctx = dict ( self . env . context , force_company = company_id , company_id = company_id )
for st in session . statement_ids :
if abs ( st . difference ) > st . journal_id . amount_authorized_diff :
# The pos manager can close statements with maximums.
if not self . user_has_groups ( " point_of_sale.group_pos_manager " ) :
raise UserError ( _ ( " Your ending balance is too different from the theoretical cash closing ( %.2f ), the maximum allowed is: %.2f . You can contact your manager to force it. " ) % ( st . difference , st . journal_id . amount_authorized_diff ) )
if ( st . journal_id . type not in [ ' bank ' , ' cash ' ] ) :
raise UserError ( _ ( " The type of the journal for your payment method should be bank or cash " ) )
st . with_context ( ctx ) . sudo ( ) . button_confirm_bank ( )
self . with_context ( ctx ) . _confirm_orders ( )
self . write ( { ' state ' : ' closed ' } )
return {
' type ' : ' ir.actions.client ' ,
' name ' : ' Point of Sale Menu ' ,
' tag ' : ' reload ' ,
' params ' : { ' menu_id ' : self . env . ref ( ' point_of_sale.menu_point_root ' ) . id } ,
}
@api.multi
def open_frontend_cb ( self ) :
if not self . ids :
return { }
for session in self . filtered ( lambda s : s . user_id . id != self . env . uid ) :
raise UserError ( _ ( " You cannot use the session of another user. This session is owned by %s . "
" Please first close this one to use this point of sale. " ) % session . user_id . name )
return {
' type ' : ' ir.actions.act_url ' ,
' target ' : ' self ' ,
' url ' : ' /pos/web/ ' ,
}
@api.multi
def open_cashbox ( self ) :
self . ensure_one ( )
context = dict ( self . _context )
balance_type = context . get ( ' balance ' ) or ' start '
context [ ' bank_statement_id ' ] = self . cash_register_id . id
context [ ' balance ' ] = balance_type
context [ ' default_pos_id ' ] = self . config_id . id
action = {
' name ' : _ ( ' Cash Control ' ) ,
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' account.bank.statement.cashbox ' ,
' view_id ' : self . env . ref ( ' account.view_account_bnk_stmt_cashbox ' ) . id ,
' type ' : ' ir.actions.act_window ' ,
' context ' : context ,
' target ' : ' new '
}
cashbox_id = None
if balance_type == ' start ' :
cashbox_id = self . cash_register_id . cashbox_start_id . id
else :
cashbox_id = self . cash_register_id . cashbox_end_id . id
if cashbox_id :
action [ ' res_id ' ] = cashbox_id
return action