2012-05-14 14:03:40 +00:00
function openerp _pos _models ( instance , module ) { //module is instance.point_of_sale
2012-04-24 16:25:46 +00:00
var QWeb = instance . web . qweb ;
2014-01-10 17:17:46 +00:00
var _t = instance . web . _t ;
2012-04-24 16:25:46 +00:00
2013-04-09 14:42:08 +00:00
var round _di = instance . web . round _decimals ;
2013-04-09 14:35:36 +00:00
var round _pr = instance . web . round _precision
2012-04-24 16:25:46 +00:00
2012-05-21 14:09:01 +00:00
// The PosModel contains the Point Of Sale's representation of the backend.
// Since the PoS must work in standalone ( Without connection to the server )
// it must contains a representation of the server's PoS backend.
// (taxes, product list, configuration options, etc.) this representation
// is fetched and stored by the PosModel at the initialisation.
// this is done asynchronously, a ready deferred alows the GUI to wait interactively
// for the loading to be completed
// There is a single instance of the PosModel for each Front-End instance, it is usually called
2012-08-14 15:21:12 +00:00
// 'pos' and is available to all widgets extending PosWidget.
2012-05-21 14:09:01 +00:00
2012-04-24 16:25:46 +00:00
module . PosModel = Backbone . Model . extend ( {
initialize : function ( session , attributes ) {
Backbone . Model . prototype . initialize . call ( this , attributes ) ;
var self = this ;
2012-08-09 17:21:13 +00:00
this . session = session ;
2012-05-21 14:09:01 +00:00
this . flush _mutex = new $ . Mutex ( ) ; // used to make sure the orders are sent to the server once at time
2014-01-10 17:17:46 +00:00
this . pos _widget = attributes . pos _widget ;
2012-08-09 17:21:13 +00:00
2014-01-27 00:22:03 +00:00
this . proxy = new module . ProxyDevice ( this ) ; // used to communicate to the hardware devices via a local proxy
2014-06-13 14:19:05 +00:00
this . barcode _reader = new module . BarcodeReader ( { 'pos' : this , proxy : this . proxy , patterns : { } } ) ; // used to read barcodes
2013-12-16 14:35:59 +00:00
this . proxy _queue = new module . JobQueue ( ) ; // used to prevent parallels communications to the proxy
this . db = new module . PosDB ( ) ; // a local database used to search trough products and categories & store pending orders
2013-01-22 13:18:01 +00:00
this . debug = jQuery . deparam ( jQuery . param . querystring ( ) ) . debug !== undefined ; //debug mode
2013-12-16 14:35:59 +00:00
// Business data; loaded from the server at launch
2013-12-24 15:11:12 +00:00
this . accounting _precision = 2 ; //TODO
this . company _logo = null ;
this . company _logo _base64 = '' ;
2013-12-16 14:35:59 +00:00
this . currency = null ;
2014-06-13 14:19:05 +00:00
this . shop = null ;
2013-12-16 14:35:59 +00:00
this . company = null ;
this . user = null ;
this . users = [ ] ;
this . partners = [ ] ;
this . cashier = null ;
this . cashregisters = [ ] ;
this . bankstatements = [ ] ;
this . taxes = [ ] ;
this . pos _session = null ;
this . config = null ;
this . units = [ ] ;
this . units _by _id = { } ;
this . pricelist = null ;
2014-06-13 14:19:05 +00:00
this . order _sequence = 1 ;
2014-01-06 13:51:19 +00:00
window . posmodel = this ;
2013-12-16 14:35:59 +00:00
// these dynamic attributes can be watched for change by other models or widgets
2012-04-24 16:25:46 +00:00
this . set ( {
2014-01-16 17:28:46 +00:00
'synch' : { state : 'connected' , pending : 0 } ,
2012-05-21 14:09:01 +00:00
'orders' : new module . OrderCollection ( ) ,
2012-09-10 15:48:28 +00:00
'selectedOrder' : null ,
2012-04-24 16:25:46 +00:00
} ) ;
2012-05-21 14:09:01 +00:00
2014-01-16 17:28:46 +00:00
this . bind ( 'change:synch' , function ( pos , synch ) {
clearTimeout ( self . synch _timeout ) ;
self . synch _timeout = setTimeout ( function ( ) {
if ( synch . state !== 'disconnected' && synch . pending > 0 ) {
self . set ( 'synch' , { state : 'disconnected' , pending : synch . pending } ) ;
}
} , 3000 ) ;
} ) ;
2013-09-05 10:43:57 +00:00
this . get ( 'orders' ) . bind ( 'remove' , function ( order , _unused _ , options ) {
self . on _removed _order ( order , options . index , options . reason ) ;
} ) ;
2012-04-24 16:25:46 +00:00
2012-07-11 16:03:25 +00:00
// We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
// Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
2012-09-03 14:25:27 +00:00
// when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
2014-01-10 17:17:46 +00:00
this . ready = this . load _server _data ( )
. then ( function ( ) {
2014-01-17 17:58:30 +00:00
if ( self . config . use _proxy ) {
return self . connect _to _proxy ( ) ;
}
2014-01-10 17:17:46 +00:00
} ) ;
2012-09-03 14:25:27 +00:00
} ,
2013-09-02 15:59:07 +00:00
// releases ressources holds by the model at the end of life of the posmodel
destroy : function ( ) {
// FIXME, should wait for flushing, return a deferred to indicate successfull destruction
// this.flush();
this . proxy . close ( ) ;
this . barcode _reader . disconnect ( ) ;
2014-01-17 17:58:30 +00:00
this . barcode _reader . disconnect _from _proxy ( ) ;
2013-09-02 15:59:07 +00:00
} ,
2014-01-17 17:58:30 +00:00
connect _to _proxy : function ( ) {
2014-01-10 17:17:46 +00:00
var self = this ;
2014-01-29 13:57:10 +00:00
var done = new $ . Deferred ( ) ;
2014-01-17 17:58:30 +00:00
this . barcode _reader . disconnect _from _proxy ( ) ;
2014-01-10 17:17:46 +00:00
this . pos _widget . loading _message ( _t ( 'Connecting to the PosBox' ) , 0 ) ;
this . pos _widget . loading _skip ( function ( ) {
self . proxy . stop _searching ( ) ;
} ) ;
2014-01-27 00:22:03 +00:00
this . proxy . autoconnect ( {
2014-01-10 17:17:46 +00:00
force _ip : self . config . proxy _ip || undefined ,
progress : function ( prog ) {
self . pos _widget . loading _progress ( prog ) ;
} ,
2014-01-27 00:22:03 +00:00
} ) . then ( function ( ) {
if ( self . config . iface _scan _via _proxy ) {
self . barcode _reader . connect _to _proxy ( ) ;
2014-01-10 17:17:46 +00:00
}
2014-01-29 13:57:10 +00:00
} ) . always ( function ( ) {
done . resolve ( ) ;
2014-01-10 17:17:46 +00:00
} ) ;
2014-01-29 13:57:10 +00:00
return done ;
2014-01-10 17:17:46 +00:00
} ,
2014-07-24 12:09:46 +00:00
// helper function to load data from the server. Obsolete use the models loader below.
2012-09-03 14:43:54 +00:00
fetch : function ( model , fields , domain , ctx ) {
2014-01-10 17:17:46 +00:00
this . _load _progress = ( this . _load _progress || 0 ) + 0.05 ;
this . pos _widget . loading _message ( _t ( 'Loading' ) + ' ' + model , this . _load _progress ) ;
2012-09-03 14:43:54 +00:00
return new instance . web . Model ( model ) . query ( fields ) . filter ( domain ) . context ( ctx ) . all ( )
2012-09-03 14:59:58 +00:00
} ,
2014-06-13 14:19:05 +00:00
2014-07-24 12:09:46 +00:00
// Server side model loaders. This is the list of the models that need to be loaded from
// the server. The models are loaded one by one by this list's order. The 'loaded' callback
// is used to store the data in the appropriate place once it has been loaded. This callback
// can return a deferred that will pause the loading of the next module.
// a shared temporary dictionary is available for loaders to communicate private variables
// used during loading such as object ids, etc.
models : [
{
model : 'res.users' ,
fields : [ 'name' , 'company_id' ] ,
2014-11-10 10:46:46 +00:00
ids : function ( self ) { return [ self . session . uid ] ; } ,
2014-07-24 12:09:46 +00:00
loaded : function ( self , users ) { self . user = users [ 0 ] ; } ,
} , {
model : 'res.company' ,
2014-09-17 14:01:53 +00:00
fields : [ 'currency_id' , 'email' , 'website' , 'company_registry' , 'vat' , 'name' , 'phone' , 'partner_id' , 'country_id' ] ,
2014-11-10 10:46:46 +00:00
ids : function ( self ) { return [ self . user . company _id [ 0 ] ] } ,
2014-07-24 12:09:46 +00:00
loaded : function ( self , companies ) { self . company = companies [ 0 ] ; } ,
} , {
2014-11-11 18:32:52 +00:00
model : 'decimal.precision' ,
fields : [ 'name' , 'digits' ] ,
loaded : function ( self , dps ) {
self . dp = { } ;
for ( var i = 0 ; i < dps . length ; i ++ ) {
self . dp [ dps [ i ] . name ] = dps [ i ] . digits ;
}
} ,
} , {
2014-07-24 12:09:46 +00:00
model : 'product.uom' ,
fields : [ ] ,
domain : null ,
loaded : function ( self , units ) {
self . units = units ;
var units _by _id = { } ;
for ( var i = 0 , len = units . length ; i < len ; i ++ ) {
units _by _id [ units [ i ] . id ] = units [ i ] ;
units [ i ] . groupable = ( units [ i ] . category _id [ 0 ] === 1 ) ;
units [ i ] . is _unit = ( units [ i ] . id === 1 ) ;
}
self . units _by _id = units _by _id ;
}
} , {
model : 'res.users' ,
fields : [ 'name' , 'ean13' ] ,
domain : null ,
loaded : function ( self , users ) { self . users = users ; } ,
} , {
model : 'res.partner' ,
2014-09-17 14:01:53 +00:00
fields : [ 'name' , 'street' , 'city' , 'state_id' , 'country_id' , 'vat' , 'phone' , 'zip' , 'mobile' , 'email' , 'ean13' , 'write_date' ] ,
2014-11-26 14:32:33 +00:00
domain : [ [ 'customer' , '=' , true ] ] ,
2014-07-24 12:09:46 +00:00
loaded : function ( self , partners ) {
self . partners = partners ;
self . db . add _partners ( partners ) ;
} ,
2014-09-17 14:01:53 +00:00
} , {
model : 'res.country' ,
fields : [ 'name' ] ,
loaded : function ( self , countries ) {
self . countries = countries ;
self . company . country = null ;
for ( var i = 0 ; i < countries . length ; i ++ ) {
if ( countries [ i ] . id === self . company . country _id [ 0 ] ) {
self . company . country = countries [ i ] ;
}
}
} ,
2014-07-24 12:09:46 +00:00
} , {
model : 'account.tax' ,
2014-12-11 15:57:48 +00:00
fields : [ 'name' , 'amount' , 'price_include' , 'include_base_amount' , 'type' ] ,
2014-07-24 12:09:46 +00:00
domain : null ,
2014-12-11 15:57:48 +00:00
loaded : function ( self , taxes ) {
self . taxes = taxes ;
self . taxes _by _id = { } ;
for ( var i = 0 ; i < taxes . length ; i ++ ) {
self . taxes _by _id [ taxes [ i ] . id ] = taxes [ i ] ;
}
} ,
2014-07-24 12:09:46 +00:00
} , {
model : 'pos.session' ,
2014-08-20 13:07:56 +00:00
fields : [ 'id' , 'journal_ids' , 'name' , 'user_id' , 'config_id' , 'start_at' , 'stop_at' , 'sequence_number' , 'login_number' ] ,
2014-07-24 12:09:46 +00:00
domain : function ( self ) { return [ [ 'state' , '=' , 'opened' ] , [ 'user_id' , '=' , self . session . uid ] ] ; } ,
2014-08-08 14:52:22 +00:00
loaded : function ( self , pos _sessions ) {
self . pos _session = pos _sessions [ 0 ] ;
var orders = self . db . get _orders ( ) ;
for ( var i = 0 ; i < orders . length ; i ++ ) {
self . pos _session . sequence _number = Math . max ( self . pos _session . sequence _number , orders [ i ] . data . sequence _number + 1 ) ;
}
} ,
2014-07-24 12:09:46 +00:00
} , {
model : 'pos.config' ,
fields : [ ] ,
domain : function ( self ) { return [ [ 'id' , '=' , self . pos _session . config _id [ 0 ] ] ] ; } ,
loaded : function ( self , configs ) {
self . config = configs [ 0 ] ;
self . config . use _proxy = self . config . iface _payment _terminal ||
self . config . iface _electronic _scale ||
self . config . iface _print _via _proxy ||
self . config . iface _scan _via _proxy ||
self . config . iface _cashdrawer ;
self . barcode _reader . add _barcode _patterns ( {
'product' : self . config . barcode _product ,
'cashier' : self . config . barcode _cashier ,
'client' : self . config . barcode _customer ,
'weight' : self . config . barcode _weight ,
'discount' : self . config . barcode _discount ,
'price' : self . config . barcode _price ,
} ) ;
2014-09-03 12:28:17 +00:00
if ( self . config . company _id [ 0 ] !== self . user . company _id [ 0 ] ) {
throw new Error ( _t ( "Error: The Point of Sale User must belong to the same company as the Point of Sale. You are probably trying to load the point of sale as an administrator in a multi-company setup, with the administrator account set to the wrong company." ) ) ;
}
2014-07-24 12:09:46 +00:00
} ,
} , {
model : 'stock.location' ,
fields : [ ] ,
2014-11-10 10:46:46 +00:00
ids : function ( self ) { return [ self . config . stock _location _id [ 0 ] ] ; } ,
2014-07-24 12:09:46 +00:00
loaded : function ( self , locations ) { self . shop = locations [ 0 ] ; } ,
} , {
model : 'product.pricelist' ,
fields : [ 'currency_id' ] ,
2014-11-10 10:46:46 +00:00
ids : function ( self ) { return [ self . config . pricelist _id [ 0 ] ] ; } ,
2014-07-24 12:09:46 +00:00
loaded : function ( self , pricelists ) { self . pricelist = pricelists [ 0 ] ; } ,
} , {
model : 'res.currency' ,
fields : [ 'symbol' , 'position' , 'rounding' , 'accuracy' ] ,
2014-11-10 10:46:46 +00:00
ids : function ( self ) { return [ self . pricelist . currency _id [ 0 ] ] ; } ,
2014-07-24 12:09:46 +00:00
loaded : function ( self , currencies ) {
self . currency = currencies [ 0 ] ;
2014-11-11 16:41:20 +00:00
if ( self . currency . rounding > 0 ) {
self . currency . decimals = Math . ceil ( Math . log ( 1.0 / self . currency . rounding ) / Math . log ( 10 ) ) ;
} else {
self . currency . decimals = 0 ;
}
2014-07-24 12:09:46 +00:00
} ,
} , {
model : 'product.packaging' ,
fields : [ 'ean' , 'product_tmpl_id' ] ,
domain : null ,
loaded : function ( self , packagings ) {
self . db . add _packagings ( packagings ) ;
} ,
} , {
model : 'pos.category' ,
fields : [ 'id' , 'name' , 'parent_id' , 'child_id' , 'image' ] ,
domain : null ,
loaded : function ( self , categories ) {
self . db . add _categories ( categories ) ;
} ,
} , {
model : 'product.product' ,
2014-08-11 14:56:01 +00:00
fields : [ 'display_name' , 'list_price' , 'price' , 'pos_categ_id' , 'taxes_id' , 'ean13' , 'default_code' ,
2014-07-24 12:09:46 +00:00
'to_weight' , 'uom_id' , 'uos_id' , 'uos_coeff' , 'mes_type' , 'description_sale' , 'description' ,
'product_tmpl_id' ] ,
2014-11-26 14:32:33 +00:00
domain : [ [ 'sale_ok' , '=' , true ] , [ 'available_in_pos' , '=' , true ] ] ,
2014-08-18 12:47:53 +00:00
context : function ( self ) { return { pricelist : self . pricelist . id , display _default _code : false } ; } ,
2014-07-24 12:09:46 +00:00
loaded : function ( self , products ) {
self . db . add _products ( products ) ;
} ,
} , {
model : 'account.bank.statement' ,
fields : [ 'account_id' , 'currency' , 'journal_id' , 'state' , 'name' , 'user_id' , 'pos_session_id' ] ,
domain : function ( self ) { return [ [ 'state' , '=' , 'open' ] , [ 'pos_session_id' , '=' , self . pos _session . id ] ] ; } ,
loaded : function ( self , bankstatements , tmp ) {
self . bankstatements = bankstatements ;
2012-07-09 15:53:51 +00:00
2014-07-24 12:09:46 +00:00
tmp . journals = [ ] ;
_ . each ( bankstatements , function ( statement ) {
tmp . journals . push ( statement . journal _id [ 0 ] ) ;
} ) ;
} ,
} , {
model : 'account.journal' ,
fields : [ ] ,
domain : function ( self , tmp ) { return [ [ 'id' , 'in' , tmp . journals ] ] ; } ,
loaded : function ( self , journals ) {
self . journals = journals ;
2012-07-09 15:53:51 +00:00
2014-07-24 12:09:46 +00:00
// associate the bank statements with their journals.
var bankstatements = self . bankstatements ;
for ( var i = 0 , ilen = bankstatements . length ; i < ilen ; i ++ ) {
for ( var j = 0 , jlen = journals . length ; j < jlen ; j ++ ) {
if ( bankstatements [ i ] . journal _id [ 0 ] === journals [ j ] . id ) {
bankstatements [ i ] . journal = journals [ j ] ;
}
2012-06-28 12:38:25 +00:00
}
2014-07-24 12:09:46 +00:00
}
self . cashregisters = bankstatements ;
} ,
} , {
2014-08-19 14:43:05 +00:00
label : 'fonts' ,
loaded : function ( self ) {
var fonts _loaded = new $ . Deferred ( ) ;
// Waiting for fonts to be loaded to prevent receipt printing
// from printing empty receipt while loading Inconsolata
// ( The font used for the receipt )
waitForWebfonts ( [ 'Lato' , 'Inconsolata' ] , function ( ) {
fonts _loaded . resolve ( ) ;
} ) ;
// The JS used to detect font loading is not 100% robust, so
// do not wait more than 5sec
setTimeout ( function ( ) {
fonts _loaded . resolve ( ) ;
} , 5000 ) ;
return fonts _loaded ;
} ,
} , {
label : 'pictures' ,
2014-07-24 12:09:46 +00:00
loaded : function ( self ) {
self . company _logo = new Image ( ) ;
var logo _loaded = new $ . Deferred ( ) ;
self . company _logo . onload = function ( ) {
var img = self . company _logo ;
var ratio = 1 ;
var targetwidth = 300 ;
var maxheight = 150 ;
if ( img . width !== targetwidth ) {
ratio = targetwidth / img . width ;
}
if ( img . height * ratio > maxheight ) {
ratio = maxheight / img . height ;
}
var width = Math . floor ( img . width * ratio ) ;
var height = Math . floor ( img . height * ratio ) ;
var c = document . createElement ( 'canvas' ) ;
c . width = width ;
c . height = height
var ctx = c . getContext ( '2d' ) ;
ctx . drawImage ( self . company _logo , 0 , 0 , width , height ) ;
2014-10-21 12:33:36 +00:00
2014-07-24 12:09:46 +00:00
self . company _logo _base64 = c . toDataURL ( ) ;
logo _loaded . resolve ( ) ;
} ;
self . company _logo . onerror = function ( ) {
logo _loaded . reject ( ) ;
} ;
2014-10-21 12:33:36 +00:00
self . company _logo . crossOrigin = "anonymous" ;
2014-09-10 12:34:58 +00:00
self . company _logo . src = '/web/binary/company_logo' + '?_' + Math . random ( ) ;
2012-08-21 10:04:52 +00:00
2014-07-24 12:09:46 +00:00
return logo _loaded ;
} ,
} ,
] ,
2012-08-21 10:04:52 +00:00
2014-07-24 12:09:46 +00:00
// loads all the needed data on the sever. returns a deferred indicating when all the data has loaded.
load _server _data : function ( ) {
var self = this ;
var loaded = new $ . Deferred ( ) ;
var progress = 0 ;
var progress _step = 1.0 / self . models . length ;
var tmp = { } ; // this is used to share a temporary state between models loaders
2012-08-21 10:04:52 +00:00
2014-07-24 12:09:46 +00:00
function load _model ( index ) {
if ( index >= self . models . length ) {
loaded . resolve ( ) ;
} else {
var model = self . models [ index ] ;
2014-08-19 14:43:05 +00:00
self . pos _widget . loading _message ( _t ( 'Loading' ) + ' ' + ( model . label || model . model || '' ) , progress ) ;
2014-07-24 12:09:46 +00:00
var fields = typeof model . fields === 'function' ? model . fields ( self , tmp ) : model . fields ;
var domain = typeof model . domain === 'function' ? model . domain ( self , tmp ) : model . domain ;
var context = typeof model . context === 'function' ? model . context ( self , tmp ) : model . context ;
2014-11-10 10:46:46 +00:00
var ids = typeof model . ids === 'function' ? model . ids ( self , tmp ) : model . ids ;
2014-07-24 12:09:46 +00:00
progress += progress _step ;
2014-06-13 14:19:05 +00:00
2014-11-10 10:46:46 +00:00
2014-07-24 12:09:46 +00:00
if ( model . model ) {
2014-11-10 10:46:46 +00:00
if ( model . ids ) {
var records = new instance . web . Model ( model . model ) . call ( 'read' , [ ids , fields ] , context ) ;
} else {
var records = new instance . web . Model ( model . model ) . query ( fields ) . filter ( domain ) . context ( context ) . all ( )
}
records . then ( function ( result ) {
2014-07-24 13:21:54 +00:00
try { // catching exceptions in model.loaded(...)
$ . when ( model . loaded ( self , result , tmp ) )
. then ( function ( ) { load _model ( index + 1 ) ; } ,
function ( err ) { loaded . reject ( err ) ; } ) ;
} catch ( err ) {
loaded . reject ( err ) ;
}
2014-07-24 12:09:46 +00:00
} , function ( err ) {
loaded . reject ( err ) ;
} ) ;
} else if ( model . loaded ) {
2014-07-24 13:21:54 +00:00
try { // catching exceptions in model.loaded(...)
$ . when ( model . loaded ( self , tmp ) )
. then ( function ( ) { load _model ( index + 1 ) ; } ,
function ( err ) { loaded . reject ( err ) ; } ) ;
} catch ( err ) {
loaded . reject ( err ) ;
}
2014-07-24 12:09:46 +00:00
} else {
load _model ( index + 1 ) ;
2012-05-08 16:42:26 +00:00
}
2014-07-24 12:09:46 +00:00
}
}
2014-07-24 13:21:54 +00:00
try {
load _model ( 0 ) ;
} catch ( err ) {
loaded . reject ( err ) ;
}
2013-12-24 15:11:12 +00:00
2012-09-03 14:25:27 +00:00
return loaded ;
2012-05-21 14:09:01 +00:00
} ,
2012-04-24 17:20:47 +00:00
2014-08-22 10:07:19 +00:00
// reload the list of partner, returns as a deferred that resolves if there were
// updated partners, and fails if not
load _new _partners : function ( ) {
var self = this ;
var def = new $ . Deferred ( ) ;
var fields = _ . find ( this . models , function ( model ) { return model . model === 'res.partner' ; } ) . fields ;
new instance . web . Model ( 'res.partner' )
. query ( fields )
. filter ( [ [ 'write_date' , '>' , this . db . get _partner _write _date ( ) ] ] )
. all ( { 'timeout' : 3000 , 'shadow' : true } )
. then ( function ( partners ) {
if ( self . db . add _partners ( partners ) ) { // check if the partners we got were real updates
def . resolve ( ) ;
} else {
def . reject ( ) ;
}
2014-12-01 14:46:29 +00:00
} , function ( err , event ) { event . preventDefault ( ) ; def . reject ( ) ; } ) ;
2014-08-22 10:07:19 +00:00
return def ;
} ,
2012-05-21 14:09:01 +00:00
// this is called when an order is removed from the order collection. It ensures that there is always an existing
// order and a valid selected order
2013-09-05 10:43:57 +00:00
on _removed _order : function ( removed _order , index , reason ) {
2014-06-13 14:19:05 +00:00
if ( ( reason === 'abandon' || removed _order . temporary ) && this . get ( 'orders' ) . size ( ) > 0 ) {
2013-09-05 10:43:57 +00:00
// when we intentionally remove an unfinished order, and there is another existing one
this . set ( { 'selectedOrder' : this . get ( 'orders' ) . at ( index ) || this . get ( 'orders' ) . last ( ) } ) ;
2013-07-02 20:40:48 +00:00
} else {
2013-09-05 10:43:57 +00:00
// when the order was automatically removed after completion,
// or when we intentionally delete the only concurrent order
this . add _new _order ( ) ;
2012-05-21 14:09:01 +00:00
}
} ,
2012-04-24 16:25:46 +00:00
2012-08-09 17:21:13 +00:00
//creates a new empty order and sets it as the current order
add _new _order : function ( ) {
var order = new module . Order ( { pos : this } ) ;
this . get ( 'orders' ) . add ( order ) ;
this . set ( 'selectedOrder' , order ) ;
2012-04-24 17:20:47 +00:00
} ,
2012-08-09 17:21:13 +00:00
2014-06-13 14:19:05 +00:00
get _order : function ( ) {
return this . get ( 'selectedOrder' ) ;
} ,
2013-09-05 10:43:57 +00:00
//removes the current order
delete _current _order : function ( ) {
this . get ( 'selectedOrder' ) . destroy ( { 'reason' : 'abandon' } ) ;
} ,
2013-04-11 12:07:19 +00:00
// saves the order locally and try to send it to the backend.
// it returns a deferred that succeeds after having tried to send the order and all the other pending orders.
push _order : function ( order ) {
2013-03-28 15:07:18 +00:00
var self = this ;
2014-07-18 11:28:22 +00:00
if ( order ) {
this . proxy . log ( 'push_order' , order . export _as _JSON ( ) ) ;
this . db . add _order ( order . export _as _JSON ( ) ) ;
}
var pushed = new $ . Deferred ( ) ;
2013-03-28 15:07:18 +00:00
this . flush _mutex . exec ( function ( ) {
2014-07-18 11:28:22 +00:00
var flushed = self . _flush _orders ( self . db . get _orders ( ) ) ;
2013-03-28 15:07:18 +00:00
2014-07-18 11:28:22 +00:00
flushed . always ( function ( ids ) {
2013-04-11 12:07:19 +00:00
pushed . resolve ( ) ;
} ) ;
} ) ;
return pushed ;
} ,
// saves the order locally and try to send it to the backend and make an invoice
// returns a deferred that succeeds when the order has been posted and successfully generated
// an invoice. This method can fail in various ways:
// error-no-client: the order must have an associated partner_id. You can retry to make an invoice once
// this error is solved
// error-transfer: there was a connection error during the transfer. You can retry to make the invoice once
// the network connection is up
push _and _invoice _order : function ( order ) {
var self = this ;
var invoiced = new $ . Deferred ( ) ;
if ( ! order . get _client ( ) ) {
invoiced . reject ( 'error-no-client' ) ;
return invoiced ;
}
2013-03-28 15:07:18 +00:00
2013-04-11 12:07:19 +00:00
var order _id = this . db . add _order ( order . export _as _JSON ( ) ) ;
this . flush _mutex . exec ( function ( ) {
var done = new $ . Deferred ( ) ; // holds the mutex
// send the order to the server
// we have a 30 seconds timeout on this push.
// FIXME: if the server takes more than 30 seconds to accept the order,
// the client will believe it wasn't successfully sent, and very bad
// things will happen as a duplicate will be sent next time
// so we must make sure the server detects and ignores duplicated orders
2014-07-18 11:28:22 +00:00
var transfer = self . _flush _orders ( [ self . db . get _order ( order _id ) ] , { timeout : 30000 , to _invoice : true } ) ;
2013-04-11 12:07:19 +00:00
transfer . fail ( function ( ) {
invoiced . reject ( 'error-transfer' ) ;
done . reject ( ) ;
} ) ;
// on success, get the order id generated by the server
transfer . pipe ( function ( order _server _id ) {
2014-07-18 11:28:22 +00:00
2013-04-11 12:07:19 +00:00
// generate the pdf and download it
self . pos _widget . do _action ( 'point_of_sale.pos_invoice_report' , { additional _context : {
active _ids : order _server _id ,
} } ) ;
2014-07-18 11:28:22 +00:00
2013-04-11 12:07:19 +00:00
invoiced . resolve ( ) ;
done . resolve ( ) ;
2013-03-28 15:07:18 +00:00
} ) ;
return done ;
2013-04-11 12:07:19 +00:00
2013-03-28 15:07:18 +00:00
} ) ;
2013-04-11 12:07:19 +00:00
return invoiced ;
2013-03-28 15:07:18 +00:00
} ,
2014-07-18 11:28:22 +00:00
// wrapper around the _save_to_server that updates the synch status widget
_flush _orders : function ( orders , options ) {
2012-04-24 16:25:46 +00:00
var self = this ;
2013-03-28 15:07:18 +00:00
2014-07-18 11:28:22 +00:00
this . set ( 'synch' , { state : 'connecting' , pending : orders . length } ) ;
2013-03-28 15:07:18 +00:00
2014-07-18 11:28:22 +00:00
return self . _save _to _server ( orders , options ) . done ( function ( server _ids ) {
2014-02-14 14:49:30 +00:00
var pending = self . db . get _orders ( ) . length ;
2014-07-18 11:28:22 +00:00
2014-02-14 14:49:30 +00:00
self . set ( 'synch' , {
state : pending ? 'connecting' : 'connected' ,
pending : pending
} ) ;
2014-07-18 11:28:22 +00:00
return server _ids ;
2014-02-14 14:49:30 +00:00
} ) ;
} ,
// send an array of orders to the server
// available options:
// - timeout: timeout for the rpc call in ms
2014-07-18 11:28:22 +00:00
// returns a deferred that resolves with the list of
// server generated ids for the sent orders
2014-02-14 14:49:30 +00:00
_save _to _server : function ( orders , options ) {
if ( ! orders || ! orders . length ) {
var result = $ . Deferred ( ) ;
2014-07-18 11:28:22 +00:00
result . resolve ( [ ] ) ;
2014-02-14 14:49:30 +00:00
return result ;
2012-08-14 15:21:12 +00:00
}
2014-02-14 14:49:30 +00:00
options = options || { } ;
var self = this ;
2014-02-24 14:41:43 +00:00
var timeout = typeof options . timeout === 'number' ? options . timeout : 7500 * orders . length ;
2013-03-28 15:07:18 +00:00
2014-02-14 14:49:30 +00:00
// we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice,
// then we want to notify the user that we are waiting on something )
var posOrderModel = new instance . web . Model ( 'pos.order' ) ;
return posOrderModel . call ( 'create_from_ui' ,
[ _ . map ( orders , function ( order ) {
order . to _invoice = options . to _invoice || false ;
return order ;
} ) ] ,
undefined ,
{
shadow : ! options . to _invoice ,
timeout : timeout
}
2014-07-18 11:28:22 +00:00
) . then ( function ( server _ids ) {
2014-02-14 14:49:30 +00:00
_ . each ( orders , function ( order ) {
self . db . remove _order ( order . id ) ;
} ) ;
2014-07-18 11:28:22 +00:00
return server _ids ;
2014-07-22 12:33:32 +00:00
} ) . fail ( function ( error , event ) {
if ( error . code === 200 ) { // Business Logic Error, not a connection problem
self . pos _widget . screen _selector . show _popup ( 'error-traceback' , {
message : error . data . message ,
comment : error . data . debug
} ) ;
}
2014-02-14 14:49:30 +00:00
// prevent an error popup creation by the rpc failure
// we want the failure to be silent as we send the orders in the background
event . preventDefault ( ) ;
console . error ( 'Failed to send orders:' , orders ) ;
} ) ;
2012-04-24 16:25:46 +00:00
} ,
2012-05-29 16:39:44 +00:00
2013-09-23 14:51:39 +00:00
scan _product : function ( parsed _code ) {
2012-08-09 17:21:13 +00:00
var self = this ;
2012-08-13 16:00:26 +00:00
var selectedOrder = this . get ( 'selectedOrder' ) ;
2013-09-23 14:51:39 +00:00
if ( parsed _code . encoding === 'ean13' ) {
var product = this . db . get _product _by _ean13 ( parsed _code . base _code ) ;
} else if ( parsed _code . encoding === 'reference' ) {
var product = this . db . get _product _by _reference ( parsed _code . code ) ;
}
2012-08-13 16:00:26 +00:00
if ( ! product ) {
return false ;
}
2013-09-23 14:51:39 +00:00
if ( parsed _code . type === 'price' ) {
2013-12-02 16:21:44 +00:00
selectedOrder . addProduct ( product , { price : parsed _code . value } ) ;
2013-09-23 14:51:39 +00:00
} else if ( parsed _code . type === 'weight' ) {
2013-12-02 16:21:44 +00:00
selectedOrder . addProduct ( product , { quantity : parsed _code . value , merge : false } ) ;
2014-06-13 14:19:05 +00:00
} else if ( parsed _code . type === 'discount' ) {
selectedOrder . addProduct ( product , { discount : parsed _code . value , merge : false } ) ;
2012-08-13 16:00:26 +00:00
} else {
2013-12-02 16:21:44 +00:00
selectedOrder . addProduct ( product ) ;
2012-08-13 16:00:26 +00:00
}
return true ;
2012-05-29 13:31:41 +00:00
} ,
2012-04-24 16:25:46 +00:00
} ) ;
2014-06-13 14:19:05 +00:00
var orderline _id = 1 ;
2012-05-21 14:09:01 +00:00
// An orderline represent one element of the content of a client's shopping cart.
// An orderline contains a product, its quantity, its price, discount. etc.
// An Order contains zero or more Orderlines.
2012-04-24 16:25:46 +00:00
module . Orderline = Backbone . Model . extend ( {
2012-07-03 16:50:39 +00:00
initialize : function ( attr , options ) {
2012-07-03 11:47:41 +00:00
this . pos = options . pos ;
this . order = options . order ;
this . product = options . product ;
2013-12-02 16:21:44 +00:00
this . price = options . product . price ;
2012-07-03 11:47:41 +00:00
this . quantity = 1 ;
2013-01-22 13:18:01 +00:00
this . quantityStr = '1' ;
2012-07-03 11:47:41 +00:00
this . discount = 0 ;
2013-01-22 13:18:01 +00:00
this . discountStr = '0' ;
2012-07-03 11:47:41 +00:00
this . type = 'unit' ;
this . selected = false ;
2014-06-13 14:19:05 +00:00
this . id = orderline _id ++ ;
} ,
clone : function ( ) {
var orderline = new module . Orderline ( { } , {
pos : this . pos ,
order : null ,
product : this . product ,
price : this . price ,
} ) ;
orderline . quantity = this . quantity ;
orderline . quantityStr = this . quantityStr ;
orderline . discount = this . discount ;
orderline . type = this . type ;
orderline . selected = false ;
return orderline ;
2012-07-03 11:47:41 +00:00
} ,
// sets a discount [0,100]%
set _discount : function ( discount ) {
2013-01-24 14:06:31 +00:00
var disc = Math . min ( Math . max ( parseFloat ( discount ) || 0 , 0 ) , 100 ) ;
this . discount = disc ;
this . discountStr = '' + disc ;
2013-12-02 16:21:44 +00:00
this . trigger ( 'change' , this ) ;
2012-07-03 11:47:41 +00:00
} ,
// returns the discount [0,100]%
get _discount : function ( ) {
return this . discount ;
} ,
2013-01-22 13:18:01 +00:00
get _discount _str : function ( ) {
return this . discountStr ;
} ,
2012-07-03 11:47:41 +00:00
get _product _type : function ( ) {
return this . type ;
} ,
// sets the quantity of the product. The quantity will be rounded according to the
// product's unity of measure properties. Quantities greater than zero will not get
// rounded to zero
set _quantity : function ( quantity ) {
2013-01-24 14:06:31 +00:00
if ( quantity === 'remove' ) {
2012-07-03 11:47:41 +00:00
this . order . removeOrderline ( this ) ;
2013-01-22 13:18:01 +00:00
return ;
} else {
2014-02-11 16:45:41 +00:00
var quant = parseFloat ( quantity ) || 0 ;
2012-06-28 12:38:25 +00:00
var unit = this . get _unit ( ) ;
2013-01-22 13:18:01 +00:00
if ( unit ) {
2014-11-11 15:18:27 +00:00
if ( unit . rounding ) {
this . quantity = round _pr ( quant , unit . rounding ) ;
this . quantityStr = this . quantity . toFixed ( Math . ceil ( Math . log ( 1.0 / unit . rounding ) / Math . log ( 10 ) ) ) ;
} else {
this . quantity = round _pr ( quant , 1 ) ;
this . quantityStr = this . quantity . toFixed ( 0 ) ;
}
2013-01-22 13:18:01 +00:00
} else {
this . quantity = quant ;
this . quantityStr = '' + this . quantity ;
2012-06-28 12:38:25 +00:00
}
2012-06-04 15:04:59 +00:00
}
2013-12-02 16:21:44 +00:00
this . trigger ( 'change' , this ) ;
2012-07-03 11:47:41 +00:00
} ,
// return the quantity of product
get _quantity : function ( ) {
return this . quantity ;
2012-06-04 15:04:59 +00:00
} ,
2013-01-22 13:18:01 +00:00
get _quantity _str : function ( ) {
return this . quantityStr ;
} ,
get _quantity _str _with _unit : function ( ) {
var unit = this . get _unit ( ) ;
2014-06-13 14:19:05 +00:00
if ( unit && ! unit . is _unit ) {
2013-01-22 13:18:01 +00:00
return this . quantityStr + ' ' + unit . name ;
} else {
return this . quantityStr ;
}
} ,
2012-07-03 11:47:41 +00:00
// return the unit of measure of the product
2012-06-28 12:38:25 +00:00
get _unit : function ( ) {
2014-11-18 14:38:07 +00:00
var unit _id = this . product . uom _id ;
2012-06-28 12:38:25 +00:00
if ( ! unit _id ) {
return undefined ;
}
unit _id = unit _id [ 0 ] ;
if ( ! this . pos ) {
return undefined ;
}
2013-12-16 14:35:59 +00:00
return this . pos . units _by _id [ unit _id ] ;
2012-06-28 12:38:25 +00:00
} ,
2012-07-03 11:47:41 +00:00
// return the product of this orderline
get _product : function ( ) {
return this . product ;
} ,
// selects or deselects this orderline
set _selected : function ( selected ) {
this . selected = selected ;
2013-12-02 16:21:44 +00:00
this . trigger ( 'change' , this ) ;
2012-07-03 11:47:41 +00:00
} ,
// returns true if this orderline is selected
is _selected : function ( ) {
return this . selected ;
} ,
2012-05-22 14:59:56 +00:00
// when we add an new orderline we want to merge it with the last line to see reduce the number of items
// in the orderline. This returns true if it makes sense to merge the two
can _be _merged _with : function ( orderline ) {
2013-12-02 16:21:44 +00:00
if ( this . get _product ( ) . id !== orderline . get _product ( ) . id ) { //only orderline of the same product can be merged
2012-05-22 14:59:56 +00:00
return false ;
2014-06-13 14:19:05 +00:00
} else if ( ! this . get _unit ( ) || ! this . get _unit ( ) . groupable ) {
return false ;
2012-07-03 11:47:41 +00:00
} else if ( this . get _product _type ( ) !== orderline . get _product _type ( ) ) {
2012-05-22 14:59:56 +00:00
return false ;
2012-07-03 11:47:41 +00:00
} else if ( this . get _discount ( ) > 0 ) { // we don't merge discounted orderlines
2012-05-22 14:59:56 +00:00
return false ;
2012-07-30 15:41:43 +00:00
} else if ( this . price !== orderline . price ) {
2012-05-22 14:59:56 +00:00
return false ;
2012-07-30 15:41:43 +00:00
} else {
return true ;
2012-05-22 14:59:56 +00:00
}
} ,
merge : function ( orderline ) {
2012-07-03 11:47:41 +00:00
this . set _quantity ( this . get _quantity ( ) + orderline . get _quantity ( ) ) ;
2012-04-24 16:25:46 +00:00
} ,
2012-07-03 11:47:41 +00:00
export _as _JSON : function ( ) {
return {
qty : this . get _quantity ( ) ,
2013-01-22 13:18:01 +00:00
price _unit : this . get _unit _price ( ) ,
2012-07-03 11:47:41 +00:00
discount : this . get _discount ( ) ,
2013-12-02 16:21:44 +00:00
product _id : this . get _product ( ) . id ,
2012-07-03 14:03:15 +00:00
} ;
} ,
2012-07-10 10:15:15 +00:00
//used to create a json of the ticket, to be sent to the printer
2012-07-03 14:03:15 +00:00
export _for _printing : function ( ) {
return {
quantity : this . get _quantity ( ) ,
unit _name : this . get _unit ( ) . name ,
2013-01-22 13:18:01 +00:00
price : this . get _unit _price ( ) ,
2012-07-03 14:03:15 +00:00
discount : this . get _discount ( ) ,
2014-08-18 12:47:53 +00:00
product _name : this . get _product ( ) . display _name ,
2013-01-22 13:18:01 +00:00
price _display : this . get _display _price ( ) ,
2012-07-03 14:03:15 +00:00
price _with _tax : this . get _price _with _tax ( ) ,
price _without _tax : this . get _price _without _tax ( ) ,
tax : this . get _tax ( ) ,
2013-12-02 16:21:44 +00:00
product _description : this . get _product ( ) . description ,
product _description _sale : this . get _product ( ) . description _sale ,
2012-07-03 11:47:41 +00:00
} ;
2012-05-11 16:02:23 +00:00
} ,
2013-01-22 13:18:01 +00:00
// changes the base price of the product for this orderline
set _unit _price : function ( price ) {
2014-11-11 18:32:52 +00:00
this . price = round _di ( parseFloat ( price ) || 0 , this . pos . dp [ 'Product Price' ] ) ;
2013-12-02 16:21:44 +00:00
this . trigger ( 'change' , this ) ;
2013-01-22 13:18:01 +00:00
} ,
get _unit _price : function ( ) {
2014-11-11 18:32:52 +00:00
return this . price ;
2013-01-22 13:18:01 +00:00
} ,
2014-11-13 15:07:52 +00:00
get _base _price : function ( ) {
2013-12-16 14:35:59 +00:00
var rounding = this . pos . currency . rounding ;
2014-12-11 15:57:48 +00:00
var price = round _pr ( round _pr ( this . get _unit _price ( ) * this . get _quantity ( ) , rounding ) * ( 1 - this . get _discount ( ) / 100.0 ) , rounding ) ;
return price ;
2013-01-22 13:18:01 +00:00
} ,
2014-11-13 15:07:52 +00:00
get _display _price : function ( ) {
return this . get _base _price ( ) ;
} ,
2012-07-03 11:47:41 +00:00
get _price _without _tax : function ( ) {
return this . get _all _prices ( ) . priceWithoutTax ;
2012-04-24 16:25:46 +00:00
} ,
2012-07-03 11:47:41 +00:00
get _price _with _tax : function ( ) {
return this . get _all _prices ( ) . priceWithTax ;
2012-04-24 16:25:46 +00:00
} ,
2012-07-03 11:47:41 +00:00
get _tax : function ( ) {
return this . get _all _prices ( ) . tax ;
2012-04-24 16:25:46 +00:00
} ,
2014-12-18 13:15:05 +00:00
get _applicable _taxes : function ( ) {
// Shenaningans because we need
// to keep the taxes ordering.
var ptaxes _ids = this . get _product ( ) . taxes _id ;
var ptaxes _set = { } ;
for ( var i = 0 ; i < ptaxes _ids . length ; i ++ ) {
ptaxes _set [ ptaxes _ids [ i ] ] = true ;
}
2014-12-11 15:57:48 +00:00
var taxes = [ ] ;
2014-12-18 13:15:05 +00:00
for ( var i = 0 ; i < this . pos . taxes . length ; i ++ ) {
if ( ptaxes _set [ this . pos . taxes [ i ] . id ] ) {
taxes . push ( this . pos . taxes [ i ] ) ;
}
2014-12-11 15:57:48 +00:00
}
return taxes ;
} ,
2014-01-06 13:51:19 +00:00
get _tax _details : function ( ) {
return this . get _all _prices ( ) . taxDetails ;
} ,
2013-01-28 17:18:00 +00:00
get _all _prices : function ( ) {
2012-04-24 16:25:46 +00:00
var self = this ;
2013-12-16 14:35:59 +00:00
var currency _rounding = this . pos . currency . rounding ;
2014-11-13 15:07:52 +00:00
var base = this . get _base _price ( ) ;
2012-04-24 16:25:46 +00:00
var totalTax = base ;
var totalNoTax = base ;
2012-07-03 11:47:41 +00:00
var product = this . get _product ( ) ;
2014-12-18 13:15:05 +00:00
var taxes = this . get _applicable _taxes ( ) ;
2012-04-24 16:25:46 +00:00
var taxtotal = 0 ;
2014-01-06 13:51:19 +00:00
var taxdetail = { } ;
2014-12-11 15:57:48 +00:00
_ . each ( taxes , function ( tax ) {
2012-04-24 16:25:46 +00:00
if ( tax . price _include ) {
var tmp ;
if ( tax . type === "percent" ) {
2013-01-28 17:18:00 +00:00
tmp = base - round _pr ( base / ( 1 + tax . amount ) , currency _rounding ) ;
2012-04-24 16:25:46 +00:00
} else if ( tax . type === "fixed" ) {
2013-01-28 17:18:00 +00:00
tmp = round _pr ( tax . amount * self . get _quantity ( ) , currency _rounding ) ;
2012-04-24 16:25:46 +00:00
} else {
throw "This type of tax is not supported by the point of sale: " + tax . type ;
}
2013-01-28 17:18:00 +00:00
tmp = round _pr ( tmp , currency _rounding ) ;
2012-04-24 16:25:46 +00:00
taxtotal += tmp ;
totalNoTax -= tmp ;
2014-01-06 13:51:19 +00:00
taxdetail [ tax . id ] = tmp ;
2012-04-24 16:25:46 +00:00
} else {
var tmp ;
if ( tax . type === "percent" ) {
tmp = tax . amount * base ;
} else if ( tax . type === "fixed" ) {
2012-07-03 11:47:41 +00:00
tmp = tax . amount * self . get _quantity ( ) ;
2012-04-24 16:25:46 +00:00
} else {
throw "This type of tax is not supported by the point of sale: " + tax . type ;
}
2014-12-18 13:15:05 +00:00
2013-01-28 17:18:00 +00:00
tmp = round _pr ( tmp , currency _rounding ) ;
2014-12-18 13:15:05 +00:00
if ( tax . include _base _amount ) {
base += tmp ;
}
2012-04-24 16:25:46 +00:00
taxtotal += tmp ;
totalTax += tmp ;
2014-01-06 13:51:19 +00:00
taxdetail [ tax . id ] = tmp ;
2012-04-24 16:25:46 +00:00
}
} ) ;
return {
"priceWithTax" : totalTax ,
"priceWithoutTax" : totalNoTax ,
"tax" : taxtotal ,
2014-01-06 13:51:19 +00:00
"taxDetails" : taxdetail ,
2012-04-24 16:25:46 +00:00
} ;
} ,
} ) ;
module . OrderlineCollection = Backbone . Collection . extend ( {
model : module . Orderline ,
} ) ;
2013-12-04 17:21:22 +00:00
// Every Paymentline contains a cashregister and an amount of money.
2012-04-24 16:25:46 +00:00
module . Paymentline = Backbone . Model . extend ( {
2012-07-03 16:50:39 +00:00
initialize : function ( attributes , options ) {
2012-07-03 11:47:41 +00:00
this . amount = 0 ;
2013-12-16 14:35:59 +00:00
this . cashregister = options . cashregister ;
this . name = this . cashregister . journal _id [ 1 ] ;
2013-12-04 17:21:22 +00:00
this . selected = false ;
2014-11-11 16:41:20 +00:00
this . pos = options . pos ;
2012-04-24 16:25:46 +00:00
} ,
2012-07-03 11:47:41 +00:00
//sets the amount of money on this payment line
set _amount : function ( value ) {
2014-11-11 16:41:20 +00:00
this . amount = round _di ( parseFloat ( value ) || 0 , this . pos . currency . decimals ) ;
2013-12-04 17:21:22 +00:00
this . trigger ( 'change:amount' , this ) ;
2012-04-24 16:25:46 +00:00
} ,
2012-07-03 11:47:41 +00:00
// returns the amount of money on this paymentline
get _amount : function ( ) {
return this . amount ;
2012-04-24 16:25:46 +00:00
} ,
2014-11-11 16:41:20 +00:00
get _amount _str : function ( ) {
return this . amount . toFixed ( this . pos . currency . decimals ) ;
} ,
2013-12-04 17:21:22 +00:00
set _selected : function ( selected ) {
if ( this . selected !== selected ) {
this . selected = selected ;
this . trigger ( 'change:selected' , this ) ;
}
2012-07-03 11:47:41 +00:00
} ,
2013-12-16 14:35:59 +00:00
// returns the associated cashregister
2012-07-03 11:47:41 +00:00
//exports as JSON for server communication
export _as _JSON : function ( ) {
2012-04-24 16:25:46 +00:00
return {
name : instance . web . datetime _to _str ( new Date ( ) ) ,
2013-12-16 14:35:59 +00:00
statement _id : this . cashregister . id ,
account _id : this . cashregister . account _id [ 0 ] ,
journal _id : this . cashregister . journal _id [ 0 ] ,
2012-07-03 11:47:41 +00:00
amount : this . get _amount ( )
2012-04-24 16:25:46 +00:00
} ;
} ,
2012-07-10 10:15:15 +00:00
//exports as JSON for receipt printing
2012-07-03 14:03:15 +00:00
export _for _printing : function ( ) {
return {
amount : this . get _amount ( ) ,
2013-12-16 14:35:59 +00:00
journal : this . cashregister . journal _id [ 1 ] ,
2012-07-03 14:03:15 +00:00
} ;
} ,
2012-04-24 16:25:46 +00:00
} ) ;
module . PaymentlineCollection = Backbone . Collection . extend ( {
model : module . Paymentline ,
} ) ;
2012-07-03 11:47:41 +00:00
2012-05-21 14:09:01 +00:00
// An order more or less represents the content of a client's shopping cart (the OrderLines)
2013-12-04 17:21:22 +00:00
// plus the associated payment information (the Paymentlines)
2012-05-21 14:09:01 +00:00
// there is always an active ('selected') order in the Pos, a new one is created
// automaticaly once an order is completed and sent to the server.
2012-04-24 16:25:46 +00:00
module . Order = Backbone . Model . extend ( {
initialize : function ( attributes ) {
Backbone . Model . prototype . initialize . apply ( this , arguments ) ;
2014-08-18 10:19:56 +00:00
this . pos = attributes . pos ;
this . sequence _number = this . pos . pos _session . sequence _number ++ ;
2013-04-11 12:07:19 +00:00
this . uid = this . generateUniqueId ( ) ;
2012-04-24 16:25:46 +00:00
this . set ( {
creationDate : new Date ( ) ,
orderLines : new module . OrderlineCollection ( ) ,
paymentLines : new module . PaymentlineCollection ( ) ,
2014-08-20 13:07:56 +00:00
name : _t ( "Order " ) + this . uid ,
2012-07-12 17:00:13 +00:00
client : null ,
2012-04-24 16:25:46 +00:00
} ) ;
2013-12-04 17:21:22 +00:00
this . selected _orderline = undefined ;
this . selected _paymentline = undefined ;
2012-07-09 15:53:51 +00:00
this . screen _data = { } ; // see ScreenSelector
2012-09-05 16:42:21 +00:00
this . receipt _type = 'receipt' ; // 'receipt' || 'invoice'
2014-06-13 14:19:05 +00:00
this . temporary = attributes . temporary || false ;
2012-04-24 16:25:46 +00:00
return this ;
} ,
2014-06-13 14:19:05 +00:00
is _empty : function ( ) {
return ( this . get ( 'orderLines' ) . models . length === 0 ) ;
} ,
2014-08-18 10:19:56 +00:00
// Generates a public identification number for the order.
// The generated number must be unique and sequential. They are made 12 digit long
2014-08-20 13:07:56 +00:00
// to fit into EAN-13 barcodes, should it be needed
2012-04-24 16:25:46 +00:00
generateUniqueId : function ( ) {
2014-08-18 10:19:56 +00:00
function zero _pad ( num , size ) {
var s = "" + num ;
while ( s . length < size ) {
s = "0" + s ;
}
return s ;
}
2014-08-26 10:38:35 +00:00
return zero _pad ( this . pos . pos _session . id , 5 ) + '-' +
2014-08-20 13:07:56 +00:00
zero _pad ( this . pos . pos _session . login _number , 3 ) + '-' +
zero _pad ( this . sequence _number , 4 ) ;
2012-04-24 16:25:46 +00:00
} ,
2014-06-13 14:19:05 +00:00
addOrderline : function ( line ) {
if ( line . order ) {
order . removeOrderline ( line ) ;
}
line . order = this ;
this . get ( 'orderLines' ) . add ( line ) ;
this . selectLine ( this . getLastOrderline ( ) ) ;
} ,
2012-07-12 17:00:13 +00:00
addProduct : function ( product , options ) {
options = options || { } ;
2013-12-02 16:21:44 +00:00
var attr = JSON . parse ( JSON . stringify ( product ) ) ;
2012-05-22 14:59:56 +00:00
attr . pos = this . pos ;
2012-06-28 12:38:25 +00:00
attr . order = this ;
2012-07-03 16:50:39 +00:00
var line = new module . Orderline ( { } , { pos : this . pos , order : this , product : product } ) ;
2012-07-09 15:53:51 +00:00
2012-07-12 17:00:13 +00:00
if ( options . quantity !== undefined ) {
line . set _quantity ( options . quantity ) ;
}
if ( options . price !== undefined ) {
2013-01-22 13:18:01 +00:00
line . set _unit _price ( options . price ) ;
2012-07-09 15:53:51 +00:00
}
2014-06-13 14:19:05 +00:00
if ( options . discount !== undefined ) {
line . set _discount ( options . discount ) ;
}
2012-05-22 14:59:56 +00:00
2012-06-28 12:38:25 +00:00
var last _orderline = this . getLastOrderline ( ) ;
2012-07-12 17:00:13 +00:00
if ( last _orderline && last _orderline . can _be _merged _with ( line ) && options . merge !== false ) {
2012-06-28 12:38:25 +00:00
last _orderline . merge ( line ) ;
2012-05-22 14:59:56 +00:00
} else {
this . get ( 'orderLines' ) . add ( line ) ;
}
2012-06-28 12:38:25 +00:00
this . selectLine ( this . getLastOrderline ( ) ) ;
2012-05-22 14:59:56 +00:00
} ,
2012-06-28 12:38:25 +00:00
removeOrderline : function ( line ) {
this . get ( 'orderLines' ) . remove ( line ) ;
this . selectLine ( this . getLastOrderline ( ) ) ;
} ,
2014-06-13 14:19:05 +00:00
getOrderline : function ( id ) {
var orderlines = this . get ( 'orderLines' ) . models ;
for ( var i = 0 ; i < orderlines . length ; i ++ ) {
if ( orderlines [ i ] . id === id ) {
return orderlines [ i ] ;
}
}
return null ;
} ,
2012-06-28 12:38:25 +00:00
getLastOrderline : function ( ) {
return this . get ( 'orderLines' ) . at ( this . get ( 'orderLines' ) . length - 1 ) ;
2012-04-24 16:25:46 +00:00
} ,
2013-12-16 14:35:59 +00:00
addPaymentline : function ( cashregister ) {
2012-07-03 16:50:39 +00:00
var paymentLines = this . get ( 'paymentLines' ) ;
2014-11-11 16:41:20 +00:00
var newPaymentline = new module . Paymentline ( { } , { cashregister : cashregister , pos : this . pos } ) ;
2013-12-16 14:35:59 +00:00
if ( cashregister . journal . type !== 'cash' ) {
2013-12-04 17:21:22 +00:00
newPaymentline . set _amount ( Math . max ( this . getDueLeft ( ) , 0 ) ) ;
2012-07-03 16:50:39 +00:00
}
paymentLines . add ( newPaymentline ) ;
2013-12-04 17:21:22 +00:00
this . selectPaymentline ( newPaymentline ) ;
} ,
removePaymentline : function ( line ) {
if ( this . selected _paymentline === line ) {
this . selectPaymentline ( undefined ) ;
}
this . get ( 'paymentLines' ) . remove ( line ) ;
2012-04-24 16:25:46 +00:00
} ,
getName : function ( ) {
return this . get ( 'name' ) ;
} ,
2013-01-22 13:18:01 +00:00
getSubtotal : function ( ) {
return ( this . get ( 'orderLines' ) ) . reduce ( ( function ( sum , orderLine ) {
return sum + orderLine . get _display _price ( ) ;
} ) , 0 ) ;
} ,
getTotalTaxIncluded : function ( ) {
2012-04-24 16:25:46 +00:00
return ( this . get ( 'orderLines' ) ) . reduce ( ( function ( sum , orderLine ) {
2012-07-03 11:47:41 +00:00
return sum + orderLine . get _price _with _tax ( ) ;
2012-04-24 16:25:46 +00:00
} ) , 0 ) ;
} ,
2012-10-05 11:05:04 +00:00
getDiscountTotal : function ( ) {
return ( this . get ( 'orderLines' ) ) . reduce ( ( function ( sum , orderLine ) {
2013-01-22 13:18:01 +00:00
return sum + ( orderLine . get _unit _price ( ) * ( orderLine . get _discount ( ) / 100 ) * orderLine . get _quantity ( ) ) ;
2012-10-05 11:05:04 +00:00
} ) , 0 ) ;
} ,
2012-04-24 16:25:46 +00:00
getTotalTaxExcluded : function ( ) {
return ( this . get ( 'orderLines' ) ) . reduce ( ( function ( sum , orderLine ) {
2012-07-03 11:47:41 +00:00
return sum + orderLine . get _price _without _tax ( ) ;
2012-04-24 16:25:46 +00:00
} ) , 0 ) ;
} ,
getTax : function ( ) {
return ( this . get ( 'orderLines' ) ) . reduce ( ( function ( sum , orderLine ) {
2012-07-03 11:47:41 +00:00
return sum + orderLine . get _tax ( ) ;
2012-04-24 16:25:46 +00:00
} ) , 0 ) ;
} ,
2014-01-06 13:51:19 +00:00
getTaxDetails : function ( ) {
var details = { } ;
var fulldetails = [ ] ;
this . get ( 'orderLines' ) . each ( function ( line ) {
var ldetails = line . get _tax _details ( ) ;
for ( var id in ldetails ) {
if ( ldetails . hasOwnProperty ( id ) ) {
details [ id ] = ( details [ id ] || 0 ) + ldetails [ id ] ;
}
}
} ) ;
for ( var id in details ) {
if ( details . hasOwnProperty ( id ) ) {
2014-12-11 15:57:48 +00:00
fulldetails . push ( { amount : details [ id ] , tax : this . pos . taxes _by _id [ id ] , name : this . pos . taxes _by _id [ id ] . name } ) ;
2014-01-06 13:51:19 +00:00
}
}
return fulldetails ;
} ,
2012-04-24 16:25:46 +00:00
getPaidTotal : function ( ) {
return ( this . get ( 'paymentLines' ) ) . reduce ( ( function ( sum , paymentLine ) {
2012-07-03 11:47:41 +00:00
return sum + paymentLine . get _amount ( ) ;
2012-04-24 16:25:46 +00:00
} ) , 0 ) ;
} ,
getChange : function ( ) {
2013-01-23 18:32:05 +00:00
return this . getPaidTotal ( ) - this . getTotalTaxIncluded ( ) ;
2012-04-24 16:25:46 +00:00
} ,
getDueLeft : function ( ) {
2013-01-23 18:32:05 +00:00
return this . getTotalTaxIncluded ( ) - this . getPaidTotal ( ) ;
2012-04-24 16:25:46 +00:00
} ,
2012-09-05 16:42:21 +00:00
// sets the type of receipt 'receipt'(default) or 'invoice'
set _receipt _type : function ( type ) {
this . receipt _type = type ;
} ,
get _receipt _type : function ( ) {
return this . receipt _type ;
} ,
2012-07-12 17:00:13 +00:00
// the client related to the current order.
set _client : function ( client ) {
this . set ( 'client' , client ) ;
} ,
get _client : function ( ) {
return this . get ( 'client' ) ;
} ,
2012-11-21 11:26:44 +00:00
get _client _name : function ( ) {
var client = this . get ( 'client' ) ;
return client ? client . name : "" ;
} ,
2012-07-09 15:53:51 +00:00
// the order also stores the screen status, as the PoS supports
// different active screens per order. This method is used to
2012-07-10 10:15:15 +00:00
// store the screen status.
2012-07-09 15:53:51 +00:00
set _screen _data : function ( key , value ) {
if ( arguments . length === 2 ) {
this . screen _data [ key ] = value ;
} else if ( arguments . length === 1 ) {
for ( key in arguments [ 0 ] ) {
this . screen _data [ key ] = arguments [ 0 ] [ key ] ;
}
}
} ,
//see set_screen_data
get _screen _data : function ( key ) {
return this . screen _data [ key ] ;
} ,
2012-07-10 10:15:15 +00:00
// exports a JSON for receipt printing
2012-07-03 14:03:15 +00:00
export _for _printing : function ( ) {
var orderlines = [ ] ;
this . get ( 'orderLines' ) . each ( function ( orderline ) {
orderlines . push ( orderline . export _for _printing ( ) ) ;
} ) ;
var paymentlines = [ ] ;
this . get ( 'paymentLines' ) . each ( function ( paymentline ) {
paymentlines . push ( paymentline . export _for _printing ( ) ) ;
} ) ;
2012-07-12 17:00:13 +00:00
var client = this . get ( 'client' ) ;
2013-12-16 14:35:59 +00:00
var cashier = this . pos . cashier || this . pos . user ;
var company = this . pos . company ;
2014-06-13 14:19:05 +00:00
var shop = this . pos . shop ;
2012-07-03 14:03:15 +00:00
var date = new Date ( ) ;
return {
orderlines : orderlines ,
paymentlines : paymentlines ,
2013-01-24 16:22:25 +00:00
subtotal : this . getSubtotal ( ) ,
2013-01-23 18:32:05 +00:00
total _with _tax : this . getTotalTaxIncluded ( ) ,
2012-07-03 14:03:15 +00:00
total _without _tax : this . getTotalTaxExcluded ( ) ,
total _tax : this . getTax ( ) ,
total _paid : this . getPaidTotal ( ) ,
2013-01-24 16:22:25 +00:00
total _discount : this . getDiscountTotal ( ) ,
2014-01-06 13:51:19 +00:00
tax _details : this . getTaxDetails ( ) ,
2012-07-03 14:03:15 +00:00
change : this . getChange ( ) ,
name : this . getName ( ) ,
client : client ? client . name : null ,
2012-08-20 13:59:57 +00:00
invoice _id : null , //TODO
2012-07-03 14:03:15 +00:00
cashier : cashier ? cashier . name : null ,
2014-01-02 14:15:19 +00:00
header : this . pos . config . receipt _header || '' ,
footer : this . pos . config . receipt _footer || '' ,
2013-12-26 16:56:20 +00:00
precision : {
price : 2 ,
money : 2 ,
quantity : 3 ,
} ,
2012-07-03 14:03:15 +00:00
date : {
year : date . getFullYear ( ) ,
month : date . getMonth ( ) ,
date : date . getDate ( ) , // day of the month
day : date . getDay ( ) , // day of the week
hour : date . getHours ( ) ,
2013-12-20 16:30:57 +00:00
minute : date . getMinutes ( ) ,
isostring : date . toISOString ( ) ,
2014-03-20 16:45:30 +00:00
localestring : date . toLocaleString ( ) ,
2012-07-03 14:03:15 +00:00
} ,
2012-07-09 15:53:51 +00:00
company : {
email : company . email ,
website : company . website ,
company _registry : company . company _registry ,
2014-01-27 16:10:05 +00:00
contact _address : company . partner _id [ 1 ] ,
2012-07-09 15:53:51 +00:00
vat : company . vat ,
name : company . name ,
phone : company . phone ,
2013-12-24 15:11:12 +00:00
logo : this . pos . company _logo _base64 ,
2012-07-09 15:53:51 +00:00
} ,
2014-06-13 14:19:05 +00:00
shop : {
name : shop . name ,
} ,
2013-12-16 14:35:59 +00:00
currency : this . pos . currency ,
2012-07-03 14:03:15 +00:00
} ;
} ,
2013-04-11 12:07:19 +00:00
export _as _JSON : function ( ) {
2012-04-24 16:25:46 +00:00
var orderLines , paymentLines ;
orderLines = [ ] ;
( this . get ( 'orderLines' ) ) . each ( _ . bind ( function ( item ) {
2012-07-03 11:47:41 +00:00
return orderLines . push ( [ 0 , 0 , item . export _as _JSON ( ) ] ) ;
2012-04-24 16:25:46 +00:00
} , this ) ) ;
paymentLines = [ ] ;
( this . get ( 'paymentLines' ) ) . each ( _ . bind ( function ( item ) {
2012-07-03 11:47:41 +00:00
return paymentLines . push ( [ 0 , 0 , item . export _as _JSON ( ) ] ) ;
2012-04-24 16:25:46 +00:00
} , this ) ) ;
return {
name : this . getName ( ) ,
amount _paid : this . getPaidTotal ( ) ,
2013-01-23 18:32:05 +00:00
amount _total : this . getTotalTaxIncluded ( ) ,
2012-04-24 16:25:46 +00:00
amount _tax : this . getTax ( ) ,
amount _return : this . getChange ( ) ,
lines : orderLines ,
2012-06-04 11:52:44 +00:00
statement _ids : paymentLines ,
2013-12-16 14:35:59 +00:00
pos _session _id : this . pos . pos _session . id ,
2013-03-29 11:00:48 +00:00
partner _id : this . get _client ( ) ? this . get _client ( ) . id : false ,
2013-12-16 14:35:59 +00:00
user _id : this . pos . cashier ? this . pos . cashier . id : this . pos . user . id ,
2013-04-11 12:07:19 +00:00
uid : this . uid ,
2014-06-13 14:19:05 +00:00
sequence _number : this . sequence _number ,
2012-04-24 16:25:46 +00:00
} ;
} ,
2012-06-28 12:38:25 +00:00
getSelectedLine : function ( ) {
return this . selected _orderline ;
} ,
selectLine : function ( line ) {
if ( line ) {
if ( line !== this . selected _orderline ) {
if ( this . selected _orderline ) {
2012-07-03 11:47:41 +00:00
this . selected _orderline . set _selected ( false ) ;
2012-06-28 12:38:25 +00:00
}
this . selected _orderline = line ;
2012-07-03 11:47:41 +00:00
this . selected _orderline . set _selected ( true ) ;
2012-06-28 12:38:25 +00:00
}
} else {
this . selected _orderline = undefined ;
}
} ,
2013-12-02 16:21:44 +00:00
deselectLine : function ( ) {
if ( this . selected _orderline ) {
this . selected _orderline . set _selected ( false ) ;
this . selected _orderline = undefined ;
}
} ,
2013-12-04 17:21:22 +00:00
selectPaymentline : function ( line ) {
if ( line !== this . selected _paymentline ) {
if ( this . selected _paymentline ) {
this . selected _paymentline . set _selected ( false ) ;
}
this . selected _paymentline = line ;
if ( this . selected _paymentline ) {
this . selected _paymentline . set _selected ( true ) ;
}
this . trigger ( 'change:selected_paymentline' , this . selected _paymentline ) ;
}
} ,
2012-04-24 16:25:46 +00:00
} ) ;
module . OrderCollection = Backbone . Collection . extend ( {
model : module . Order ,
} ) ;
/ *
The numpad handles both the choice of the property currently being modified
( quantity , price or discount ) and the edition of the corresponding numeric value .
* /
module . NumpadState = Backbone . Model . extend ( {
defaults : {
buffer : "0" ,
mode : "quantity"
} ,
appendNewChar : function ( newChar ) {
var oldBuffer ;
oldBuffer = this . get ( 'buffer' ) ;
if ( oldBuffer === '0' ) {
this . set ( {
buffer : newChar
} ) ;
} else if ( oldBuffer === '-0' ) {
this . set ( {
buffer : "-" + newChar
} ) ;
} else {
this . set ( {
buffer : ( this . get ( 'buffer' ) ) + newChar
} ) ;
}
2013-01-24 14:06:31 +00:00
this . trigger ( 'set_value' , this . get ( 'buffer' ) ) ;
2012-04-24 16:25:46 +00:00
} ,
deleteLastChar : function ( ) {
2013-01-24 14:06:31 +00:00
if ( this . get ( 'buffer' ) === "" ) {
2013-01-22 13:18:01 +00:00
if ( this . get ( 'mode' ) === 'quantity' ) {
2013-01-24 14:06:31 +00:00
this . trigger ( 'set_value' , 'remove' ) ;
2013-01-22 13:18:01 +00:00
} else {
2013-01-24 14:06:31 +00:00
this . trigger ( 'set_value' , this . get ( 'buffer' ) ) ;
2012-06-28 12:38:25 +00:00
}
} else {
2013-01-24 14:06:31 +00:00
var newBuffer = this . get ( 'buffer' ) . slice ( 0 , - 1 ) || "" ;
this . set ( { buffer : newBuffer } ) ;
this . trigger ( 'set_value' , this . get ( 'buffer' ) ) ;
2012-04-24 16:25:46 +00:00
}
} ,
switchSign : function ( ) {
var oldBuffer ;
oldBuffer = this . get ( 'buffer' ) ;
this . set ( {
2014-02-11 16:45:41 +00:00
buffer : oldBuffer [ 0 ] === '-' ? oldBuffer . substr ( 1 ) : "-" + oldBuffer
2012-04-24 16:25:46 +00:00
} ) ;
2013-01-24 14:06:31 +00:00
this . trigger ( 'set_value' , this . get ( 'buffer' ) ) ;
2012-04-24 16:25:46 +00:00
} ,
changeMode : function ( newMode ) {
this . set ( {
buffer : "0" ,
mode : newMode
} ) ;
} ,
reset : function ( ) {
this . set ( {
buffer : "0" ,
mode : "quantity"
} ) ;
} ,
2013-01-22 13:18:01 +00:00
resetValue : function ( ) {
this . set ( { buffer : '0' } ) ;
2012-06-28 12:38:25 +00:00
} ,
2012-04-24 16:25:46 +00:00
} ) ;
}