@@ -244,8 +244,7 @@ def calculate_hra_exemption_for_period(doc):
244244 return exemptions
245245
246246
247- @frappe .whitelist ()
248- def generate_ewb_json (dt , dn ):
247+ def get_ewb_data (dt , dn ):
249248 if dt != 'Sales Invoice' :
250249 frappe .throw (_ ('e-Way Bill JSON can only be generated from Sales Invoice' ))
251250
@@ -254,26 +253,8 @@ def generate_ewb_json(dt, dn):
254253 ewaybills = []
255254 for doc_name in dn :
256255 doc = frappe .get_doc (dt , doc_name )
257- if doc .docstatus != 1 :
258- frappe .throw (_ ('e-Way Bill JSON can only be generated from submitted document' ))
259-
260- if doc .is_return :
261- frappe .throw (_ ('e-Way Bill JSON cannot be generated for Sales Return as of now' ))
262-
263- if doc .ewaybill :
264- frappe .throw (_ ('e-Way Bill already exists for this document' ))
265256
266- reqd_fields = ['company_gstin' , 'company_address' , 'customer_address' ,
267- 'shipping_address_name' , 'mode_of_transport' , 'distance' ]
268-
269- for fieldname in reqd_fields :
270- if not doc .get (fieldname ):
271- frappe .throw (_ ('{} is required to generate e-Way Bill JSON' .format (
272- doc .meta .get_label (fieldname )
273- )))
274-
275- if len (doc .company_gstin ) < 15 :
276- frappe .throw (_ ('You must be a registered supplier to generate e-Way Bill' ))
257+ validate_sales_invoice (doc )
277258
278259 data = frappe ._dict ({
279260 "transporterId" : "" ,
@@ -294,110 +275,21 @@ def generate_ewb_json(dt, dn):
294275 data .docDate = frappe .utils .formatdate (doc .posting_date , 'dd/mm/yyyy' )
295276
296277 company_address = frappe .get_doc ('Address' , doc .company_address )
297- data .fromPincode = validate_pincode (company_address .pincode , 'Company Address' )
298- data .fromStateCode = data .actualFromStateCode = validate_state_code (
299- company_address .gst_state_number , 'Company Address' )
300-
301278 billing_address = frappe .get_doc ('Address' , doc .customer_address )
302- if not doc .billing_address_gstin or len (doc .billing_address_gstin ) < 15 :
303- data .toGstin = 'URP'
304- set_gst_state_and_state_number (billing_address )
305- else :
306- data .toGstin = doc .billing_address_gstin
307279
308- data .toPincode = validate_pincode (billing_address .pincode , 'Customer Address' )
309- data .toStateCode = validate_state_code (billing_address .gst_state_number , 'Customer Address' )
280+ shipping_address = frappe .get_doc ('Address' , doc .shipping_address_name )
310281
311- if doc .customer_address != doc .shipping_address_name :
312- data .transType = 2
313- shipping_address = frappe .get_doc ('Address' , doc .shipping_address_name )
314- set_gst_state_and_state_number (shipping_address )
315- data .actualToStateCode = validate_state_code (shipping_address .gst_state_number , 'Shipping Address' )
316- else :
317- data .transType = 1
318- data .actualToStateCode = data .toStateCode
319- shipping_address = billing_address
282+ data = get_address_details (data , doc , company_address , billing_address )
320283
321284 data .itemList = []
322285 data .totalValue = doc .total
323- for attr in ['cgstValue' , 'sgstValue' , 'igstValue' , 'cessValue' , 'OthValue' ]:
324- data [attr ] = 0
325-
326- gst_accounts = get_gst_accounts (doc .company , account_wise = True )
327- tax_map = {
328- 'sgst_account' : ['sgstRate' , 'sgstValue' ],
329- 'cgst_account' : ['cgstRate' , 'cgstValue' ],
330- 'igst_account' : ['igstRate' , 'igstValue' ],
331- 'cess_account' : ['cessRate' , 'cessValue' ]
332- }
333- item_data_attrs = ['sgstRate' , 'cgstRate' , 'igstRate' , 'cessRate' , 'cessNonAdvol' ]
334- hsn_wise_charges , hsn_taxable_amount = get_itemised_tax_breakup_data (doc , account_wise = True )
335- for hsn_code , taxable_amount in hsn_taxable_amount .items ():
336- item_data = frappe ._dict ()
337- if not hsn_code :
338- frappe .throw (_ ('GST HSN Code does not exist for one or more items' ))
339- item_data .hsnCode = int (hsn_code )
340- item_data .taxableAmount = taxable_amount
341- item_data .qtyUnit = ""
342- for attr in item_data_attrs :
343- item_data [attr ] = 0
344-
345- for account , tax_detail in hsn_wise_charges .get (hsn_code , {}).items ():
346- account_type = gst_accounts .get (account , '' )
347- for tax_acc , attrs in tax_map .items ():
348- if account_type == tax_acc :
349- item_data [attrs [0 ]] = tax_detail .get ('tax_rate' )
350- data [attrs [1 ]] += tax_detail .get ('tax_amount' )
351- break
352- else :
353- data .OthValue += tax_detail .get ('tax_amount' )
354286
355- data . itemList . append ( item_data )
287+ data = get_item_list ( data , doc )
356288
357289 disable_rounded = frappe .db .get_single_value ('Global Defaults' , 'disable_rounded_total' )
358290 data .totInvValue = doc .grand_total if disable_rounded else doc .rounded_total
359291
360- if doc .distance > 4000 :
361- frappe .throw (_ ('Distance cannot be greater than 4000 kms' ))
362-
363- data .transDistance = round (doc .distance )
364-
365- transport_modes = {
366- 'Road' : 1 ,
367- 'Rail' : 2 ,
368- 'Air' : 3 ,
369- 'Ship' : 4
370- }
371-
372- vehicle_types = {
373- 'Regular' : 'R' ,
374- 'Over Dimensional Cargo (ODC)' : 'O'
375- }
376-
377- data .transMode = transport_modes .get (doc .mode_of_transport )
378-
379- if doc .mode_of_transport == 'Road' :
380- if not doc .gst_transporter_id and not doc .vehicle_no :
381- frappe .throw (_ ('Either GST Transporter ID or Vehicle No is required if Mode of Transport is Road' ))
382- if doc .vehicle_no :
383- data .vehicleNo = doc .vehicle_no .replace (' ' , '' )
384- if not doc .gst_vehicle_type :
385- frappe .throw (_ ('Vehicle Type is required if Mode of Transport is Road' ))
386- else :
387- data .vehicleType = vehicle_types .get (doc .gst_vehicle_type )
388- else :
389- if not doc .lr_no or not doc .lr_date :
390- frappe .throw (_ ('Transport Receipt No and Date are mandatory for your chosen Mode of Transport' ))
391-
392- if doc .lr_no :
393- data .transDocNo = doc .lr_no
394-
395- if doc .lr_date :
396- data .transDocDate = frappe .utils .formatdate (doc .lr_date , 'dd/mm/yyyy' )
397-
398- if doc .gst_transporter_id :
399- validate_gstin_check_digit (doc .gst_transporter_id , label = 'GST Transporter ID' )
400- data .transporterId = doc .gst_transporter_id
292+ data = get_transport_details (data , doc )
401293
402294 fields = {
403295 "/. -" : {
@@ -431,14 +323,155 @@ def generate_ewb_json(dt, dn):
431323 'billLists' : ewaybills
432324 }
433325
326+ return data
327+
328+ @frappe .whitelist ()
329+ def generate_ewb_json (dt , dn ):
330+
331+ data = get_ewb_data (dt , dn )
332+
434333 frappe .local .response .filecontent = json .dumps (data , indent = 4 , sort_keys = True )
435334 frappe .local .response .type = 'download'
436335
437- if len (ewaybills ) > 1 :
336+ if len (data [ 'billLists' ] ) > 1 :
438337 doc_name = 'Bulk'
338+ else :
339+ doc_name = dn
439340
440341 frappe .local .response .filename = '{0}_e-WayBill_Data_{1}.json' .format (doc_name , frappe .utils .random_string (5 ))
441342
343+
344+ def get_address_details (data , doc , company_address , billing_address ):
345+ data .fromPincode = validate_pincode (company_address .pincode , 'Company Address' )
346+ data .fromStateCode = data .actualFromStateCode = validate_state_code (
347+ company_address .gst_state_number , 'Company Address' )
348+
349+ if not doc .billing_address_gstin or len (doc .billing_address_gstin ) < 15 :
350+ data .toGstin = 'URP'
351+ set_gst_state_and_state_number (billing_address )
352+ else :
353+ data .toGstin = doc .billing_address_gstin
354+
355+ data .toPincode = validate_pincode (billing_address .pincode , 'Customer Address' )
356+ data .toStateCode = validate_state_code (billing_address .gst_state_number , 'Customer Address' )
357+
358+ if doc .customer_address != doc .shipping_address_name :
359+ data .transType = 2
360+ shipping_address = frappe .get_doc ('Address' , doc .shipping_address_name )
361+ set_gst_state_and_state_number (shipping_address )
362+ data .actualToStateCode = validate_state_code (shipping_address .gst_state_number , 'Shipping Address' )
363+ else :
364+ data .transType = 1
365+ data .actualToStateCode = data .toStateCode
366+ shipping_address = billing_address
367+
368+ return data
369+
370+ def get_item_list (data , doc ):
371+ for attr in ['cgstValue' , 'sgstValue' , 'igstValue' , 'cessValue' , 'OthValue' ]:
372+ data [attr ] = 0
373+
374+ gst_accounts = get_gst_accounts (doc .company , account_wise = True )
375+ tax_map = {
376+ 'sgst_account' : ['sgstRate' , 'sgstValue' ],
377+ 'cgst_account' : ['cgstRate' , 'cgstValue' ],
378+ 'igst_account' : ['igstRate' , 'igstValue' ],
379+ 'cess_account' : ['cessRate' , 'cessValue' ]
380+ }
381+ item_data_attrs = ['sgstRate' , 'cgstRate' , 'igstRate' , 'cessRate' , 'cessNonAdvol' ]
382+ hsn_wise_charges , hsn_taxable_amount = get_itemised_tax_breakup_data (doc , account_wise = True )
383+ for hsn_code , taxable_amount in hsn_taxable_amount .items ():
384+ item_data = frappe ._dict ()
385+ if not hsn_code :
386+ frappe .throw (_ ('GST HSN Code does not exist for one or more items' ))
387+ item_data .hsnCode = int (hsn_code )
388+ item_data .taxableAmount = taxable_amount
389+ item_data .qtyUnit = ""
390+ for attr in item_data_attrs :
391+ item_data [attr ] = 0
392+
393+ for account , tax_detail in hsn_wise_charges .get (hsn_code , {}).items ():
394+ account_type = gst_accounts .get (account , '' )
395+ for tax_acc , attrs in tax_map .items ():
396+ if account_type == tax_acc :
397+ item_data [attrs [0 ]] = tax_detail .get ('tax_rate' )
398+ data [attrs [1 ]] += tax_detail .get ('tax_amount' )
399+ break
400+ else :
401+ data .OthValue += tax_detail .get ('tax_amount' )
402+
403+ data .itemList .append (item_data )
404+
405+ return data
406+
407+ def validate_sales_invoice (doc ):
408+ if doc .docstatus != 1 :
409+ frappe .throw (_ ('e-Way Bill JSON can only be generated from submitted document' ))
410+
411+ if doc .is_return :
412+ frappe .throw (_ ('e-Way Bill JSON cannot be generated for Sales Return as of now' ))
413+
414+ if doc .ewaybill :
415+ frappe .throw (_ ('e-Way Bill already exists for this document' ))
416+
417+ reqd_fields = ['company_gstin' , 'company_address' , 'customer_address' ,
418+ 'shipping_address_name' , 'mode_of_transport' , 'distance' ]
419+
420+ for fieldname in reqd_fields :
421+ if not doc .get (fieldname ):
422+ frappe .throw (_ ('{} is required to generate e-Way Bill JSON' .format (
423+ doc .meta .get_label (fieldname )
424+ )))
425+
426+ if len (doc .company_gstin ) < 15 :
427+ frappe .throw (_ ('You must be a registered supplier to generate e-Way Bill' ))
428+
429+ def get_transport_details (data , doc ):
430+ if doc .distance > 4000 :
431+ frappe .throw (_ ('Distance cannot be greater than 4000 kms' ))
432+
433+ data .transDistance = round (doc .distance )
434+
435+ transport_modes = {
436+ 'Road' : 1 ,
437+ 'Rail' : 2 ,
438+ 'Air' : 3 ,
439+ 'Ship' : 4
440+ }
441+
442+ vehicle_types = {
443+ 'Regular' : 'R' ,
444+ 'Over Dimensional Cargo (ODC)' : 'O'
445+ }
446+
447+ data .transMode = transport_modes .get (doc .mode_of_transport )
448+
449+ if doc .mode_of_transport == 'Road' :
450+ if not doc .gst_transporter_id and not doc .vehicle_no :
451+ frappe .throw (_ ('Either GST Transporter ID or Vehicle No is required if Mode of Transport is Road' ))
452+ if doc .vehicle_no :
453+ data .vehicleNo = doc .vehicle_no .replace (' ' , '' )
454+ if not doc .gst_vehicle_type :
455+ frappe .throw (_ ('Vehicle Type is required if Mode of Transport is Road' ))
456+ else :
457+ data .vehicleType = vehicle_types .get (doc .gst_vehicle_type )
458+ else :
459+ if not doc .lr_no or not doc .lr_date :
460+ frappe .throw (_ ('Transport Receipt No and Date are mandatory for your chosen Mode of Transport' ))
461+
462+ if doc .lr_no :
463+ data .transDocNo = doc .lr_no
464+
465+ if doc .lr_date :
466+ data .transDocDate = frappe .utils .formatdate (doc .lr_date , 'dd/mm/yyyy' )
467+
468+ if doc .gst_transporter_id :
469+ validate_gstin_check_digit (doc .gst_transporter_id , label = 'GST Transporter ID' )
470+ data .transporterId = doc .gst_transporter_id
471+
472+ return data
473+
474+
442475def validate_pincode (pincode , address ):
443476 pin_not_found = "Pin Code doesn't exist for {}"
444477 incorrect_pin = "Pin Code for {} is incorrecty formatted. It must be 6 digits (without spaces)"
0 commit comments