2020-07-23 13:21:26 +00:00
erpnext . PointOfSale . Controller = class {
2020-09-10 13:58:46 +00:00
constructor ( wrapper ) {
2020-07-23 13:21:26 +00:00
this . wrapper = $ ( wrapper ) . find ( '.layout-main-section' ) ;
this . page = wrapper . page ;
2020-11-23 08:10:46 +00:00
this . check _opening _entry ( ) ;
2020-07-23 13:21:26 +00:00
}
2020-10-26 05:47:04 +00:00
fetch _opening _entry ( ) {
return frappe . call ( "erpnext.selling.page.point_of_sale.point_of_sale.check_opening_entry" , { "user" : frappe . session . user } ) ;
}
2020-07-23 13:21:26 +00:00
check _opening _entry ( ) {
2020-10-26 05:47:04 +00:00
this . fetch _opening _entry ( ) . then ( ( r ) => {
if ( r . message . length ) {
// assuming only one opening voucher is available for the current user
this . prepare _app _defaults ( r . message [ 0 ] ) ;
} else {
this . create _opening _voucher ( ) ;
}
} ) ;
2020-07-23 13:21:26 +00:00
}
create _opening _voucher ( ) {
2020-11-23 12:23:34 +00:00
const me = this ;
2020-07-23 13:21:26 +00:00
const table _fields = [
2020-10-26 05:47:04 +00:00
{
fieldname : "mode_of_payment" , fieldtype : "Link" ,
in _list _view : 1 , label : "Mode of Payment" ,
options : "Mode of Payment" , reqd : 1
} ,
{
fieldname : "opening_amount" , fieldtype : "Currency" ,
in _list _view : 1 , label : "Opening Amount" ,
2020-11-18 09:30:34 +00:00
options : "company:company_currency" ,
2020-10-26 05:47:04 +00:00
change : function ( ) {
dialog . fields _dict . balance _details . df . data . some ( d => {
if ( d . idx == this . doc . idx ) {
d . opening _amount = this . value ;
dialog . fields _dict . balance _details . grid . refresh ( ) ;
return true ;
}
} ) ;
}
}
2020-07-23 13:21:26 +00:00
] ;
2020-10-26 05:47:04 +00:00
const fetch _pos _payment _methods = ( ) => {
const pos _profile = dialog . fields _dict . pos _profile . get _value ( ) ;
if ( ! pos _profile ) return ;
frappe . db . get _doc ( "POS Profile" , pos _profile ) . then ( ( { payments } ) => {
dialog . fields _dict . balance _details . df . data = [ ] ;
payments . forEach ( pay => {
const { mode _of _payment } = pay ;
dialog . fields _dict . balance _details . df . data . push ( { mode _of _payment , opening _amount : '0' } ) ;
} ) ;
dialog . fields _dict . balance _details . grid . refresh ( ) ;
} ) ;
}
2020-07-23 13:21:26 +00:00
const dialog = new frappe . ui . Dialog ( {
title : _ _ ( 'Create POS Opening Entry' ) ,
2020-10-26 05:47:04 +00:00
static : true ,
2020-07-23 13:21:26 +00:00
fields : [
{
fieldtype : 'Link' , label : _ _ ( 'Company' ) , default : frappe . defaults . get _default ( 'company' ) ,
options : 'Company' , fieldname : 'company' , reqd : 1
} ,
{
fieldtype : 'Link' , label : _ _ ( 'POS Profile' ) ,
options : 'POS Profile' , fieldname : 'pos_profile' , reqd : 1 ,
2021-01-28 12:28:55 +00:00
get _query : ( ) => pos _profile _query ,
2020-10-26 05:47:04 +00:00
onchange : ( ) => fetch _pos _payment _methods ( )
2020-07-23 13:21:26 +00:00
} ,
{
fieldname : "balance_details" ,
fieldtype : "Table" ,
label : "Opening Balance Details" ,
cannot _add _rows : false ,
in _place _edit : true ,
reqd : 1 ,
data : [ ] ,
fields : table _fields
}
] ,
2020-11-23 12:23:34 +00:00
primary _action : async function ( { company , pos _profile , balance _details } ) {
2020-07-23 13:21:26 +00:00
if ( ! balance _details . length ) {
frappe . show _alert ( {
message : _ _ ( "Please add Mode of payments and opening balance details." ) ,
indicator : 'red'
} )
2020-10-26 05:47:04 +00:00
return frappe . utils . play _sound ( "error" ) ;
2020-07-23 13:21:26 +00:00
}
2021-03-15 07:19:14 +00:00
// filter balance details for empty rows
balance _details = balance _details . filter ( d => d . mode _of _payment ) ;
2020-10-26 05:47:04 +00:00
const method = "erpnext.selling.page.point_of_sale.point_of_sale.create_opening_voucher" ;
const res = await frappe . call ( { method , args : { pos _profile , company , balance _details } , freeze : true } ) ;
2020-11-23 12:23:34 +00:00
! res . exc && me . prepare _app _defaults ( res . message ) ;
2020-10-26 05:47:04 +00:00
dialog . hide ( ) ;
2020-07-23 13:21:26 +00:00
} ,
primary _action _label : _ _ ( 'Submit' )
} ) ;
dialog . show ( ) ;
2021-05-12 12:12:06 +00:00
const pos _profile _query = {
query : 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query' ,
filters : { company : dialog . fields _dict . company . get _value ( ) }
} ;
2020-07-23 13:21:26 +00:00
}
2020-12-10 10:52:01 +00:00
async prepare _app _defaults ( data ) {
2020-07-23 13:21:26 +00:00
this . pos _opening = data . name ;
this . company = data . company ;
this . pos _profile = data . pos _profile ;
this . pos _opening _time = data . period _start _date ;
2020-12-10 10:52:01 +00:00
this . item _stock _map = { } ;
this . settings = { } ;
2020-07-23 13:21:26 +00:00
frappe . db . get _value ( 'Stock Settings' , undefined , 'allow_negative_stock' ) . then ( ( { message } ) => {
this . allow _negative _stock = flt ( message . allow _negative _stock ) || false ;
} ) ;
2022-03-21 07:17:35 +00:00
frappe . call ( {
method : "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data" ,
args : { "pos_profile" : this . pos _profile } ,
callback : ( res ) => {
const profile = res . message ;
Object . assign ( this . settings , profile ) ;
this . settings . customer _groups = profile . customer _groups . map ( group => group . name ) ;
this . make _app ( ) ;
}
2020-07-23 13:21:26 +00:00
} ) ;
}
set _opening _entry _status ( ) {
this . page . set _title _sub (
` <span class="indicator orange">
< a class = "text-muted" href = "#Form/POS%20Opening%20Entry/${this.pos_opening}" >
Opened at $ { moment ( this . pos _opening _time ) . format ( "Do MMMM, h:mma" ) }
< / a >
< / s p a n > ` ) ;
}
make _app ( ) {
2020-12-03 14:00:12 +00:00
this . prepare _dom ( ) ;
this . prepare _components ( ) ;
this . prepare _menu ( ) ;
this . make _new _invoice ( ) ;
2020-07-23 13:21:26 +00:00
}
prepare _dom ( ) {
2020-10-26 05:47:04 +00:00
this . wrapper . append (
2020-11-09 18:21:24 +00:00
` <div class="point-of-sale-app"></div> `
2020-07-23 13:21:26 +00:00
) ;
2020-11-09 18:21:24 +00:00
this . $components _wrapper = this . wrapper . find ( '.point-of-sale-app' ) ;
2020-07-23 13:21:26 +00:00
}
prepare _components ( ) {
this . init _item _selector ( ) ;
this . init _item _details ( ) ;
this . init _item _cart ( ) ;
this . init _payments ( ) ;
this . init _recent _order _list ( ) ;
this . init _order _summary ( ) ;
}
prepare _menu ( ) {
this . page . clear _menu ( ) ;
2020-10-26 05:47:04 +00:00
this . page . add _menu _item ( _ _ ( "Open Form View" ) , this . open _form _view . bind ( this ) , false , 'Ctrl+F' ) ;
2020-07-23 13:21:26 +00:00
2020-10-26 05:47:04 +00:00
this . page . add _menu _item ( _ _ ( "Toggle Recent Orders" ) , this . toggle _recent _order . bind ( this ) , false , 'Ctrl+O' ) ;
2020-07-23 13:21:26 +00:00
2020-10-26 05:47:04 +00:00
this . page . add _menu _item ( _ _ ( "Save as Draft" ) , this . save _draft _invoice . bind ( this ) , false , 'Ctrl+S' ) ;
2020-07-23 13:21:26 +00:00
2020-10-26 05:47:04 +00:00
this . page . add _menu _item ( _ _ ( 'Close the POS' ) , this . close _pos . bind ( this ) , false , 'Shift+Ctrl+C' ) ;
}
2020-07-23 13:21:26 +00:00
2020-10-26 05:47:04 +00:00
open _form _view ( ) {
frappe . model . sync ( this . frm . doc ) ;
frappe . set _route ( "Form" , this . frm . doc . doctype , this . frm . doc . name ) ;
}
2020-07-23 13:21:26 +00:00
2020-10-26 05:47:04 +00:00
toggle _recent _order ( ) {
2020-11-23 07:27:06 +00:00
const show = this . recent _order _list . $component . is ( ':hidden' ) ;
2020-10-26 05:47:04 +00:00
this . toggle _recent _order _list ( show ) ;
2020-07-23 13:21:26 +00:00
}
save _draft _invoice ( ) {
if ( ! this . $components _wrapper . is ( ":visible" ) ) return ;
if ( this . frm . doc . items . length == 0 ) {
frappe . show _alert ( {
2021-02-01 14:42:47 +00:00
message : _ _ ( "You must add atleast one item to save it as draft." ) ,
2020-07-23 13:21:26 +00:00
indicator : 'red'
} ) ;
frappe . utils . play _sound ( "error" ) ;
return ;
}
this . frm . save ( undefined , undefined , undefined , ( ) => {
frappe . show _alert ( {
2021-02-01 14:21:25 +00:00
message : _ _ ( "There was an error saving the document." ) ,
indicator : 'red'
2020-07-23 13:21:26 +00:00
} ) ;
frappe . utils . play _sound ( "error" ) ;
} ) . then ( ( ) => {
frappe . run _serially ( [
( ) => frappe . dom . freeze ( ) ,
( ) => this . make _new _invoice ( ) ,
( ) => frappe . dom . unfreeze ( ) ,
] ) ;
2021-02-01 14:21:25 +00:00
} ) ;
2020-07-23 13:21:26 +00:00
}
close _pos ( ) {
if ( ! this . $components _wrapper . is ( ":visible" ) ) return ;
let voucher = frappe . model . get _new _doc ( 'POS Closing Entry' ) ;
voucher . pos _profile = this . frm . doc . pos _profile ;
voucher . user = frappe . session . user ;
voucher . company = this . frm . doc . company ;
voucher . pos _opening _entry = this . pos _opening ;
voucher . period _end _date = frappe . datetime . now _datetime ( ) ;
voucher . posting _date = frappe . datetime . now _date ( ) ;
frappe . set _route ( 'Form' , 'POS Closing Entry' , voucher . name ) ;
}
init _item _selector ( ) {
this . item _selector = new erpnext . PointOfSale . ItemSelector ( {
wrapper : this . $components _wrapper ,
pos _profile : this . pos _profile ,
2020-12-10 10:52:01 +00:00
settings : this . settings ,
2020-07-23 13:21:26 +00:00
events : {
item _selected : args => this . on _cart _update ( args ) ,
2020-12-10 10:52:01 +00:00
get _frm : ( ) => this . frm || { }
2020-07-23 13:21:26 +00:00
}
} )
}
init _item _cart ( ) {
this . cart = new erpnext . PointOfSale . ItemCart ( {
wrapper : this . $components _wrapper ,
2020-12-10 10:52:01 +00:00
settings : this . settings ,
2020-07-23 13:21:26 +00:00
events : {
get _frm : ( ) => this . frm ,
2021-06-22 15:48:20 +00:00
cart _item _clicked : ( item ) => {
const item _row = this . get _item _from _frm ( item ) ;
2020-07-23 13:21:26 +00:00
this . item _details . toggle _item _details _section ( item _row ) ;
} ,
numpad _event : ( value , action ) => this . update _item _field ( value , action ) ,
2022-02-02 14:03:48 +00:00
checkout : ( ) => this . save _and _checkout ( ) ,
2020-07-23 13:21:26 +00:00
edit _cart : ( ) => this . payment . edit _cart ( ) ,
customer _details _updated : ( details ) => {
this . customer _details = details ;
// will add/remove LP payment method
this . payment . render _loyalty _points _payment _mode ( ) ;
2020-12-10 10:52:01 +00:00
}
2020-07-23 13:21:26 +00:00
}
} )
}
init _item _details ( ) {
this . item _details = new erpnext . PointOfSale . ItemDetails ( {
wrapper : this . $components _wrapper ,
2021-01-28 12:28:55 +00:00
settings : this . settings ,
2020-07-23 13:21:26 +00:00
events : {
get _frm : ( ) => this . frm ,
toggle _item _selector : ( minimize ) => {
this . item _selector . resize _selector ( minimize ) ;
this . cart . toggle _numpad ( minimize ) ;
} ,
2021-06-22 15:48:20 +00:00
form _updated : ( item , field , value ) => {
const item _row = frappe . model . get _doc ( item . doctype , item . name ) ;
if ( item _row && item _row [ field ] != value ) {
const args = {
field ,
2020-07-23 13:21:26 +00:00
value ,
2021-06-22 15:48:20 +00:00
item : this . item _details . current _item
2021-06-24 08:59:22 +00:00
} ;
2021-06-24 09:31:33 +00:00
return this . on _cart _update ( args ) ;
}
2021-05-27 08:19:45 +00:00
return Promise . resolve ( ) ;
} ,
highlight _cart _item : ( item ) => {
const cart _item = this . cart . get _cart _item ( item ) ;
this . cart . toggle _item _highlight ( cart _item ) ;
2020-07-23 13:21:26 +00:00
} ,
item _field _focused : ( fieldname ) => {
this . cart . toggle _numpad _field _edit ( fieldname ) ;
} ,
set _value _in _current _cart _item : ( selector , value ) => {
this . cart . update _selector _value _in _cart _item ( selector , value , this . item _details . current _item ) ;
} ,
2021-06-22 15:48:20 +00:00
clone _new _batch _item _in _frm : ( batch _serial _map , item ) => {
2020-07-23 13:21:26 +00:00
// called if serial nos are 'auto_selected' and if those serial nos belongs to multiple batches
// for each unique batch new item row is added in the form & cart
Object . keys ( batch _serial _map ) . forEach ( batch => {
2021-06-22 15:48:20 +00:00
const item _to _clone = this . frm . doc . items . find ( i => i . name == item . name ) ;
2020-07-23 13:21:26 +00:00
const new _row = this . frm . add _child ( "items" , { ... item _to _clone } ) ;
// update new serialno and batch
new _row . batch _no = batch ;
new _row . serial _no = batch _serial _map [ batch ] . join ( ` \n ` ) ;
new _row . qty = batch _serial _map [ batch ] . length ;
this . frm . doc . items . forEach ( row => {
2021-06-22 15:48:20 +00:00
if ( item . item _code === row . item _code ) {
2020-07-23 13:21:26 +00:00
this . update _cart _html ( row ) ;
}
} ) ;
} )
} ,
remove _item _from _cart : ( ) => this . remove _item _from _cart ( ) ,
get _item _stock _map : ( ) => this . item _stock _map ,
close _item _details : ( ) => {
2021-06-22 15:48:20 +00:00
this . item _details . toggle _item _details _section ( null ) ;
this . cart . prev _action = null ;
2020-07-23 13:21:26 +00:00
this . cart . toggle _item _highlight ( ) ;
} ,
get _available _stock : ( item _code , warehouse ) => this . get _available _stock ( item _code , warehouse )
}
} ) ;
}
init _payments ( ) {
this . payment = new erpnext . PointOfSale . Payment ( {
wrapper : this . $components _wrapper ,
events : {
get _frm : ( ) => this . frm || { } ,
get _customer _details : ( ) => this . customer _details || { } ,
toggle _other _sections : ( show ) => {
if ( show ) {
2020-11-13 12:03:20 +00:00
this . item _details . $component . is ( ':visible' ) ? this . item _details . $component . css ( 'display' , 'none' ) : '' ;
2022-04-26 08:58:33 +00:00
this . item _selector . toggle _component ( false ) ;
2020-07-23 13:21:26 +00:00
} else {
2022-04-26 08:58:33 +00:00
this . item _selector . toggle _component ( true ) ;
2020-07-23 13:21:26 +00:00
}
} ,
submit _invoice : ( ) => {
this . frm . savesubmit ( )
. then ( ( r ) => {
this . toggle _components ( false ) ;
this . order _summary . toggle _component ( true ) ;
this . order _summary . load _summary _of ( this . frm . doc , true ) ;
frappe . show _alert ( {
indicator : 'green' ,
2020-10-26 05:47:04 +00:00
message : _ _ ( 'POS invoice {0} created succesfully' , [ r . doc . name ] )
2020-07-23 13:21:26 +00:00
} ) ;
} ) ;
}
}
} ) ;
}
init _recent _order _list ( ) {
this . recent _order _list = new erpnext . PointOfSale . PastOrderList ( {
wrapper : this . $components _wrapper ,
events : {
open _invoice _data : ( name ) => {
frappe . db . get _doc ( 'POS Invoice' , name ) . then ( ( doc ) => {
this . order _summary . load _summary _of ( doc ) ;
} ) ;
} ,
2020-11-23 08:03:40 +00:00
reset _summary : ( ) => this . order _summary . toggle _summary _placeholder ( true )
2020-07-23 13:21:26 +00:00
}
} )
}
init _order _summary ( ) {
this . order _summary = new erpnext . PointOfSale . PastOrderSummary ( {
wrapper : this . $components _wrapper ,
events : {
get _frm : ( ) => this . frm ,
process _return : ( name ) => {
this . recent _order _list . toggle _component ( false ) ;
frappe . db . get _doc ( 'POS Invoice' , name ) . then ( ( doc ) => {
frappe . run _serially ( [
( ) => this . make _return _invoice ( doc ) ,
( ) => this . cart . load _invoice ( ) ,
( ) => this . item _selector . toggle _component ( true )
] ) ;
} ) ;
} ,
edit _order : ( name ) => {
this . recent _order _list . toggle _component ( false ) ;
frappe . run _serially ( [
( ) => this . frm . refresh ( name ) ,
2021-03-24 12:26:24 +00:00
( ) => this . frm . call ( 'reset_mode_of_payments' ) ,
2020-07-23 13:21:26 +00:00
( ) => this . cart . load _invoice ( ) ,
( ) => this . item _selector . toggle _component ( true )
] ) ;
} ,
2021-01-28 12:28:55 +00:00
delete _order : ( name ) => {
frappe . model . delete _doc ( this . frm . doc . doctype , name , ( ) => {
this . recent _order _list . refresh _list ( ) ;
} ) ;
} ,
2020-07-23 13:21:26 +00:00
new _order : ( ) => {
frappe . run _serially ( [
( ) => frappe . dom . freeze ( ) ,
( ) => this . make _new _invoice ( ) ,
( ) => this . item _selector . toggle _component ( true ) ,
( ) => frappe . dom . unfreeze ( ) ,
] ) ;
}
}
} )
}
toggle _recent _order _list ( show ) {
this . toggle _components ( ! show ) ;
this . recent _order _list . toggle _component ( show ) ;
this . order _summary . toggle _component ( show ) ;
}
toggle _components ( show ) {
this . cart . toggle _component ( show ) ;
this . item _selector . toggle _component ( show ) ;
// do not show item details or payment if recent order is toggled off
! show ? ( this . item _details . toggle _component ( false ) || this . payment . toggle _component ( false ) ) : '' ;
}
make _new _invoice ( ) {
return frappe . run _serially ( [
2020-12-03 14:00:12 +00:00
( ) => frappe . dom . freeze ( ) ,
2020-07-23 13:21:26 +00:00
( ) => this . make _sales _invoice _frm ( ) ,
( ) => this . set _pos _profile _data ( ) ,
( ) => this . set _pos _profile _status ( ) ,
( ) => this . cart . load _invoice ( ) ,
2020-12-03 14:00:12 +00:00
( ) => frappe . dom . unfreeze ( )
2020-07-23 13:21:26 +00:00
] ) ;
}
make _sales _invoice _frm ( ) {
const doctype = 'POS Invoice' ;
return new Promise ( resolve => {
if ( this . frm ) {
this . frm = this . get _new _frm ( this . frm ) ;
this . frm . doc . items = [ ] ;
this . frm . doc . is _pos = 1
resolve ( ) ;
} else {
frappe . model . with _doctype ( doctype , ( ) => {
this . frm = this . get _new _frm ( ) ;
this . frm . doc . items = [ ] ;
this . frm . doc . is _pos = 1
resolve ( ) ;
} ) ;
}
} ) ;
}
get _new _frm ( _frm ) {
const doctype = 'POS Invoice' ;
const page = $ ( '<div>' ) ;
const frm = _frm || new frappe . ui . form . Form ( doctype , page , false ) ;
const name = frappe . model . make _new _doc _and _get _name ( doctype , true ) ;
frm . refresh ( name ) ;
return frm ;
}
async make _return _invoice ( doc ) {
frappe . dom . freeze ( ) ;
this . frm = this . get _new _frm ( this . frm ) ;
this . frm . doc . items = [ ] ;
2022-05-09 09:35:05 +00:00
return frappe . call ( {
2020-07-23 13:21:26 +00:00
method : "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return" ,
args : {
'source_name' : doc . name ,
'target_doc' : this . frm . doc
2022-05-09 09:35:05 +00:00
} ,
callback : ( r ) => {
frappe . model . sync ( r . message ) ;
frappe . get _doc ( r . message . doctype , r . message . name ) . _ _run _link _triggers = false ;
this . set _pos _profile _data ( ) . then ( ( ) => {
frappe . dom . unfreeze ( ) ;
} ) ;
2020-07-23 13:21:26 +00:00
}
} ) ;
}
set _pos _profile _data ( ) {
if ( this . company && ! this . frm . doc . company ) this . frm . doc . company = this . company ;
if ( this . pos _profile && ! this . frm . doc . pos _profile ) this . frm . doc . pos _profile = this . pos _profile ;
if ( ! this . frm . doc . company ) return ;
2020-10-26 05:47:04 +00:00
return this . frm . trigger ( "set_pos_data" ) ;
2020-07-23 13:21:26 +00:00
}
set _pos _profile _status ( ) {
2020-10-26 05:47:04 +00:00
this . page . set _indicator ( this . pos _profile , "blue" ) ;
2020-07-23 13:21:26 +00:00
}
async on _cart _update ( args ) {
frappe . dom . freeze ( ) ;
2021-02-26 11:19:28 +00:00
let item _row = undefined ;
2020-07-23 13:21:26 +00:00
try {
let { field , value , item } = args ;
2021-06-22 15:48:20 +00:00
item _row = this . get _item _from _frm ( item ) ;
const item _row _exists = ! $ . isEmptyObject ( item _row ) ;
2020-07-23 13:21:26 +00:00
2021-06-22 15:48:20 +00:00
const from _selector = field === 'qty' && value === "+1" ;
if ( from _selector )
value = flt ( item _row . qty ) + flt ( value ) ;
2020-07-23 13:21:26 +00:00
2021-06-22 15:48:20 +00:00
if ( item _row _exists ) {
if ( field === 'qty' )
value = flt ( value ) ;
2020-07-23 13:21:26 +00:00
2020-10-26 05:47:04 +00:00
if ( [ 'qty' , 'conversion_factor' ] . includes ( field ) && value > 0 && ! this . allow _negative _stock ) {
const qty _needed = field === 'qty' ? value * item _row . conversion _factor : item _row . qty * value ;
await this . check _stock _availability ( item _row , qty _needed , this . frm . doc . set _warehouse ) ;
}
2020-11-18 09:30:34 +00:00
2021-06-22 15:48:20 +00:00
if ( this . is _current _item _being _edited ( item _row ) || from _selector ) {
2020-07-23 13:21:26 +00:00
await frappe . model . set _value ( item _row . doctype , item _row . name , field , value ) ;
this . update _cart _html ( item _row ) ;
}
} else {
2021-06-22 15:48:20 +00:00
if ( ! this . frm . doc . customer )
return this . raise _customer _selection _alert ( ) ;
2020-11-06 12:49:36 +00:00
2021-06-22 15:48:20 +00:00
const { item _code , batch _no , serial _no , rate } = item ;
2020-07-23 13:21:26 +00:00
2021-06-22 15:48:20 +00:00
if ( ! item _code )
return ;
const new _item = { item _code , batch _no , rate , [ field ] : value } ;
2020-07-23 13:21:26 +00:00
2020-10-26 05:47:04 +00:00
if ( serial _no ) {
await this . check _serial _no _availablilty ( item _code , this . frm . doc . set _warehouse , serial _no ) ;
2021-06-22 15:48:20 +00:00
new _item [ 'serial_no' ] = serial _no ;
2020-10-26 05:47:04 +00:00
}
2020-07-23 13:21:26 +00:00
2021-06-22 15:48:20 +00:00
if ( field === 'serial_no' )
new _item [ 'qty' ] = value . split ( ` \n ` ) . length || 0 ;
2020-07-23 13:21:26 +00:00
2021-06-22 15:48:20 +00:00
item _row = this . frm . add _child ( 'items' , new _item ) ;
2020-07-23 13:21:26 +00:00
if ( field === 'qty' && value !== 0 && ! this . allow _negative _stock )
await this . check _stock _availability ( item _row , value , this . frm . doc . set _warehouse ) ;
await this . trigger _new _item _events ( item _row ) ;
2021-08-19 08:11:10 +00:00
2021-05-27 08:19:45 +00:00
this . update _cart _html ( item _row ) ;
2020-07-23 13:21:26 +00:00
2021-06-22 15:48:20 +00:00
if ( this . item _details . $component . is ( ':visible' ) )
this . edit _item _details _of ( item _row ) ;
2022-03-21 07:54:11 +00:00
if ( this . check _serial _batch _selection _needed ( item _row ) && ! this . item _details . $component . is ( ':visible' ) )
2021-06-22 15:48:20 +00:00
this . edit _item _details _of ( item _row ) ;
2020-11-18 09:30:34 +00:00
}
2021-02-26 11:19:28 +00:00
2020-07-23 13:21:26 +00:00
} catch ( error ) {
console . log ( error ) ;
} finally {
frappe . dom . unfreeze ( ) ;
2021-02-26 11:19:28 +00:00
return item _row ;
2020-07-23 13:21:26 +00:00
}
}
2021-06-22 15:48:20 +00:00
raise _customer _selection _alert ( ) {
frappe . dom . unfreeze ( ) ;
frappe . show _alert ( {
message : _ _ ( 'You must select a customer before adding an item.' ) ,
indicator : 'orange'
} ) ;
frappe . utils . play _sound ( "error" ) ;
}
get _item _from _frm ( { name , item _code , batch _no , uom , rate } ) {
let item _row = null ;
if ( name ) {
item _row = this . frm . doc . items . find ( i => i . name == name ) ;
} else {
// if item is clicked twice from item selector
// then "item_code, batch_no, uom, rate" will help in getting the exact item
// to increase the qty by one
const has _batch _no = batch _no ;
item _row = this . frm . doc . items . find (
i => i . item _code === item _code
&& ( ! has _batch _no || ( has _batch _no && i . batch _no === batch _no ) )
&& ( i . uom === uom )
&& ( i . rate == rate )
) ;
}
return item _row || { } ;
2020-07-23 13:21:26 +00:00
}
edit _item _details _of ( item _row ) {
this . item _details . toggle _item _details _section ( item _row ) ;
}
is _current _item _being _edited ( item _row ) {
2021-06-22 15:48:20 +00:00
return item _row . name == this . item _details . current _item . name ;
2020-07-23 13:21:26 +00:00
}
update _cart _html ( item _row , remove _item ) {
this . cart . update _item _html ( item _row , remove _item ) ;
this . cart . update _totals _section ( this . frm ) ;
}
check _serial _batch _selection _needed ( item _row ) {
// right now item details is shown for every type of item.
// if item details is not shown for every item then this fn will be needed
const serialized = item _row . has _serial _no ;
const batched = item _row . has _batch _no ;
const no _serial _selected = ! item _row . serial _no ;
const no _batch _selected = ! item _row . batch _no ;
2020-11-18 09:30:34 +00:00
if ( ( serialized && no _serial _selected ) || ( batched && no _batch _selected ) ||
2020-07-23 13:21:26 +00:00
( serialized && batched && ( no _batch _selected || no _serial _selected ) ) ) {
return true ;
}
return false ;
}
async trigger _new _item _events ( item _row ) {
2020-10-26 05:47:04 +00:00
await this . frm . script _manager . trigger ( 'item_code' , item _row . doctype , item _row . name ) ;
await this . frm . script _manager . trigger ( 'qty' , item _row . doctype , item _row . name ) ;
2020-07-23 13:21:26 +00:00
}
async check _stock _availability ( item _row , qty _needed , warehouse ) {
2022-01-17 14:08:05 +00:00
const resp = ( await this . get _available _stock ( item _row . item _code , warehouse ) ) . message ;
const available _qty = resp [ 0 ] ;
const is _stock _item = resp [ 1 ] ;
2020-07-23 13:21:26 +00:00
frappe . dom . unfreeze ( ) ;
2020-10-26 05:47:04 +00:00
const bold _item _code = item _row . item _code . bold ( ) ;
const bold _warehouse = warehouse . bold ( ) ;
const bold _available _qty = available _qty . toString ( ) . bold ( )
2020-07-23 13:21:26 +00:00
if ( ! ( available _qty > 0 ) ) {
2022-01-17 14:08:05 +00:00
if ( is _stock _item ) {
2021-12-22 13:44:47 +00:00
frappe . model . clear _doc ( item _row . doctype , item _row . name ) ;
frappe . throw ( {
title : _ _ ( "Not Available" ) ,
message : _ _ ( 'Item Code: {0} is not available under warehouse {1}.' , [ bold _item _code , bold _warehouse ] )
2022-01-03 05:17:13 +00:00
} ) ;
2022-01-17 14:08:05 +00:00
} else {
return ;
}
2020-07-23 13:21:26 +00:00
} else if ( available _qty < qty _needed ) {
2022-01-04 13:44:40 +00:00
frappe . throw ( {
2020-11-25 10:07:54 +00:00
message : _ _ ( 'Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.' , [ bold _item _code , bold _warehouse , bold _available _qty ] ) ,
2020-07-23 13:21:26 +00:00
indicator : 'orange'
} ) ;
frappe . utils . play _sound ( "error" ) ;
}
frappe . dom . freeze ( ) ;
}
2020-10-26 05:47:04 +00:00
async check _serial _no _availablilty ( item _code , warehouse , serial _no ) {
const method = "erpnext.stock.doctype.serial_no.serial_no.get_pos_reserved_serial_nos" ;
const args = { filters : { item _code , warehouse } }
const res = await frappe . call ( { method , args } ) ;
if ( res . message . includes ( serial _no ) ) {
frappe . throw ( {
title : _ _ ( "Not Available" ) ,
message : _ _ ( 'Serial No: {0} has already been transacted into another POS Invoice.' , [ serial _no . bold ( ) ] )
} ) ;
}
}
2020-07-23 13:21:26 +00:00
get _available _stock ( item _code , warehouse ) {
const me = this ;
return frappe . call ( {
method : "erpnext.accounts.doctype.pos_invoice.pos_invoice.get_stock_availability" ,
args : {
'item_code' : item _code ,
'warehouse' : warehouse ,
} ,
callback ( res ) {
if ( ! me . item _stock _map [ item _code ] )
2022-01-17 14:23:39 +00:00
me . item _stock _map [ item _code ] = { } ;
2022-01-17 14:08:05 +00:00
me . item _stock _map [ item _code ] [ warehouse ] = res . message [ 0 ] ;
2020-07-23 13:21:26 +00:00
}
} ) ;
}
update _item _field ( value , field _or _action ) {
if ( field _or _action === 'checkout' ) {
2021-06-22 15:48:20 +00:00
this . item _details . toggle _item _details _section ( null ) ;
2020-07-23 13:21:26 +00:00
} else if ( field _or _action === 'remove' ) {
this . remove _item _from _cart ( ) ;
} else {
const field _control = this . item _details [ ` ${ field _or _action } _control ` ] ;
if ( ! field _control ) return ;
field _control . set _focus ( ) ;
value != "" && field _control . set _value ( value ) ;
}
}
remove _item _from _cart ( ) {
frappe . dom . freeze ( ) ;
const { doctype , name , current _item } = this . item _details ;
2022-03-21 07:54:11 +00:00
return frappe . model . set _value ( doctype , name , 'qty' , 0 )
2021-01-28 12:28:55 +00:00
. then ( ( ) => {
frappe . model . clear _doc ( doctype , name ) ;
this . update _cart _html ( current _item , true ) ;
2021-06-22 15:48:20 +00:00
this . item _details . toggle _item _details _section ( null ) ;
2021-01-28 12:28:55 +00:00
frappe . dom . unfreeze ( ) ;
} )
. catch ( e => console . log ( e ) ) ;
2020-07-23 13:21:26 +00:00
}
2022-02-02 14:03:48 +00:00
async save _and _checkout ( ) {
2022-03-21 08:23:58 +00:00
if ( this . frm . is _dirty ( ) ) {
2022-04-09 10:58:10 +00:00
let save _error = false ;
await this . frm . save ( null , null , null , ( ) => save _error = true ) ;
2022-03-21 08:23:58 +00:00
// only move to payment section if save is successful
2022-04-09 10:58:10 +00:00
! save _error && this . payment . checkout ( ) ;
// show checkout button on error
save _error && setTimeout ( ( ) => {
this . cart . toggle _checkout _btn ( true ) ;
} , 300 ) ; // wait for save to finish
2022-03-21 08:23:58 +00:00
} else {
this . payment . checkout ( ) ;
}
2022-02-02 14:03:48 +00:00
}
2021-02-01 14:21:25 +00:00
} ;