fix: merge conflict
diff --git a/.eslintrc b/.eslintrc
index 3b6ab74..e40502a 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -151,6 +151,7 @@
 		"context": true,
 		"before": true,
 		"beforeEach": true,
-		"onScan": true
+		"onScan": true,
+		"extend_cscript": true
 	}
 }
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index be425ec..e7fa354 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -8,5 +8,8 @@
 #
 # $ git config blame.ignoreRevsFile .git-blame-ignore-revs
 
+# Replace use of Class.extend with native JS class
+1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
+
 # This commit just changes spaces to tabs for indentation in some files
 5f473611bd6ed57703716244a054d3fb5ba9cd23
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 7b0f944..f7a7122 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -44,3 +44,4 @@
 bench get-app erpnext "${GITHUB_WORKSPACE}"
 bench start &
 bench --site test_site reinstall --yes
+bench build --app frappe
diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml
index 92685e2..69afa15 100644
--- a/.github/workflows/server-tests.yml
+++ b/.github/workflows/server-tests.yml
@@ -1,6 +1,10 @@
 name: Server
 
-on: [pull_request, workflow_dispatch]
+on:
+  pull_request:
+  workflow_dispatch:
+  push:
+    branches: [ develop ]
 
 jobs:
   test:
@@ -87,6 +91,7 @@
           coveralls
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
           COVERALLS_FLAG_NAME: run-${{ matrix.container }}
           COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
           COVERALLS_PARALLEL: true
diff --git a/.gitignore b/.gitignore
index 652fbdc..63c51c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 .wnf-lang-status
 *.egg-info
 dist/
+erpnext/public/dist
 erpnext/docs/current
 *.swp
 *.swo
diff --git a/README.md b/README.md
index 0a556f5..c6fc251 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
         <p>ERP made simple</p>
     </p>
 
-[![CI](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml)
+[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
 [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
 [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
 
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
index da1d10d..d60c559 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general.json
@@ -1,1609 +1,1609 @@
 {
-    "country_code": "fr", 
-    "name": "France - Plan Comptable General", 
+    "country_code": "fr",
+    "name": "France - Plan Comptable General",
     "tree": {
         "1-Comptes de Capitaux": {
             "10-Capital et R\u00e9serves": {
                 "101-Capital": {
-                    "1011-Capital souscrit - non appel\u00e9": {}, 
-                    "1012-Capital souscrit - appel\u00e9, non vers\u00e9": {}, 
+                    "1011-Capital souscrit - non appel\u00e9": {},
+                    "1012-Capital souscrit - appel\u00e9, non vers\u00e9": {},
                     "1013-Capital souscrit - appel\u00e9, vers\u00e9": {
-                        "10131-Capital non amorti": {}, 
+                        "10131-Capital non amorti": {},
                         "10132-Capital amorti": {}
-                    }, 
+                    },
                     "1018-Capital souscrit soumis \u00e0 des r\u00e9glementations particuli\u00e8res": {}
-                }, 
-                "102-Fonds fiduciaires": {}, 
+                },
+                "102-Fonds fiduciaires": {},
                 "104-Primes li\u00e9es au capital social": {
-                    "1041-Primes d'\u00e9mission": {}, 
-                    "1042-Primes de fusion": {}, 
-                    "1043-Primes d'apport": {}, 
-                    "1044-Primes de conversion d'obligations en actions": {}, 
+                    "1041-Primes d'\u00e9mission": {},
+                    "1042-Primes de fusion": {},
+                    "1043-Primes d'apport": {},
+                    "1044-Primes de conversion d'obligations en actions": {},
                     "1045-Bons de souscription d'actions": {}
-                }, 
+                },
                 "105-Ecarts de r\u00e9\u00e9valuation": {
-                    "1051-R\u00e9serve sp\u00e9ciale de r\u00e9\u00e9valuation": {}, 
-                    "1052-Ecart de r\u00e9\u00e9valuation libre": {}, 
-                    "1053-R\u00e9serve de r\u00e9\u00e9valuation": {}, 
-                    "1055-Ecarts de r\u00e9\u00e9valuation (autres op\u00e9rations l\u00e9gales)": {}, 
-                    "1057-Autres \u00e9carts de r\u00e9\u00e9valuation en France": {}, 
+                    "1051-R\u00e9serve sp\u00e9ciale de r\u00e9\u00e9valuation": {},
+                    "1052-Ecart de r\u00e9\u00e9valuation libre": {},
+                    "1053-R\u00e9serve de r\u00e9\u00e9valuation": {},
+                    "1055-Ecarts de r\u00e9\u00e9valuation (autres op\u00e9rations l\u00e9gales)": {},
+                    "1057-Autres \u00e9carts de r\u00e9\u00e9valuation en France": {},
                     "1058-Autres \u00e9carts de r\u00e9\u00e9valuation \u00e0 l'\u00e9tranger": {}
-                }, 
+                },
                 "106-R\u00e9serves": {
                     "1061-R\u00e9serve l\u00e9gale": {
-                        "10611-R\u00e9serve l\u00e9gale proprement dite": {}, 
+                        "10611-R\u00e9serve l\u00e9gale proprement dite": {},
                         "10612-Plus-values nettes \u00e0 long terme": {}
-                    }, 
-                    "1062-R\u00e9serves indisponibles": {}, 
-                    "1063-R\u00e9serves statutaires ou contractuelles": {}, 
+                    },
+                    "1062-R\u00e9serves indisponibles": {},
+                    "1063-R\u00e9serves statutaires ou contractuelles": {},
                     "1064-R\u00e9serves r\u00e9glement\u00e9es": {
-                        "10641-Plus-values nettes \u00e0 long terme": {}, 
-                        "10643-R\u00e9serves cons\u00e9cutives \u00e0 l'octroi de subventions d'investissement": {}, 
+                        "10641-Plus-values nettes \u00e0 long terme": {},
+                        "10643-R\u00e9serves cons\u00e9cutives \u00e0 l'octroi de subventions d'investissement": {},
                         "10648-Autres r\u00e9serves r\u00e9glement\u00e9es": {}
-                    }, 
+                    },
                     "1068-Autres r\u00e9serves": {
-                        "10681-R\u00e9serve de propre assureur": {}, 
+                        "10681-R\u00e9serve de propre assureur": {},
                         "10688-R\u00e9serves diverses": {}
                     }
-                }, 
-                "107-Ecarts d'\u00e9quivalence": {}, 
-                "108-Compte de l'exploitant": {}, 
+                },
+                "107-Ecarts d'\u00e9quivalence": {},
+                "108-Compte de l'exploitant": {},
                 "109-Actionnaires: Capital souscrit - non appel\u00e9": {}
-            }, 
+            },
             "11-Report \u00e0 Nouveau": {
-                "110-Report \u00e0 nouveau (solde cr\u00e9diteur)": {}, 
+                "110-Report \u00e0 nouveau (solde cr\u00e9diteur)": {},
                 "119-Report \u00e0 nouveau (solde d\u00e9biteur)": {}
-            }, 
+            },
             "12-R\u00e9sultat de l'Exercice": {
-                "120-R\u00e9sultat de l'exercice (b\u00e9n\u00e9fice)": {}, 
+                "120-R\u00e9sultat de l'exercice (b\u00e9n\u00e9fice)": {},
                 "129-R\u00e9sultat de l'exercice (perte)": {}
-            }, 
+            },
             "13-Subventions d'Investissement": {
                 "131-Subventions d'\u00e9quipement": {
-                    "1311-Etat": {}, 
-                    "1312-R\u00e9gions": {}, 
-                    "1313-D\u00e9partements": {}, 
-                    "1314-Communes": {}, 
-                    "1315-Collectivit\u00e9s publiques": {}, 
-                    "1316-Entreprises publiques": {}, 
-                    "1317-Entreprises et organismes priv\u00e9s": {}, 
+                    "1311-Etat": {},
+                    "1312-R\u00e9gions": {},
+                    "1313-D\u00e9partements": {},
+                    "1314-Communes": {},
+                    "1315-Collectivit\u00e9s publiques": {},
+                    "1316-Entreprises publiques": {},
+                    "1317-Entreprises et organismes priv\u00e9s": {},
                     "1318-Autres": {}
-                }, 
-                "138-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {}, 
+                },
+                "138-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {},
                 "139-Subventions d'investissement inscrites au compte de r\u00e9sultat": {
                     "1391-Subventions d'\u00e9quipement": {
-                        "13911-Subventions d'\u00e9quipement": {
-                            "13911-Etat": {}, 
-                            "13912-R\u00e9gions": {}, 
-                            "13913-D\u00e9partements": {}, 
-                            "13914-Communes": {}, 
-                            "13915-Collectivit\u00e9s publiques": {}, 
-                            "13916-Entreprises publiques": {}, 
-                            "13917-Entreprises et organismes priv\u00e9s": {}, 
-                            "13918-Autres": {}
-                        }
-                    }, 
+                        "13911-Etat": {},
+                        "13912-R\u00e9gions": {},
+                        "13913-D\u00e9partements": {},
+                        "13914-Communes": {},
+                        "13915-Collectivit\u00e9s publiques": {},
+                        "13916-Entreprises publiques": {},
+                        "13917-Entreprises et organismes priv\u00e9s": {},
+                        "13918-Autres": {}
+                    },
                     "1398-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 1391)": {}
                 }
-            }, 
+            },
             "14-Provisions R\u00e9glement\u00e9es": {
                 "142-Provisions r\u00e9glement\u00e9es relative aux immobilisations": {
-                    "1423-Provisions pour reconstitution des gisements miniers et p\u00e9troliers": {}, 
+                    "1423-Provisions pour reconstitution des gisements miniers et p\u00e9troliers": {},
                     "1424-Provisions pour investissement (participation des salari\u00e9s)": {}
-                }, 
+                },
                 "143-Provisions r\u00e9glement\u00e9es relatives aux stocks": {
-                    "1431-Hausse des prix": {}, 
+                    "1431-Hausse des prix": {},
                     "1432-Fluctuation des cours": {}
-                }, 
-                "144-Provisions r\u00e9glement\u00e9es relatives aux autres \u00e9l\u00e9ments de l'actif": {}, 
-                "145-Amortissements d\u00e9rogatoires": {}, 
-                "146-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {}, 
-                "147-Plus-values r\u00e9investies": {}, 
+                },
+                "144-Provisions r\u00e9glement\u00e9es relatives aux autres \u00e9l\u00e9ments de l'actif": {},
+                "145-Amortissements d\u00e9rogatoires": {},
+                "146-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {},
+                "147-Plus-values r\u00e9investies": {},
                 "148-Autres provisions r\u00e9glement\u00e9es": {}
-            }, 
+            },
             "15-Provisions": {
                 "151-Provisions pour risques": {
-                    "1511-Provisions pour litiges": {}, 
-                    "1512-Provisions pour garanties donn\u00e9es aux clients": {}, 
-                    "1513-Provisions pour pertes sur march\u00e9s \u00e0 terme": {}, 
-                    "1514-Provisions pour amendes et p\u00e9nalit\u00e9s": {}, 
-                    "1515-Provisions pour pertes de change": {}, 
-                    "1516-Provisions pour pertes sur contrats": {}, 
+                    "1511-Provisions pour litiges": {},
+                    "1512-Provisions pour garanties donn\u00e9es aux clients": {},
+                    "1513-Provisions pour pertes sur march\u00e9s \u00e0 terme": {},
+                    "1514-Provisions pour amendes et p\u00e9nalit\u00e9s": {},
+                    "1515-Provisions pour pertes de change": {},
+                    "1516-Provisions pour pertes sur contrats": {},
                     "1518-Autres provisions pour risques": {}
-                }, 
-                "153-Provisions pour pensions et obligations similaires": {}, 
-                "154-Provisions pour restructurations": {}, 
-                "155-Provisions pour imp\u00f4ts": {}, 
-                "156-Provisions pour renouvellement des immobilisations (entreprises concessionnaires) ": {}, 
+                },
+                "153-Provisions pour pensions et obligations similaires": {},
+                "154-Provisions pour restructurations": {},
+                "155-Provisions pour imp\u00f4ts": {},
+                "156-Provisions pour renouvellement des immobilisations (entreprises concessionnaires) ": {},
                 "157-Provisions pour charges \u00e0 r\u00e9partir sur plusieurs exercices": {
                     "1572-Provisions pour gros entretien ou grandes r\u00e9visions": {}
-                }, 
+                },
                 "158-Autres provisions pour charges": {
                     "1581-Provisions pour remises en \u00e9tat": {}
                 }
-            }, 
+            },
             "16-Emprunts et Dettes Assimil\u00e9es": {
-                "161-Emprunts obligataires convertibles": {}, 
-                "162-Obligations repr\u00e9sentatives de passifs nets remis en fiducie": {}, 
-                "163-Autres emprunts obligataires": {}, 
-                "164-Emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {}, 
+                "161-Emprunts obligataires convertibles": {},
+                "162-Obligations repr\u00e9sentatives de passifs nets remis en fiducie": {},
+                "163-Autres emprunts obligataires": {},
+                "164-Emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {},
                 "165-D\u00e9p\u00f4ts et cautionnements re\u00e7us": {
-                    "1651-D\u00e9p\u00f4ts": {}, 
+                    "1651-D\u00e9p\u00f4ts": {},
                     "1655-Cautionnements": {}
-                }, 
+                },
                 "166-Participation des salari\u00e9s aux r\u00e9sultats": {
-                    "1661-Comptes bloqu\u00e9s": {}, 
+                    "1661-Comptes bloqu\u00e9s": {},
                     "1662-Fonds de participation": {}
-                }, 
+                },
                 "167-Emprunts et dettes assortis de conditions particuli\u00e8res": {
-                    "1671-Emissions de titres participatifs": {}, 
-                    "1674-Avances conditionn\u00e9es de l'Etat": {}, 
+                    "1671-Emissions de titres participatifs": {},
+                    "1674-Avances conditionn\u00e9es de l'Etat": {},
                     "1675-Emprunts participatifs": {}
-                }, 
+                },
                 "168-Autres emprunts et dettes assimil\u00e9es": {
-                    "1681-Autres emprunts": {}, 
-                    "1685-Rentes viag\u00e8res capitalis\u00e9es": {}, 
-                    "1687-Autres dettes": {}, 
+                    "1681-Autres emprunts": {},
+                    "1685-Rentes viag\u00e8res capitalis\u00e9es": {},
+                    "1687-Autres dettes": {},
                     "1688-Int\u00e9r\u00eats courus": {
-                        "16881-Int\u00e9r\u00eats courus sur emprunts obligataires convertibles": {}, 
-                        "16883-Int\u00e9r\u00eats courus sur autres emprunts obligataires": {}, 
-                        "16884-Int\u00e9r\u00eats courus sur emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {}, 
-                        "16885-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements re\u00e7us": {}, 
-                        "16886-Int\u00e9r\u00eats courus sur participation des salari\u00e9s aux r\u00e9sultats": {}, 
-                        "16887-Int\u00e9r\u00eats courus sur emprunts et dettes assortis de conditions particuli\u00e8res": {}, 
+                        "16881-Int\u00e9r\u00eats courus sur emprunts obligataires convertibles": {},
+                        "16883-Int\u00e9r\u00eats courus sur autres emprunts obligataires": {},
+                        "16884-Int\u00e9r\u00eats courus sur emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {},
+                        "16885-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements re\u00e7us": {},
+                        "16886-Int\u00e9r\u00eats courus sur participation des salari\u00e9s aux r\u00e9sultats": {},
+                        "16887-Int\u00e9r\u00eats courus sur emprunts et dettes assortis de conditions particuli\u00e8res": {},
                         "16888-Int\u00e9r\u00eats courus sur autres emprunts et dettes assimil\u00e9es": {}
-                    }, 
+                    },
                     "169-Primes de remboursement des obligations": {}
                 }
-            }, 
+            },
             "17-Dettes Rattach\u00e9es \u00e0 des Participations": {
-                "171-Dettes rattach\u00e9es \u00e0 des participations (groupe)": {}, 
-                "174-Dettes rattach\u00e9es \u00e0 des participations (hors groupe)": {}, 
+                "171-Dettes rattach\u00e9es \u00e0 des participations (groupe)": {},
+                "174-Dettes rattach\u00e9es \u00e0 des participations (hors groupe)": {},
                 "178-Dettes rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
-                    "1781-Principal": {}, 
+                    "1781-Principal": {},
                     "1788-Int\u00e9r\u00eats courus": {}
                 }
-            }, 
+            },
             "18-Comptes de liaison des \u00e9tablisssements et soci\u00e9t\u00e9s en participation": {
-                "181-Comptes de liaison des \u00e9tablissements": {}, 
-                "186-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (charges)": {}, 
-                "187-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (produits)": {}, 
+                "181-Comptes de liaison des \u00e9tablissements": {},
+                "186-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (charges)": {},
+                "187-Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (produits)": {},
                 "188-Comptes de liaison des soci\u00e9t\u00e9s en participation": {}
-            }, 
+            },
             "root_type": "Equity"
-        }, 
+        },
         "2-Comptes d'Immobilisations": {
             "20-Immobilisations incorporelles": {
                 "201-Frais \u00e9tablissement": {
-                    "2011-Frais de constitution": {}, 
+                    "2011-Frais de constitution": {},
                     "2012-Frais de premier \u00e9tablissement": {
-                        "20121-Frais de prospection": {}, 
+                        "20121-Frais de prospection": {},
                         "20122-Frais de publicit\u00e9": {}
-                    }, 
+                    },
                     "2013-Frais d'augmentation de capital et d'op\u00e9rations diverses (fusions, scissions, transformations)": {}
-                }, 
-                "203-Frais de recherche et de d\u00e9veloppement": {}, 
-                "205-Concessions et droits similaires, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {}, 
-                "206-Droit au bail": {}, 
-                "207-Fonds commercial": {}, 
+                },
+                "203-Frais de recherche et de d\u00e9veloppement": {},
+                "205-Concessions et droits similaires, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {},
+                "206-Droit au bail": {},
+                "207-Fonds commercial": {},
                 "208-Autres immobilisations incorporelles": {
                     "2081-Mali de fusion sur actifs incorporels": {}
                 }
-            }, 
+            },
             "21-Immobilisations corporelles": {
                 "211-Terrains": {
                     "2111-Terrains nus": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2112-Terrains am\u00e9nag\u00e9s": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2113-Sous-sols et sur-sols": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2114-Terrains de carri\u00e8res (tr\u00e9fonds)": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2115-Terrains b\u00e2tis": {
                         "21151-Ensembles immobiliers industriels (A, B)": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21155-Ensembles immobiliers administratifs et commerciaux (A, B)": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21158-Autres ensembles immobiliers": {
                             "211581-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
                                 "account_type": "Fixed Asset"
-                            }, 
+                            },
                             "211588-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
                                 "account_type": "Fixed Asset"
-                            }, 
+                            },
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "account_type": "Fixed Asset"
-                }, 
+                },
                 "212-Agencements et am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 211)": {
                     "account_type": "Fixed Asset"
-                }, 
+                },
                 "213-Constructions": {
                     "2131-B\u00e2timents": {
                         "21311-Ensembles immobiliers industriels (A, B)": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21315-Ensembles immobiliers administratifs et commerciaux (A, B)": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21318-Autres ensembles immobiliers": {
                             "213181-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
                                 "account_type": "Fixed Asset"
-                            }, 
+                            },
                             "213188-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
                                 "account_type": "Fixed Asset"
-                            }, 
+                            },
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2135-Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements des constructions": {
                         "21351-Ensembles immobiliers industriels (A, B)": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21355-Ensembles immobiliers administratifs et commerciaux (A, B)": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21358-Autres ensembles immobiliers": {
                             "213581-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
                                 "account_type": "Fixed Asset"
-                            }, 
+                            },
                             "213588-Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
                                 "account_type": "Fixed Asset"
-                            }, 
+                            },
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2138-Ouvrages d'infrastructure": {
                         "21381-Voies de terre": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21382-Voies de fer": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21383-Voies d'eau": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21384-Barrages": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21385-Pistes d'a\u00e9rodromes": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "account_type": "Fixed Asset"
-                }, 
+                },
                 "214-Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte 213)": {
                     "account_type": "Fixed Asset"
-                }, 
+                },
                 "215-Installations techniques, mat\u00e9riel et outillage industriels": {
                     "2151-Installations complexes sp\u00e9cialis\u00e9es": {
                         "21511-Installations complexes sp\u00e9cialis\u00e9es - sur sol propre": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21514-Installations complexes sp\u00e9cialis\u00e9es - sur sol d'autrui": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2153-Installations \u00e0 caract\u00e8re sp\u00e9cifique": {
                         "21531-Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol propre": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "21534-Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol d'autrui": {
                             "account_type": "Fixed Asset"
-                        }, 
+                        },
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2154-Mat\u00e9riel industriel": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2155-Outillage industriel": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2157-Agencements et am\u00e9nagements du mat\u00e9riel et outillage industriel": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "account_type": "Fixed Asset"
-                }, 
+                },
                 "218-Autres immobilisations corporelles": {
                     "2181-Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements divers": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2182-Mat\u00e9riel de transport": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2183-Mat\u00e9riel de bureau et mat\u00e9riel informatique": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2184-Mobilier": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2185-Cheptel": {
                         "account_type": "Fixed Asset"
-                    }, 
+                    },
                     "2186-Emballages r\u00e9cup\u00e9rables": {
                         "account_type": "Fixed Asset"
-                    }, 
-                    "2187-Mali de fusion sur actifs corporels": {}, 
+                    },
+                    "2187-Mali de fusion sur actifs corporels": {},
                     "account_type": "Fixed Asset"
-                }, 
+                },
                 "account_type": "Fixed Asset"
-            }, 
-            "22-Immobilisations mises en concession": {}, 
+            },
+            "22-Immobilisations mises en concession": {},
             "23-Immobilisations en cours": {
                 "231-Immobilisations corporelles en cours": {
-                    "2312-Terrains": {}, 
-                    "2313-Constructions": {}, 
-                    "2315-Installations techniques, mat\u00e9riel et outillage industriels": {}, 
+                    "2312-Terrains": {},
+                    "2313-Constructions": {},
+                    "2315-Installations techniques, mat\u00e9riel et outillage industriels": {},
                     "2318-Autres immobilisations corporelles": {}
-                }, 
-                "232-Immobilisations incorporelles en cours": {}, 
-                "237-Avances et acomptes vers\u00e9s sur commandes d'immobilisations incorporelles": {}, 
+                },
+                "232-Immobilisations incorporelles en cours": {},
+                "237-Avances et acomptes vers\u00e9s sur commandes d'immobilisations incorporelles": {},
                 "238-Avances et acomptes vers\u00e9s sur commandes d'immobilisations corporelles": {
-                    "2382-Terrains": {}, 
-                    "2383-Constructions": {}, 
-                    "2385-Installations techniques, mat\u00e9riel et outillage industriels": {}, 
+                    "2382-Terrains": {},
+                    "2383-Constructions": {},
+                    "2385-Installations techniques, mat\u00e9riel et outillage industriels": {},
                     "2388-Autres immobilisations corporelles": {}
                 }
-            }, 
+            },
             "25-Parts dans des entreprises li\u00e9es et cr\u00e9ances sur des entreprises li\u00e9es": {
                 "is_group": 1
-            }, 
+            },
             "26-Participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
                 "261-Titres de participation": {
-                    "2611-Actions": {}, 
+                    "2611-Actions": {},
                     "2618-Autres titres": {}
-                }, 
+                },
                 "266-Autres formes de participation": {
                     "2661-Droit repr\u00e9sentatifs d'actifs nets remis en fiducie": {}
-                }, 
+                },
                 "267-Cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
-                    "2671-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (groupe)": {}, 
-                    "2674-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (hors groupe)": {}, 
-                    "2675-Versements repr\u00e9sentatifs d'apports non capitalis\u00e9s (appel de fonds)": {}, 
-                    "2676-Avances consolidables": {}, 
-                    "2677-Autres cr\u00e9ances rattach\u00e9es \u00e0 des participations": {}, 
+                    "2671-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (groupe)": {},
+                    "2674-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (hors groupe)": {},
+                    "2675-Versements repr\u00e9sentatifs d'apports non capitalis\u00e9s (appel de fonds)": {},
+                    "2676-Avances consolidables": {},
+                    "2677-Autres cr\u00e9ances rattach\u00e9es \u00e0 des participations": {},
                     "2678-Int\u00e9r\u00eats courus": {}
-                }, 
+                },
                 "268-Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
-                    "2681-Principal": {}, 
+                    "2681-Principal": {},
                     "2688-Int\u00e9r\u00eats courus": {}
-                }, 
+                },
                 "269-Versements restant \u00e0 effectuer sur titres de participation non lib\u00e9r\u00e9s": {}
-            }, 
+            },
             "27-Autres immobilisations financi\u00e8res": {
                 "271-Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille (droit de propri\u00e9t\u00e9)": {
-                    "2711-Actions": {}, 
+                    "2711-Actions": {},
                     "2718-Autres titres": {}
-                }, 
+                },
                 "272-Titres immobilis\u00e9s (droit de cr\u00e9ance)": {
-                    "2721-Obligations": {}, 
+                    "2721-Obligations": {},
                     "2722-Bons": {}
-                }, 
-                "273-Titres immobilis\u00e9s de l'activit\u00e9 de portefeuille": {}, 
+                },
+                "273-Titres immobilis\u00e9s de l'activit\u00e9 de portefeuille": {},
                 "274-Pr\u00eats": {
-                    "2741-Pr\u00eats participatifs": {}, 
-                    "2742-Pr\u00eats aux associ\u00e9s": {}, 
-                    "2743-Pr\u00eats au personnel": {}, 
+                    "2741-Pr\u00eats participatifs": {},
+                    "2742-Pr\u00eats aux associ\u00e9s": {},
+                    "2743-Pr\u00eats au personnel": {},
                     "2748-Autres pr\u00eats": {}
-                }, 
+                },
                 "275-D\u00e9p\u00f4ts et cautionnements vers\u00e9s": {
-                    "2751-D\u00e9p\u00f4ts": {}, 
+                    "2751-D\u00e9p\u00f4ts": {},
                     "2755-Cautionnements": {}
-                }, 
+                },
                 "276-Autres cr\u00e9ances immobilis\u00e9es": {
-                    "2761-Cr\u00e9ances diverses": {}, 
+                    "2761-Cr\u00e9ances diverses": {},
                     "2768-Int\u00e9r\u00eats courus": {
-                        "27682-Int\u00e9r\u00eats courus sur titres immobilis\u00e9s (droit de cr\u00e9ance)": {}, 
-                        "27684-Int\u00e9r\u00eats courus sur pr\u00eats": {}, 
-                        "27685-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements": {}, 
+                        "27682-Int\u00e9r\u00eats courus sur titres immobilis\u00e9s (droit de cr\u00e9ance)": {},
+                        "27684-Int\u00e9r\u00eats courus sur pr\u00eats": {},
+                        "27685-Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements": {},
                         "27688-Int\u00e9r\u00eats courus sur cr\u00e9ances diverses": {}
                     }
-                }, 
+                },
                 "277-(Actions propres ou parts propres)": {
-                    "2771-Actions propres ou parts propres": {}, 
+                    "2771-Actions propres ou parts propres": {},
                     "2772-Actions propres ou parts propres en voie d'annulation": {}
-                }, 
-                "278-Mali de fusion sur actifs financiers": {}, 
+                },
+                "278-Mali de fusion sur actifs financiers": {},
                 "279-Versements restant \u00e0 effectuer sur titres immobilis\u00e9s non lib\u00e9r\u00e9s": {}
-            }, 
+            },
             "28-Amortissements des immobilisations": {
                 "280-Amortissements des immobilisations incorporelles": {
                     "2801-Frais d'\u00e9tablissement (m\u00eame ventilation que celle du compte 212)": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2803-Frais de recherche et de d\u00e9veloppement": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2805-Concessions et droits similaires, brevets, licences, logiciels, droits et valeurs similaires": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2807-Fonds commercial": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2808-Autres immobilisations incorporelles": {
                         "28081-Mali de fusion sur actifs incorporels": {
                             "account_type": "Accumulated Depreciation"
-                        }, 
+                        },
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "account_type": "Accumulated Depreciation"
-                }, 
+                },
                 "281-Amortissements des immobilisations corporelles": {
                     "2811-Terrains de gisement": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2812-Agencements, am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 212)": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2813-Constructions (m\u00eame ventilation que celle du compte 213)": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2814-Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte du 214)": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2815-Installations techniques, mat\u00e9riel et outillage industriels (m\u00eame ventilation que celle du compte 218)": {
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "2818-Autres immobilisations corporelles (m\u00eame ventilation que celle du compte 218)": {
                         "28187-Mali de fusion sur actifs corporels": {
                             "account_type": "Accumulated Depreciation"
-                        }, 
+                        },
                         "account_type": "Accumulated Depreciation"
-                    }, 
+                    },
                     "account_type": "Accumulated Depreciation"
-                }, 
-                "282-Amortissements des immobilisations mises en concession": {}, 
+                },
+                "282-Amortissements des immobilisations mises en concession": {},
                 "account_type": "Accumulated Depreciation"
-            }, 
+            },
             "29-D\u00e9pr\u00e9ciations des immobilisations": {
                 "290-D\u00e9pr\u00e9ciations des immobilisations incorporelles": {
-                    "2905-Marques,  proc\u00e9d\u00e9s, droits et valeurs similaires": {}, 
-                    "2906-Droit au bail": {}, 
-                    "2907-Fonds commercial": {}, 
+                    "2905-Marques,  proc\u00e9d\u00e9s, droits et valeurs similaires": {},
+                    "2906-Droit au bail": {},
+                    "2907-Fonds commercial": {},
                     "2908-Autres immobilisations incorporelles": {
                         "29081-Mali de fusion sur actifs incorporels": {}
                     }
-                }, 
+                },
                 "291-D\u00e9pr\u00e9ciations des immobilisations corporelles (m\u00eame ventilation que celle du compte 21)": {
                     "2911-Terrains (autres que terrains de gisement)": {
                         "29187-Mali de fusion sur actifs corporels": {}
                     }
-                }, 
-                "292-D\u00e9pr\u00e9ciations des immobilisations mises en concession": {}, 
+                },
+                "292-D\u00e9pr\u00e9ciations des immobilisations mises en concession": {},
                 "293-D\u00e9pr\u00e9ciations des immobilisations en cours": {
-                    "2931-Immobilisations corporelles en cours": {}, 
+                    "2931-Immobilisations corporelles en cours": {},
                     "2932-Immobilisations incorporelles en cours": {}
-                }, 
+                },
                 "296-D\u00e9pr\u00e9ciations des participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
-                    "2961-Titres de participation": {}, 
-                    "2966-Autres formes de participation": {}, 
-                    "2967-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (m\u00eame ventilation que celle du compte 267)": {}, 
+                    "2961-Titres de participation": {},
+                    "2966-Autres formes de participation": {},
+                    "2967-Cr\u00e9ances rattach\u00e9es \u00e0 des participations (m\u00eame ventilation que celle du compte 267)": {},
                     "2968-Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation (m\u00eame ventilation que celle du compte 268)": {}
-                }, 
+                },
                 "297-D\u00e9pr\u00e9ciations des autres immobilisations financi\u00e8res": {
-                    "2971-Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille - droit de propri\u00e9t\u00e9": {}, 
-                    "2972-Titres immobilis\u00e9s - droit de cr\u00e9ance (m\u00eame ventilation que celle du compte 272)": {}, 
-                    "2973- Titres immobilis\u00e9s de l'activit\u00e9 de portefuille": {}, 
-                    "2974-Pr\u00eats (m\u00eame ventilation que celle du compte 274)": {}, 
-                    "2975-D\u00e9p\u00f4ts et cautionnements vers\u00e9s (m\u00eame ventilation que celle du compte 275)": {}, 
+                    "2971-Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille - droit de propri\u00e9t\u00e9": {},
+                    "2972-Titres immobilis\u00e9s - droit de cr\u00e9ance (m\u00eame ventilation que celle du compte 272)": {},
+                    "2973- Titres immobilis\u00e9s de l'activit\u00e9 de portefuille": {},
+                    "2974-Pr\u00eats (m\u00eame ventilation que celle du compte 274)": {},
+                    "2975-D\u00e9p\u00f4ts et cautionnements vers\u00e9s (m\u00eame ventilation que celle du compte 275)": {},
                     "2976-Autres cr\u00e9ances immobilis\u00e9es (m\u00eame ventilation que celle du compte 276)": {
                         "29787-Mali de fusion sur actifs financiers": {}
                     }
                 }
-            }, 
+            },
             "root_type": "Asset"
-        }, 
+        },
         "3-Comptes de Stocks et En-Cours": {
             "31-Mati\u00e8res premi\u00e8res (et fournitures)": {
-                "311-Mati\u00e8res (ou groupe) A": {}, 
-                "312-Mati\u00e8res (ou groupe) B": {}, 
+                "311-Mati\u00e8res (ou groupe) A": {},
+                "312-Mati\u00e8res (ou groupe) B": {},
                 "317-Fournitures A, B, C, ...": {}
-            }, 
+            },
             "32-Autres approvisionnements": {
                 "321-Mat\u00e8res consommables": {
-                    "3211-Mati\u00e8res (ou groupe) C": {}, 
+                    "3211-Mati\u00e8res (ou groupe) C": {},
                     "3212-Mati\u00e8res (ou groupe) D": {}
-                }, 
+                },
                 "322-Fournitures consommables": {
-                    "3221-Combustibles": {}, 
-                    "3222-Produits d'entretien": {}, 
-                    "3223-Fournitures d'atelier et d'usine": {}, 
-                    "3224-Fournitures de magasin": {}, 
+                    "3221-Combustibles": {},
+                    "3222-Produits d'entretien": {},
+                    "3223-Fournitures d'atelier et d'usine": {},
+                    "3224-Fournitures de magasin": {},
                     "3225-Fournitures de bureau": {}
-                }, 
+                },
                 "326-Emballages": {
-                    "3261-Emballages perdus": {}, 
-                    "3265-Emballages r\u00e9cup\u00e9rables non identifiables": {}, 
+                    "3261-Emballages perdus": {},
+                    "3265-Emballages r\u00e9cup\u00e9rables non identifiables": {},
                     "3267-Emballages \u00e0 usage mixte": {}
                 }
-            }, 
+            },
             "33-En-cours de production de biens": {
                 "331-Produits en cours": {
-                    "3311-Produits en cours P1": {}, 
+                    "3311-Produits en cours P1": {},
                     "3312-Produits en cours P2": {}
-                }, 
+                },
                 "335-Travaux en cours": {
-                    "Travaux en cours T1": {}, 
-                    "Travaux en cours T2": {}
+                    "3351-Travaux en cours T1": {},
+                    "3352-Travaux en cours T2": {}
                 }
-            }, 
+            },
             "34-En-cours de production de services": {
                 "341-Etudes en cours": {
-                    "3411-Etudes en cours E1": {}, 
+                    "3411-Etudes en cours E1": {},
                     "3412-Etudes en cours E2": {}
-                }, 
+                },
                 "345-Prestations de services en cours": {
-                    "3451-Prestations de services S1": {}, 
+                    "3451-Prestations de services S1": {},
                     "3452-Prestations de services S2": {}
                 }
-            }, 
+            },
             "35-Stocks de produits": {
                 "351-Produits interm\u00e9diaires": {
                     "3511-Produits interm\u00e9diaires (ou groupe) A": {
-                        "account_type": "Stock", 
+                        "account_type": "Stock",
                         "is_group": 1
-                    }, 
+                    },
                     "3512-Produits interm\u00e9diaires (ou groupe) B": {
-                        "account_type": "Stock", 
+                        "account_type": "Stock",
                         "is_group": 1
-                    }, 
+                    },
                     "account_type": "Stock"
-                }, 
+                },
                 "355-Produits finis": {
                     "3551-Produits finis (ou groupe) A": {
-                        "account_type": "Stock", 
+                        "account_type": "Stock",
                         "is_group": 1
-                    }, 
+                    },
                     "3552-Produits finis (ou groupe) B": {
-                        "account_type": "Stock", 
+                        "account_type": "Stock",
                         "is_group": 1
-                    }, 
+                    },
                     "account_type": "Stock"
-                }, 
+                },
                 "358-Produits r\u00e9siduels (ou mati\u00e8res de r\u00e9cup\u00e9ration)": {
                     "3581-D\u00e9chets": {
-                        "account_type": "Stock", 
+                        "account_type": "Stock",
                         "is_group": 1
-                    }, 
+                    },
                     "3585-Rebuts": {
-                        "account_type": "Stock", 
+                        "account_type": "Stock",
                         "is_group": 1
-                    }, 
+                    },
                     "3586-Mati\u00e8res de r\u00e9cup\u00e9ration": {
-                        "account_type": "Stock", 
+                        "account_type": "Stock",
                         "is_group": 1
-                    }, 
+                    },
                     "account_type": "Stock"
-                }, 
+                },
                 "account_type": "Stock"
-            }, 
-            "36-(Compte \u00e0 ouvrir, le cas \u00e9ch\u00e9ant, sous l'intitul\u00e9 \"stocks provenant d'immobilisations\")": {}, 
+            },
+            "36-(Compte \u00e0 ouvrir, le cas \u00e9ch\u00e9ant, sous l'intitul\u00e9 \"stocks provenant d'immobilisations\")": {},
             "37-Stocks de marchandises": {
-                "371-Marchandises (ou groupe) A": {}, 
+                "371-Marchandises (ou groupe) A": {},
                 "372-Marchandises (ou groupe) B": {}
-            }, 
-            "38-Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {}, 
+            },
+            "38-Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {
+                "account_type": "Stock"
+            },
             "39-D\u00e9pr\u00e9ciations des stocks et en-cours": {
                 "391-D\u00e9pr\u00e9ciations des mati\u00e8res premi\u00e8res (et fournitures)": {
-                    "3911-Mati\u00e8res (ou groupe) A": {}, 
-                    "3912-Mati\u00e8res (ou groupe) B": {}, 
+                    "3911-Mati\u00e8res (ou groupe) A": {},
+                    "3912-Mati\u00e8res (ou groupe) B": {},
                     "3917-Fournitures A, B, C, ...": {}
-                }, 
+                },
                 "392-D\u00e9pr\u00e9ciations des autres approvisionnements": {
-                    "3921-Mati\u00e8res consommables (m\u00eame ventilation que celle du compte 321)": {}, 
-                    "3922-Fournitures consommables (m\u00eame ventilation que celle du compte 322)": {}, 
+                    "3921-Mati\u00e8res consommables (m\u00eame ventilation que celle du compte 321)": {},
+                    "3922-Fournitures consommables (m\u00eame ventilation que celle du compte 322)": {},
                     "3926-Emballages (m\u00eame ventilation que celle du compte 326)": {}
-                }, 
+                },
                 "393-D\u00e9pr\u00e9ciations des en-cours de production de biens": {
-                    "3931-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {}, 
+                    "3931-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {},
                     "3935-Travaux en cours (m\u00eame ventilation que celle du compte 335)": {}
-                }, 
+                },
                 "394-D\u00e9pr\u00e9ciations des en-cours de production de services": {
-                    "3941-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {}, 
+                    "3941-Etudes en cours (m\u00eame ventilation que celle du compte 341)": {},
                     "3945-Prestations de services en cours (m\u00eame ventilation que celle du compte 345)": {}
-                }, 
+                },
                 "395-D\u00e9pr\u00e9ciations des stocks de produits": {
-                    "3951-Produits interm\u00e9diaires (m\u00eame ventilation que celle du compte 351)": {}, 
+                    "3951-Produits interm\u00e9diaires (m\u00eame ventilation que celle du compte 351)": {},
                     "3955-Produits finis (m\u00eame ventilation que celle du compte 355)": {}
-                }, 
+                },
                 "397-D\u00e9pr\u00e9ciations des stocks de marchandises": {
-                    "3971-Marchandise (ou groupe) A": {}, 
+                    "3971-Marchandise (ou groupe) A": {},
                     "3972-Marchandise (ou groupe) B": {}
                 }
-            }, 
+            },
             "root_type": "Asset"
-        }, 
+        },
         "4-Comptes de Tiers (ACTIF)": {
             "40-Fournisseurs et Comptes Rattach\u00e9s (ACTIF)": {
                 "409-Fournisseurs d\u00e9biteurs": {
-                    "4091-Fournisseurs - Avances et acomptes vers\u00e9s sur commandes": {}, 
-                    "4096-Fournisseurs - Cr\u00e9ances pour emballages et mat\u00e9riel \u00e0 rendre": {}, 
+                    "4091-Fournisseurs - Avances et acomptes vers\u00e9s sur commandes": {},
+                    "4096-Fournisseurs - Cr\u00e9ances pour emballages et mat\u00e9riel \u00e0 rendre": {},
                     "4097-Fournisseurs - Autres avoirs": {
-                        "40971-Fournisseurs d'exploitation": {}, 
+                        "40971-Fournisseurs d'exploitation": {},
                         "40974-Fournisseurs d'immobilisation": {}
-                    }, 
+                    },
                     "4098-Rabais, remises, ristournes \u00e0 obtenir et autres avoirs non encore re\u00e7us": {}
                 }
-            }, 
+            },
             "41-Clients et comptes rattach\u00e9s (ACTIF)": {
                 "410-Clients et Comptes rattach\u00e9s": {
                     "account_type": "Receivable"
-                }, 
+                },
                 "411-Clients": {
                     "4111-Clients - Ventes de biens ou de prestations de services": {
                         "account_type": "Receivable"
-                    }, 
+                    },
                     "4117-Clients - Retenues de garantie": {
                         "account_type": "Receivable"
-                    }, 
+                    },
                     "account_type": "Receivable"
-                }, 
+                },
                 "413-Clients - Effets \u00e0 recevoir": {
                     "account_type": "Receivable"
-                }, 
+                },
                 "416-Clients douteux ou litigieux": {
                     "account_type": "Receivable"
-                }, 
+                },
                 "418-Clients - Produits non encore factur\u00e9s": {
                     "4181-Clients - Factures \u00e0 \u00e9tablir": {
                         "account_type": "Receivable"
-                    }, 
+                    },
                     "4188-Clients - Int\u00e9r\u00eats courus": {
                         "account_type": "Receivable"
-                    }, 
+                    },
                     "account_type": "Receivable"
-                }, 
+                },
                 "account_type": "Receivable"
-            }, 
+            },
             "42-Personnel et comptes rattach\u00e9s (ACTIF)": {
                 "425-Personnel - Avances et acomptes": {}
-            }, 
+            },
             "43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (ACTIF)": {
-                "431-S\u00e9curit\u00e9 sociale": {}, 
-                "437-Autres organismes sociaux": {}, 
+                "431-S\u00e9curit\u00e9 sociale": {},
+                "437-Autres organismes sociaux": {},
                 "438-Organismes sociaux - Produits \u00e0 recevoir": {
                     "4387-Produits \u00e0 recevoir": {}
                 }
-            }, 
+            },
             "44-Etat et autres collectivit\u00e9s publiques (ACTIF)": {
                 "441-Etat - Subventions \u00e0 recevoir": {
-                    "4411-Subventions d'investissement": {}, 
-                    "4417-Subventions d'exploitation": {}, 
-                    "4418-Subventions d'\u00e9quilibre": {}, 
+                    "4411-Subventions d'investissement": {},
+                    "4417-Subventions d'exploitation": {},
+                    "4418-Subventions d'\u00e9quilibre": {},
                     "4419-Avances sur subventions": {}
-                }, 
+                },
                 "443-Op\u00e9rations particuli\u00e8res avec l'Etat, les collectivit\u00e9s publiques, les organismes internationaux": {
-                    "4431-Cr\u00e9ances sur l'Etat r\u00e9sultant de la suppression de la r\u00e8gle du d\u00e9calage d'un mois en mati\u00e8re de TVA": {}, 
+                    "4431-Cr\u00e9ances sur l'Etat r\u00e9sultant de la suppression de la r\u00e8gle du d\u00e9calage d'un mois en mati\u00e8re de TVA": {},
                     "4438-Int\u00e9r\u00eats courus sur cr\u00e9ances figurant au compte 4431": {}
-                }, 
+                },
                 "445-Etat - Taxes sur le chiffre d'affaires (ACTIF)": {
-                    "4452-TVA due intracommunautaire": {}, 
+                    "4452-TVA due intracommunautaire": {},
                     "4456-Taxes sur le chiffre d'affaires d\u00e9ductibles": {
-                        "44562-TVA sur immobilisations": {}, 
-                        "44563-TVA transf\u00e9r\u00e9e par d'autres entreprises": {}, 
+                        "44562-TVA sur immobilisations": {},
+                        "44563-TVA transf\u00e9r\u00e9e par d'autres entreprises": {},
                         "44566-TVA sur autres biens et services": {
                             "tax_rate": 20.0
-                        }, 
-                        "44567-Cr\u00e9dit de TVA \u00e0 reporter": {}, 
+                        },
+                        "44567-Cr\u00e9dit de TVA \u00e0 reporter": {},
                         "44568-Taxes assimil\u00e9es \u00e0 la TVA": {}
-                    }, 
+                    },
                     "4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (ACTIF)": {
-                        "44581-Acomptes - R\u00e9gime simplifi\u00e9 d'imposition": {}, 
-                        "44582-Acomptes - R\u00e9gime du forfait": {}, 
-                        "44583-Remboursement de taxes sur le chiffre d'affaires demand\u00e9": {}, 
+                        "44581-Acomptes - R\u00e9gime simplifi\u00e9 d'imposition": {},
+                        "44582-Acomptes - R\u00e9gime du forfait": {},
+                        "44583-Remboursement de taxes sur le chiffre d'affaires demand\u00e9": {},
                         "44586-Taxes sur le chiffre d'affaires sur factures non parvenues": {}
                     }
-                }, 
+                },
                 "448-Etat - Charges \u00e0 payer et produits \u00e0 recevoir": {
-                    "4482-Charges fiscales sur cong\u00e9s \u00e0 payer": {}, 
-                    "4486-Charges \u00e0 payer": {}, 
+                    "4482-Charges fiscales sur cong\u00e9s \u00e0 payer": {},
+                    "4486-Charges \u00e0 payer": {},
                     "4487-Produits \u00e0 recevoir": {}
                 }
-            }, 
+            },
             "45-Groupe et associ\u00e9s (ACTIF)": {
                 "456-Associ\u00e9s - Op\u00e9rations sur le capital (ACTIF)": {
                     "4562-Apporteurs - Capital appel\u00e9, non vers\u00e9": {
-                        "45621-Actionnaires - Capital souscrit et appel\u00e9, non vers\u00e9": {}, 
+                        "45621-Actionnaires - Capital souscrit et appel\u00e9, non vers\u00e9": {},
                         "45625-Associ\u00e9s - Capital appel\u00e9, non vers\u00e9": {}
                     }
                 }
-            }, 
+            },
             "46-D\u00e9biteurs divers et cr\u00e9diteurs divers (ACTIF)": {
-                "462-Cr\u00e9ances sur cessions d'immobilisations": {}, 
-                "465-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {}, 
-                "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (ACTIF)": {}, 
+                "462-Cr\u00e9ances sur cessions d'immobilisations": {},
+                "465-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {},
+                "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (ACTIF)": {},
                 "468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (ACTIF)": {
                     "4687-Produits \u00e0 recevoir": {}
                 }
-            }, 
+            },
             "47-Comptes transitoires ou d'attente (ACTIF)": {
                 "471-Comptes d'attente (ACTIF)": {
                     "account_type": "Temporary"
-                }, 
+                },
                 "476-Diff\u00e9rences de conversion (ACTIF)": {
-                    "4761-Diminution des cr\u00e9ances": {}, 
-                    "4762-Augmentation des dettes": {}, 
+                    "4761-Diminution des cr\u00e9ances": {},
+                    "4762-Augmentation des dettes": {},
                     "4768-Diff\u00e9rences compens\u00e9es par couverture de change": {}
-                }, 
+                },
                 "478-Autres comptes transitoires (ACTIF)": {
-                    "4781-Mali de fusion sur actif circulant": {}, 
+                    "4781-Mali de fusion sur actif circulant": {},
                     "4786-Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (ACTIF)": {}
                 }
-            }, 
+            },
             "48-Comptes de r\u00e9gularisation (ACTIF)": {
                 "481-Charges \u00e0 r\u00e9partir sur plusieurs exercices": {
                     "4816-Frais d'\u00e9mission des emprunts": {}
-                }, 
-                "486-Charges constat\u00e9es d'avance": {}, 
+                },
+                "486-Charges constat\u00e9es d'avance": {},
                 "488-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (ACTIF)": {
                     "4886-Charges": {}
                 }
-            }, 
+            },
             "49-D\u00e9pr\u00e9ciation des comptes de tiers (ACTIF)": {
-                "491-D\u00e9pr\u00e9ciations des comptes clients": {}, 
+                "491-D\u00e9pr\u00e9ciations des comptes clients": {},
                 "495-D\u00e9pr\u00e9ciations des comptes du groupe et des associ\u00e9s": {
-                    "4951-Comptes du groupe": {}, 
-                    "4955-Comptes courants des associ\u00e9s": {}, 
+                    "4951-Comptes du groupe": {},
+                    "4955-Comptes courants des associ\u00e9s": {},
                     "4958-Op\u00e9rations faites en commun et en GIE": {}
-                }, 
+                },
                 "496-D\u00e9pr\u00e9ciations des comptes de d\u00e9biteurs divers": {
-                    "4962-Cr\u00e9ances sur cessions d'immobilisations": {}, 
-                    "4965-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {}, 
+                    "4962-Cr\u00e9ances sur cessions d'immobilisations": {},
+                    "4965-Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {},
                     "4967-Autres comptes d\u00e9biteurs": {}
                 }
-            }, 
+            },
             "root_type": "Asset"
-        }, 
+        },
         "4-Comptes de Tiers (PASSIF)": {
             "40-Fournisseurs et Comptes Rattach\u00e9s (PASSIF)": {
                 "401-Fournisseurs": {
                     "4011-Fournisseurs - Achats de biens ou de prestations de services": {
                         "account_type": "Payable"
-                    }, 
+                    },
                     "4017-Fournisseurs - Retenues de garantie": {
                         "account_type": "Payable"
-                    }, 
+                    },
                     "account_type": "Payable"
-                }, 
+                },
                 "403-Fournisseurs - Effets \u00e0 payer": {
                     "account_type": "Payable"
-                }, 
+                },
                 "404-Fournisseurs d'immobilisations": {
                     "4041-Fournisseurs - Achats d'immobilisations": {
                         "account_type": "Payable"
-                    }, 
+                    },
                     "4047-Fournisseurs d'immobilisations - Retenues de garantie": {
                         "account_type": "Payable"
-                    }, 
+                    },
                     "account_type": "Payable"
-                }, 
+                },
                 "405-Fournisseurs d'immobilisations - Effets \u00e0 payer": {
                     "account_type": "Payable"
-                }, 
+                },
                 "408-Fournisseurs - Factures non parvenues": {
                     "4081-Fournisseurs": {
                         "account_type": "Stock Received But Not Billed"
-                    }, 
+                    },
                     "4084-Fournisseurs d'immobilisations": {
                         "account_type": "Stock Received But Not Billed"
-                    }, 
+                    },
                     "4088-Fournisseurs - Int\u00e9r\u00eats courus": {
                         "account_type": "Stock Received But Not Billed"
-                    }, 
+                    },
                     "account_type": "Stock Received But Not Billed"
-                }, 
+                },
                 "account_type": "Payable"
-            }, 
+            },
             "41-Clients et comptes rattach\u00e9s (PASSIF)": {
                 "419-Clients cr\u00e9diteurs": {
-                    "4191-Clients - Avances et acomptes re\u00e7us sur commandes": {}, 
-                    "4196-Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {}, 
-                    "4197-Clients - Autres avoirs": {}, 
+                    "4191-Clients - Avances et acomptes re\u00e7us sur commandes": {},
+                    "4196-Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {},
+                    "4197-Clients - Autres avoirs": {},
                     "4198-Rabais, remises, ristournes \u00e0 accorder et autres avoirs \u00e0 \u00e9tablir": {}
                 }
-            }, 
+            },
             "42-Personnel et comptes rattach\u00e9s (PASSIF)": {
-                "421-Personnel - R\u00e9mun\u00e9rations dues": {}, 
-                "422-Comit\u00e9s d'entreprises, d'\u00e9tablissement...": {}, 
+                "421-Personnel - R\u00e9mun\u00e9rations dues": {},
+                "422-Comit\u00e9s d'entreprises, d'\u00e9tablissement...": {},
                 "424-Participation des salari\u00e9s aux r\u00e9sultats": {
-                    "4246-R\u00e9serve sp\u00e9ciale": {}, 
+                    "4246-R\u00e9serve sp\u00e9ciale": {},
                     "4248-Comptes courants": {}
-                }, 
-                "426-Personnel - D\u00e9p\u00f4ts": {}, 
-                "427-Personnel - Oppositions": {}, 
+                },
+                "426-Personnel - D\u00e9p\u00f4ts": {},
+                "427-Personnel - Oppositions": {},
                 "428-Personnel - Charges \u00e0 payer et produits \u00e0 recevoir": {
-                    "4282-Dettes provisionn\u00e9es pour cong\u00e9s \u00e0 payer": {}, 
-                    "4284-Dettes provisionn\u00e9es pour participation des salari\u00e9s aux r\u00e9sultats": {}, 
-                    "4286-Autres charges \u00e0 payer": {}, 
+                    "4282-Dettes provisionn\u00e9es pour cong\u00e9s \u00e0 payer": {},
+                    "4284-Dettes provisionn\u00e9es pour participation des salari\u00e9s aux r\u00e9sultats": {},
+                    "4286-Autres charges \u00e0 payer": {},
                     "4287-Produits \u00e0 recevoir": {}
                 }
-            }, 
+            },
             "43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (PASSIF)": {
                 "438-Organismes sociaux - Charges \u00e0 payer": {
-                    "4382-Charges sociales sur cong\u00e9s \u00e0 payer": {}, 
+                    "4382-Charges sociales sur cong\u00e9s \u00e0 payer": {},
                     "4386-Autres charges \u00e0 payer": {}
                 }
-            }, 
+            },
             "44-Etat et autres collectivit\u00e9s publiques (PASSIF)": {
                 "442-Etat - Imp\u00f4ts et taxes recouvrables sur des tiers": {
-                    "4424-Obligataires": {}, 
+                    "4424-Obligataires": {},
                     "4425-Associ\u00e9s": {}
-                }, 
-                "444-Etat - Imp\u00f4ts sur les b\u00e9n\u00e9fices": {}, 
+                },
+                "444-Etat - Imp\u00f4ts sur les b\u00e9n\u00e9fices": {},
                 "445-Etat - Taxes sur le chiffre d'affaires (PASSIF)": {
                     "4455-Taxes sur le chiffre d'affaires \u00e0 d\u00e9caisser": {
-                        "44551-TVA \u00e0 d\u00e9caisser": {}, 
+                        "44551-TVA \u00e0 d\u00e9caisser": {},
                         "44558-Taxes assimil\u00e9es \u00e0 la TVA": {}
-                    }, 
+                    },
                     "4457-Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": {
                         "44571-TVA collect\u00e9e": {
-                            "account_type": "Tax", 
+                            "account_type": "Tax",
                             "is_group": 1
-                        }, 
+                        },
                         "44578-Taxes assimil\u00e9es \u00e0 la TVA": {}
-                    }, 
+                    },
                     "4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (PASSIF)": {
-                        "44584-TVA r\u00e9cup\u00e9r\u00e9e  d'avance": {}, 
+                        "44584-TVA r\u00e9cup\u00e9r\u00e9e  d'avance": {},
                         "44587-Taxes sur le chiffre d'affaires sur factures \u00e0 \u00e9tablir": {}
                     }
-                }, 
-                "446-Obligations cautionn\u00e9es": {}, 
-                "447-Autres imp\u00f4ts, taxes et versements assimil\u00e9s": {}, 
+                },
+                "446-Obligations cautionn\u00e9es": {},
+                "447-Autres imp\u00f4ts, taxes et versements assimil\u00e9s": {},
                 "449-Quotas d'\u00e9mission \u00e0 acqu\u00e9rir": {}
-            }, 
+            },
             "45-Groupe et associ\u00e9s (PASSIF)": {
-                "451-Groupe (PASSIF)": {}, 
+                "451-Groupe (PASSIF)": {},
                 "455-Associ\u00e9s - Comptes courants (PASSIF)": {
-                    "4551-Principal (PASSIF)": {}, 
+                    "4551-Principal (PASSIF)": {},
                     "4558-Int\u00e9r\u00eats courus (PASSIF)": {}
-                }, 
+                },
                 "456-Associ\u00e9s - Op\u00e9rations sur le capital (PASSIF)": {
                     "4561-Associ\u00e9s - Comptes d'apport en soci\u00e9t\u00e9": {
-                        "45611-Apports en nature": {}, 
+                        "45611-Apports en nature": {},
                         "45615-Apports en num\u00e9raire": {}
-                    }, 
-                    "4563-Associ\u00e9s - Versements re\u00e7us sur augmentation de capital": {}, 
-                    "4564-Associ\u00e9s - Versements anticip\u00e9s": {}, 
-                    "4566-Actionnaires d\u00e9faillants": {}, 
+                    },
+                    "4563-Associ\u00e9s - Versements re\u00e7us sur augmentation de capital": {},
+                    "4564-Associ\u00e9s - Versements anticip\u00e9s": {},
+                    "4566-Actionnaires d\u00e9faillants": {},
                     "4567-Associ\u00e9s - Capital \u00e0 rembourser": {}
-                }, 
-                "457-Associ\u00e9s - Dividendes \u00e0 payer": {}, 
+                },
+                "457-Associ\u00e9s - Dividendes \u00e0 payer": {},
                 "458-Associ\u00e9s - Op\u00e9rations faites en commun et en GIE": {
-                    "4581-Op\u00e9rations courantes": {}, 
+                    "4581-Op\u00e9rations courantes": {},
                     "4588-Int\u00e9r\u00eats courus": {}
                 }
-            }, 
+            },
             "46-D\u00e9biteurs divers et cr\u00e9diteurs divers (PASSIF)": {
-                "464-Dettes sur acquisitions de valeurs mobili\u00e8res de placement": {}, 
-                "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (PASSIF)": {}, 
+                "464-Dettes sur acquisitions de valeurs mobili\u00e8res de placement": {},
+                "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (PASSIF)": {},
                 "468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (PASSIF)": {
                     "4686-Charges \u00e0 payer": {}
                 }
-            }, 
+            },
             "47-Comptes transitoires ou d'attente (PASSIF)": {
                 "471-Comptes d'attente (PASSIF)": {
                     "account_type": "Temporary"
-                }, 
+                },
                 "477-Diff\u00e9rences de conversion (PASSIF)": {
-                    "4771-Augmentation des cr\u00e9ances": {}, 
-                    "4772-Diminution des dettes": {}, 
+                    "4771-Augmentation des cr\u00e9ances": {},
+                    "4772-Diminution des dettes": {},
                     "4778-Diff\u00e9rences compens\u00e9es par couverture de change": {}
-                }, 
+                },
                 "478-Autres comptes transitoires (PASSIF)": {
                     "4787-Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (PASSIF)": {}
                 }
-            }, 
+            },
             "48-Comptes de r\u00e9gularisation (PASSIF)": {
-                "487-Produits constat\u00e9s d'avance": {}, 
+                "487-Produits constat\u00e9s d'avance": {},
                 "488-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (PASSIF)": {
                     "4887-Produits": {}
                 }
-            }, 
+            },
             "root_type": "Liability"
-        }, 
+        },
         "5-Comptes Financiers": {
             "50-Valeurs mobili\u00e8res de placement": {
-                "501-Parts dans des entreprises li\u00e9es": {}, 
+                "501-Parts dans des entreprises li\u00e9es": {},
                 "502-Actions propres": {
-                    "5021-Actions destin\u00e9es \u00e0 \u00eatre attribu\u00e9es aux employ\u00e9s et affect\u00e9es \u00e0 des plans d\u00e9termin\u00e9s": {}, 
+                    "5021-Actions destin\u00e9es \u00e0 \u00eatre attribu\u00e9es aux employ\u00e9s et affect\u00e9es \u00e0 des plans d\u00e9termin\u00e9s": {},
                     "5022-Actions disponibles pour \u00eatre attribu\u00e9es aux employ\u00e9s ou pour la r\u00e9gularisation des cours de bourse": {}
-                }, 
+                },
                 "503-Actions": {
-                    "5031-Titres cot\u00e9s": {}, 
+                    "5031-Titres cot\u00e9s": {},
                     "5035-Titres non cot\u00e9s": {}
-                }, 
-                "504-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {}, 
-                "505-Obligations et bons \u00e9mis par la soci\u00e9t\u00e9 et rachet\u00e9s par elle": {}, 
+                },
+                "504-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {},
+                "505-Obligations et bons \u00e9mis par la soci\u00e9t\u00e9 et rachet\u00e9s par elle": {},
                 "506-Obligations": {
-                    "5061-Titres cot\u00e9s": {}, 
+                    "5061-Titres cot\u00e9s": {},
                     "5065-Titres non cot\u00e9s": {}
-                }, 
-                "507-Bons du Tr\u00e9sor et bons de caisse \u00e0 court terme": {}, 
+                },
+                "507-Bons du Tr\u00e9sor et bons de caisse \u00e0 court terme": {},
                 "508-Autres valeurs mobili\u00e8res de placement et autres cr\u00e9ances assimil\u00e9es": {
-                    "5081-Autres valeurs mobili\u00e8res": {}, 
-                    "5082-Bons de souscription": {}, 
+                    "5081-Autres valeurs mobili\u00e8res": {},
+                    "5082-Bons de souscription": {},
                     "5088-Int\u00e9r\u00eats courus sur obligations, bons et valeurs assimil\u00e9es": {}
-                }, 
+                },
                 "509-Versements restant \u00e0 effectuer sur valeurs mobili\u00e8res de placement non lib\u00e9r\u00e9es": {}
-            }, 
+            },
             "51-Banques, \u00e9tablissements financiers et assimil\u00e9s": {
                 "511-Valeurs \u00e0 l'encaissement": {
-                    "5111-Coupons \u00e9chus \u00e0 l'encaissement": {}, 
-                    "5112-Ch\u00e8ques \u00e0 encaisser": {}, 
-                    "5113-Effets \u00e0 l'encaissement": {}, 
+                    "5111-Coupons \u00e9chus \u00e0 l'encaissement": {},
+                    "5112-Ch\u00e8ques \u00e0 encaisser": {},
+                    "5113-Effets \u00e0 l'encaissement": {},
                     "5114-Effets \u00e0 l'escompte": {}
-                }, 
+                },
                 "512-Banques": {
                     "5121-Comptes en monnaie nationale": {
                         "account_type": "Bank"
-                    }, 
+                    },
                     "5124-Comptes en devises": {
                         "account_type": "Bank"
-                    }, 
+                    },
                     "account_type": "Bank"
-                }, 
-                "514-Ch\u00e8ques postaux": {}, 
-                "515-\"Caisses\" du Tr\u00e9sor et des \u00e9tablissements publics": {}, 
-                "516-Soci\u00e9t\u00e9s de bourse": {}, 
-                "517-Autres organismes financiers": {}, 
+                },
+                "514-Ch\u00e8ques postaux": {},
+                "515-\"Caisses\" du Tr\u00e9sor et des \u00e9tablissements publics": {},
+                "516-Soci\u00e9t\u00e9s de bourse": {},
+                "517-Autres organismes financiers": {},
                 "518-Int\u00e9r\u00eats courus": {
-                    "5181-Int\u00e9r\u00eats courus \u00e0 payer": {}, 
+                    "5181-Int\u00e9r\u00eats courus \u00e0 payer": {},
                     "5188-Int\u00e9r\u00eats courus \u00e0 recevoir": {}
-                }, 
+                },
                 "519-Concours bancaires courants": {
-                    "5191-Cr\u00e9dit de mobilisation des cr\u00e9ances commerciales (CMCC)": {}, 
-                    "5193-Mobilisation de cr\u00e9ances n\u00e9es \u00e0 l'\u00e9tranger": {}, 
+                    "5191-Cr\u00e9dit de mobilisation des cr\u00e9ances commerciales (CMCC)": {},
+                    "5193-Mobilisation de cr\u00e9ances n\u00e9es \u00e0 l'\u00e9tranger": {},
                     "5198-Int\u00e9r\u00eats courus sur concours bancaires courants": {}
                 }
-            }, 
+            },
             "52-Instruments de tr\u00e9sorerie": {
                 "is_group": 1
-            }, 
+            },
             "53-Caisse": {
                 "531-Caisse si\u00e8ge social": {
                     "5311-Caisse en monnaie nationale": {
                         "account_type": "Cash"
-                    }, 
+                    },
                     "5314-Caisse en devises": {
                         "account_type": "Cash"
-                    }, 
+                    },
                     "account_type": "Cash"
-                }, 
+                },
                 "532-Caisse succursale (ou usine) A": {
                     "account_type": "Cash"
-                }, 
+                },
                 "533-Caisse succursale (ou usine) B": {
                     "account_type": "Cash"
-                }, 
+                },
                 "account_type": "Cash"
-            }, 
+            },
             "54-R\u00e9gies d'avance et accr\u00e9ditifs": {
                 "is_group": 1
-            }, 
+            },
             "58-Virements internes": {
                 "is_group": 1
-            }, 
+            },
             "59-D\u00e9pr\u00e9ciations des comptes financiers": {
                 "590-D\u00e9pr\u00e9ciations des valeurs mobili\u00e8res de placement": {
-                    "5903-Actions": {}, 
-                    "5904-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {}, 
-                    "5906-Obligations": {}, 
+                    "5903-Actions": {},
+                    "5904-Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {},
+                    "5906-Obligations": {},
                     "5908-Autres valeurs mobili\u00e8res de placement et cr\u00e9ances assimil\u00e9es": {}
                 }
-            }, 
+            },
             "root_type": "Asset"
-        }, 
+        },
         "6-Comptes de Charges": {
             "60-Achats (sauf 603)": {
                 "601-Achats stock\u00e9s - Mati\u00e8res premi\u00e8res (et fournitures)": {
                     "6011-Mati\u00e8res (ou groupe) A": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6012-Mati\u00e8res (ou groupe) B": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6017-Fournitures A, B, C...": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "account_type": "Cost of Goods Sold"
-                }, 
+                },
                 "602-Achats stock\u00e9s - Autres approvisionnements": {
                     "6021-Mati\u00e8res consommables": {
                         "60211-Mati\u00e8res (ou groupe) C": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "60212-Mati\u00e8res (ou groupe) D": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6022-Fournitures consommables": {
                         "60221-Combustibles": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "60222-Produits d'entretien": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "60223-Fournitures d'atelier et d'usine": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "60224-Fournitures de magasin": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "60225-Fournitures de bureau": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6026-Emballages": {
                         "60261-Emballages perdus": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "60265-Emballages r\u00e9cup\u00e9rables non identifiables": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "60267-Emballages \u00e0 usage mixte": {
                             "account_type": "Cost of Goods Sold"
-                        }, 
+                        },
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "account_type": "Cost of Goods Sold"
-                }, 
+                },
                 "603-Variations des stocks (approvisionnements et marchandises)": {
                     "6031-Variation des stocks de mati\u00e8res premi\u00e8res (et fournitures)": {
                         "account_type": "Stock Adjustment"
-                    }, 
+                    },
                     "6032-Variation des stocks des autres approvisionnements": {
                         "account_type": "Stock Adjustment"
-                    }, 
+                    },
                     "6037-Variation des stocks de marchandises": {
                         "account_type": "Stock Adjustment"
-                    }, 
+                    },
                     "account_type": "Stock Adjustment"
-                }, 
+                },
                 "604-Achats d'\u00e9tudes et prestations de service": {
                     "account_type": "Cost of Goods Sold"
-                }, 
+                },
                 "605-Achats de mat\u00e9riel, \u00e9quipements et travaux": {
                     "account_type": "Cost of Goods Sold"
-                }, 
+                },
                 "606-Achats non stock\u00e9s de mati\u00e8res et founitures": {
                     "6061-Fournitures non stockables (eau, \u00e9nergie...)": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6063-Fournitures d'entretien et de petit \u00e9quipement": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6064-Fournitures administratives": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6068-Autres mati\u00e8res et fournitures": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "account_type": "Cost of Goods Sold"
-                }, 
+                },
                 "607-Achats de marchandises": {
                     "6071-Marchandises (ou groupe) A": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "6072-Marchandises (ou groupe) B": {
                         "account_type": "Cost of Goods Sold"
-                    }, 
+                    },
                     "account_type": "Cost of Goods Sold"
-                }, 
+                },
                 "608-(Compte r\u00e9serv\u00e9, le cas \u00e9ch\u00e9ant, \u00e0 la recapitulation des Frais accessoires incorpor\u00e9s aux achats)": {
                     "account_type": "Expenses Included In Valuation"
-                }, 
+                },
                 "609-Rabais, remises et ristournes obtenus sur achats": {
-                    "6091-Rabais, remises et ristournes obtenus sur achats - de mati\u00e8res premi\u00e8res (et fournitures)": {}, 
-                    "6092-Rabais, remises et ristournes obtenus sur achats - d'autres approvisionnements stock\u00e9s": {}, 
-                    "6094-Rabais, remises et ristournes obtenus sur achats - d'\u00e9tudes et prestations de services": {}, 
-                    "6095-Rabais, remises et ristournes obtenus sur achats - de mat\u00e9riel, \u00e9quipements et travaux": {}, 
-                    "6096-Rabais, remises et ristournes obtenus sur achats - d'approvisionnements non stock\u00e9s": {}, 
-                    "6097-Rabais, remises et ristournes obtenus sur achats - de marchandises": {}, 
+                    "6091-Rabais, remises et ristournes obtenus sur achats - de mati\u00e8res premi\u00e8res (et fournitures)": {},
+                    "6092-Rabais, remises et ristournes obtenus sur achats - d'autres approvisionnements stock\u00e9s": {},
+                    "6094-Rabais, remises et ristournes obtenus sur achats - d'\u00e9tudes et prestations de services": {},
+                    "6095-Rabais, remises et ristournes obtenus sur achats - de mat\u00e9riel, \u00e9quipements et travaux": {},
+                    "6096-Rabais, remises et ristournes obtenus sur achats - d'approvisionnements non stock\u00e9s": {},
+                    "6097-Rabais, remises et ristournes obtenus sur achats - de marchandises": {},
                     "6098-Rabais, remises et ristournes non affect\u00e9s": {}
                 }
-            }, 
+            },
             "61-Services ext\u00e9rieurs": {
-                "611-Sous-traitance g\u00e9n\u00e9rale": {}, 
+                "611-Sous-traitance g\u00e9n\u00e9rale": {},
                 "612-Redevances de cr\u00e9dit-bail": {
-                    "6122-Cr\u00e9dit-bail mobilier": {}, 
+                    "6122-Cr\u00e9dit-bail mobilier": {},
                     "6125-Cr\u00e9dit-bail immobilier": {}
-                }, 
+                },
                 "613-Locations": {
-                    "6132-Locations immobili\u00e8res": {}, 
-                    "6135-Locations mobili\u00e8res": {}, 
+                    "6132-Locations immobili\u00e8res": {},
+                    "6135-Locations mobili\u00e8res": {},
                     "6136-Malis sur emballages": {}
-                }, 
-                "614-Charges locatives et de copropri\u00e9t\u00e9": {}, 
+                },
+                "614-Charges locatives et de copropri\u00e9t\u00e9": {},
                 "615-Entretiens et r\u00e9parations": {
-                    "6152-Entretiens et r\u00e9parations - sur biens immobiliers": {}, 
-                    "6155-Entretiens et r\u00e9parations - sur biens mobiliers": {}, 
+                    "6152-Entretiens et r\u00e9parations - sur biens immobiliers": {},
+                    "6155-Entretiens et r\u00e9parations - sur biens mobiliers": {},
                     "6156-Maintenance": {}
-                }, 
+                },
                 "616-Primes d'assurance": {
-                    "6161-Multirisques": {}, 
-                    "6162-Assurance obligatoire dommage construction": {}, 
+                    "6161-Multirisques": {},
+                    "6162-Assurance obligatoire dommage construction": {},
                     "6163-Assurance-transport": {
-                        "61636-Assurance-transport - sur achats": {}, 
-                        "61637-Assurance-transport - sur ventes": {}, 
+                        "61636-Assurance-transport - sur achats": {},
+                        "61637-Assurance-transport - sur ventes": {},
                         "61638-Assurance-transport - sur autres biens": {}
-                    }, 
-                    "6164-Risques d'exploitation": {}, 
+                    },
+                    "6164-Risques d'exploitation": {},
                     "6165-Insolvabilit\u00e9 clients": {}
-                }, 
-                "617-Etudes et recherches": {}, 
+                },
+                "617-Etudes et recherches": {},
                 "618-Divers": {
-                    "6181-Documentation g\u00e9n\u00e9rale": {}, 
-                    "6183-Documentation technique": {}, 
+                    "6181-Documentation g\u00e9n\u00e9rale": {},
+                    "6183-Documentation technique": {},
                     "6185-Frais de colloques, s\u00e9minaires, conf\u00e9rences": {}
-                }, 
+                },
                 "619-Rabais, remises et ristournes obtenus sur services ext\u00e9rieurs": {}
-            }, 
+            },
             "62-Autres services ext\u00e9rieurs": {
                 "621-Personnel ext\u00e9rieur \u00e0 l'entreprise": {
-                    "6211-Personnel int\u00e9rimaire": {}, 
+                    "6211-Personnel int\u00e9rimaire": {},
                     "6214-Personnel d\u00e9tach\u00e9 ou pr\u00eat\u00e9 \u00e0 l'entreprise": {}
-                }, 
+                },
                 "622-R\u00e9mun\u00e9rations d'interm\u00e9diaires et honoraires": {
-                    "6221-Commissions et courtages sur achats": {}, 
-                    "6222-Commissions et courtages sur ventes": {}, 
-                    "6224-R\u00e9mun\u00e9rations des transitaires": {}, 
-                    "6225-R\u00e9mun\u00e9rations d'affacturage": {}, 
-                    "6226-Honoraires": {}, 
-                    "6227-Frais d'actes et de contentieux": {}, 
+                    "6221-Commissions et courtages sur achats": {},
+                    "6222-Commissions et courtages sur ventes": {},
+                    "6224-R\u00e9mun\u00e9rations des transitaires": {},
+                    "6225-R\u00e9mun\u00e9rations d'affacturage": {},
+                    "6226-Honoraires": {},
+                    "6227-Frais d'actes et de contentieux": {},
                     "6228-Divers": {}
-                }, 
+                },
                 "623-Publicit\u00e9, publications, relations publiques": {
-                    "6231-Annonces et insertions": {}, 
-                    "6232-Echantillons": {}, 
-                    "6233-Foires et expositions": {}, 
-                    "6234-Cadeaux \u00e0 la client\u00e8le": {}, 
-                    "6235-Primes": {}, 
-                    "6236-Catalogues et imprim\u00e9s": {}, 
-                    "6237-Publications": {}, 
+                    "6231-Annonces et insertions": {},
+                    "6232-Echantillons": {},
+                    "6233-Foires et expositions": {},
+                    "6234-Cadeaux \u00e0 la client\u00e8le": {},
+                    "6235-Primes": {},
+                    "6236-Catalogues et imprim\u00e9s": {},
+                    "6237-Publications": {},
                     "6238-Divers (pourboires, dons courants...)": {}
-                }, 
+                },
                 "624-Transports de biens et transports collectifs du personnel": {
-                    "6241-Transports sur achats": {}, 
+                    "6241-Transports sur achats": {},
                     "6242-Transports sur ventes": {
                         "account_type": "Chargeable"
-                    }, 
-                    "6243-Transports entre \u00e9tablissements ou chantiers": {}, 
-                    "6244-Transports administratifs": {}, 
-                    "6247-Transports collectifs du personnel": {}, 
+                    },
+                    "6243-Transports entre \u00e9tablissements ou chantiers": {},
+                    "6244-Transports administratifs": {},
+                    "6247-Transports collectifs du personnel": {},
                     "6248-Divers": {}
-                }, 
+                },
                 "625-D\u00e9placements, missions et r\u00e9ceptions": {
-                    "6251-Voyages et d\u00e9placements": {}, 
-                    "6255-Frais de d\u00e9m\u00e9nagement": {}, 
-                    "6256-Missions": {}, 
+                    "6251-Voyages et d\u00e9placements": {},
+                    "6255-Frais de d\u00e9m\u00e9nagement": {},
+                    "6256-Missions": {},
                     "6257-R\u00e9ceptions": {}
-                }, 
-                "626-Frais postaux et de t\u00e9l\u00e9communications": {}, 
+                },
+                "626-Frais postaux et de t\u00e9l\u00e9communications": {},
                 "627-Services bancaires et assimil\u00e9s": {
-                    "6271-Frais sur titres (achat, vente, garde)": {}, 
-                    "6272-Commissions et frais sur \u00e9mission d'emprunts": {}, 
-                    "6275-Frais sur effets": {}, 
-                    "6276-Location de coffres": {}, 
+                    "6271-Frais sur titres (achat, vente, garde)": {},
+                    "6272-Commissions et frais sur \u00e9mission d'emprunts": {},
+                    "6275-Frais sur effets": {},
+                    "6276-Location de coffres": {},
                     "6278-Autres frais et commissions sur prestations de services": {}
-                }, 
+                },
                 "628-Divers": {
-                    "6281-Concours divers (cotisations...)": {}, 
+                    "6281-Concours divers (cotisations...)": {},
                     "6284-Frais de recrutement de personnel": {}
-                }, 
+                },
                 "629-Rabais, remises et ristournes obtenus sur autres services ext\u00e9rieurs": {}
-            }, 
+            },
             "63-Imp\u00f4ts, taxes et versements assimil\u00e9s": {
                 "631-Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (administrations des imp\u00f4ts)": {
-                    "6311-Taxes sur les salaires": {}, 
-                    "6312-Taxe d'apprentissage": {}, 
-                    "6313-Participation des employeurs \u00e0 la formation professionnelle continue": {}, 
-                    "6314-Cotisation pour d\u00e9faut d'investissement obligatoire dans la construction": {}, 
+                    "6311-Taxes sur les salaires": {},
+                    "6312-Taxe d'apprentissage": {},
+                    "6313-Participation des employeurs \u00e0 la formation professionnelle continue": {},
+                    "6314-Cotisation pour d\u00e9faut d'investissement obligatoire dans la construction": {},
                     "6318-Autres": {}
-                }, 
+                },
                 "633-Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (autres organismes)": {
-                    "6331-Versement de transport": {}, 
-                    "6332-Allocations logement": {}, 
-                    "6333-Participation des employeurs \u00e0 la formation professionnelle continue": {}, 
-                    "6334-Participation des employeurs \u00e0 l'effort de construction": {}, 
-                    "6335-Versements lib\u00e9ratoires ouvrant droit \u00e0 l'\u00e9xon\u00e9ration de la taxe d'apprentissage": {}, 
+                    "6331-Versement de transport": {},
+                    "6332-Allocations logement": {},
+                    "6333-Participation des employeurs \u00e0 la formation professionnelle continue": {},
+                    "6334-Participation des employeurs \u00e0 l'effort de construction": {},
+                    "6335-Versements lib\u00e9ratoires ouvrant droit \u00e0 l'\u00e9xon\u00e9ration de la taxe d'apprentissage": {},
                     "6338-Autres": {}
-                }, 
+                },
                 "635-Autres imp\u00f4ts, taxes et versements assimil\u00e9s (administrations des imp\u00f4ts)": {
                     "6351-Imp\u00f4ts directs (sauf imp\u00f4ts sur les b\u00e9n\u00e9fices)": {
-                        "63511-Contribution \u00e9conomique territoriale": {}, 
-                        "63512-Taxes fonci\u00e8res": {}, 
-                        "63513-Autres imp\u00f4ts locaux": {}, 
+                        "63511-Contribution \u00e9conomique territoriale": {},
+                        "63512-Taxes fonci\u00e8res": {},
+                        "63513-Autres imp\u00f4ts locaux": {},
                         "63514-Taxe sur les v\u00e9hicules des soci\u00e9t\u00e9s": {}
-                    }, 
-                    "6352-Taxes sur le chiffre d'affaires non r\u00e9cup\u00e9rables": {}, 
-                    "6353-Imp\u00f4ts indirects": {}, 
+                    },
+                    "6352-Taxes sur le chiffre d'affaires non r\u00e9cup\u00e9rables": {},
+                    "6353-Imp\u00f4ts indirects": {},
                     "6354-Droits d'enregistrement et de timbre": {
                         "63541-Droits de mutation": {}
-                    }, 
+                    },
                     "6358-Autres droits": {}
-                }, 
+                },
                 "637-Autres imp\u00f4ts, taxes et versements assimil\u00e9s (autres organismes)": {
-                    "6371-Contribution sociale de solidarit\u00e9 \u00e0 la charge des soci\u00e9t\u00e9s": {}, 
-                    "6372-Taxes per\u00e7ues par les organismes publics internationaux": {}, 
-                    "6374-Imp\u00f4ts et taxes exigibles \u00e0 l'\u00e9tranger": {}, 
+                    "6371-Contribution sociale de solidarit\u00e9 \u00e0 la charge des soci\u00e9t\u00e9s": {},
+                    "6372-Taxes per\u00e7ues par les organismes publics internationaux": {},
+                    "6374-Imp\u00f4ts et taxes exigibles \u00e0 l'\u00e9tranger": {},
                     "6378-Taxes diverses": {}
                 }
-            }, 
+            },
             "64-Charges de personnel": {
                 "641-R\u00e9mun\u00e9rations du personnel": {
-                    "6411-Salaires, appointements": {}, 
-                    "6412-Cong\u00e9s pay\u00e9s": {}, 
-                    "6413-Primes et gratifications": {}, 
-                    "6414-Indemnit\u00e9s et avantages divers": {}, 
+                    "6411-Salaires, appointements": {},
+                    "6412-Cong\u00e9s pay\u00e9s": {},
+                    "6413-Primes et gratifications": {},
+                    "6414-Indemnit\u00e9s et avantages divers": {},
                     "6415-Suppl\u00e9ment familial": {}
-                }, 
-                "644-R\u00e9mun\u00e9ration du travail de l'exploitant": {}, 
+                },
+                "644-R\u00e9mun\u00e9ration du travail de l'exploitant": {},
                 "645-Charges de s\u00e9curit\u00e9 sociale et de pr\u00e9voyance": {
-                    "6451-Cotisations \u00e0 l'URSSAF": {}, 
-                    "6452-Cotisations aux mutuelles": {}, 
-                    "6453-Cotisations aux caisses de retraites": {}, 
+                    "6451-Cotisations \u00e0 l'URSSAF": {},
+                    "6452-Cotisations aux mutuelles": {},
+                    "6453-Cotisations aux caisses de retraites": {},
                     "6454-Cotisations aux ASSEDIC": {}
-                }, 
-                "646-Cotisations sociales personnelles de l'exploitant": {}, 
+                },
+                "646-Cotisations sociales personnelles de l'exploitant": {},
                 "647-Autres charges sociales": {
                     "is_group": 1
-                }, 
+                },
                 "648-Autres charges de personnel": {}
-            }, 
+            },
             "65-Autres charges de gestion courante": {
                 "651-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
-                    "6511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {}, 
-                    "6516-Droits d'auteur et de reproduction": {}, 
+                    "6511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {},
+                    "6516-Droits d'auteur et de reproduction": {},
                     "6518-Autres droits et valeurs similaires": {}
-                }, 
-                "653-Jetons de pr\u00e9sence": {}, 
+                },
+                "653-Jetons de pr\u00e9sence": {},
                 "654-Pertes sur cr\u00e9ances irr\u00e9couvrables": {
-                    "6541-Cr\u00e9ances de l'exercice": {}, 
+                    "6541-Cr\u00e9ances de l'exercice": {},
                     "6544-Cr\u00e9ances des exercices ant\u00e9rieurs": {}
-                }, 
+                },
                 "655-Quotes-parts de r\u00e9sultat sur op\u00e9rations faites en commun": {
-                    "6551-Quote-part de b\u00e9n\u00e9fice transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {}, 
+                    "6551-Quote-part de b\u00e9n\u00e9fice transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {},
                     "6555-Quote-part de perte support\u00e9e (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {}
-                }, 
-                "656-Pertes de change sur cr\u00e9ances et dettes commerciales": {}, 
+                },
+                "656-Pertes de change sur cr\u00e9ances et dettes commerciales": {},
                 "658-Charges diverses de gestion courante": {}
-            }, 
+            },
             "66-Charges financi\u00e8res": {
                 "661-Charges d'int\u00e9r\u00eats": {
                     "6611-Int\u00e9r\u00eats des emprunts et dettes": {
-                        "66116-Int\u00e9r\u00eats des emprunts et dettes - des emprunts et dettes assimil\u00e9es": {}, 
+                        "66116-Int\u00e9r\u00eats des emprunts et dettes - des emprunts et dettes assimil\u00e9es": {},
                         "66117-Int\u00e9r\u00eats des emprunts et dettes - des dettes rattach\u00e9es \u00e0 des participations": {}
-                    }, 
-                    "6612-Charges de la fiducie, r\u00e9sultat de la p\u00e9riode": {}, 
-                    "6615-Int\u00e9r\u00eats des comptes courants et des d\u00e9p\u00f4ts cr\u00e9diteurs": {}, 
-                    "6616-Int\u00e9r\u00eats bancaires et sur op\u00e9rations de financement (escompte...)": {}, 
-                    "6617-Int\u00e9r\u00eats des obligations cautionn\u00e9es": {}, 
+                    },
+                    "6612-Charges de la fiducie, r\u00e9sultat de la p\u00e9riode": {},
+                    "6615-Int\u00e9r\u00eats des comptes courants et des d\u00e9p\u00f4ts cr\u00e9diteurs": {},
+                    "6616-Int\u00e9r\u00eats bancaires et sur op\u00e9rations de financement (escompte...)": {},
+                    "6617-Int\u00e9r\u00eats des obligations cautionn\u00e9es": {},
                     "6618-Int\u00e9r\u00eats des autres dettes": {
-                        "66181-Int\u00e9r\u00eats des autres dettes - des dettes commerciales": {}, 
+                        "66181-Int\u00e9r\u00eats des autres dettes - des dettes commerciales": {},
                         "66188-Int\u00e9r\u00eats des autres dettes - des dettes diverses": {}
                     }
-                }, 
-                "664-Pertes sur cr\u00e9ances li\u00e9es \u00e0 des participations": {}, 
-                "665-Escomptes accord\u00e9s": {}, 
+                },
+                "664-Pertes sur cr\u00e9ances li\u00e9es \u00e0 des participations": {},
+                "665-Escomptes accord\u00e9s": {},
                 "666-Pertes de change financi\u00e8res": {
                     "account_type": "Round Off"
-                }, 
-                "667-Charges nettes sur cessions de valeurs mobili\u00e8res de placement": {}, 
+                },
+                "667-Charges nettes sur cessions de valeurs mobili\u00e8res de placement": {},
                 "668-Autres charges financi\u00e8res": {}
-            }, 
+            },
             "67-Charges exceptionnelles": {
                 "671-Charges exceptionnelles sur op\u00e9rations de gestion": {
-                    "6711-P\u00e9nalit\u00e9s sur march\u00e9s (et d\u00e9dits pay\u00e9s sur achats et ventes)": {}, 
-                    "6712-P\u00e9nalit\u00e9s, amendes fiscales et p\u00e9nales": {}, 
-                    "6713-Dons, lib\u00e9ralit\u00e9s": {}, 
-                    "6714-Cr\u00e9ances devenues irr\u00e9couvrables dans l'exercice": {}, 
-                    "6715-Subventions accord\u00e9es": {}, 
-                    "6717-Rappel d'imp\u00f4ts (autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices)": {}, 
+                    "6711-P\u00e9nalit\u00e9s sur march\u00e9s (et d\u00e9dits pay\u00e9s sur achats et ventes)": {},
+                    "6712-P\u00e9nalit\u00e9s, amendes fiscales et p\u00e9nales": {},
+                    "6713-Dons, lib\u00e9ralit\u00e9s": {},
+                    "6714-Cr\u00e9ances devenues irr\u00e9couvrables dans l'exercice": {},
+                    "6715-Subventions accord\u00e9es": {},
+                    "6717-Rappel d'imp\u00f4ts (autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices)": {},
                     "6718-Autres charges exceptionnelles sur op\u00e9rations de gestion": {}
-                }, 
-                "672-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les charges sur exercices ant\u00e9rieurs)": {}, 
+                },
+                "672-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les charges sur exercices ant\u00e9rieurs)": {},
                 "674-Op\u00e9rations de constitution ou liquidation des fiducies": {
-                    "6741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {}, 
+                    "6741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {},
                     "6742-Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {}
-                }, 
+                },
                 "675-Valeurs comptables des \u00e9l\u00e9ments d'actif c\u00e9d\u00e9s": {
-                    "6751-Immobilisations incorporelles": {}, 
-                    "6752-Immobilisations corporelles": {}, 
-                    "6756-Immobilisations financi\u00e8res": {}, 
+                    "6751-Immobilisations incorporelles": {},
+                    "6752-Immobilisations corporelles": {},
+                    "6756-Immobilisations financi\u00e8res": {},
                     "6758-Autres \u00e9l\u00e9ments d'actif": {}
-                }, 
+                },
                 "678-Autres charges exceptionnelles": {
-                    "6781-Mali provenant de clauses d'indexation": {}, 
-                    "6782-Lots": {}, 
-                    "6783-Malis provenant du rachat par l'entreprise d'actions et obligations \u00e9mises par elles-m\u00eame": {}, 
+                    "6781-Mali provenant de clauses d'indexation": {},
+                    "6782-Lots": {},
+                    "6783-Malis provenant du rachat par l'entreprise d'actions et obligations \u00e9mises par elles-m\u00eame": {},
                     "6788-Charges exceptionnelles diverses": {}
                 }
-            }, 
+            },
             "68-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions": {
                 "681-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges d'exploitation": {
                     "6811-Dotations aux amortissements sur immobilisations incorporelles et corporelles": {
                         "68111-Immobilisations incorporelles": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "68112-Immobilisations corporelles": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6812-Dotations aux amortissements des charges d'exploitation \u00e0 r\u00e9partir": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6815-Dotations aux provisions d'exploitation": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6816-Dotations aux d\u00e9pr\u00e9ciations des immobilisations incorporelles et corporelles": {
                         "68161-Immobilisations incorporelles": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "68162-Immobilisations corporelles": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6817-Dotations pour d\u00e9pr\u00e9ciations des actifs circulants": {
                         "68173-Stocks et en-cours": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "68174-Cr\u00e9ances": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "account_type": "Depreciation"
-                }, 
+                },
                 "686-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges financi\u00e8res": {
                     "6861-Dotations aux amortissements des primes de remboursement des obligations": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6865-Dotations aux provisions financi\u00e8res": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6866-Dotations aux d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
                         "68662-Immobilisations financi\u00e8res": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "68665-Valeurs mobili\u00e8res de placement": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6868-Autres dotations": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "account_type": "Depreciation"
-                }, 
+                },
                 "687-Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges exceptionnelles": {
                     "6871-Dotations aux amortissements exceptionnels des immobilisations": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6872-Dotations aux provisions r\u00e9glement\u00e9es (immobilisations)": {
                         "68725-Amortissements d\u00e9rogatoires": {
                             "account_type": "Depreciation"
-                        }, 
+                        },
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6873-Dotations aux provisions r\u00e9glement\u00e9es (stocks)": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6874-Dotations aux autres provisions r\u00e9glement\u00e9es": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6875-Dotations aux provisions exceptionnelles": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "6876-Dotations aux d\u00e9pr\u00e9ciations exceptionnelles": {
                         "account_type": "Depreciation"
-                    }, 
+                    },
                     "account_type": "Depreciation"
-                }, 
+                },
                 "account_type": "Depreciation"
-            }, 
+            },
             "69-Participation des salari\u00e9s, imp\u00f4ts sur les b\u00e9n\u00e9fices et assimil\u00e9s": {
-                "691-Participation des salari\u00e9s aux r\u00e9sultats": {}, 
+                "691-Participation des salari\u00e9s aux r\u00e9sultats": {},
                 "695-Imp\u00f4ts sur les b\u00e9n\u00e9fices": {
-                    "6951-Imp\u00f4ts dus en France": {}, 
-                    "6952-Contribution additionnelle \u00e0 l'imp\u00f4t sur les b\u00e9n\u00e9fices": {}, 
+                    "6951-Imp\u00f4ts dus en France": {},
+                    "6952-Contribution additionnelle \u00e0 l'imp\u00f4t sur les b\u00e9n\u00e9fices": {},
                     "6954-Imp\u00f4ts dus \u00e0 l'\u00e9tranger": {}
-                }, 
-                "696-Suppl\u00e9ments d'imp\u00f4ts sur les soci\u00e9t\u00e9s, li\u00e9s aux distributions": {}, 
+                },
+                "696-Suppl\u00e9ments d'imp\u00f4ts sur les soci\u00e9t\u00e9s, li\u00e9s aux distributions": {},
                 "698-Int\u00e9gration fiscale": {
-                    "6981-Int\u00e9gration fiscale - Charges": {}, 
+                    "6981-Int\u00e9gration fiscale - Charges": {},
                     "6989-Int\u00e9gration fiscale - Produits": {}
-                }, 
+                },
                 "699-Produits - Report en arri\u00e8re des d\u00e9ficits": {}
-            }, 
+            },
             "root_type": "Expense"
-        }, 
+        },
         "7-Comptes de Produits": {
             "70-Ventes de produits fabriqu\u00e9s, prestations de services, marchandises": {
                 "701-Ventes de produits finis": {
-                    "7011-Produits finis (ou groupe) A": {}, 
+                    "7011-Produits finis (ou groupe) A": {},
                     "7012-Produits (ou groupe) B": {}
-                }, 
-                "702-Ventes de produits interm\u00e9diaires": {}, 
-                "703-Ventes de produits r\u00e9siduels": {}, 
+                },
+                "702-Ventes de produits interm\u00e9diaires": {},
+                "703-Ventes de produits r\u00e9siduels": {},
                 "704-Travaux": {
-                    "7041-Travaux de cat\u00e9gorie (ou activit\u00e9) A": {}, 
+                    "7041-Travaux de cat\u00e9gorie (ou activit\u00e9) A": {},
                     "7042-Travaux de cat\u00e9gorie (ou activit\u00e9) B": {}
-                }, 
-                "705-Etudes": {}, 
-                "706-Prestations de services": {}, 
+                },
+                "705-Etudes": {},
+                "706-Prestations de services": {},
                 "707-Ventes de marchandises": {
-                    "7071-Marchandises (ou groupe) A": {}, 
+                    "7071-Marchandises (ou groupe) A": {},
                     "7072-Marchandises (ou groupe) B": {}
-                }, 
+                },
                 "708-Produits des activit\u00e9s annexes": {
-                    "7081-Produits des services exploit\u00e9s dans l'int\u00e9r\u00eat du personnel": {}, 
-                    "7082-Commissions et courtages": {}, 
-                    "7083-Locations diverses": {}, 
-                    "7084-Mise \u00e0 disposition de personnel factur\u00e9e": {}, 
-                    "7085-Ports et frais accessoires factur\u00e9s": {}, 
-                    "7086-Bonis sur reprises d'emballages consign\u00e9s": {}, 
-                    "7087-Bonifications obtenues des clients et primes sur ventes": {}, 
+                    "7081-Produits des services exploit\u00e9s dans l'int\u00e9r\u00eat du personnel": {},
+                    "7082-Commissions et courtages": {},
+                    "7083-Locations diverses": {},
+                    "7084-Mise \u00e0 disposition de personnel factur\u00e9e": {},
+                    "7085-Ports et frais accessoires factur\u00e9s": {},
+                    "7086-Bonis sur reprises d'emballages consign\u00e9s": {},
+                    "7087-Bonifications obtenues des clients et primes sur ventes": {},
                     "7088-Autres produits d'activit\u00e9s annexes (cessions d'approvisionnements...)": {}
-                }, 
+                },
                 "709-Rabais, remises et ristournes accord\u00e9s par l'entreprise": {
-                    "7091-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits finis": {}, 
-                    "7092-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits interm\u00e9diaires": {}, 
-                    "7094-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur travaux": {}, 
-                    "7095-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur \u00e9tudes": {}, 
-                    "7096-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur prestations de services": {}, 
-                    "7097-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de marchandises": {}, 
+                    "7091-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits finis": {},
+                    "7092-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits interm\u00e9diaires": {},
+                    "7094-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur travaux": {},
+                    "7095-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur \u00e9tudes": {},
+                    "7096-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur prestations de services": {},
+                    "7097-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de marchandises": {},
                     "7098-Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur produits des activit\u00e9s annexes": {}
                 }
-            }, 
+            },
             "71-Production stock\u00e9e (ou d\u00e9stockage)": {
                 "713-Variation des stocks (en-cours de production, produits)": {
                     "7133-Variation des en-cours de production de biens": {
-                        "71331-Produits en cours": {}, 
+                        "71331-Produits en cours": {},
                         "71335-Travaux en cours": {}
-                    }, 
+                    },
                     "7134-Variation des en-cours de production de services": {
-                        "71341-Etudes en cours": {}, 
+                        "71341-Etudes en cours": {},
                         "71345-Prestations de services en cours": {}
-                    }, 
+                    },
                     "7135-Variation des stocks de produits": {
-                        "71351-Produits interm\u00e9diaires": {}, 
-                        "71355-Produits finis": {}, 
+                        "71351-Produits interm\u00e9diaires": {},
+                        "71355-Produits finis": {},
                         "71358-Produits r\u00e9siduels": {}
                     }
                 }
-            }, 
+            },
             "72-Production immobilis\u00e9e": {
-                "721-Immobilisations incorporelles": {}, 
+                "721-Immobilisations incorporelles": {},
                 "722-Immobilisations corporelles": {}
-            }, 
+            },
             "74-Subventions d'exploitation": {
                 "is_group": 1
-            }, 
+            },
             "75-Autres produits de gestion courante": {
                 "751-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
-                    "7511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {}, 
-                    "7516-Droits d'auteur et de reproduction": {}, 
+                    "7511-Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {},
+                    "7516-Droits d'auteur et de reproduction": {},
                     "7518-Autres droits et valeurs similaires": {}
-                }, 
-                "752-Revenus des immeubles non affect\u00e9s aux activit\u00e9s professionnelles": {}, 
-                "753-Jetons de pr\u00e9sence et r\u00e9mun\u00e9rations d'administrateurs, g\u00e9rants...": {}, 
-                "754-Ristournes per\u00e7ues des coop\u00e9ratives (provenant des exc\u00e9dents)": {}, 
+                },
+                "752-Revenus des immeubles non affect\u00e9s aux activit\u00e9s professionnelles": {},
+                "753-Jetons de pr\u00e9sence et r\u00e9mun\u00e9rations d'administrateurs, g\u00e9rants...": {},
+                "754-Ristournes per\u00e7ues des coop\u00e9ratives (provenant des exc\u00e9dents)": {},
                 "755-Quotes-parts de r\u00e9sultats sur op\u00e9rations faites en commun": {
-                    "7551-Quote-part de perte transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {}, 
+                    "7551-Quote-part de perte transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {},
                     "7555-Quote-part de b\u00e9n\u00e9fice attribu\u00e9 (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {}
-                }, 
-                "756-Gains de change sur cr\u00e9ances et dettes commerciales": {}, 
+                },
+                "756-Gains de change sur cr\u00e9ances et dettes commerciales": {},
                 "758-Produits divers de gestion courante": {}
-            }, 
+            },
             "76-Produits financiers": {
                 "761-Produits de participations": {
-                    "7611-Revenus des titres de participation": {}, 
-                    "7612-Produits de la fiducie, r\u00e9sultat de la p\u00e9riode": {}, 
-                    "7616-Revenus sur autres formes de participation": {}, 
+                    "7611-Revenus des titres de participation": {},
+                    "7612-Produits de la fiducie, r\u00e9sultat de la p\u00e9riode": {},
+                    "7616-Revenus sur autres formes de participation": {},
                     "7617-Revenus des cr\u00e9ances rattach\u00e9es \u00e0 des participations": {}
-                }, 
+                },
                 "762-Produits des autres immobilisations financi\u00e8res": {
-                    "7621-Revenus des titres immobilis\u00e9s": {}, 
-                    "7626-Revenus des pr\u00eats": {}, 
+                    "7621-Revenus des titres immobilis\u00e9s": {},
+                    "7626-Revenus des pr\u00eats": {},
                     "7627-Revenus des cr\u00e9ances immobilis\u00e9es": {}
-                }, 
+                },
                 "763-Revenus des autres cr\u00e9ances": {
-                    "7631-Revenus des cr\u00e9ances commerciales": {}, 
+                    "7631-Revenus des cr\u00e9ances commerciales": {},
                     "7638-Revenus des cr\u00e9ances diverses": {}
-                }, 
-                "764-Revenus des valeurs mobili\u00e8res de placement": {}, 
-                "765-Escomptes obtenus": {}, 
+                },
+                "764-Revenus des valeurs mobili\u00e8res de placement": {},
+                "765-Escomptes obtenus": {},
                 "766-Gains de change financi\u00e8res": {
                     "account_type": "Round Off"
-                }, 
-                "767-Produits nets sur cessions de valeurs mobili\u00e8res de placement": {}, 
+                },
+                "767-Produits nets sur cessions de valeurs mobili\u00e8res de placement": {},
                 "768-Autres produits financiers": {}
-            }, 
+            },
             "77-Produits exceptionnels": {
                 "771-Produits exceptionnels sur op\u00e9rations de gestion": {
-                    "7711-D\u00e9dits et p\u00e9nalit\u00e9s per\u00e7us sur achats et sur ventes": {}, 
-                    "7713-Lib\u00e9ralit\u00e9s re\u00e7ues": {}, 
-                    "7714-Rentr\u00e9es sur cr\u00e9ances amorties": {}, 
-                    "7715-Subventions d'\u00e9quilibre": {}, 
-                    "7717-D\u00e9gr\u00e8vements d'imp\u00f4ts autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices": {}, 
+                    "7711-D\u00e9dits et p\u00e9nalit\u00e9s per\u00e7us sur achats et sur ventes": {},
+                    "7713-Lib\u00e9ralit\u00e9s re\u00e7ues": {},
+                    "7714-Rentr\u00e9es sur cr\u00e9ances amorties": {},
+                    "7715-Subventions d'\u00e9quilibre": {},
+                    "7717-D\u00e9gr\u00e8vements d'imp\u00f4ts autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices": {},
                     "7718-Autres produits exceptionnels sur op\u00e9rations de gestion": {}
-                }, 
-                "772-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les Produits sur exercices ant\u00e9rieurs)": {}, 
+                },
+                "772-(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les Produits sur exercices ant\u00e9rieurs)": {},
                 "774-Op\u00e9rations de constitution ou liquidation des fiducies": {
-                    "7741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {}, 
+                    "7741-Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {},
                     "7742-Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {}
-                }, 
+                },
                 "775-Produits des cessions d'\u00e9l\u00e9ments d'actif": {
-                    "7751-Immobilisations incorporelles": {}, 
-                    "7752-Immobilisations corporelles": {}, 
-                    "7756-Immobilisations financi\u00e8res": {}, 
+                    "7751-Immobilisations incorporelles": {},
+                    "7752-Immobilisations corporelles": {},
+                    "7756-Immobilisations financi\u00e8res": {},
                     "7758-Autres \u00e9l\u00e9ments d'actif": {}
-                }, 
-                "777-Quote-part des subventions d'investissement vir\u00e9e au r\u00e9sultat de l'exercice": {}, 
+                },
+                "777-Quote-part des subventions d'investissement vir\u00e9e au r\u00e9sultat de l'exercice": {},
                 "778-Autres produits exceptionnels": {
-                    "7781-Bonis provenant de clauses d'indexation": {}, 
-                    "7782-Lots": {}, 
-                    "7783-Bonis provenant du rachat par l'entreprise d'actions et d'obligations \u00e9mises par elle-m\u00eame": {}, 
+                    "7781-Bonis provenant de clauses d'indexation": {},
+                    "7782-Lots": {},
+                    "7783-Bonis provenant du rachat par l'entreprise d'actions et d'obligations \u00e9mises par elle-m\u00eame": {},
                     "7788-Produits exceptionnels divers": {}
                 }
-            }, 
+            },
             "78-Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions": {
                 "781-Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits d'exploitation)": {
                     "7811-Reprises sur amortissements des immobilisations incorporelles et corporelles": {
-                        "78111-Immobilisations incorporelles": {}, 
+                        "78111-Immobilisations incorporelles": {},
                         "78112-Immobilisations corporelles": {}
-                    }, 
-                    "7815-Reprises sur provisions d'exploitation": {}, 
+                    },
+                    "7815-Reprises sur provisions d'exploitation": {},
                     "7816-Reprises sur d\u00e9pr\u00e9ciations des immobilisations corporelles et incorporelles": {
-                        "78161-Immobilisations incorporelles": {}, 
+                        "78161-Immobilisations incorporelles": {},
                         "78162-Immobilisations corporelles": {}
-                    }, 
+                    },
                     "7817-Reprises sur d\u00e9pr\u00e9ciations des actifs circulants": {
-                        "78173-Stocks et en-cours": {}, 
+                        "78173-Stocks et en-cours": {},
                         "78174-Cr\u00e9ances": {}
                     }
-                }, 
+                },
                 "786-Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits financiers)": {
-                    "7865-Reprises sur provisions financi\u00e8res": {}, 
+                    "7865-Reprises sur provisions financi\u00e8res": {},
                     "7866-Reprises sur d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
-                        "78662-Immobilisations financi\u00e8res": {}, 
+                        "78662-Immobilisations financi\u00e8res": {},
                         "78665-Valeurs mobili\u00e8res de placement": {}
                     }
-                }, 
+                },
                 "787-Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits exceptionnels)": {
                     "7872-Reprises sur provisions r\u00e9glement\u00e9es (immobilisations)": {
-                        "78725-Amortissements d\u00e9rogatoires": {}, 
-                        "78726-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {}, 
+                        "78725-Amortissements d\u00e9rogatoires": {},
+                        "78726-Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {},
                         "78727-Plus-values r\u00e9investies": {}
-                    }, 
-                    "7873-Reprises sur provisions r\u00e9glement\u00e9es (stocks)": {}, 
-                    "7874-Reprises sur autres provisions r\u00e9glement\u00e9es": {}, 
-                    "7875-Reprises sur provisions exceptionnelles": {}, 
+                    },
+                    "7873-Reprises sur provisions r\u00e9glement\u00e9es (stocks)": {},
+                    "7874-Reprises sur autres provisions r\u00e9glement\u00e9es": {},
+                    "7875-Reprises sur provisions exceptionnelles": {},
                     "7876-Reprises sur d\u00e9pr\u00e9ciations exceptionnelles": {}
                 }
-            }, 
+            },
             "79-Transferts de charges": {
-                "791-Transferts de charges d'exploitation": {}, 
-                "796-Transferts de charges financi\u00e8res": {}, 
+                "791-Transferts de charges d'exploitation": {},
+                "796-Transferts de charges financi\u00e8res": {},
                 "797-Transferts de charges exceptionnelles": {}
-            }, 
+            },
             "root_type": "Income"
         }
     }
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json
new file mode 100644
index 0000000..b667379
--- /dev/null
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json
@@ -0,0 +1,3144 @@
+{
+    "country_code": "fr",
+    "name": "France - Plan Comptable General avec code",
+    "tree": {
+        "Comptes de Capitaux": {
+            "root_type": "Equity",
+            "Capital et R\u00e9serves": {
+                "Capital": {
+                    "Capital souscrit - non appel\u00e9": {
+                        "account_number": "1011"
+                    },
+                    "Capital souscrit - appel\u00e9, non vers\u00e9": {
+                        "account_number": "1012"
+                    },
+                    "Capital souscrit - appel\u00e9, vers\u00e9": {
+                        "Capital non amorti": {
+                            "account_number": "10131"
+                        },
+                        "Capital amorti": {
+                            "account_number": "10132"
+                        },
+                        "account_number": "1013"
+                    },
+                    "Capital souscrit soumis \u00e0 des r\u00e9glementations particuli\u00e8res": {
+                        "account_number": "1018"
+                    },
+                    "account_number": "101"
+                },
+                "Fonds fiduciaires": {
+                    "account_number": "102"
+                },
+                "Primes li\u00e9es au capital social": {
+                    "Primes d'\u00e9mission": {
+                        "account_number": "1041"
+                    },
+                    "Primes de fusion": {
+                        "account_number": "1042"
+                    },
+                    "Primes d'apport": {
+                        "account_number": "1043"
+                    },
+                    "Primes de conversion d'obligations en actions": {
+                        "account_number": "1044"
+                    },
+                    "Bons de souscription d'actions": {
+                        "account_number": "1045"
+                    },
+                    "account_number": "104"
+                },
+                "Ecarts de r\u00e9\u00e9valuation": {
+                    "R\u00e9serve sp\u00e9ciale de r\u00e9\u00e9valuation": {
+                        "account_number": "1051"
+                    },
+                    "Ecart de r\u00e9\u00e9valuation libre": {
+                        "account_number": "1052"
+                    },
+                    "R\u00e9serve de r\u00e9\u00e9valuation": {
+                        "account_number": "1053"
+                    },
+                    "Ecarts de r\u00e9\u00e9valuation (autres op\u00e9rations l\u00e9gales)": {
+                        "account_number": "1055"
+                    },
+                    "Autres \u00e9carts de r\u00e9\u00e9valuation en France": {
+                        "account_number": "1057"
+                    },
+                    "Autres \u00e9carts de r\u00e9\u00e9valuation \u00e0 l'\u00e9tranger": {
+                        "account_number": "1058"
+                    },
+                    "account_number": "105"
+                },
+                "R\u00e9serves": {
+                    "R\u00e9serve l\u00e9gale": {
+                        "R\u00e9serve l\u00e9gale proprement dite": {
+                            "account_number": "10611"
+                        },
+                        "Plus-values nettes \u00e0 long terme": {
+                            "account_number": "10612"
+                        },
+                        "account_number": "1061"
+                    },
+                    "R\u00e9serves indisponibles": {
+                        "account_number": "1062"
+                    },
+                    "R\u00e9serves statutaires ou contractuelles": {
+                        "account_number": "1063"
+                    },
+                    "R\u00e9serves r\u00e9glement\u00e9es": {
+                        "Plus-values nettes \u00e0 long terme": {
+                            "account_number": "10641"
+                        },
+                        "R\u00e9serves cons\u00e9cutives \u00e0 l'octroi de subventions d'investissement": {
+                            "account_number": "10643"
+                        },
+                        "Autres r\u00e9serves r\u00e9glement\u00e9es": {
+                            "account_number": "10648"
+                        },
+                        "account_number": "1064"
+                    },
+                    "Autres r\u00e9serves": {
+                        "R\u00e9serve de propre assureur": {
+                            "account_number": "10681"
+                        },
+                        "R\u00e9serves diverses": {
+                            "account_number": "10688"
+                        },
+                        "account_number": "1068"
+                    },
+                    "account_number": "106"
+                },
+                "Ecarts d'\u00e9quivalence": {
+                    "account_number": "107"
+                },
+                "Compte de l'exploitant": {
+                    "account_number": "108"
+                },
+                "Actionnaires: Capital souscrit - non appel\u00e9": {
+                    "account_number": "109"
+                },
+                "account_number": "10"
+            },
+            "Report \u00e0 Nouveau": {
+                "Report \u00e0 nouveau (solde cr\u00e9diteur)": {
+                    "account_number": "110"
+                },
+                "Report \u00e0 nouveau (solde d\u00e9biteur)": {
+                    "account_number": "119"
+                },
+                "account_number": "11"
+            },
+            "R\u00e9sultat de l'Exercice": {
+                "R\u00e9sultat de l'exercice (b\u00e9n\u00e9fice)": {
+                    "account_number": "120"
+                },
+                "R\u00e9sultat de l'exercice (perte)": {
+                    "account_number": "129"
+                },
+                "account_number": "12"
+            },
+            "Subventions d'Investissement": {
+                "Subventions d'\u00e9quipement": {
+                    "Etat": {
+                        "account_number": "1311"
+                    },
+                    "R\u00e9gions": {
+                        "account_number": "1312"
+                    },
+                    "D\u00e9partements": {
+                        "account_number": "1313"
+                    },
+                    "Communes": {
+                        "account_number": "1314"
+                    },
+                    "Collectivit\u00e9s publiques": {
+                        "account_number": "1315"
+                    },
+                    "Entreprises publiques": {
+                        "account_number": "1316"
+                    },
+                    "Entreprises et organismes priv\u00e9s": {
+                        "account_number": "1317"
+                    },
+                    "Autres": {
+                        "account_number": "1318"
+                    },
+                    "account_number": "131"
+                },
+                "Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {
+                    "account_number": "138"
+                },
+                "Subventions d'investissement inscrites au compte de r\u00e9sultat": {
+                    "Subventions d'\u00e9quipement": {
+                        "Etat": {
+                            "account_number": "13911"
+                        },
+                        "R\u00e9gions": {
+                            "account_number": "13912"
+                        },
+                        "D\u00e9partements": {
+                            "account_number": "13913"
+                        },
+                        "Communes": {
+                            "account_number": "13914"
+                        },
+                        "Collectivit\u00e9s publiques": {
+                            "account_number": "13915"
+                        },
+                        "Entreprises publiques": {
+                            "account_number": "13916"
+                        },
+                        "Entreprises et organismes priv\u00e9s": {
+                            "account_number": "13917"
+                        },
+                        "Autres": {
+                            "account_number": "13918"
+                        },
+                        "account_number": "1391"
+                    },
+                    "Autres subventions d'investissement (m\u00eame ventilation que celle du compte 1391)": {
+                        "account_number": "1398"
+                    },
+                    "account_number": "139"
+                },
+                "account_number": "13"
+            },
+            "Provisions R\u00e9glement\u00e9es": {
+                "Provisions r\u00e9glement\u00e9es relative aux immobilisations": {
+                    "Provisions pour reconstitution des gisements miniers et p\u00e9troliers": {
+                        "account_number": "1423"
+                    },
+                    "Provisions pour investissement (participation des salari\u00e9s)": {
+                        "account_number": "1424"
+                    },
+                    "account_number": "142"
+                },
+                "Provisions r\u00e9glement\u00e9es relatives aux stocks": {
+                    "Hausse des prix": {
+                        "account_number": "1431"
+                    },
+                    "Fluctuation des cours": {
+                        "account_number": "1432"
+                    },
+                    "account_number": "143"
+                },
+                "Provisions r\u00e9glement\u00e9es relatives aux autres \u00e9l\u00e9ments de l'actif": {
+                    "account_number": "144"
+                },
+                "Amortissements d\u00e9rogatoires": {
+                    "account_number": "145"
+                },
+                "Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {
+                    "account_number": "146"
+                },
+                "Plus-values r\u00e9investies": {
+                    "account_number": "147"
+                },
+                "Autres provisions r\u00e9glement\u00e9es": {
+                    "account_number": "148"
+                },
+                "account_number": "14"
+            },
+            "Provisions": {
+                "Provisions pour risques": {
+                    "Provisions pour litiges": {
+                        "account_number": "1511"
+                    },
+                    "Provisions pour garanties donn\u00e9es aux clients": {
+                        "account_number": "1512"
+                    },
+                    "Provisions pour pertes sur march\u00e9s \u00e0 terme": {
+                        "account_number": "1513"
+                    },
+                    "Provisions pour amendes et p\u00e9nalit\u00e9s": {
+                        "account_number": "1514"
+                    },
+                    "Provisions pour pertes de change": {
+                        "account_number": "1515"
+                    },
+                    "Provisions pour pertes sur contrats": {
+                        "account_number": "1516"
+                    },
+                    "Autres provisions pour risques": {
+                        "account_number": "1518"
+                    },
+                    "account_number": "151"
+                },
+                "Provisions pour pensions et obligations similaires": {
+                    "account_number": "153"
+                },
+                "Provisions pour restructurations": {
+                    "account_number": "154"
+                },
+                "Provisions pour imp\u00f4ts": {
+                    "account_number": "155"
+                },
+                "Provisions pour renouvellement des immobilisations (entreprises concessionnaires) ": {
+                    "account_number": "156"
+                },
+                "Provisions pour charges \u00e0 r\u00e9partir sur plusieurs exercices": {
+                    "Provisions pour gros entretien ou grandes r\u00e9visions": {
+                        "account_number": "1572"
+                    },
+                    "account_number": "157"
+                },
+                "Autres provisions pour charges": {
+                    "Provisions pour remises en \u00e9tat": {
+                        "account_number": "1581"
+                    },
+                    "account_number": "158"
+                },
+                "account_number": "15"
+            },
+            "Emprunts et Dettes Assimil\u00e9es": {
+                "Emprunts obligataires convertibles": {
+                    "account_number": "161"
+                },
+                "Obligations repr\u00e9sentatives de passifs nets remis en fiducie": {
+                    "account_number": "162"
+                },
+                "Autres emprunts obligataires": {
+                    "account_number": "163"
+                },
+                "Emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {
+                    "account_number": "164"
+                },
+                "D\u00e9p\u00f4ts et cautionnements re\u00e7us": {
+                    "D\u00e9p\u00f4ts": {
+                        "account_number": "1651"
+                    },
+                    "Cautionnements": {
+                        "account_number": "1655"
+                    },
+                    "account_number": "165"
+                },
+                "Participation des salari\u00e9s aux r\u00e9sultats": {
+                    "Comptes bloqu\u00e9s": {
+                        "account_number": "1661"
+                    },
+                    "Fonds de participation": {
+                        "account_number": "1662"
+                    },
+                    "account_number": "166"
+                },
+                "Emprunts et dettes assortis de conditions particuli\u00e8res": {
+                    "Emissions de titres participatifs": {
+                        "account_number": "1671"
+                    },
+                    "Avances conditionn\u00e9es de l'Etat": {
+                        "account_number": "1674"
+                    },
+                    "Emprunts participatifs": {
+                        "account_number": "1675"
+                    },
+                    "account_number": "167"
+                },
+                "Autres emprunts et dettes assimil\u00e9es": {
+                    "Autres emprunts": {
+                        "account_number": "1681"
+                    },
+                    "Rentes viag\u00e8res capitalis\u00e9es": {
+                        "account_number": "1685"
+                    },
+                    "Autres dettes": {
+                        "account_number": "1687"
+                    },
+                    "Int\u00e9r\u00eats courus": {
+                        "Int\u00e9r\u00eats courus sur emprunts obligataires convertibles": {
+                            "account_number": "16881"
+                        },
+                        "Int\u00e9r\u00eats courus sur autres emprunts obligataires": {
+                            "account_number": "16883"
+                        },
+                        "Int\u00e9r\u00eats courus sur emprunts aupr\u00e8s des \u00e9tablissements de cr\u00e9dit": {
+                            "account_number": "16884"
+                        },
+                        "Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements re\u00e7us": {
+                            "account_number": "16885"
+                        },
+                        "Int\u00e9r\u00eats courus sur participation des salari\u00e9s aux r\u00e9sultats": {
+                            "account_number": "16886"
+                        },
+                        "Int\u00e9r\u00eats courus sur emprunts et dettes assortis de conditions particuli\u00e8res": {
+                            "account_number": "16887"
+                        },
+                        "Int\u00e9r\u00eats courus sur autres emprunts et dettes assimil\u00e9es": {
+                            "account_number": "16888"
+                        },
+                        "account_number": "1688"
+                    },
+                    "Primes de remboursement des obligations": {
+                        "account_number": "169"
+                    },
+                    "account_number": "168"
+                },
+                "account_number": "16"
+            },
+            "Dettes Rattach\u00e9es \u00e0 des Participations": {
+                "Dettes rattach\u00e9es \u00e0 des participations (groupe)": {
+                    "account_number": "171"
+                },
+                "Dettes rattach\u00e9es \u00e0 des participations (hors groupe)": {
+                    "account_number": "174"
+                },
+                "Dettes rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
+                    "Principal": {
+                        "account_number": "1781"
+                    },
+                    "Int\u00e9r\u00eats courus": {
+                        "account_number": "1788"
+                    },
+                    "account_number": "178"
+                },
+                "account_number": "17"
+            },
+            "Comptes de liaison des \u00e9tablisssements et soci\u00e9t\u00e9s en participation": {
+                "Comptes de liaison des \u00e9tablissements": {
+                    "account_number": "181"
+                },
+                "Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (charges)": {
+                    "account_number": "186"
+                },
+                "Biens et prestations de services \u00e9chang\u00e9s entre \u00e9tablissements (produits)": {
+                    "account_number": "187"
+                },
+                "Comptes de liaison des soci\u00e9t\u00e9s en participation": {
+                    "account_number": "188"
+                },
+                "account_number": "18"
+            },
+            "account_number": "1"
+        },
+        "Comptes d'Immobilisations": {
+            "root_type": "Asset",
+            "Immobilisations incorporelles": {
+                "Frais \u00e9tablissement": {
+                    "Frais de constitution": {
+                        "account_number": "2011"
+                    },
+                    "Frais de premier \u00e9tablissement": {
+                        "Frais de prospection": {
+                            "account_number": "20121"
+                        },
+                        "Frais de publicit\u00e9": {
+                            "account_number": "20122"
+                        },
+                        "account_number": "2012"
+                    },
+                    "Frais d'augmentation de capital et d'op\u00e9rations diverses (fusions, scissions, transformations)": {
+                        "account_number": "2013"
+                    },
+                    "account_number": "201"
+                },
+                "Frais de recherche et de d\u00e9veloppement": {
+                    "account_number": "203"
+                },
+                "Concessions et droits similaires, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
+                    "account_number": "205"
+                },
+                "Droit au bail": {
+                    "account_number": "206"
+                },
+                "Fonds commercial": {
+                    "account_number": "207"
+                },
+                "Autres immobilisations incorporelles": {
+                    "Mali de fusion sur actifs incorporels": {
+                        "account_number": "2081"
+                    },
+                    "account_number": "208"
+                },
+                "account_number": "20"
+            },
+            "Immobilisations corporelles": {
+                "account_type": "Fixed Asset",
+                "Terrains": {
+                    "account_type": "Fixed Asset",
+                    "Terrains nus": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2111"
+                    },
+                    "Terrains am\u00e9nag\u00e9s": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2112"
+                    },
+                    "Sous-sols et sur-sols": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2113"
+                    },
+                    "Terrains de carri\u00e8res (tr\u00e9fonds)": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2114"
+                    },
+                    "Terrains b\u00e2tis": {
+                        "account_type": "Fixed Asset",
+                        "Ensembles immobiliers industriels (A, B)": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21151"
+                        },
+                        "Ensembles immobiliers administratifs et commerciaux (A, B)": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21155"
+                        },
+                        "Autres ensembles immobiliers": {
+                            "account_type": "Fixed Asset",
+                            "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
+                                "account_type": "Fixed Asset",
+                                "account_number": "211581"
+                            },
+                            "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
+                                "account_type": "Fixed Asset",
+                                "account_number": "211588"
+                            },
+                            "account_number": "21158"
+                        },
+                        "account_number": "2115"
+                    },
+                    "account_number": "211"
+                },
+                "Agencements et am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 211)": {
+                    "account_type": "Fixed Asset",
+                    "account_number": "212"
+                },
+                "Constructions": {
+                    "account_type": "Fixed Asset",
+                    "B\u00e2timents": {
+                        "account_type": "Fixed Asset",
+                        "Ensembles immobiliers industriels (A, B)": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21311"
+                        },
+                        "Ensembles immobiliers administratifs et commerciaux (A, B)": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21315"
+                        },
+                        "Autres ensembles immobiliers": {
+                            "account_type": "Fixed Asset",
+                            "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
+                                "account_type": "Fixed Asset",
+                                "account_number": "213181"
+                            },
+                            "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
+                                "account_type": "Fixed Asset",
+                                "account_number": "213188"
+                            },
+                            "account_number": "21318"
+                        },
+                        "account_number": "2131"
+                    },
+                    "Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements des constructions": {
+                        "account_type": "Fixed Asset",
+                        "Ensembles immobiliers industriels (A, B)": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21351"
+                        },
+                        "Ensembles immobiliers administratifs et commerciaux (A, B)": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21355"
+                        },
+                        "Autres ensembles immobiliers": {
+                            "account_type": "Fixed Asset",
+                            "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations professionnelles (A, B)": {
+                                "account_type": "Fixed Asset",
+                                "account_number": "213581"
+                            },
+                            "Autres ensembles immobiliers affect\u00e9s aux op\u00e9rations non professionnelles (A, B)": {
+                                "account_type": "Fixed Asset",
+                                "account_number": "213588"
+                            },
+                            "account_number": "21358"
+                        },
+                        "account_number": "2135"
+                    },
+                    "Ouvrages d'infrastructure": {
+                        "account_type": "Fixed Asset",
+                        "Voies de terre": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21381"
+                        },
+                        "Voies de fer": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21382"
+                        },
+                        "Voies d'eau": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21383"
+                        },
+                        "Barrages": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21384"
+                        },
+                        "Pistes d'a\u00e9rodromes": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21385"
+                        },
+                        "account_number": "2138"
+                    },
+                    "account_number": "213"
+                },
+                "Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte 213)": {
+                    "account_type": "Fixed Asset",
+                    "account_number": "214"
+                },
+                "Installations techniques, mat\u00e9riel et outillage industriels": {
+                    "account_type": "Fixed Asset",
+                    "Installations complexes sp\u00e9cialis\u00e9es": {
+                        "account_type": "Fixed Asset",
+                        "Installations complexes sp\u00e9cialis\u00e9es - sur sol propre": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21511"
+                        },
+                        "Installations complexes sp\u00e9cialis\u00e9es - sur sol d'autrui": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21514"
+                        },
+                        "account_number": "2151"
+                    },
+                    "Installations \u00e0 caract\u00e8re sp\u00e9cifique": {
+                        "account_type": "Fixed Asset",
+                        "Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol propre": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21531"
+                        },
+                        "Installations \u00e0 caract\u00e8re sp\u00e9cifique - sur sol d'autrui": {
+                            "account_type": "Fixed Asset",
+                            "account_number": "21534"
+                        },
+                        "account_number": "2153"
+                    },
+                    "Mat\u00e9riel industriel": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2154"
+                    },
+                    "Outillage industriel": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2155"
+                    },
+                    "Agencements et am\u00e9nagements du mat\u00e9riel et outillage industriel": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2157"
+                    },
+                    "account_number": "215"
+                },
+                "Autres immobilisations corporelles": {
+                    "account_type": "Fixed Asset",
+                    "Installations g\u00e9n\u00e9rales, agencements, am\u00e9nagements divers": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2181"
+                    },
+                    "Mat\u00e9riel de transport": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2182"
+                    },
+                    "Mat\u00e9riel de bureau et mat\u00e9riel informatique": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2183"
+                    },
+                    "Mobilier": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2184"
+                    },
+                    "Cheptel": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2185"
+                    },
+                    "Emballages r\u00e9cup\u00e9rables": {
+                        "account_type": "Fixed Asset",
+                        "account_number": "2186"
+                    },
+                    "Mali de fusion sur actifs corporels": {
+                        "account_number": "2187"
+                    },
+                    "account_number": "218"
+                },
+                "account_number": "21"
+            },
+            "Immobilisations mises en concession": {
+                "account_number": "22"
+            },
+            "Immobilisations en cours": {
+                "Immobilisations corporelles en cours": {
+                    "Terrains": {
+                        "account_number": "2312"
+                    },
+                    "Constructions": {
+                        "account_number": "2313"
+                    },
+                    "Installations techniques, mat\u00e9riel et outillage industriels": {
+                        "account_number": "2315"
+                    },
+                    "Autres immobilisations corporelles": {
+                        "account_number": "2318"
+                    },
+                    "account_number": "231"
+                },
+                "Immobilisations incorporelles en cours": {
+                    "account_number": "232"
+                },
+                "Avances et acomptes vers\u00e9s sur commandes d'immobilisations incorporelles": {
+                    "account_number": "237"
+                },
+                "Avances et acomptes vers\u00e9s sur commandes d'immobilisations corporelles": {
+                    "Terrains": {
+                        "account_number": "2382"
+                    },
+                    "Constructions": {
+                        "account_number": "2383"
+                    },
+                    "Installations techniques, mat\u00e9riel et outillage industriels": {
+                        "account_number": "2385"
+                    },
+                    "Autres immobilisations corporelles": {
+                        "account_number": "2388"
+                    },
+                    "account_number": "238"
+                },
+                "account_number": "23"
+            },
+            "Parts dans des entreprises li\u00e9es et cr\u00e9ances sur des entreprises li\u00e9es": {
+                "is_group": 1,
+                "account_number": "25"
+            },
+            "Participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+                "Titres de participation": {
+                    "Actions": {
+                        "account_number": "2611"
+                    },
+                    "Autres titres": {
+                        "account_number": "2618"
+                    },
+                    "account_number": "261"
+                },
+                "Autres formes de participation": {
+                    "Droit repr\u00e9sentatifs d'actifs nets remis en fiducie": {
+                        "account_number": "2661"
+                    },
+                    "account_number": "266"
+                },
+                "Cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+                    "Cr\u00e9ances rattach\u00e9es \u00e0 des participations (groupe)": {
+                        "account_number": "2671"
+                    },
+                    "Cr\u00e9ances rattach\u00e9es \u00e0 des participations (hors groupe)": {
+                        "account_number": "2674"
+                    },
+                    "Versements repr\u00e9sentatifs d'apports non capitalis\u00e9s (appel de fonds)": {
+                        "account_number": "2675"
+                    },
+                    "Avances consolidables": {
+                        "account_number": "2676"
+                    },
+                    "Autres cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+                        "account_number": "2677"
+                    },
+                    "Int\u00e9r\u00eats courus": {
+                        "account_number": "2678"
+                    },
+                    "account_number": "267"
+                },
+                "Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation": {
+                    "Principal": {
+                        "account_number": "2681"
+                    },
+                    "Int\u00e9r\u00eats courus": {
+                        "account_number": "2688"
+                    },
+                    "account_number": "268"
+                },
+                "Versements restant \u00e0 effectuer sur titres de participation non lib\u00e9r\u00e9s": {
+                    "account_number": "269"
+                },
+                "account_number": "26"
+            },
+            "Autres immobilisations financi\u00e8res": {
+                "Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille (droit de propri\u00e9t\u00e9)": {
+                    "Actions": {
+                        "account_number": "2711"
+                    },
+                    "Autres titres": {
+                        "account_number": "2718"
+                    },
+                    "account_number": "271"
+                },
+                "Titres immobilis\u00e9s (droit de cr\u00e9ance)": {
+                    "Obligations": {
+                        "account_number": "2721"
+                    },
+                    "Bons": {
+                        "account_number": "2722"
+                    },
+                    "account_number": "272"
+                },
+                "Titres immobilis\u00e9s de l'activit\u00e9 de portefeuille": {
+                    "account_number": "273"
+                },
+                "Pr\u00eats": {
+                    "Pr\u00eats participatifs": {
+                        "account_number": "2741"
+                    },
+                    "Pr\u00eats aux associ\u00e9s": {
+                        "account_number": "2742"
+                    },
+                    "Pr\u00eats au personnel": {
+                        "account_number": "2743"
+                    },
+                    "Autres pr\u00eats": {
+                        "account_number": "2748"
+                    },
+                    "account_number": "274"
+                },
+                "D\u00e9p\u00f4ts et cautionnements vers\u00e9s": {
+                    "D\u00e9p\u00f4ts": {
+                        "account_number": "2751"
+                    },
+                    "Cautionnements": {
+                        "account_number": "2755"
+                    },
+                    "account_number": "275"
+                },
+                "Autres cr\u00e9ances immobilis\u00e9es": {
+                    "Cr\u00e9ances diverses": {
+                        "account_number": "2761"
+                    },
+                    "Int\u00e9r\u00eats courus": {
+                        "Int\u00e9r\u00eats courus sur titres immobilis\u00e9s (droit de cr\u00e9ance)": {
+                            "account_number": "27682"
+                        },
+                        "Int\u00e9r\u00eats courus sur pr\u00eats": {
+                            "account_number": "27684"
+                        },
+                        "Int\u00e9r\u00eats courus sur d\u00e9p\u00f4ts et cautionnements": {
+                            "account_number": "27685"
+                        },
+                        "Int\u00e9r\u00eats courus sur cr\u00e9ances diverses": {
+                            "account_number": "27688"
+                        },
+                        "account_number": "2768"
+                    },
+                    "account_number": "276"
+                },
+                "(Actions propres ou parts propres)": {
+                    "Actions propres ou parts propres": {
+                        "account_number": "2771"
+                    },
+                    "Actions propres ou parts propres en voie d'annulation": {
+                        "account_number": "2772"
+                    },
+                    "account_number": "277"
+                },
+                "Mali de fusion sur actifs financiers": {
+                    "account_number": "278"
+                },
+                "Versements restant \u00e0 effectuer sur titres immobilis\u00e9s non lib\u00e9r\u00e9s": {
+                    "account_number": "279"
+                },
+                "account_number": "27"
+            },
+            "Amortissements des immobilisations": {
+                "account_type": "Accumulated Depreciation",
+                "Amortissements des immobilisations incorporelles": {
+                    "account_type": "Accumulated Depreciation",
+                    "Frais d'\u00e9tablissement (m\u00eame ventilation que celle du compte 212)": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2801"
+                    },
+                    "Frais de recherche et de d\u00e9veloppement": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2803"
+                    },
+                    "Concessions et droits similaires, brevets, licences, logiciels, droits et valeurs similaires": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2805"
+                    },
+                    "Fonds commercial": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2807"
+                    },
+                    "Autres immobilisations incorporelles": {
+                        "account_type": "Accumulated Depreciation",
+                        "Mali de fusion sur actifs incorporels": {
+                            "account_type": "Accumulated Depreciation",
+                            "account_number": "28081"
+                        },
+                        "account_number": "2808"
+                    },
+                    "account_number": "280"
+                },
+                "Amortissements des immobilisations corporelles": {
+                    "account_type": "Accumulated Depreciation",
+                    "Terrains de gisement": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2811"
+                    },
+                    "Agencements, am\u00e9nagements de terrains (m\u00eame ventilation que celle du compte 212)": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2812"
+                    },
+                    "Constructions (m\u00eame ventilation que celle du compte 213)": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2813"
+                    },
+                    "Constructions sur sol d'autrui (m\u00eame ventilation que celle du compte du 214)": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2814"
+                    },
+                    "Installations techniques, mat\u00e9riel et outillage industriels (m\u00eame ventilation que celle du compte 218)": {
+                        "account_type": "Accumulated Depreciation",
+                        "account_number": "2815"
+                    },
+                    "Autres immobilisations corporelles (m\u00eame ventilation que celle du compte 218)": {
+                        "account_type": "Accumulated Depreciation",
+                        "Mali de fusion sur actifs corporels": {
+                            "account_type": "Accumulated Depreciation",
+                            "account_number": "28187"
+                        },
+                        "account_number": "2818"
+                    },
+                    "account_number": "281"
+                },
+                "Amortissements des immobilisations mises en concession": {
+                    "account_number": "282"
+                },
+                "account_number": "28"
+            },
+            "D\u00e9pr\u00e9ciations des immobilisations": {
+                "D\u00e9pr\u00e9ciations des immobilisations incorporelles": {
+                    "Marques,  proc\u00e9d\u00e9s, droits et valeurs similaires": {
+                        "account_number": "2905"
+                    },
+                    "Droit au bail": {
+                        "account_number": "2906"
+                    },
+                    "Fonds commercial": {
+                        "account_number": "2907"
+                    },
+                    "Autres immobilisations incorporelles": {
+                        "Mali de fusion sur actifs incorporels": {
+                            "account_number": "29081"
+                        },
+                        "account_number": "2908"
+                    },
+                    "account_number": "290"
+                },
+                "D\u00e9pr\u00e9ciations des immobilisations corporelles (m\u00eame ventilation que celle du compte 21)": {
+                    "Terrains (autres que terrains de gisement)": {
+                        "Mali de fusion sur actifs corporels": {
+                            "account_number": "29187"
+                        },
+                        "account_number": "2911"
+                    },
+                    "account_number": "291"
+                },
+                "D\u00e9pr\u00e9ciations des immobilisations mises en concession": {
+                    "account_number": "292"
+                },
+                "D\u00e9pr\u00e9ciations des immobilisations en cours": {
+                    "Immobilisations corporelles en cours": {
+                        "account_number": "2931"
+                    },
+                    "Immobilisations incorporelles en cours": {
+                        "account_number": "2932"
+                    },
+                    "account_number": "293"
+                },
+                "D\u00e9pr\u00e9ciations des participations et cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+                    "Titres de participation": {
+                        "account_number": "2961"
+                    },
+                    "Autres formes de participation": {
+                        "account_number": "2966"
+                    },
+                    "Cr\u00e9ances rattach\u00e9es \u00e0 des participations (m\u00eame ventilation que celle du compte 267)": {
+                        "account_number": "2967"
+                    },
+                    "Cr\u00e9ances rattach\u00e9es \u00e0 des soci\u00e9t\u00e9s en participation (m\u00eame ventilation que celle du compte 268)": {
+                        "account_number": "2968"
+                    },
+                    "account_number": "296"
+                },
+                "D\u00e9pr\u00e9ciations des autres immobilisations financi\u00e8res": {
+                    "Titres immobilis\u00e9s autres que les titres immobilis\u00e9s de l'activit\u00e9 de portefeuille - droit de propri\u00e9t\u00e9": {
+                        "account_number": "2971"
+                    },
+                    "Titres immobilis\u00e9s - droit de cr\u00e9ance (m\u00eame ventilation que celle du compte 272)": {
+                        "account_number": "2972"
+                    },
+                    " Titres immobilis\u00e9s de l'activit\u00e9 de portefuille": {
+                        "account_number": "2973"
+                    },
+                    "Pr\u00eats (m\u00eame ventilation que celle du compte 274)": {
+                        "account_number": "2974"
+                    },
+                    "D\u00e9p\u00f4ts et cautionnements vers\u00e9s (m\u00eame ventilation que celle du compte 275)": {
+                        "account_number": "2975"
+                    },
+                    "Autres cr\u00e9ances immobilis\u00e9es (m\u00eame ventilation que celle du compte 276)": {
+                        "Mali de fusion sur actifs financiers": {
+                            "account_number": "29787"
+                        },
+                        "account_number": "2976"
+                    },
+                    "account_number": "297"
+                },
+                "account_number": "29"
+            },
+            "account_number": "2"
+        },
+        "Comptes de Stocks et En-Cours": {
+            "root_type": "Asset",
+            "Mati\u00e8res premi\u00e8res (et fournitures)": {
+                "Mati\u00e8res (ou groupe) A": {
+                    "account_number": "311"
+                },
+                "Mati\u00e8res (ou groupe) B": {
+                    "account_number": "312"
+                },
+                "Fournitures A, B, C, ...": {
+                    "account_number": "317"
+                },
+                "account_number": "31"
+            },
+            "Autres approvisionnements": {
+                "Mat\u00e8res consommables": {
+                    "Mati\u00e8res (ou groupe) C": {
+                        "account_number": "3211"
+                    },
+                    "Mati\u00e8res (ou groupe) D": {
+                        "account_number": "3212"
+                    },
+                    "account_number": "321"
+                },
+                "Fournitures consommables": {
+                    "Combustibles": {
+                        "account_number": "3221"
+                    },
+                    "Produits d'entretien": {
+                        "account_number": "3222"
+                    },
+                    "Fournitures d'atelier et d'usine": {
+                        "account_number": "3223"
+                    },
+                    "Fournitures de magasin": {
+                        "account_number": "3224"
+                    },
+                    "Fournitures de bureau": {
+                        "account_number": "3225"
+                    },
+                    "account_number": "322"
+                },
+                "Emballages": {
+                    "Emballages perdus": {
+                        "account_number": "3261"
+                    },
+                    "Emballages r\u00e9cup\u00e9rables non identifiables": {
+                        "account_number": "3265"
+                    },
+                    "Emballages \u00e0 usage mixte": {
+                        "account_number": "3267"
+                    },
+                    "account_number": "326"
+                },
+                "account_number": "32"
+            },
+            "En-cours de production de biens": {
+                "Produits en cours": {
+                    "Produits en cours P1": {
+                        "account_number": "3311"
+                    },
+                    "Produits en cours P2": {
+                        "account_number": "3312"
+                    },
+                    "account_number": "331"
+                },
+                "Travaux en cours": {
+                    "Travaux en cours T1": {
+                        "account_number": "3351"
+                    },
+                    "Travaux en cours T2": {
+                        "account_number": "3352"
+                    },
+                    "account_number": "335"
+                },
+                "account_number": "33"
+            },
+            "En-cours de production de services": {
+                "Etudes en cours": {
+                    "Etudes en cours E1": {
+                        "account_number": "3411"
+                    },
+                    "Etudes en cours E2": {
+                        "account_number": "3412"
+                    },
+                    "account_number": "341"
+                },
+                "Prestations de services en cours": {
+                    "Prestations de services S1": {
+                        "account_number": "3451"
+                    },
+                    "Prestations de services S2": {
+                        "account_number": "3452"
+                    },
+                    "account_number": "345"
+                },
+                "account_number": "34"
+            },
+            "Stocks de produits": {
+                "account_type": "Stock",
+                "Produits interm\u00e9diaires": {
+                    "account_type": "Stock",
+                    "Produits interm\u00e9diaires (ou groupe) A": {
+                        "account_type": "Stock",
+                        "is_group": 1,
+                        "account_number": "3511"
+                    },
+                    "Produits interm\u00e9diaires (ou groupe) B": {
+                        "account_type": "Stock",
+                        "is_group": 1,
+                        "account_number": "3512"
+                    },
+                    "account_number": "351"
+                },
+                "Produits finis": {
+                    "account_type": "Stock",
+                    "Produits finis (ou groupe) A": {
+                        "account_type": "Stock",
+                        "is_group": 1,
+                        "account_number": "3551"
+                    },
+                    "Produits finis (ou groupe) B": {
+                        "account_type": "Stock",
+                        "is_group": 1,
+                        "account_number": "3552"
+                    },
+                    "account_number": "355"
+                },
+                "Produits r\u00e9siduels (ou mati\u00e8res de r\u00e9cup\u00e9ration)": {
+                    "account_type": "Stock",
+                    "D\u00e9chets": {
+                        "account_type": "Stock",
+                        "is_group": 1,
+                        "account_number": "3581"
+                    },
+                    "Rebuts": {
+                        "account_type": "Stock",
+                        "is_group": 1,
+                        "account_number": "3585"
+                    },
+                    "Mati\u00e8res de r\u00e9cup\u00e9ration": {
+                        "account_type": "Stock",
+                        "is_group": 1,
+                        "account_number": "3586"
+                    },
+                    "account_number": "358"
+                },
+                "account_number": "35"
+            },
+            "(Compte \u00e0 ouvrir, le cas \u00e9ch\u00e9ant, sous l'intitul\u00e9 \"stocks provenant d'immobilisations\")": {
+                "account_number": "36"
+            },
+            "Stocks de marchandises": {
+                "Marchandises (ou groupe) A": {
+                    "account_number": "371"
+                },
+                "Marchandises (ou groupe) B": {
+                    "account_number": "372"
+                },
+                "account_number": "37"
+            },
+            "Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {
+                "account_type": "Stock",
+                "account_number": "38"
+            },
+            "D\u00e9pr\u00e9ciations des stocks et en-cours": {
+                "D\u00e9pr\u00e9ciations des mati\u00e8res premi\u00e8res (et fournitures)": {
+                    "Mati\u00e8res (ou groupe) A": {
+                        "account_number": "3911"
+                    },
+                    "Mati\u00e8res (ou groupe) B": {
+                        "account_number": "3912"
+                    },
+                    "Fournitures A, B, C, ...": {
+                        "account_number": "3917"
+                    },
+                    "account_number": "391"
+                },
+                "D\u00e9pr\u00e9ciations des autres approvisionnements": {
+                    "Mati\u00e8res consommables (m\u00eame ventilation que celle du compte 321)": {
+                        "account_number": "3921"
+                    },
+                    "Fournitures consommables (m\u00eame ventilation que celle du compte 322)": {
+                        "account_number": "3922"
+                    },
+                    "Emballages (m\u00eame ventilation que celle du compte 326)": {
+                        "account_number": "3926"
+                    },
+                    "account_number": "392"
+                },
+                "D\u00e9pr\u00e9ciations des en-cours de production de biens": {
+                    "Etudes en cours (m\u00eame ventilation que celle du compte 341)": {
+                        "account_number": "3931"
+                    },
+                    "Travaux en cours (m\u00eame ventilation que celle du compte 335)": {
+                        "account_number": "3935"
+                    },
+                    "account_number": "393"
+                },
+                "D\u00e9pr\u00e9ciations des en-cours de production de services": {
+                    "Etudes en cours (m\u00eame ventilation que celle du compte 341)": {
+                        "account_number": "3941"
+                    },
+                    "Prestations de services en cours (m\u00eame ventilation que celle du compte 345)": {
+                        "account_number": "3945"
+                    },
+                    "account_number": "394"
+                },
+                "D\u00e9pr\u00e9ciations des stocks de produits": {
+                    "Produits interm\u00e9diaires (m\u00eame ventilation que celle du compte 351)": {
+                        "account_number": "3951"
+                    },
+                    "Produits finis (m\u00eame ventilation que celle du compte 355)": {
+                        "account_number": "3955"
+                    },
+                    "account_number": "395"
+                },
+                "D\u00e9pr\u00e9ciations des stocks de marchandises": {
+                    "Marchandise (ou groupe) A": {
+                        "account_number": "3971"
+                    },
+                    "Marchandise (ou groupe) B": {
+                        "account_number": "3972"
+                    },
+                    "account_number": "397"
+                },
+                "account_number": "39"
+            },
+            "account_number": "3"
+        },
+        "4-Comptes de Tiers (ACTIF)": {
+            "root_type": "Asset",
+            "40-Fournisseurs et Comptes Rattach\u00e9s (ACTIF)": {
+                "Fournisseurs d\u00e9biteurs": {
+                    "Fournisseurs - Avances et acomptes vers\u00e9s sur commandes": {
+                        "account_number": "4091"
+                    },
+                    "Fournisseurs - Cr\u00e9ances pour emballages et mat\u00e9riel \u00e0 rendre": {
+                        "account_number": "4096"
+                    },
+                    "Fournisseurs - Autres avoirs": {
+                        "Fournisseurs d'exploitation": {
+                            "account_number": "40971"
+                        },
+                        "Fournisseurs d'immobilisation": {
+                            "account_number": "40974"
+                        },
+                        "account_number": "4097"
+                    },
+                    "Rabais, remises, ristournes \u00e0 obtenir et autres avoirs non encore re\u00e7us": {
+                        "account_number": "4098"
+                    },
+                    "account_number": "409"
+                }
+            },
+            "41-Clients et comptes rattach\u00e9s (ACTIF)": {
+                "account_type": "Receivable",
+                "Clients et Comptes rattach\u00e9s": {
+                    "account_type": "Receivable",
+                    "account_number": "410"
+                },
+                "Clients": {
+                    "account_type": "Receivable",
+                    "Clients - Ventes de biens ou de prestations de services": {
+                        "account_type": "Receivable",
+                        "account_number": "4111"
+                    },
+                    "Clients - Retenues de garantie": {
+                        "account_type": "Receivable",
+                        "account_number": "4117"
+                    },
+                    "account_number": "411"
+                },
+                "Clients - Effets \u00e0 recevoir": {
+                    "account_type": "Receivable",
+                    "account_number": "413"
+                },
+                "Clients douteux ou litigieux": {
+                    "account_type": "Receivable",
+                    "account_number": "416"
+                },
+                "Clients - Produits non encore factur\u00e9s": {
+                    "account_type": "Receivable",
+                    "Clients - Factures \u00e0 \u00e9tablir": {
+                        "account_type": "Receivable",
+                        "account_number": "4181"
+                    },
+                    "Clients - Int\u00e9r\u00eats courus": {
+                        "account_type": "Receivable",
+                        "account_number": "4188"
+                    },
+                    "account_number": "418"
+                }
+            },
+            "42-Personnel et comptes rattach\u00e9s (ACTIF)": {
+                "Personnel - Avances et acomptes": {
+                    "account_number": "425"
+                }
+            },
+            "43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (ACTIF)": {
+                "S\u00e9curit\u00e9 sociale": {
+                    "account_number": "431"
+                },
+                "Autres organismes sociaux": {
+                    "account_number": "437"
+                },
+                "438-Organismes sociaux - Produits \u00e0 recevoir": {
+                    "Produits \u00e0 recevoir": {
+                        "account_number": "4387"
+                    }
+                }
+            },
+            "44-Etat et autres collectivit\u00e9s publiques (ACTIF)": {
+                "Etat - Subventions \u00e0 recevoir": {
+                    "Subventions d'investissement": {
+                        "account_number": "4411"
+                    },
+                    "Subventions d'exploitation": {
+                        "account_number": "4417"
+                    },
+                    "Subventions d'\u00e9quilibre": {
+                        "account_number": "4418"
+                    },
+                    "Avances sur subventions": {
+                        "account_number": "4419"
+                    },
+                    "account_number": "441"
+                },
+                "Op\u00e9rations particuli\u00e8res avec l'Etat, les collectivit\u00e9s publiques, les organismes internationaux": {
+                    "Cr\u00e9ances sur l'Etat r\u00e9sultant de la suppression de la r\u00e8gle du d\u00e9calage d'un mois en mati\u00e8re de TVA": {
+                        "account_number": "4431"
+                    },
+                    "Int\u00e9r\u00eats courus sur cr\u00e9ances figurant au compte 4431": {
+                        "account_number": "4438"
+                    },
+                    "account_number": "443"
+                },
+                "Etat - Taxes sur le chiffre d'affaires (ACTIF)": {
+                    "TVA due intracommunautaire": {
+                        "account_number": "4452"
+                    },
+                    "Taxes sur le chiffre d'affaires d\u00e9ductibles": {
+                        "TVA sur immobilisations": {
+                            "account_number": "44562"
+                        },
+                        "TVA transf\u00e9r\u00e9e par d'autres entreprises": {
+                            "account_number": "44563"
+                        },
+                        "TVA sur autres biens et services": {
+                            "tax_rate": 20,
+                            "account_number": "44566"
+                        },
+                        "Cr\u00e9dit de TVA \u00e0 reporter": {
+                            "account_number": "44567"
+                        },
+                        "Taxes assimil\u00e9es \u00e0 la TVA": {
+                            "account_number": "44568"
+                        },
+                        "account_number": "4456"
+                    },
+                    "4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (ACTIF)": {
+                        "Acomptes - R\u00e9gime simplifi\u00e9 d'imposition": {
+                            "account_number": "44581"
+                        },
+                        "Acomptes - R\u00e9gime du forfait": {
+                            "account_number": "44582"
+                        },
+                        "Remboursement de taxes sur le chiffre d'affaires demand\u00e9": {
+                            "account_number": "44583"
+                        },
+                        "Taxes sur le chiffre d'affaires sur factures non parvenues": {
+                            "account_number": "44586"
+                        }
+                    }
+                },
+                "Etat - Charges \u00e0 payer et produits \u00e0 recevoir": {
+                    "Charges fiscales sur cong\u00e9s \u00e0 payer": {
+                        "account_number": "4482"
+                    },
+                    "Charges \u00e0 payer": {
+                        "account_number": "4486"
+                    },
+                    "Produits \u00e0 recevoir": {
+                        "account_number": "4487"
+                    },
+                    "account_number": "448"
+                }
+            },
+            "45-Groupe et associ\u00e9s (ACTIF)": {
+                "Associ\u00e9s - Op\u00e9rations sur le capital (ACTIF)": {
+                    "456-Apporteurs - Capital appel\u00e9, non vers\u00e9": {
+                        "Actionnaires - Capital souscrit et appel\u00e9, non vers\u00e9": {
+                            "account_number": "45621"
+                        },
+                        "Associ\u00e9s - Capital appel\u00e9, non vers\u00e9": {
+                            "account_number": "45625"
+                        },
+                        "account_number": "4562"
+                    }
+                }
+            },
+            "46-D\u00e9biteurs divers et cr\u00e9diteurs divers (ACTIF)": {
+                "Cr\u00e9ances sur cessions d'immobilisations": {
+                    "account_number": "462"
+                },
+                "Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {
+                    "account_number": "465"
+                },
+                "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (ACTIF)": {},
+                "468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (ACTIF)": {
+                    "Produits \u00e0 recevoir": {
+                        "account_number": "4687"
+                    }
+                }
+            },
+            "47-Comptes transitoires ou d'attente (ACTIF)": {
+                "471-Comptes d'attente (ACTIF)": {
+                    "account_type": "Temporary"
+                },
+                "Diff\u00e9rences de conversion (ACTIF)": {
+                    "Diminution des cr\u00e9ances": {
+                        "account_number": "4761"
+                    },
+                    "Augmentation des dettes": {
+                        "account_number": "4762"
+                    },
+                    "Diff\u00e9rences compens\u00e9es par couverture de change": {
+                        "account_number": "4768"
+                    },
+                    "account_number": "476"
+                },
+                "Autres comptes transitoires (ACTIF)": {
+                    "Mali de fusion sur actif circulant": {
+                        "account_number": "4781"
+                    },
+                    "478-Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (ACTIF)": {
+                        "account_number": "4786"
+                    }
+                }
+            },
+            "48-Comptes de r\u00e9gularisation (ACTIF)": {
+                "Charges \u00e0 r\u00e9partir sur plusieurs exercices": {
+                    "Frais d'\u00e9mission des emprunts": {
+                        "account_number": "4816"
+                    },
+                    "account_number": "481"
+                },
+                "Charges constat\u00e9es d'avance": {
+                    "account_number": "486"
+                },
+                "488-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (ACTIF)": {
+                    "Charges": {
+                        "account_number": "4886"
+                    }
+                }
+            },
+            "49-D\u00e9pr\u00e9ciation des comptes de tiers (ACTIF)": {
+                "D\u00e9pr\u00e9ciations des comptes clients": {
+                    "account_number": "491"
+                },
+                "D\u00e9pr\u00e9ciations des comptes du groupe et des associ\u00e9s": {
+                    "Comptes du groupe": {
+                        "account_number": "4951"
+                    },
+                    "Comptes courants des associ\u00e9s": {
+                        "account_number": "4955"
+                    },
+                    "Op\u00e9rations faites en commun et en GIE": {
+                        "account_number": "4958"
+                    },
+                    "account_number": "495"
+                },
+                "D\u00e9pr\u00e9ciations des comptes de d\u00e9biteurs divers": {
+                    "Cr\u00e9ances sur cessions d'immobilisations": {
+                        "account_number": "4962"
+                    },
+                    "Cr\u00e9ances sur cessions de valeurs mobili\u00e8res de placement": {
+                        "account_number": "4965"
+                    },
+                    "Autres comptes d\u00e9biteurs": {
+                        "account_number": "4967"
+                    },
+                    "account_number": "496"
+                }
+            }
+        },
+        "4-Comptes de Tiers (PASSIF)": {
+            "root_type": "Liability",
+            "40-Fournisseurs et Comptes Rattach\u00e9s (PASSIF)": {
+                "account_type": "Payable",
+                "Fournisseurs": {
+                    "account_type": "Payable",
+                    "Fournisseurs - Achats de biens ou de prestations de services": {
+                        "account_type": "Payable",
+                        "account_number": "4011"
+                    },
+                    "Fournisseurs - Retenues de garantie": {
+                        "account_type": "Payable",
+                        "account_number": "4017"
+                    },
+                    "account_number": "401"
+                },
+                "Fournisseurs - Effets \u00e0 payer": {
+                    "account_type": "Payable",
+                    "account_number": "403"
+                },
+                "Fournisseurs d'immobilisations": {
+                    "account_type": "Payable",
+                    "Fournisseurs - Achats d'immobilisations": {
+                        "account_type": "Payable",
+                        "account_number": "4041"
+                    },
+                    "Fournisseurs d'immobilisations - Retenues de garantie": {
+                        "account_type": "Payable",
+                        "account_number": "4047"
+                    },
+                    "account_number": "404"
+                },
+                "Fournisseurs d'immobilisations - Effets \u00e0 payer": {
+                    "account_type": "Payable",
+                    "account_number": "405"
+                },
+                "Fournisseurs - Factures non parvenues": {
+                    "account_type": "Stock Received But Not Billed",
+                    "Fournisseurs": {
+                        "account_type": "Stock Received But Not Billed",
+                        "account_number": "4081"
+                    },
+                    "Fournisseurs d'immobilisations": {
+                        "account_type": "Stock Received But Not Billed",
+                        "account_number": "4084"
+                    },
+                    "Fournisseurs - Int\u00e9r\u00eats courus": {
+                        "account_type": "Stock Received But Not Billed",
+                        "account_number": "4088"
+                    },
+                    "account_number": "408"
+                }
+            },
+            "41-Clients et comptes rattach\u00e9s (PASSIF)": {
+                "Clients cr\u00e9diteurs": {
+                    "Clients - Avances et acomptes re\u00e7us sur commandes": {
+                        "account_number": "4191"
+                    },
+                    "Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {
+                        "account_number": "4196"
+                    },
+                    "Clients - Autres avoirs": {
+                        "account_number": "4197"
+                    },
+                    "Rabais, remises, ristournes \u00e0 accorder et autres avoirs \u00e0 \u00e9tablir": {
+                        "account_number": "4198"
+                    },
+                    "account_number": "419"
+                }
+            },
+            "42-Personnel et comptes rattach\u00e9s (PASSIF)": {
+                "Personnel - R\u00e9mun\u00e9rations dues": {
+                    "account_number": "421"
+                },
+                "Comit\u00e9s d'entreprises, d'\u00e9tablissement...": {
+                    "account_number": "422"
+                },
+                "Participation des salari\u00e9s aux r\u00e9sultats": {
+                    "R\u00e9serve sp\u00e9ciale": {
+                        "account_number": "4246"
+                    },
+                    "Comptes courants": {
+                        "account_number": "4248"
+                    },
+                    "account_number": "424"
+                },
+                "Personnel - D\u00e9p\u00f4ts": {
+                    "account_number": "426"
+                },
+                "Personnel - Oppositions": {
+                    "account_number": "427"
+                },
+                "Personnel - Charges \u00e0 payer et produits \u00e0 recevoir": {
+                    "Dettes provisionn\u00e9es pour cong\u00e9s \u00e0 payer": {
+                        "account_number": "4282"
+                    },
+                    "Dettes provisionn\u00e9es pour participation des salari\u00e9s aux r\u00e9sultats": {
+                        "account_number": "4284"
+                    },
+                    "Autres charges \u00e0 payer": {
+                        "account_number": "4286"
+                    },
+                    "Produits \u00e0 recevoir": {
+                        "account_number": "4287"
+                    },
+                    "account_number": "428"
+                }
+            },
+            "43-S\u00e9curit\u00e9 sociale et autres organismes sociaux (PASSIF)": {
+                "438-Organismes sociaux - Charges \u00e0 payer": {
+                    "Charges sociales sur cong\u00e9s \u00e0 payer": {
+                        "account_number": "4382"
+                    },
+                    "Autres charges \u00e0 payer": {
+                        "account_number": "4386"
+                    }
+                }
+            },
+            "44-Etat et autres collectivit\u00e9s publiques (PASSIF)": {
+                "Etat - Imp\u00f4ts et taxes recouvrables sur des tiers": {
+                    "Obligataires": {
+                        "account_number": "4424"
+                    },
+                    "Associ\u00e9s": {
+                        "account_number": "4425"
+                    },
+                    "account_number": "442"
+                },
+                "Etat - Imp\u00f4ts sur les b\u00e9n\u00e9fices": {
+                    "account_number": "444"
+                },
+                "Etat - Taxes sur le chiffre d'affaires (PASSIF)": {
+                    "Taxes sur le chiffre d'affaires \u00e0 d\u00e9caisser": {
+                        "TVA \u00e0 d\u00e9caisser": {
+                            "account_number": "44551"
+                        },
+                        "Taxes assimil\u00e9es \u00e0 la TVA": {
+                            "account_number": "44558"
+                        },
+                        "account_number": "4455"
+                    },
+                    "Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": {
+                        "TVA collect\u00e9e": {
+                            "account_type": "Tax",
+                            "is_group": 1,
+                            "account_number": "44571"
+                        },
+                        "Taxes assimil\u00e9es \u00e0 la TVA": {
+                            "account_number": "44578"
+                        },
+                        "account_number": "4457"
+                    },
+                    "4458-Taxes sur le chiffre d'affaires \u00e0 r\u00e9gulariser ou en attente (PASSIF)": {
+                        "TVA r\u00e9cup\u00e9r\u00e9e  d'avance": {
+                            "account_number": "44584"
+                        },
+                        "Taxes sur le chiffre d'affaires sur factures \u00e0 \u00e9tablir": {
+                            "account_number": "44587"
+                        }
+                    }
+                },
+                "Obligations cautionn\u00e9es": {
+                    "account_number": "446"
+                },
+                "Autres imp\u00f4ts, taxes et versements assimil\u00e9s": {
+                    "account_number": "447"
+                },
+                "Quotas d'\u00e9mission \u00e0 acqu\u00e9rir": {
+                    "account_number": "449"
+                }
+            },
+            "45-Groupe et associ\u00e9s (PASSIF)": {
+                "Groupe (PASSIF)": {
+                    "account_number": "451"
+                },
+                "Associ\u00e9s - Comptes courants (PASSIF)": {
+                    "Principal (PASSIF)": {
+                        "account_number": "4551"
+                    },
+                    "Int\u00e9r\u00eats courus (PASSIF)": {
+                        "account_number": "4558"
+                    },
+                    "account_number": "455"
+                },
+                "Associ\u00e9s - Op\u00e9rations sur le capital (PASSIF)": {
+                    "456-Associ\u00e9s - Comptes d'apport en soci\u00e9t\u00e9": {
+                        "Apports en nature": {
+                            "account_number": "45611"
+                        },
+                        "Apports en num\u00e9raire": {
+                            "account_number": "45615"
+                        },
+                        "account_number": "4561"
+                    },
+                    "Associ\u00e9s - Versements re\u00e7us sur augmentation de capital": {
+                        "account_number": "4563"
+                    },
+                    "Associ\u00e9s - Versements anticip\u00e9s": {
+                        "account_number": "4564"
+                    },
+                    "Actionnaires d\u00e9faillants": {
+                        "account_number": "4566"
+                    },
+                    "Associ\u00e9s - Capital \u00e0 rembourser": {
+                        "account_number": "4567"
+                    }
+                },
+                "Associ\u00e9s - Dividendes \u00e0 payer": {
+                    "account_number": "457"
+                },
+                "Associ\u00e9s - Op\u00e9rations faites en commun et en GIE": {
+                    "Op\u00e9rations courantes": {
+                        "account_number": "4581"
+                    },
+                    "Int\u00e9r\u00eats courus": {
+                        "account_number": "4588"
+                    },
+                    "account_number": "458"
+                }
+            },
+            "46-D\u00e9biteurs divers et cr\u00e9diteurs divers (PASSIF)": {
+                "Dettes sur acquisitions de valeurs mobili\u00e8res de placement": {
+                    "account_number": "464"
+                },
+                "467-Autres comptes d\u00e9biteurs ou cr\u00e9diteurs (PASSIF)": {},
+                "468-Divers - Charges \u00e0 payer et produits \u00e0 recevoir (PASSIF)": {
+                    "Charges \u00e0 payer": {
+                        "account_number": "4686"
+                    }
+                }
+            },
+            "47-Comptes transitoires ou d'attente (PASSIF)": {
+                "471-Comptes d'attente (PASSIF)": {
+                    "account_type": "Temporary"
+                },
+                "Diff\u00e9rences de conversion (PASSIF)": {
+                    "Augmentation des cr\u00e9ances": {
+                        "account_number": "4771"
+                    },
+                    "Diminution des dettes": {
+                        "account_number": "4772"
+                    },
+                    "Diff\u00e9rences compens\u00e9es par couverture de change": {
+                        "account_number": "4778"
+                    },
+                    "account_number": "477"
+                },
+                "478-Autres comptes transitoires (PASSIF)": {
+                    "Diff\u00e9rences d'\u00e9valuation sur instruments de tr\u00e9sorerie (PASSIF)": {
+                        "account_number": "4787"
+                    }
+                }
+            },
+            "48-Comptes de r\u00e9gularisation (PASSIF)": {
+                "Produits constat\u00e9s d'avance": {
+                    "account_number": "487"
+                },
+                "448-Comptes de r\u00e9partition p\u00e9riodique des charges et des produits (PASSIF)": {
+                    "Produits": {
+                        "account_number": "4887"
+                    }
+                }
+            }
+        },
+        "Comptes Financiers": {
+            "root_type": "Asset",
+            "Valeurs mobili\u00e8res de placement": {
+                "Parts dans des entreprises li\u00e9es": {
+                    "account_number": "501"
+                },
+                "Actions propres": {
+                    "Actions destin\u00e9es \u00e0 \u00eatre attribu\u00e9es aux employ\u00e9s et affect\u00e9es \u00e0 des plans d\u00e9termin\u00e9s": {
+                        "account_number": "5021"
+                    },
+                    "Actions disponibles pour \u00eatre attribu\u00e9es aux employ\u00e9s ou pour la r\u00e9gularisation des cours de bourse": {
+                        "account_number": "5022"
+                    },
+                    "account_number": "502"
+                },
+                "Actions": {
+                    "Titres cot\u00e9s": {
+                        "account_number": "5031"
+                    },
+                    "Titres non cot\u00e9s": {
+                        "account_number": "5035"
+                    },
+                    "account_number": "503"
+                },
+                "Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {
+                    "account_number": "504"
+                },
+                "Obligations et bons \u00e9mis par la soci\u00e9t\u00e9 et rachet\u00e9s par elle": {
+                    "account_number": "505"
+                },
+                "Obligations": {
+                    "Titres cot\u00e9s": {
+                        "account_number": "5061"
+                    },
+                    "Titres non cot\u00e9s": {
+                        "account_number": "5065"
+                    },
+                    "account_number": "506"
+                },
+                "Bons du Tr\u00e9sor et bons de caisse \u00e0 court terme": {
+                    "account_number": "507"
+                },
+                "Autres valeurs mobili\u00e8res de placement et autres cr\u00e9ances assimil\u00e9es": {
+                    "Autres valeurs mobili\u00e8res": {
+                        "account_number": "5081"
+                    },
+                    "Bons de souscription": {
+                        "account_number": "5082"
+                    },
+                    "Int\u00e9r\u00eats courus sur obligations, bons et valeurs assimil\u00e9es": {
+                        "account_number": "5088"
+                    },
+                    "account_number": "508"
+                },
+                "Versements restant \u00e0 effectuer sur valeurs mobili\u00e8res de placement non lib\u00e9r\u00e9es": {
+                    "account_number": "509"
+                },
+                "account_number": "50"
+            },
+            "Banques, \u00e9tablissements financiers et assimil\u00e9s": {
+                "Valeurs \u00e0 l'encaissement": {
+                    "Coupons \u00e9chus \u00e0 l'encaissement": {
+                        "account_number": "5111"
+                    },
+                    "Ch\u00e8ques \u00e0 encaisser": {
+                        "account_number": "5112"
+                    },
+                    "Effets \u00e0 l'encaissement": {
+                        "account_number": "5113"
+                    },
+                    "Effets \u00e0 l'escompte": {
+                        "account_number": "5114"
+                    },
+                    "account_number": "511"
+                },
+                "Banques": {
+                    "account_type": "Bank",
+                    "Comptes en monnaie nationale": {
+                        "account_type": "Bank",
+                        "account_number": "5121"
+                    },
+                    "Comptes en devises": {
+                        "account_type": "Bank",
+                        "account_number": "5124"
+                    },
+                    "account_number": "512"
+                },
+                "Ch\u00e8ques postaux": {
+                    "account_number": "514"
+                },
+                "\"Caisses\" du Tr\u00e9sor et des \u00e9tablissements publics": {
+                    "account_number": "515"
+                },
+                "Soci\u00e9t\u00e9s de bourse": {
+                    "account_number": "516"
+                },
+                "Autres organismes financiers": {
+                    "account_number": "517"
+                },
+                "Int\u00e9r\u00eats courus": {
+                    "Int\u00e9r\u00eats courus \u00e0 payer": {
+                        "account_number": "5181"
+                    },
+                    "Int\u00e9r\u00eats courus \u00e0 recevoir": {
+                        "account_number": "5188"
+                    },
+                    "account_number": "518"
+                },
+                "Concours bancaires courants": {
+                    "Cr\u00e9dit de mobilisation des cr\u00e9ances commerciales (CMCC)": {
+                        "account_number": "5191"
+                    },
+                    "Mobilisation de cr\u00e9ances n\u00e9es \u00e0 l'\u00e9tranger": {
+                        "account_number": "5193"
+                    },
+                    "Int\u00e9r\u00eats courus sur concours bancaires courants": {
+                        "account_number": "5198"
+                    },
+                    "account_number": "519"
+                },
+                "account_number": "51"
+            },
+            "Instruments de tr\u00e9sorerie": {
+                "is_group": 1,
+                "account_number": "52"
+            },
+            "Caisse": {
+                "account_type": "Cash",
+                "Caisse si\u00e8ge social": {
+                    "account_type": "Cash",
+                    "Caisse en monnaie nationale": {
+                        "account_type": "Cash",
+                        "account_number": "5311"
+                    },
+                    "Caisse en devises": {
+                        "account_type": "Cash",
+                        "account_number": "5314"
+                    },
+                    "account_number": "531"
+                },
+                "Caisse succursale (ou usine) A": {
+                    "account_type": "Cash",
+                    "account_number": "532"
+                },
+                "Caisse succursale (ou usine) B": {
+                    "account_type": "Cash",
+                    "account_number": "533"
+                },
+                "account_number": "53"
+            },
+            "R\u00e9gies d'avance et accr\u00e9ditifs": {
+                "is_group": 1,
+                "account_number": "54"
+            },
+            "Virements internes": {
+                "is_group": 1,
+                "account_number": "58"
+            },
+            "D\u00e9pr\u00e9ciations des comptes financiers": {
+                "D\u00e9pr\u00e9ciations des valeurs mobili\u00e8res de placement": {
+                    "Actions": {
+                        "account_number": "5903"
+                    },
+                    "Autres titres conf\u00e9rant un droit de propri\u00e9t\u00e9": {
+                        "account_number": "5904"
+                    },
+                    "Obligations": {
+                        "account_number": "5906"
+                    },
+                    "Autres valeurs mobili\u00e8res de placement et cr\u00e9ances assimil\u00e9es": {
+                        "account_number": "5908"
+                    },
+                    "account_number": "590"
+                },
+                "account_number": "59"
+            },
+            "account_number": "5"
+        },
+        "Comptes de Charges": {
+            "root_type": "Expense",
+            "Achats (sauf 603)": {
+                "Achats stock\u00e9s - Mati\u00e8res premi\u00e8res (et fournitures)": {
+                    "account_type": "Cost of Goods Sold",
+                    "Mati\u00e8res (ou groupe) A": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6011"
+                    },
+                    "Mati\u00e8res (ou groupe) B": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6012"
+                    },
+                    "Fournitures A, B, C...": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6017"
+                    },
+                    "account_number": "601"
+                },
+                "Achats stock\u00e9s - Autres approvisionnements": {
+                    "account_type": "Cost of Goods Sold",
+                    "Mati\u00e8res consommables": {
+                        "account_type": "Cost of Goods Sold",
+                        "Mati\u00e8res (ou groupe) C": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60211"
+                        },
+                        "Mati\u00e8res (ou groupe) D": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60212"
+                        },
+                        "account_number": "6021"
+                    },
+                    "Fournitures consommables": {
+                        "account_type": "Cost of Goods Sold",
+                        "Combustibles": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60221"
+                        },
+                        "Produits d'entretien": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60222"
+                        },
+                        "Fournitures d'atelier et d'usine": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60223"
+                        },
+                        "Fournitures de magasin": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60224"
+                        },
+                        "Fournitures de bureau": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60225"
+                        },
+                        "account_number": "6022"
+                    },
+                    "Emballages": {
+                        "account_type": "Cost of Goods Sold",
+                        "Emballages perdus": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60261"
+                        },
+                        "Emballages r\u00e9cup\u00e9rables non identifiables": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60265"
+                        },
+                        "Emballages \u00e0 usage mixte": {
+                            "account_type": "Cost of Goods Sold",
+                            "account_number": "60267"
+                        },
+                        "account_number": "6026"
+                    },
+                    "account_number": "602"
+                },
+                "Variations des stocks (approvisionnements et marchandises)": {
+                    "account_type": "Stock Adjustment",
+                    "Variation des stocks de mati\u00e8res premi\u00e8res (et fournitures)": {
+                        "account_type": "Stock Adjustment",
+                        "account_number": "6031"
+                    },
+                    "Variation des stocks des autres approvisionnements": {
+                        "account_type": "Stock Adjustment",
+                        "account_number": "6032"
+                    },
+                    "Variation des stocks de marchandises": {
+                        "account_type": "Stock Adjustment",
+                        "account_number": "6037"
+                    },
+                    "account_number": "603"
+                },
+                "Achats d'\u00e9tudes et prestations de service": {
+                    "account_type": "Cost of Goods Sold",
+                    "account_number": "604"
+                },
+                "Achats de mat\u00e9riel, \u00e9quipements et travaux": {
+                    "account_type": "Cost of Goods Sold",
+                    "account_number": "605"
+                },
+                "Achats non stock\u00e9s de mati\u00e8res et founitures": {
+                    "account_type": "Cost of Goods Sold",
+                    "Fournitures non stockables (eau, \u00e9nergie...)": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6061"
+                    },
+                    "Fournitures d'entretien et de petit \u00e9quipement": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6063"
+                    },
+                    "Fournitures administratives": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6064"
+                    },
+                    "Autres mati\u00e8res et fournitures": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6068"
+                    },
+                    "account_number": "606"
+                },
+                "Achats de marchandises": {
+                    "account_type": "Cost of Goods Sold",
+                    "Marchandises (ou groupe) A": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6071"
+                    },
+                    "Marchandises (ou groupe) B": {
+                        "account_type": "Cost of Goods Sold",
+                        "account_number": "6072"
+                    },
+                    "account_number": "607"
+                },
+                "(Compte r\u00e9serv\u00e9, le cas \u00e9ch\u00e9ant, \u00e0 la recapitulation des Frais accessoires incorpor\u00e9s aux achats)": {
+                    "account_type": "Expenses Included In Valuation",
+                    "account_number": "608"
+                },
+                "Rabais, remises et ristournes obtenus sur achats": {
+                    "Rabais, remises et ristournes obtenus sur achats - de mati\u00e8res premi\u00e8res (et fournitures)": {
+                        "account_number": "6091"
+                    },
+                    "Rabais, remises et ristournes obtenus sur achats - d'autres approvisionnements stock\u00e9s": {
+                        "account_number": "6092"
+                    },
+                    "Rabais, remises et ristournes obtenus sur achats - d'\u00e9tudes et prestations de services": {
+                        "account_number": "6094"
+                    },
+                    "Rabais, remises et ristournes obtenus sur achats - de mat\u00e9riel, \u00e9quipements et travaux": {
+                        "account_number": "6095"
+                    },
+                    "Rabais, remises et ristournes obtenus sur achats - d'approvisionnements non stock\u00e9s": {
+                        "account_number": "6096"
+                    },
+                    "Rabais, remises et ristournes obtenus sur achats - de marchandises": {
+                        "account_number": "6097"
+                    },
+                    "Rabais, remises et ristournes non affect\u00e9s": {
+                        "account_number": "6098"
+                    },
+                    "account_number": "609"
+                },
+                "account_number": "60"
+            },
+            "Services ext\u00e9rieurs": {
+                "Sous-traitance g\u00e9n\u00e9rale": {
+                    "account_number": "611"
+                },
+                "Redevances de cr\u00e9dit-bail": {
+                    "Cr\u00e9dit-bail mobilier": {
+                        "account_number": "6122"
+                    },
+                    "Cr\u00e9dit-bail immobilier": {
+                        "account_number": "6125"
+                    },
+                    "account_number": "612"
+                },
+                "Locations": {
+                    "Locations immobili\u00e8res": {
+                        "account_number": "6132"
+                    },
+                    "Locations mobili\u00e8res": {
+                        "account_number": "6135"
+                    },
+                    "Malis sur emballages": {
+                        "account_number": "6136"
+                    },
+                    "account_number": "613"
+                },
+                "Charges locatives et de copropri\u00e9t\u00e9": {
+                    "account_number": "614"
+                },
+                "Entretiens et r\u00e9parations": {
+                    "Entretiens et r\u00e9parations - sur biens immobiliers": {
+                        "account_number": "6152"
+                    },
+                    "Entretiens et r\u00e9parations - sur biens mobiliers": {
+                        "account_number": "6155"
+                    },
+                    "Maintenance": {
+                        "account_number": "6156"
+                    },
+                    "account_number": "615"
+                },
+                "Primes d'assurance": {
+                    "Multirisques": {
+                        "account_number": "6161"
+                    },
+                    "Assurance obligatoire dommage construction": {
+                        "account_number": "6162"
+                    },
+                    "Assurance-transport": {
+                        "Assurance-transport - sur achats": {
+                            "account_number": "61636"
+                        },
+                        "Assurance-transport - sur ventes": {
+                            "account_number": "61637"
+                        },
+                        "Assurance-transport - sur autres biens": {
+                            "account_number": "61638"
+                        },
+                        "account_number": "6163"
+                    },
+                    "Risques d'exploitation": {
+                        "account_number": "6164"
+                    },
+                    "Insolvabilit\u00e9 clients": {
+                        "account_number": "6165"
+                    },
+                    "account_number": "616"
+                },
+                "Etudes et recherches": {
+                    "account_number": "617"
+                },
+                "Divers": {
+                    "Documentation g\u00e9n\u00e9rale": {
+                        "account_number": "6181"
+                    },
+                    "Documentation technique": {
+                        "account_number": "6183"
+                    },
+                    "Frais de colloques, s\u00e9minaires, conf\u00e9rences": {
+                        "account_number": "6185"
+                    },
+                    "account_number": "618"
+                },
+                "Rabais, remises et ristournes obtenus sur services ext\u00e9rieurs": {
+                    "account_number": "619"
+                },
+                "account_number": "61"
+            },
+            "Autres services ext\u00e9rieurs": {
+                "Personnel ext\u00e9rieur \u00e0 l'entreprise": {
+                    "Personnel int\u00e9rimaire": {
+                        "account_number": "6211"
+                    },
+                    "Personnel d\u00e9tach\u00e9 ou pr\u00eat\u00e9 \u00e0 l'entreprise": {
+                        "account_number": "6214"
+                    },
+                    "account_number": "621"
+                },
+                "R\u00e9mun\u00e9rations d'interm\u00e9diaires et honoraires": {
+                    "Commissions et courtages sur achats": {
+                        "account_number": "6221"
+                    },
+                    "Commissions et courtages sur ventes": {
+                        "account_number": "6222"
+                    },
+                    "R\u00e9mun\u00e9rations des transitaires": {
+                        "account_number": "6224"
+                    },
+                    "R\u00e9mun\u00e9rations d'affacturage": {
+                        "account_number": "6225"
+                    },
+                    "Honoraires": {
+                        "account_number": "6226"
+                    },
+                    "Frais d'actes et de contentieux": {
+                        "account_number": "6227"
+                    },
+                    "Divers": {
+                        "account_number": "6228"
+                    },
+                    "account_number": "622"
+                },
+                "Publicit\u00e9, publications, relations publiques": {
+                    "Annonces et insertions": {
+                        "account_number": "6231"
+                    },
+                    "Echantillons": {
+                        "account_number": "6232"
+                    },
+                    "Foires et expositions": {
+                        "account_number": "6233"
+                    },
+                    "Cadeaux \u00e0 la client\u00e8le": {
+                        "account_number": "6234"
+                    },
+                    "Primes": {
+                        "account_number": "6235"
+                    },
+                    "Catalogues et imprim\u00e9s": {
+                        "account_number": "6236"
+                    },
+                    "Publications": {
+                        "account_number": "6237"
+                    },
+                    "Divers (pourboires, dons courants...)": {
+                        "account_number": "6238"
+                    },
+                    "account_number": "623"
+                },
+                "Transports de biens et transports collectifs du personnel": {
+                    "Transports sur achats": {
+                        "account_number": "6241"
+                    },
+                    "Transports sur ventes": {
+                        "account_type": "Chargeable",
+                        "account_number": "6242"
+                    },
+                    "Transports entre \u00e9tablissements ou chantiers": {
+                        "account_number": "6243"
+                    },
+                    "Transports administratifs": {
+                        "account_number": "6244"
+                    },
+                    "Transports collectifs du personnel": {
+                        "account_number": "6247"
+                    },
+                    "Divers": {
+                        "account_number": "6248"
+                    },
+                    "account_number": "624"
+                },
+                "D\u00e9placements, missions et r\u00e9ceptions": {
+                    "Voyages et d\u00e9placements": {
+                        "account_number": "6251"
+                    },
+                    "Frais de d\u00e9m\u00e9nagement": {
+                        "account_number": "6255"
+                    },
+                    "Missions": {
+                        "account_number": "6256"
+                    },
+                    "R\u00e9ceptions": {
+                        "account_number": "6257"
+                    },
+                    "account_number": "625"
+                },
+                "Frais postaux et de t\u00e9l\u00e9communications": {
+                    "account_number": "626"
+                },
+                "Services bancaires et assimil\u00e9s": {
+                    "Frais sur titres (achat, vente, garde)": {
+                        "account_number": "6271"
+                    },
+                    "Commissions et frais sur \u00e9mission d'emprunts": {
+                        "account_number": "6272"
+                    },
+                    "Frais sur effets": {
+                        "account_number": "6275"
+                    },
+                    "Location de coffres": {
+                        "account_number": "6276"
+                    },
+                    "Autres frais et commissions sur prestations de services": {
+                        "account_number": "6278"
+                    },
+                    "account_number": "627"
+                },
+                "Divers": {
+                    "Concours divers (cotisations...)": {
+                        "account_number": "6281"
+                    },
+                    "Frais de recrutement de personnel": {
+                        "account_number": "6284"
+                    },
+                    "account_number": "628"
+                },
+                "Rabais, remises et ristournes obtenus sur autres services ext\u00e9rieurs": {
+                    "account_number": "629"
+                },
+                "account_number": "62"
+            },
+            "Imp\u00f4ts, taxes et versements assimil\u00e9s": {
+                "Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (administrations des imp\u00f4ts)": {
+                    "Taxes sur les salaires": {
+                        "account_number": "6311"
+                    },
+                    "Taxe d'apprentissage": {
+                        "account_number": "6312"
+                    },
+                    "Participation des employeurs \u00e0 la formation professionnelle continue": {
+                        "account_number": "6313"
+                    },
+                    "Cotisation pour d\u00e9faut d'investissement obligatoire dans la construction": {
+                        "account_number": "6314"
+                    },
+                    "Autres": {
+                        "account_number": "6318"
+                    },
+                    "account_number": "631"
+                },
+                "Imp\u00f4ts, taxes et versements assimil\u00e9s sur r\u00e9mun\u00e9rations (autres organismes)": {
+                    "Versement de transport": {
+                        "account_number": "6331"
+                    },
+                    "Allocations logement": {
+                        "account_number": "6332"
+                    },
+                    "Participation des employeurs \u00e0 la formation professionnelle continue": {
+                        "account_number": "6333"
+                    },
+                    "Participation des employeurs \u00e0 l'effort de construction": {
+                        "account_number": "6334"
+                    },
+                    "Versements lib\u00e9ratoires ouvrant droit \u00e0 l'\u00e9xon\u00e9ration de la taxe d'apprentissage": {
+                        "account_number": "6335"
+                    },
+                    "Autres": {
+                        "account_number": "6338"
+                    },
+                    "account_number": "633"
+                },
+                "Autres imp\u00f4ts, taxes et versements assimil\u00e9s (administrations des imp\u00f4ts)": {
+                    "Imp\u00f4ts directs (sauf imp\u00f4ts sur les b\u00e9n\u00e9fices)": {
+                        "Contribution \u00e9conomique territoriale": {
+                            "account_number": "63511"
+                        },
+                        "Taxes fonci\u00e8res": {
+                            "account_number": "63512"
+                        },
+                        "Autres imp\u00f4ts locaux": {
+                            "account_number": "63513"
+                        },
+                        "Taxe sur les v\u00e9hicules des soci\u00e9t\u00e9s": {
+                            "account_number": "63514"
+                        },
+                        "account_number": "6351"
+                    },
+                    "Taxes sur le chiffre d'affaires non r\u00e9cup\u00e9rables": {
+                        "account_number": "6352"
+                    },
+                    "Imp\u00f4ts indirects": {
+                        "account_number": "6353"
+                    },
+                    "Droits d'enregistrement et de timbre": {
+                        "Droits de mutation": {
+                            "account_number": "63541"
+                        },
+                        "account_number": "6354"
+                    },
+                    "Autres droits": {
+                        "account_number": "6358"
+                    },
+                    "account_number": "635"
+                },
+                "Autres imp\u00f4ts, taxes et versements assimil\u00e9s (autres organismes)": {
+                    "Contribution sociale de solidarit\u00e9 \u00e0 la charge des soci\u00e9t\u00e9s": {
+                        "account_number": "6371"
+                    },
+                    "Taxes per\u00e7ues par les organismes publics internationaux": {
+                        "account_number": "6372"
+                    },
+                    "Imp\u00f4ts et taxes exigibles \u00e0 l'\u00e9tranger": {
+                        "account_number": "6374"
+                    },
+                    "Taxes diverses": {
+                        "account_number": "6378"
+                    },
+                    "account_number": "637"
+                },
+                "account_number": "63"
+            },
+            "Charges de personnel": {
+                "R\u00e9mun\u00e9rations du personnel": {
+                    "Salaires, appointements": {
+                        "account_number": "6411"
+                    },
+                    "Cong\u00e9s pay\u00e9s": {
+                        "account_number": "6412"
+                    },
+                    "Primes et gratifications": {
+                        "account_number": "6413"
+                    },
+                    "Indemnit\u00e9s et avantages divers": {
+                        "account_number": "6414"
+                    },
+                    "Suppl\u00e9ment familial": {
+                        "account_number": "6415"
+                    },
+                    "account_number": "641"
+                },
+                "R\u00e9mun\u00e9ration du travail de l'exploitant": {
+                    "account_number": "644"
+                },
+                "Charges de s\u00e9curit\u00e9 sociale et de pr\u00e9voyance": {
+                    "Cotisations \u00e0 l'URSSAF": {
+                        "account_number": "6451"
+                    },
+                    "Cotisations aux mutuelles": {
+                        "account_number": "6452"
+                    },
+                    "Cotisations aux caisses de retraites": {
+                        "account_number": "6453"
+                    },
+                    "Cotisations aux ASSEDIC": {
+                        "account_number": "6454"
+                    },
+                    "account_number": "645"
+                },
+                "Cotisations sociales personnelles de l'exploitant": {
+                    "account_number": "646"
+                },
+                "Autres charges sociales": {
+                    "is_group": 1,
+                    "account_number": "647"
+                },
+                "Autres charges de personnel": {
+                    "account_number": "648"
+                },
+                "account_number": "64"
+            },
+            "Autres charges de gestion courante": {
+                "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
+                    "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {
+                        "account_number": "6511"
+                    },
+                    "Droits d'auteur et de reproduction": {
+                        "account_number": "6516"
+                    },
+                    "Autres droits et valeurs similaires": {
+                        "account_number": "6518"
+                    },
+                    "account_number": "651"
+                },
+                "Jetons de pr\u00e9sence": {
+                    "account_number": "653"
+                },
+                "Pertes sur cr\u00e9ances irr\u00e9couvrables": {
+                    "Cr\u00e9ances de l'exercice": {
+                        "account_number": "6541"
+                    },
+                    "Cr\u00e9ances des exercices ant\u00e9rieurs": {
+                        "account_number": "6544"
+                    },
+                    "account_number": "654"
+                },
+                "Quotes-parts de r\u00e9sultat sur op\u00e9rations faites en commun": {
+                    "Quote-part de b\u00e9n\u00e9fice transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {
+                        "account_number": "6551"
+                    },
+                    "Quote-part de perte support\u00e9e (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {
+                        "account_number": "6555"
+                    },
+                    "account_number": "655"
+                },
+                "Pertes de change sur cr\u00e9ances et dettes commerciales": {
+                    "account_number": "656"
+                },
+                "Charges diverses de gestion courante": {
+                    "account_number": "658"
+                },
+                "account_number": "65"
+            },
+            "Charges financi\u00e8res": {
+                "Charges d'int\u00e9r\u00eats": {
+                    "Int\u00e9r\u00eats des emprunts et dettes": {
+                        "Int\u00e9r\u00eats des emprunts et dettes - des emprunts et dettes assimil\u00e9es": {
+                            "account_number": "66116"
+                        },
+                        "Int\u00e9r\u00eats des emprunts et dettes - des dettes rattach\u00e9es \u00e0 des participations": {
+                            "account_number": "66117"
+                        },
+                        "account_number": "6611"
+                    },
+                    "Charges de la fiducie, r\u00e9sultat de la p\u00e9riode": {
+                        "account_number": "6612"
+                    },
+                    "Int\u00e9r\u00eats des comptes courants et des d\u00e9p\u00f4ts cr\u00e9diteurs": {
+                        "account_number": "6615"
+                    },
+                    "Int\u00e9r\u00eats bancaires et sur op\u00e9rations de financement (escompte...)": {
+                        "account_number": "6616"
+                    },
+                    "Int\u00e9r\u00eats des obligations cautionn\u00e9es": {
+                        "account_number": "6617"
+                    },
+                    "Int\u00e9r\u00eats des autres dettes": {
+                        "Int\u00e9r\u00eats des autres dettes - des dettes commerciales": {
+                            "account_number": "66181"
+                        },
+                        "Int\u00e9r\u00eats des autres dettes - des dettes diverses": {
+                            "account_number": "66188"
+                        },
+                        "account_number": "6618"
+                    },
+                    "account_number": "661"
+                },
+                "Pertes sur cr\u00e9ances li\u00e9es \u00e0 des participations": {
+                    "account_number": "664"
+                },
+                "Escomptes accord\u00e9s": {
+                    "account_number": "665"
+                },
+                "Pertes de change financi\u00e8res": {
+                    "account_type": "Round Off",
+                    "account_number": "666"
+                },
+                "Charges nettes sur cessions de valeurs mobili\u00e8res de placement": {
+                    "account_number": "667"
+                },
+                "Autres charges financi\u00e8res": {
+                    "account_number": "668"
+                },
+                "account_number": "66"
+            },
+            "Charges exceptionnelles": {
+                "Charges exceptionnelles sur op\u00e9rations de gestion": {
+                    "P\u00e9nalit\u00e9s sur march\u00e9s (et d\u00e9dits pay\u00e9s sur achats et ventes)": {
+                        "account_number": "6711"
+                    },
+                    "P\u00e9nalit\u00e9s, amendes fiscales et p\u00e9nales": {
+                        "account_number": "6712"
+                    },
+                    "Dons, lib\u00e9ralit\u00e9s": {
+                        "account_number": "6713"
+                    },
+                    "Cr\u00e9ances devenues irr\u00e9couvrables dans l'exercice": {
+                        "account_number": "6714"
+                    },
+                    "Subventions accord\u00e9es": {
+                        "account_number": "6715"
+                    },
+                    "Rappel d'imp\u00f4ts (autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices)": {
+                        "account_number": "6717"
+                    },
+                    "Autres charges exceptionnelles sur op\u00e9rations de gestion": {
+                        "account_number": "6718"
+                    },
+                    "account_number": "671"
+                },
+                "(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les charges sur exercices ant\u00e9rieurs)": {
+                    "account_number": "672"
+                },
+                "Op\u00e9rations de constitution ou liquidation des fiducies": {
+                    "Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {
+                        "account_number": "6741"
+                    },
+                    "Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {
+                        "account_number": "6742"
+                    },
+                    "account_number": "674"
+                },
+                "Valeurs comptables des \u00e9l\u00e9ments d'actif c\u00e9d\u00e9s": {
+                    "Immobilisations incorporelles": {
+                        "account_number": "6751"
+                    },
+                    "Immobilisations corporelles": {
+                        "account_number": "6752"
+                    },
+                    "Immobilisations financi\u00e8res": {
+                        "account_number": "6756"
+                    },
+                    "Autres \u00e9l\u00e9ments d'actif": {
+                        "account_number": "6758"
+                    },
+                    "account_number": "675"
+                },
+                "Autres charges exceptionnelles": {
+                    "Mali provenant de clauses d'indexation": {
+                        "account_number": "6781"
+                    },
+                    "Lots": {
+                        "account_number": "6782"
+                    },
+                    "Malis provenant du rachat par l'entreprise d'actions et obligations \u00e9mises par elles-m\u00eame": {
+                        "account_number": "6783"
+                    },
+                    "Charges exceptionnelles diverses": {
+                        "account_number": "6788"
+                    },
+                    "account_number": "678"
+                },
+                "account_number": "67"
+            },
+            "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions": {
+                "account_type": "Depreciation",
+                "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges d'exploitation": {
+                    "account_type": "Depreciation",
+                    "Dotations aux amortissements sur immobilisations incorporelles et corporelles": {
+                        "account_type": "Depreciation",
+                        "Immobilisations incorporelles": {
+                            "account_type": "Depreciation",
+                            "account_number": "68111"
+                        },
+                        "Immobilisations corporelles": {
+                            "account_type": "Depreciation",
+                            "account_number": "68112"
+                        },
+                        "account_number": "6811"
+                    },
+                    "Dotations aux amortissements des charges d'exploitation \u00e0 r\u00e9partir": {
+                        "account_type": "Depreciation",
+                        "account_number": "6812"
+                    },
+                    "Dotations aux provisions d'exploitation": {
+                        "account_type": "Depreciation",
+                        "account_number": "6815"
+                    },
+                    "Dotations aux d\u00e9pr\u00e9ciations des immobilisations incorporelles et corporelles": {
+                        "account_type": "Depreciation",
+                        "Immobilisations incorporelles": {
+                            "account_type": "Depreciation",
+                            "account_number": "68161"
+                        },
+                        "Immobilisations corporelles": {
+                            "account_type": "Depreciation",
+                            "account_number": "68162"
+                        },
+                        "account_number": "6816"
+                    },
+                    "Dotations pour d\u00e9pr\u00e9ciations des actifs circulants": {
+                        "account_type": "Depreciation",
+                        "Stocks et en-cours": {
+                            "account_type": "Depreciation",
+                            "account_number": "68173"
+                        },
+                        "Cr\u00e9ances": {
+                            "account_type": "Depreciation",
+                            "account_number": "68174"
+                        },
+                        "account_number": "6817"
+                    },
+                    "account_number": "681"
+                },
+                "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges financi\u00e8res": {
+                    "account_type": "Depreciation",
+                    "Dotations aux amortissements des primes de remboursement des obligations": {
+                        "account_type": "Depreciation",
+                        "account_number": "6861"
+                    },
+                    "Dotations aux provisions financi\u00e8res": {
+                        "account_type": "Depreciation",
+                        "account_number": "6865"
+                    },
+                    "Dotations aux d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
+                        "account_type": "Depreciation",
+                        "Immobilisations financi\u00e8res": {
+                            "account_type": "Depreciation",
+                            "account_number": "68662"
+                        },
+                        "Valeurs mobili\u00e8res de placement": {
+                            "account_type": "Depreciation",
+                            "account_number": "68665"
+                        },
+                        "account_number": "6866"
+                    },
+                    "Autres dotations": {
+                        "account_type": "Depreciation",
+                        "account_number": "6868"
+                    },
+                    "account_number": "686"
+                },
+                "Dotations aux amortissements, d\u00e9pr\u00e9ciations et provisions - Charges exceptionnelles": {
+                    "account_type": "Depreciation",
+                    "Dotations aux amortissements exceptionnels des immobilisations": {
+                        "account_type": "Depreciation",
+                        "account_number": "6871"
+                    },
+                    "Dotations aux provisions r\u00e9glement\u00e9es (immobilisations)": {
+                        "account_type": "Depreciation",
+                        "Amortissements d\u00e9rogatoires": {
+                            "account_type": "Depreciation",
+                            "account_number": "68725"
+                        },
+                        "account_number": "6872"
+                    },
+                    "Dotations aux provisions r\u00e9glement\u00e9es (stocks)": {
+                        "account_type": "Depreciation",
+                        "account_number": "6873"
+                    },
+                    "Dotations aux autres provisions r\u00e9glement\u00e9es": {
+                        "account_type": "Depreciation",
+                        "account_number": "6874"
+                    },
+                    "Dotations aux provisions exceptionnelles": {
+                        "account_type": "Depreciation",
+                        "account_number": "6875"
+                    },
+                    "Dotations aux d\u00e9pr\u00e9ciations exceptionnelles": {
+                        "account_type": "Depreciation",
+                        "account_number": "6876"
+                    },
+                    "account_number": "687"
+                },
+                "account_number": "68"
+            },
+            "Participation des salari\u00e9s, imp\u00f4ts sur les b\u00e9n\u00e9fices et assimil\u00e9s": {
+                "Participation des salari\u00e9s aux r\u00e9sultats": {
+                    "account_number": "691"
+                },
+                "Imp\u00f4ts sur les b\u00e9n\u00e9fices": {
+                    "Imp\u00f4ts dus en France": {
+                        "account_number": "6951"
+                    },
+                    "Contribution additionnelle \u00e0 l'imp\u00f4t sur les b\u00e9n\u00e9fices": {
+                        "account_number": "6952"
+                    },
+                    "Imp\u00f4ts dus \u00e0 l'\u00e9tranger": {
+                        "account_number": "6954"
+                    },
+                    "account_number": "695"
+                },
+                "Suppl\u00e9ments d'imp\u00f4ts sur les soci\u00e9t\u00e9s, li\u00e9s aux distributions": {
+                    "account_number": "696"
+                },
+                "Int\u00e9gration fiscale": {
+                    "Int\u00e9gration fiscale - Charges": {
+                        "account_number": "6981"
+                    },
+                    "Int\u00e9gration fiscale - Produits": {
+                        "account_number": "6989"
+                    },
+                    "account_number": "698"
+                },
+                "Produits - Report en arri\u00e8re des d\u00e9ficits": {
+                    "account_number": "699"
+                },
+                "account_number": "69"
+            },
+            "account_number": "6"
+        },
+        "Comptes de Produits": {
+            "root_type": "Income",
+            "Ventes de produits fabriqu\u00e9s, prestations de services, marchandises": {
+                "Ventes de produits finis": {
+                    "Produits finis (ou groupe) A": {
+                        "account_number": "7011"
+                    },
+                    "Produits (ou groupe) B": {
+                        "account_number": "7012"
+                    },
+                    "account_number": "701"
+                },
+                "Ventes de produits interm\u00e9diaires": {
+                    "account_number": "702"
+                },
+                "Ventes de produits r\u00e9siduels": {
+                    "account_number": "703"
+                },
+                "Travaux": {
+                    "Travaux de cat\u00e9gorie (ou activit\u00e9) A": {
+                        "account_number": "7041"
+                    },
+                    "Travaux de cat\u00e9gorie (ou activit\u00e9) B": {
+                        "account_number": "7042"
+                    },
+                    "account_number": "704"
+                },
+                "Etudes": {
+                    "account_number": "705"
+                },
+                "Prestations de services": {
+                    "account_number": "706"
+                },
+                "Ventes de marchandises": {
+                    "Marchandises (ou groupe) A": {
+                        "account_number": "7071"
+                    },
+                    "Marchandises (ou groupe) B": {
+                        "account_number": "7072"
+                    },
+                    "account_number": "707"
+                },
+                "Produits des activit\u00e9s annexes": {
+                    "Produits des services exploit\u00e9s dans l'int\u00e9r\u00eat du personnel": {
+                        "account_number": "7081"
+                    },
+                    "Commissions et courtages": {
+                        "account_number": "7082"
+                    },
+                    "Locations diverses": {
+                        "account_number": "7083"
+                    },
+                    "Mise \u00e0 disposition de personnel factur\u00e9e": {
+                        "account_number": "7084"
+                    },
+                    "Ports et frais accessoires factur\u00e9s": {
+                        "account_number": "7085"
+                    },
+                    "Bonis sur reprises d'emballages consign\u00e9s": {
+                        "account_number": "7086"
+                    },
+                    "Bonifications obtenues des clients et primes sur ventes": {
+                        "account_number": "7087"
+                    },
+                    "Autres produits d'activit\u00e9s annexes (cessions d'approvisionnements...)": {
+                        "account_number": "7088"
+                    },
+                    "account_number": "708"
+                },
+                "Rabais, remises et ristournes accord\u00e9s par l'entreprise": {
+                    "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits finis": {
+                        "account_number": "7091"
+                    },
+                    "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de produits interm\u00e9diaires": {
+                        "account_number": "7092"
+                    },
+                    "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur travaux": {
+                        "account_number": "7094"
+                    },
+                    "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur \u00e9tudes": {
+                        "account_number": "7095"
+                    },
+                    "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur prestations de services": {
+                        "account_number": "7096"
+                    },
+                    "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur ventes de marchandises": {
+                        "account_number": "7097"
+                    },
+                    "Rabais, remises et ristournes accord\u00e9s par l'entreprise - sur produits des activit\u00e9s annexes": {
+                        "account_number": "7098"
+                    },
+                    "account_number": "709"
+                },
+                "account_number": "70"
+            },
+            "Production stock\u00e9e (ou d\u00e9stockage)": {
+                "Variation des stocks (en-cours de production, produits)": {
+                    "Variation des en-cours de production de biens": {
+                        "Produits en cours": {
+                            "account_number": "71331"
+                        },
+                        "Travaux en cours": {
+                            "account_number": "71335"
+                        },
+                        "account_number": "7133"
+                    },
+                    "Variation des en-cours de production de services": {
+                        "Etudes en cours": {
+                            "account_number": "71341"
+                        },
+                        "Prestations de services en cours": {
+                            "account_number": "71345"
+                        },
+                        "account_number": "7134"
+                    },
+                    "Variation des stocks de produits": {
+                        "Produits interm\u00e9diaires": {
+                            "account_number": "71351"
+                        },
+                        "Produits finis": {
+                            "account_number": "71355"
+                        },
+                        "Produits r\u00e9siduels": {
+                            "account_number": "71358"
+                        },
+                        "account_number": "7135"
+                    },
+                    "account_number": "713"
+                },
+                "account_number": "71"
+            },
+            "Production immobilis\u00e9e": {
+                "Immobilisations incorporelles": {
+                    "account_number": "721"
+                },
+                "Immobilisations corporelles": {
+                    "account_number": "722"
+                },
+                "account_number": "72"
+            },
+            "Subventions d'exploitation": {
+                "is_group": 1,
+                "account_number": "74"
+            },
+            "Autres produits de gestion courante": {
+                "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels, droits et valeurs similaires": {
+                    "Redevances pour concessions, brevets, licences, marques, proc\u00e9d\u00e9s, logiciels": {
+                        "account_number": "7511"
+                    },
+                    "Droits d'auteur et de reproduction": {
+                        "account_number": "7516"
+                    },
+                    "Autres droits et valeurs similaires": {
+                        "account_number": "7518"
+                    },
+                    "account_number": "751"
+                },
+                "Revenus des immeubles non affect\u00e9s aux activit\u00e9s professionnelles": {
+                    "account_number": "752"
+                },
+                "Jetons de pr\u00e9sence et r\u00e9mun\u00e9rations d'administrateurs, g\u00e9rants...": {
+                    "account_number": "753"
+                },
+                "Ristournes per\u00e7ues des coop\u00e9ratives (provenant des exc\u00e9dents)": {
+                    "account_number": "754"
+                },
+                "Quotes-parts de r\u00e9sultats sur op\u00e9rations faites en commun": {
+                    "Quote-part de perte transf\u00e9r\u00e9e (comptabilit\u00e9 du g\u00e9rant)": {
+                        "account_number": "7551"
+                    },
+                    "Quote-part de b\u00e9n\u00e9fice attribu\u00e9 (comptabilit\u00e9 des associ\u00e9s non g\u00e9rants)": {
+                        "account_number": "7555"
+                    },
+                    "account_number": "755"
+                },
+                "Gains de change sur cr\u00e9ances et dettes commerciales": {
+                    "account_number": "756"
+                },
+                "Produits divers de gestion courante": {
+                    "account_number": "758"
+                },
+                "account_number": "75"
+            },
+            "Produits financiers": {
+                "Produits de participations": {
+                    "Revenus des titres de participation": {
+                        "account_number": "7611"
+                    },
+                    "Produits de la fiducie, r\u00e9sultat de la p\u00e9riode": {
+                        "account_number": "7612"
+                    },
+                    "Revenus sur autres formes de participation": {
+                        "account_number": "7616"
+                    },
+                    "Revenus des cr\u00e9ances rattach\u00e9es \u00e0 des participations": {
+                        "account_number": "7617"
+                    },
+                    "account_number": "761"
+                },
+                "Produits des autres immobilisations financi\u00e8res": {
+                    "Revenus des titres immobilis\u00e9s": {
+                        "account_number": "7621"
+                    },
+                    "Revenus des pr\u00eats": {
+                        "account_number": "7626"
+                    },
+                    "Revenus des cr\u00e9ances immobilis\u00e9es": {
+                        "account_number": "7627"
+                    },
+                    "account_number": "762"
+                },
+                "Revenus des autres cr\u00e9ances": {
+                    "Revenus des cr\u00e9ances commerciales": {
+                        "account_number": "7631"
+                    },
+                    "Revenus des cr\u00e9ances diverses": {
+                        "account_number": "7638"
+                    },
+                    "account_number": "763"
+                },
+                "Revenus des valeurs mobili\u00e8res de placement": {
+                    "account_number": "764"
+                },
+                "Escomptes obtenus": {
+                    "account_number": "765"
+                },
+                "Gains de change financi\u00e8res": {
+                    "account_type": "Round Off",
+                    "account_number": "766"
+                },
+                "Produits nets sur cessions de valeurs mobili\u00e8res de placement": {
+                    "account_number": "767"
+                },
+                "Autres produits financiers": {
+                    "account_number": "768"
+                },
+                "account_number": "76"
+            },
+            "Produits exceptionnels": {
+                "Produits exceptionnels sur op\u00e9rations de gestion": {
+                    "D\u00e9dits et p\u00e9nalit\u00e9s per\u00e7us sur achats et sur ventes": {
+                        "account_number": "7711"
+                    },
+                    "Lib\u00e9ralit\u00e9s re\u00e7ues": {
+                        "account_number": "7713"
+                    },
+                    "Rentr\u00e9es sur cr\u00e9ances amorties": {
+                        "account_number": "7714"
+                    },
+                    "Subventions d'\u00e9quilibre": {
+                        "account_number": "7715"
+                    },
+                    "D\u00e9gr\u00e8vements d'imp\u00f4ts autres qu'imp\u00f4ts sur les b\u00e9n\u00e9fices": {
+                        "account_number": "7717"
+                    },
+                    "Autres produits exceptionnels sur op\u00e9rations de gestion": {
+                        "account_number": "7718"
+                    },
+                    "account_number": "771"
+                },
+                "(Compte \u00e0 la disposition des entit\u00e9s pour enregistrer, en cours d'exercice, les Produits sur exercices ant\u00e9rieurs)": {
+                    "account_number": "772"
+                },
+                "Op\u00e9rations de constitution ou liquidation des fiducies": {
+                    "Op\u00e9rations li\u00e9es \u00e0 la constitution de la fiducie - transfert des \u00e9l\u00e9ments": {
+                        "account_number": "7741"
+                    },
+                    "Op\u00e9rations li\u00e9es \u00e0 la liquidation de la fiducie": {
+                        "account_number": "7742"
+                    },
+                    "account_number": "774"
+                },
+                "Produits des cessions d'\u00e9l\u00e9ments d'actif": {
+                    "Immobilisations incorporelles": {
+                        "account_number": "7751"
+                    },
+                    "Immobilisations corporelles": {
+                        "account_number": "7752"
+                    },
+                    "Immobilisations financi\u00e8res": {
+                        "account_number": "7756"
+                    },
+                    "Autres \u00e9l\u00e9ments d'actif": {
+                        "account_number": "7758"
+                    },
+                    "account_number": "775"
+                },
+                "Quote-part des subventions d'investissement vir\u00e9e au r\u00e9sultat de l'exercice": {
+                    "account_number": "777"
+                },
+                "Autres produits exceptionnels": {
+                    "Bonis provenant de clauses d'indexation": {
+                        "account_number": "7781"
+                    },
+                    "Lots": {
+                        "account_number": "7782"
+                    },
+                    "Bonis provenant du rachat par l'entreprise d'actions et d'obligations \u00e9mises par elle-m\u00eame": {
+                        "account_number": "7783"
+                    },
+                    "Produits exceptionnels divers": {
+                        "account_number": "7788"
+                    },
+                    "account_number": "778"
+                },
+                "account_number": "77"
+            },
+            "Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions": {
+                "Reprises sur amortissements, d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits d'exploitation)": {
+                    "Reprises sur amortissements des immobilisations incorporelles et corporelles": {
+                        "Immobilisations incorporelles": {
+                            "account_number": "78111"
+                        },
+                        "Immobilisations corporelles": {
+                            "account_number": "78112"
+                        },
+                        "account_number": "7811"
+                    },
+                    "Reprises sur provisions d'exploitation": {
+                        "account_number": "7815"
+                    },
+                    "Reprises sur d\u00e9pr\u00e9ciations des immobilisations corporelles et incorporelles": {
+                        "Immobilisations incorporelles": {
+                            "account_number": "78161"
+                        },
+                        "Immobilisations corporelles": {
+                            "account_number": "78162"
+                        },
+                        "account_number": "7816"
+                    },
+                    "Reprises sur d\u00e9pr\u00e9ciations des actifs circulants": {
+                        "Stocks et en-cours": {
+                            "account_number": "78173"
+                        },
+                        "Cr\u00e9ances": {
+                            "account_number": "78174"
+                        },
+                        "account_number": "7817"
+                    },
+                    "account_number": "781"
+                },
+                "Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits financiers)": {
+                    "Reprises sur provisions financi\u00e8res": {
+                        "account_number": "7865"
+                    },
+                    "Reprises sur d\u00e9pr\u00e9ciations des \u00e9l\u00e9ments financiers": {
+                        "Immobilisations financi\u00e8res": {
+                            "account_number": "78662"
+                        },
+                        "Valeurs mobili\u00e8res de placement": {
+                            "account_number": "78665"
+                        },
+                        "account_number": "7866"
+                    },
+                    "account_number": "786"
+                },
+                "Reprises sur d\u00e9pr\u00e9ciations et provisions (\u00e0 inscrire dans les produits exceptionnels)": {
+                    "Reprises sur provisions r\u00e9glement\u00e9es (immobilisations)": {
+                        "Amortissements d\u00e9rogatoires": {
+                            "account_number": "78725"
+                        },
+                        "Provision sp\u00e9ciale de r\u00e9\u00e9valuation": {
+                            "account_number": "78726"
+                        },
+                        "Plus-values r\u00e9investies": {
+                            "account_number": "78727"
+                        },
+                        "account_number": "7872"
+                    },
+                    "Reprises sur provisions r\u00e9glement\u00e9es (stocks)": {
+                        "account_number": "7873"
+                    },
+                    "Reprises sur autres provisions r\u00e9glement\u00e9es": {
+                        "account_number": "7874"
+                    },
+                    "Reprises sur provisions exceptionnelles": {
+                        "account_number": "7875"
+                    },
+                    "Reprises sur d\u00e9pr\u00e9ciations exceptionnelles": {
+                        "account_number": "7876"
+                    },
+                    "account_number": "787"
+                },
+                "account_number": "78"
+            },
+            "Transferts de charges": {
+                "Transferts de charges d'exploitation": {
+                    "account_number": "791"
+                },
+                "Transferts de charges financi\u00e8res": {
+                    "account_number": "796"
+                },
+                "Transferts de charges exceptionnelles": {
+                    "account_number": "797"
+                },
+                "account_number": "79"
+            },
+            "account_number": "7"
+        }
+    }
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 2735b1c..703e93c 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -257,9 +257,10 @@
   },
   {
    "default": "1",
+   "description": "If enabled, ledger entries will be posted for change amount in POS transactions",
    "fieldname": "post_change_gl_entries",
    "fieldtype": "Check",
-   "label": "Post Ledger Entries for Given Change"
+   "label": "Create Ledger Entries for Change Amount"
   }
  ],
  "icon": "icon-cog",
@@ -267,7 +268,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-05-25 12:34:05.858669",
+ "modified": "2021-06-17 20:26:03.721202",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
index f7d471b..78e7ff6 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -15,7 +15,7 @@
 	},
 
 	refresh: function (frm) {
-		frappe.require("assets/js/bank-reconciliation-tool.min.js", () =>
+		frappe.require("bank-reconciliation-tool.bundle.js", () =>
 			frm.trigger("make_reconciliation_tool")
 		);
 		frm.upload_statement_button = frm.page.set_secondary_action(
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
index 016f29a..0a2e0bc 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
@@ -320,7 +320,7 @@
 			return;
 		}
 
-		frappe.require("/assets/js/data_import_tools.min.js", () => {
+		frappe.require("data_import_tools.bundle.js", () => {
 			frm.import_preview = new frappe.data_import.ImportPreview({
 				wrapper: frm.get_field("import_preview").$wrapper,
 				doctype: frm.doc.reference_doctype,
diff --git a/erpnext/accounts/doctype/c_form/c_form.py b/erpnext/accounts/doctype/c_form/c_form.py
index fd86ed4..cfe28f3 100644
--- a/erpnext/accounts/doctype/c_form/c_form.py
+++ b/erpnext/accounts/doctype/c_form/c_form.py
@@ -54,7 +54,7 @@
 			frappe.throw(_("Please enter atleast 1 invoice in the table"))
 
 	def set_total_invoiced_amount(self):
-		total = sum([flt(d.grand_total) for d in self.get('invoices')])
+		total = sum(flt(d.grand_total) for d in self.get('invoices'))
 		frappe.db.set(self, 'total_invoiced_amount', total)
 
 	@frappe.whitelist()
diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.py b/erpnext/accounts/doctype/coupon_code/coupon_code.py
index 7829c93..55c1193 100644
--- a/erpnext/accounts/doctype/coupon_code/coupon_code.py
+++ b/erpnext/accounts/doctype/coupon_code/coupon_code.py
@@ -14,7 +14,7 @@
 
 		if not self.coupon_code:
 			if self.coupon_type == "Promotional":
-				self.coupon_code =''.join([i for i in self.coupon_name if not i.isdigit()])[0:8].upper()
+				self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
 			elif self.coupon_type == "Gift Card":
 				self.coupon_code = frappe.generate_hash()[:10].upper()
 		
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index 95d2ee5..b73d8bf 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -42,7 +42,7 @@
 					record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)))
 
 	def calculate_total_amount(self):
-		self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices])
+		self.total_amount = sum(flt(d.outstanding_amount) for d in self.invoices)
 
 	def on_submit(self):
 		self.update_sales_invoice()
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index d76641d..957a50f 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -194,19 +194,19 @@
 	refresh_field("accounts");
 }
 
-erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
-	onload: function() {
+erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Controller {
+	onload() {
 		this.load_defaults();
 		this.setup_queries();
 		this.setup_balance_formatter();
 		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
-	},
+	}
 
-	onload_post_render: function() {
+	onload_post_render() {
 		cur_frm.get_field("accounts").grid.set_multiple_add("account");
-	},
+	}
 
-	load_defaults: function() {
+	load_defaults() {
 		//this.frm.show_print_first = true;
 		if(this.frm.doc.__islocal && this.frm.doc.company) {
 			frappe.model.set_default_values(this.frm.doc);
@@ -216,9 +216,9 @@
 			var posting_date = this.frm.doc.posting_date;
 			if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
 		}
-	},
+	}
 
-	setup_queries: function() {
+	setup_queries() {
 		var me = this;
 
 		me.frm.set_query("account", "accounts", function(doc, cdt, cdn) {
@@ -324,9 +324,9 @@
 		});
 
 
-	},
+	}
 
-	setup_balance_formatter: function() {
+	setup_balance_formatter() {
 		const formatter = function(value, df, options, doc) {
 			var currency = frappe.meta.get_field_currency(df, doc);
 			var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
@@ -337,9 +337,9 @@
 		};
 		this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
 		this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
-	},
+	}
 
-	reference_name: function(doc, cdt, cdn) {
+	reference_name(doc, cdt, cdn) {
 		var d = frappe.get_doc(cdt, cdn);
 
 		if(d.reference_name) {
@@ -351,9 +351,9 @@
 				this.get_outstanding('Journal Entry', d.reference_name, doc.company, d);
 			}
 		}
-	},
+	}
 
-	get_outstanding: function(doctype, docname, company, child, due_date) {
+	get_outstanding(doctype, docname, company, child, due_date) {
 		var me = this;
 		var args = {
 			"doctype": doctype,
@@ -375,9 +375,9 @@
 				}
 			}
 		});
-	},
+	}
 
-	accounts_add: function(doc, cdt, cdn) {
+	accounts_add(doc, cdt, cdn) {
 		var row = frappe.get_doc(cdt, cdn);
 		$.each(doc.accounts, function(i, d) {
 			if(d.account && d.party && d.party_type) {
@@ -400,9 +400,9 @@
 		cur_frm.cscript.update_totals(doc);
 
 		erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'accounts');
-	},
+	}
 
-});
+};
 
 cur_frm.script_manager.make(erpnext.accounts.JournalEntry);
 
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index ed1bd28..937597b 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -196,8 +196,8 @@
 					frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account))
 
 	def check_credit_limit(self):
-		customers = list(set([d.party for d in self.get("accounts")
-			if d.party_type=="Customer" and d.party and flt(d.debit) > 0]))
+		customers = list(set(d.party for d in self.get("accounts")
+			if d.party_type=="Customer" and d.party and flt(d.debit) > 0))
 		if customers:
 			from erpnext.selling.doctype.customer.customer import check_credit_limit
 			for customer in customers:
diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
index 88667d7..bff6422 100644
--- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
+++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
@@ -21,7 +21,7 @@
 			idx += 1
 
 	def validate(self):
-		total = sum([flt(d.percentage_allocation) for d in self.get("percentages")])
+		total = sum(flt(d.percentage_allocation) for d in self.get("percentages"))
 
 		if flt(total, 2) != 100.0:
 			frappe.throw(_("Percentage Allocation should be equal to 100%") + \
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
index b2e8626..a8c07d6 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
@@ -49,7 +49,15 @@
 				doc: frm.doc,
 				btn: $(btn_primary),
 				method: "make_invoices",
-				freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
+				freeze: 1,
+				freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
+				callback: function(r) {
+					if (r.message.length == 1) {
+						frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type]));
+					} else if (r.message.length < 50) {
+						frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type]));
+					}
+				}
 			});
 		});
 
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index 29dc96e..d76d909 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -216,7 +216,8 @@
 	return names
 
 def publish(index, total, doctype):
-	if total < 5: return
+	if total < 50:
+		return
 	frappe.publish_realtime(
 		"opening_invoice_creation_progress",
 		dict(
@@ -241,4 +242,3 @@
 
 	return accounts[0].name
 
-
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index d3ac3a6..439b1ed 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -7,6 +7,8 @@
 
 frappe.ui.form.on('Payment Entry', {
 	onload: function(frm) {
+		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
+
 		if(frm.doc.__islocal) {
 			if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
 			if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index b195332..b6b2bef 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -310,7 +310,7 @@
 		for k, v in no_oustanding_refs.items():
 			frappe.msgprint(
 				_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
-					.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
+					.format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount"))
 				+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
 				title=_("Warning"), indicator="orange")
 
@@ -511,7 +511,7 @@
 	def set_unallocated_amount(self):
 		self.unallocated_amount = 0
 		if self.party:
-			total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
+			total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
 			if self.payment_type == "Receive" \
 				and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \
 				and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate):
@@ -536,7 +536,7 @@
 		else:
 			self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax)
 
-		total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
+		total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
 
 		self.difference_amount = flt(self.difference_amount - total_deductions,
 			self.precision("difference_amount"))
@@ -552,8 +552,8 @@
 		if ((self.payment_type=="Pay" and self.party_type=="Customer")
 				or (self.payment_type=="Receive" and self.party_type=="Supplier")):
 
-			total_negative_outstanding = sum([abs(flt(d.outstanding_amount))
-				for d in self.get("references") if flt(d.outstanding_amount) < 0])
+			total_negative_outstanding = sum(abs(flt(d.outstanding_amount))
+				for d in self.get("references") if flt(d.outstanding_amount) < 0)
 
 			paid_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
 			additional_charges = sum([flt(d.amount) for d in self.deductions])
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index d1523cd..c71a62d 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -34,8 +34,8 @@
 	}
 });
 
-erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
-	onload: function() {
+erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationController extends frappe.ui.form.Controller {
+	onload() {
 		var me = this;
 
 		this.frm.set_query("party", function() {
@@ -84,18 +84,18 @@
 				frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
 			}
 		};
-	},
+	}
 
-	refresh: function() {
+	refresh() {
 		this.frm.disable_save();
 		this.toggle_primary_action();
-	},
+	}
 
-	onload_post_render: function() {
+	onload_post_render() {
 		this.toggle_primary_action();
-	},
+	}
 
-	party: function() {
+	party() {
 		var me = this
 		if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
 			return frappe.call({
@@ -112,9 +112,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	get_unreconciled_entries: function() {
+	get_unreconciled_entries() {
 		var me = this;
 		return this.frm.call({
 			doc: me.frm.doc,
@@ -125,9 +125,9 @@
 			}
 		});
 
-	},
+	}
 
-	reconcile: function() {
+	reconcile() {
 		var me = this;
 		var show_dialog = me.frm.doc.payments.filter(d => d.difference_amount && !d.difference_account);
 
@@ -209,9 +209,9 @@
 		} else {
 			this.reconcile_payment_entries();
 		}
-	},
+	}
 
-	reconcile_payment_entries: function() {
+	reconcile_payment_entries() {
 		var me = this;
 
 		return this.frm.call({
@@ -222,9 +222,9 @@
 				me.toggle_primary_action();
 			}
 		});
-	},
+	}
 
-	set_invoice_options: function() {
+	set_invoice_options() {
 		var me = this;
 		var invoices = [];
 
@@ -244,9 +244,9 @@
 		}
 
 		refresh_field("payments");
-	},
+	}
 
-	toggle_primary_action: function() {
+	toggle_primary_action() {
 		if ((this.frm.doc.payments || []).length) {
 			this.frm.fields_dict.reconcile.$input
 				&& this.frm.fields_dict.reconcile.$input.addClass("btn-primary");
@@ -260,6 +260,6 @@
 		}
 	}
 
-});
+};
 
-$.extend(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm}));
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 4689787..438951d 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -112,7 +112,7 @@
 		if not data_of_completed_requests:
 			return self.grand_total
 
-		request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests])
+		request_amounts = sum(json.loads(d).get('request_amount') for d in data_of_completed_requests)
 		return request_amounts
 
 	def on_cancel(self):
diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
index 80e3348..39627eb 100644
--- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
+++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
@@ -26,7 +26,7 @@
 	def check_duplicate_terms(self):
 		terms = []
 		for term in self.terms:
-			term_info = (term.credit_days, term.credit_months, term.due_date_based_on)
+			term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
 			if term_info in terms:
 				frappe.msgprint(
 					_('The Payment Term at row {0} is possibly a duplicate.').format(term.idx),
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index b0a5b04..9cfb478 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -142,7 +142,7 @@
 				sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
 				sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
 			from `tabGL Entry` t1, `tabAccount` t2
-			where t1.account = t2.name and t2.report_type = 'Profit and Loss'
+			where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
 			and t2.docstatus < 2 and t2.company = %s
 			and t1.posting_date between %s and %s
 			group by t1.account, {dimension_fields}
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 493bd44..181e9f8 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -4,18 +4,18 @@
 {% include 'erpnext/selling/sales_common.js' %};
 frappe.provide("erpnext.accounts");
 
-erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
+erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController {
 	setup(doc) {
 		this.setup_posting_date_time_check();
-		this._super(doc);
-	},
+		super.setup(doc);
+	}
 
-	company: function() {
+	company() {
 		erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
-	},
+	}
 
 	onload(doc) {
-		this._super();
+		super.onload();
 		this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
 		if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
 			this.frm.script_manager.trigger("is_pos");
@@ -23,10 +23,10 @@
 		}
 
 		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
-	},
+	}
 
 	refresh(doc) {
-		this._super();
+		super.refresh();
 		if (doc.docstatus == 1 && !doc.is_return) {
 			this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
 			this.frm.page.set_inner_btn_group_as_primary(__('Create'));
@@ -36,13 +36,13 @@
 			this.frm.return_print_format = "Sales Invoice Return";
 			this.frm.set_value('consolidated_invoice', '');
 		}
-	},
+	}
 
-	is_pos: function() {
+	is_pos() {
 		this.set_pos_data();
-	},
+	}
 
-	set_pos_data: async function() {
+	async set_pos_data() {
 		if(this.frm.doc.is_pos) {
 			this.frm.set_value("allocate_advances_automatically", 0);
 			if(!this.frm.doc.company) {
@@ -69,7 +69,7 @@
 				}
 			}
 		}
-	},
+	}
 
 	customer() {
 		if (!this.frm.doc.customer) return
@@ -86,13 +86,13 @@
 			}, () => {
 				this.apply_pricing_rule();
 			});
-	},
+	}
 
-	amount: function(){
+	amount(){
 		this.write_off_outstanding_amount_automatically()
-	},
+	}
 
-	change_amount: function(){
+	change_amount(){
 		if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
 			this.calculate_write_off_amount();
 		}else {
@@ -101,16 +101,16 @@
 		}
 
 		this.frm.refresh_fields();
-	},
+	}
 
-	loyalty_amount: function(){
+	loyalty_amount(){
 		this.calculate_outstanding_amount();
 		this.frm.refresh_field("outstanding_amount");
 		this.frm.refresh_field("paid_amount");
 		this.frm.refresh_field("base_paid_amount");
-	},
+	}
 
-	write_off_outstanding_amount_automatically: function() {
+	write_off_outstanding_amount_automatically() {
 		if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
 			frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
 			// this will make outstanding amount 0
@@ -125,17 +125,17 @@
 
 		this.calculate_outstanding_amount(false);
 		this.frm.refresh_fields();
-	},
+	}
 
-	make_sales_return: function() {
+	make_sales_return() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return",
 			frm: cur_frm
 		})
-	},
-})
+	}
+}
 
-$.extend(cur_frm.cscript, new erpnext.selling.POSInvoiceController({ frm: cur_frm }))
+extend_cscript(cur_frm.cscript, new erpnext.selling.POSInvoiceController({ frm: cur_frm }))
 
 frappe.ui.form.on('POS Invoice', {
 	redeem_loyalty_points: function(frm) {
@@ -235,4 +235,4 @@
 				});
 		});
 	}
-});
\ No newline at end of file
+});
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index d23b952..b54d0e7 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -20,9 +20,9 @@
 class MultiplePricingRuleConflict(frappe.ValidationError): pass
 
 apply_on_table = {
-    'Item Code': 'items',
-    'Item Group': 'item_groups',
-    'Brand': 'brands'
+	'Item Code': 'items',
+	'Item Group': 'item_groups',
+	'Brand': 'brands'
 }
 
 def get_pricing_rules(args, doc=None):
@@ -183,7 +183,7 @@
 			condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
 				table=table,
 				field=field,
-				parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups])
+				parent_groups=", ".join(frappe.db.escape(d) for d in parent_groups)
 			)
 
 			frappe.flags.tree_conditions[key] = condition
@@ -264,7 +264,7 @@
 
 	# find pricing rule with highest priority
 	if pricing_rules:
-		max_priority = max([cint(p.priority) for p in pricing_rules])
+		max_priority = max(cint(p.priority) for p in pricing_rules)
 		if max_priority:
 			pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
 
@@ -272,14 +272,14 @@
 		pricing_rules = list(pricing_rules)
 
 	if len(pricing_rules) > 1:
-		rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules]))
+		rate_or_discount = list(set(d.rate_or_discount for d in pricing_rules))
 		if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
 			pricing_rules = list(filter(lambda x: x.for_price_list==args.price_list, pricing_rules)) \
 				or pricing_rules
 
 	if len(pricing_rules) > 1 and not args.for_shopping_cart:
 		frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
-			.format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict)
+			.format("\n".join(d.name for d in pricing_rules)), MultiplePricingRuleConflict)
 	elif pricing_rules:
 		return pricing_rules[0]
 
@@ -541,7 +541,7 @@
 
 def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
 	if pricing_rule_args:
-		items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item])
+		items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
 
 		for args in pricing_rule_args:
 			if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
@@ -589,4 +589,4 @@
 		elif transaction_type=='cancelled':
 			if coupon.used>0:
 				coupon.used=coupon.used-1
-				coupon.save(ignore_permissions=True)
\ No newline at end of file
+				coupon.save(ignore_permissions=True)
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index 7328f16..f8d191c 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -106,4 +106,4 @@
 		{{ terms_and_conditions }}
 	</div>
 	{% endif %}
-</div>
\ No newline at end of file
+</div>
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
index 27a5f50..295a3b8 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
@@ -286,7 +286,7 @@
   }
  ],
  "links": [],
- "modified": "2021-05-21 10:14:22.426672",
+ "modified": "2021-05-21 11:14:22.426672",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Process Statement Of Accounts",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index f58c8f4..7562418 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -4,10 +4,10 @@
 frappe.provide("erpnext.accounts");
 {% include 'erpnext/public/js/controllers/buying.js' %};
 
-erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
-	setup: function(doc) {
+erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.BuyingController {
+	setup(doc) {
 		this.setup_posting_date_time_check();
-		this._super(doc);
+		super.setup(doc);
 
 		// formatter for purchase invoice item
 		if(this.frm.doc.update_stock) {
@@ -25,14 +25,10 @@
 				}
 			};
 		});
-	},
+	}
 
-	company: function() {
-		erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
-	},
-
-	onload: function() {
-		this._super();
+	onload() {
+		super.onload();
 
 		if(!this.frm.doc.__islocal) {
 			// show credit_to in print format
@@ -48,11 +44,11 @@
 		}
 
 		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
-	},
+	}
 
-	refresh: function(doc) {
+	refresh(doc) {
 		const me = this;
-		this._super();
+		super.refresh();
 
 		hide_fields(this.frm.doc);
 		// Show / Hide button
@@ -161,26 +157,26 @@
 		}
 
 		this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
-	},
+	}
 
-	unblock_invoice: function() {
+	unblock_invoice() {
 		const me = this;
 		frappe.call({
 			'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.unblock_invoice',
 			'args': {'name': me.frm.doc.name},
 			'callback': (r) => me.frm.reload_doc()
 		});
-	},
+	}
 
-	block_invoice: function() {
+	block_invoice() {
 		this.make_comment_dialog_and_block_invoice();
-	},
+	}
 
-	change_release_date: function() {
+	change_release_date() {
 		this.make_dialog_and_set_release_date();
-	},
+	}
 
-	can_change_release_date: function(date) {
+	can_change_release_date(date) {
 		const diff = frappe.datetime.get_diff(date, frappe.datetime.nowdate());
 		if (diff < 0) {
 			frappe.throw(__('New release date should be in the future'));
@@ -188,9 +184,9 @@
 		} else {
 			return true;
 		}
-	},
+	}
 
-	make_comment_dialog_and_block_invoice: function(){
+	make_comment_dialog_and_block_invoice(){
 		const me = this;
 
 		const title = __('Block Invoice');
@@ -232,9 +228,9 @@
 		});
 
 		this.dialog.show();
-	},
+	}
 
-	make_dialog_and_set_release_date: function() {
+	make_dialog_and_set_release_date() {
 		const me = this;
 
 		const title = __('Set New Release Date');
@@ -263,17 +259,17 @@
 		});
 
 		this.dialog.show();
-	},
+	}
 
-	set_release_date: function(data) {
+	set_release_date(data) {
 		return frappe.call({
 			'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.change_release_date',
 			'args': data,
 			'callback': (r) => this.frm.reload_doc()
 		});
-	},
+	}
 
-	supplier: function() {
+	supplier() {
 		var me = this;
 
 		// Do not update if inter company reference is there as the details will already be updated
@@ -295,9 +291,9 @@
 				me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
 				me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
 			})
-	},
+	}
 
-	apply_tds: function(frm) {
+	apply_tds(frm) {
 		var me = this;
 
 		if (!me.frm.doc.apply_tds) {
@@ -307,9 +303,9 @@
 			me.frm.set_value("tax_withholding_category", me.frm.supplier_tds);
 			me.frm.set_df_property("tax_withholding_category", "hidden", 0);
 		}
-	},
+	}
 
-	credit_to: function() {
+	credit_to() {
 		var me = this;
 		if(this.frm.doc.credit_to) {
 			me.frm.call({
@@ -327,16 +323,16 @@
 				}
 			});
 		}
-	},
+	}
 
-	make_inter_company_invoice: function(frm) {
+	make_inter_company_invoice(frm) {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_inter_company_sales_invoice",
 			frm: frm
 		});
-	},
+	}
 
-	is_paid: function() {
+	is_paid() {
 		hide_fields(this.frm.doc);
 		if(cint(this.frm.doc.is_paid)) {
 			this.frm.set_value("allocate_advances_automatically", 0);
@@ -347,44 +343,44 @@
 		}
 		this.calculate_outstanding_amount();
 		this.frm.refresh_fields();
-	},
+	}
 
-	write_off_amount: function() {
+	write_off_amount() {
 		this.set_in_company_currency(this.frm.doc, ["write_off_amount"]);
 		this.calculate_outstanding_amount();
 		this.frm.refresh_fields();
-	},
+	}
 
-	paid_amount: function() {
+	paid_amount() {
 		this.set_in_company_currency(this.frm.doc, ["paid_amount"]);
 		this.write_off_amount();
 		this.frm.refresh_fields();
-	},
+	}
 
-	allocated_amount: function() {
+	allocated_amount() {
 		this.calculate_total_advance();
 		this.frm.refresh_fields();
-	},
+	}
 
-	items_add: function(doc, cdt, cdn) {
+	items_add(doc, cdt, cdn) {
 		var row = frappe.get_doc(cdt, cdn);
 		this.frm.script_manager.copy_from_first_row("items", row,
 			["expense_account", "cost_center", "project"]);
-	},
+	}
 
-	on_submit: function() {
+	on_submit() {
 		$.each(this.frm.doc["items"] || [], function(i, row) {
 			if(row.purchase_receipt) frappe.model.clear_doc("Purchase Receipt", row.purchase_receipt)
 		})
-	},
+	}
 
-	make_debit_note: function() {
+	make_debit_note() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_debit_note",
 			frm: cur_frm
 		})
-	},
-});
+	}
+};
 
 cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice);
 
@@ -569,5 +565,9 @@
 			frm: frm,
 			freeze_message: __("Creating Purchase Receipt ...")
 		})
-	}
+	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
 })
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index a714ac7..00ef7d5 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -175,9 +175,7 @@
    "hidden": 1,
    "label": "Title",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "naming_series",
@@ -189,9 +187,7 @@
    "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
    "print_hide": 1,
    "reqd": 1,
-   "set_only_once": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "set_only_once": 1
   },
   {
    "fieldname": "supplier",
@@ -203,9 +199,7 @@
    "options": "Supplier",
    "print_hide": 1,
    "reqd": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "bold": 1,
@@ -217,9 +211,7 @@
    "label": "Supplier Name",
    "oldfieldname": "supplier_name",
    "oldfieldtype": "Data",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fetch_from": "supplier.tax_id",
@@ -227,27 +219,21 @@
    "fieldtype": "Read Only",
    "label": "Tax Id",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "due_date",
    "fieldtype": "Date",
    "label": "Due Date",
    "oldfieldname": "due_date",
-   "oldfieldtype": "Date",
-   "show_days": 1,
-   "show_seconds": 1
+   "oldfieldtype": "Date"
   },
   {
    "default": "0",
    "fieldname": "is_paid",
    "fieldtype": "Check",
    "label": "Is Paid",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
@@ -255,25 +241,19 @@
    "fieldtype": "Check",
    "label": "Is Return (Debit Note)",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
    "fieldname": "apply_tds",
    "fieldtype": "Check",
    "label": "Apply Tax Withholding Amount",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break1",
    "fieldtype": "Column Break",
    "oldfieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -283,17 +263,13 @@
    "label": "Company",
    "options": "Company",
    "print_hide": 1,
-   "remember_last_selected_value": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "remember_last_selected_value": 1
   },
   {
    "fieldname": "cost_center",
    "fieldtype": "Link",
    "label": "Cost Center",
-   "options": "Cost Center",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Cost Center"
   },
   {
    "default": "Today",
@@ -305,9 +281,7 @@
    "oldfieldtype": "Date",
    "print_hide": 1,
    "reqd": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "fieldname": "posting_time",
@@ -316,8 +290,6 @@
    "no_copy": 1,
    "print_hide": 1,
    "print_width": "100px",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "100px"
   },
   {
@@ -326,9 +298,7 @@
    "fieldname": "set_posting_time",
    "fieldtype": "Check",
    "label": "Edit Posting Date and Time",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "amended_from",
@@ -340,58 +310,44 @@
    "oldfieldtype": "Link",
    "options": "Purchase Invoice",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "eval:doc.on_hold",
    "fieldname": "sb_14",
    "fieldtype": "Section Break",
-   "label": "Hold Invoice",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Hold Invoice"
   },
   {
    "default": "0",
    "fieldname": "on_hold",
    "fieldtype": "Check",
-   "label": "Hold Invoice",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Hold Invoice"
   },
   {
    "depends_on": "eval:doc.on_hold",
    "description": "Once set, this invoice will be on hold till the set date",
    "fieldname": "release_date",
    "fieldtype": "Date",
-   "label": "Release Date",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Release Date"
   },
   {
    "fieldname": "cb_17",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "depends_on": "eval:doc.on_hold",
    "fieldname": "hold_comment",
    "fieldtype": "Small Text",
-   "label": "Reason For Putting On Hold",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Reason For Putting On Hold"
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "bill_no",
    "fieldname": "supplier_invoice_details",
    "fieldtype": "Section Break",
-   "label": "Supplier Invoice Details",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Supplier Invoice Details"
   },
   {
    "fieldname": "bill_no",
@@ -399,15 +355,11 @@
    "label": "Supplier Invoice No",
    "oldfieldname": "bill_no",
    "oldfieldtype": "Data",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_15",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "bill_date",
@@ -416,17 +368,13 @@
    "no_copy": 1,
    "oldfieldname": "bill_date",
    "oldfieldtype": "Date",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "depends_on": "return_against",
    "fieldname": "returns",
    "fieldtype": "Section Break",
-   "label": "Returns",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Returns"
   },
   {
    "depends_on": "return_against",
@@ -436,34 +384,26 @@
    "no_copy": 1,
    "options": "Purchase Invoice",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "fieldname": "section_addresses",
    "fieldtype": "Section Break",
-   "label": "Address and Contact",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Address and Contact"
   },
   {
    "fieldname": "supplier_address",
    "fieldtype": "Link",
    "label": "Select Supplier Address",
    "options": "Address",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "address_display",
    "fieldtype": "Small Text",
    "label": "Address",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "contact_person",
@@ -471,67 +411,51 @@
    "in_global_search": 1,
    "label": "Contact Person",
    "options": "Contact",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "contact_display",
    "fieldtype": "Small Text",
    "label": "Contact",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "contact_mobile",
    "fieldtype": "Small Text",
    "label": "Mobile No",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "contact_email",
    "fieldtype": "Small Text",
    "label": "Contact Email",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "col_break_address",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "shipping_address",
    "fieldtype": "Link",
    "label": "Select Shipping Address",
    "options": "Address",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "shipping_address_display",
    "fieldtype": "Small Text",
    "label": "Shipping Address",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "fieldname": "currency_and_price_list",
    "fieldtype": "Section Break",
    "label": "Currency and Price List",
-   "options": "fa fa-tag",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-tag"
   },
   {
    "fieldname": "currency",
@@ -540,9 +464,7 @@
    "oldfieldname": "currency",
    "oldfieldtype": "Select",
    "options": "Currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "conversion_rate",
@@ -551,24 +473,18 @@
    "oldfieldname": "conversion_rate",
    "oldfieldtype": "Currency",
    "precision": "9",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break2",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "buying_price_list",
    "fieldtype": "Link",
    "label": "Price List",
    "options": "Price List",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "price_list_currency",
@@ -576,18 +492,14 @@
    "label": "Price List Currency",
    "options": "Currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "plc_conversion_rate",
    "fieldtype": "Float",
    "label": "Price List Exchange Rate",
    "precision": "9",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
@@ -596,15 +508,11 @@
    "label": "Ignore Pricing Rule",
    "no_copy": 1,
    "permlevel": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "sec_warehouse",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "depends_on": "update_stock",
@@ -613,9 +521,7 @@
    "fieldtype": "Link",
    "label": "Set Accepted Warehouse",
    "options": "Warehouse",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "depends_on": "update_stock",
@@ -625,15 +531,11 @@
    "label": "Rejected Warehouse",
    "no_copy": 1,
    "options": "Warehouse",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "col_break_warehouse",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "No",
@@ -641,33 +543,25 @@
    "fieldtype": "Select",
    "label": "Raw Materials Supplied",
    "options": "No\nYes",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "items_section",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
-   "options": "fa fa-shopping-cart",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-shopping-cart"
   },
   {
    "default": "0",
    "fieldname": "update_stock",
    "fieldtype": "Check",
    "label": "Update Stock",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "scan_barcode",
    "fieldtype": "Data",
-   "label": "Scan Barcode",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Scan Barcode"
   },
   {
    "allow_bulk_edit": 1,
@@ -677,56 +571,43 @@
    "oldfieldname": "entries",
    "oldfieldtype": "Table",
    "options": "Purchase Invoice Item",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "pricing_rule_details",
    "fieldtype": "Section Break",
-   "label": "Pricing Rules",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Pricing Rules"
   },
   {
    "fieldname": "pricing_rules",
    "fieldtype": "Table",
    "label": "Pricing Rule Detail",
    "options": "Pricing Rule Detail",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible_depends_on": "supplied_items",
    "fieldname": "raw_materials_supplied",
    "fieldtype": "Section Break",
-   "label": "Raw Materials Supplied",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Raw Materials Supplied"
   },
   {
+   "depends_on": "update_stock",
    "fieldname": "supplied_items",
    "fieldtype": "Table",
    "label": "Supplied Items",
-   "options": "Purchase Receipt Item Supplied",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "no_copy": 1,
+   "options": "Purchase Receipt Item Supplied"
   },
   {
    "fieldname": "section_break_26",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "fieldname": "total_qty",
    "fieldtype": "Float",
    "label": "Total Quantity",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_total",
@@ -734,9 +615,7 @@
    "label": "Total (Company Currency)",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_net_total",
@@ -746,24 +625,18 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_28",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "total",
    "fieldtype": "Currency",
    "label": "Total",
    "options": "currency",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "net_total",
@@ -773,56 +646,42 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "total_net_weight",
    "fieldtype": "Float",
    "label": "Total Net Weight",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "taxes_section",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
-   "options": "fa fa-money",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-money"
   },
   {
    "fieldname": "tax_category",
    "fieldtype": "Link",
    "label": "Tax Category",
    "options": "Tax Category",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_49",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "shipping_rule",
    "fieldtype": "Link",
    "label": "Shipping Rule",
    "options": "Shipping Rule",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "section_break_51",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "fieldname": "taxes_and_charges",
@@ -831,9 +690,7 @@
    "oldfieldname": "purchase_other_charges",
    "oldfieldtype": "Link",
    "options": "Purchase Taxes and Charges Template",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "taxes",
@@ -841,17 +698,13 @@
    "label": "Purchase Taxes and Charges",
    "oldfieldname": "purchase_tax_details",
    "oldfieldtype": "Table",
-   "options": "Purchase Taxes and Charges",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Purchase Taxes and Charges"
   },
   {
    "collapsible": 1,
    "fieldname": "sec_tax_breakup",
    "fieldtype": "Section Break",
-   "label": "Tax Breakup",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Tax Breakup"
   },
   {
    "fieldname": "other_charges_calculation",
@@ -860,17 +713,13 @@
    "no_copy": 1,
    "oldfieldtype": "HTML",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "totals",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
-   "options": "fa fa-money",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-money"
   },
   {
    "fieldname": "base_taxes_and_charges_added",
@@ -880,9 +729,7 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_taxes_and_charges_deducted",
@@ -892,9 +739,7 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_total_taxes_and_charges",
@@ -904,15 +749,11 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_40",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "taxes_and_charges_added",
@@ -922,9 +763,7 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "taxes_and_charges_deducted",
@@ -934,9 +773,7 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "total_taxes_and_charges",
@@ -944,18 +781,14 @@
    "label": "Total Taxes and Charges",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "discount_amount",
    "fieldname": "section_break_44",
    "fieldtype": "Section Break",
-   "label": "Additional Discount",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Additional Discount"
   },
   {
    "default": "Grand Total",
@@ -963,9 +796,7 @@
    "fieldtype": "Select",
    "label": "Apply Additional Discount On",
    "options": "\nGrand Total\nNet Total",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "base_discount_amount",
@@ -973,38 +804,28 @@
    "label": "Additional Discount Amount (Company Currency)",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_46",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "additional_discount_percentage",
    "fieldtype": "Float",
    "label": "Additional Discount Percentage",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "discount_amount",
    "fieldtype": "Currency",
    "label": "Additional Discount Amount",
    "options": "currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "section_break_49",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "fieldname": "base_grand_total",
@@ -1014,9 +835,7 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1026,9 +845,7 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1038,9 +855,7 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_in_words",
@@ -1050,17 +865,13 @@
    "oldfieldname": "in_words",
    "oldfieldtype": "Data",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break8",
    "fieldtype": "Column Break",
    "oldfieldtype": "Column Break",
    "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -1071,9 +882,7 @@
    "oldfieldname": "grand_total_import",
    "oldfieldtype": "Currency",
    "options": "currency",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1083,9 +892,7 @@
    "no_copy": 1,
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1095,9 +902,7 @@
    "no_copy": 1,
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "in_words",
@@ -1107,9 +912,7 @@
    "oldfieldname": "in_words_import",
    "oldfieldtype": "Data",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "total_advance",
@@ -1120,9 +923,7 @@
    "oldfieldtype": "Currency",
    "options": "party_account_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "outstanding_amount",
@@ -1133,18 +934,14 @@
    "oldfieldtype": "Currency",
    "options": "party_account_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "default": "0",
    "depends_on": "grand_total",
    "fieldname": "disable_rounded_total",
    "fieldtype": "Check",
-   "label": "Disable Rounded Total",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Disable Rounded Total"
   },
   {
    "collapsible": 1,
@@ -1152,26 +949,20 @@
    "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)",
    "fieldname": "payments_section",
    "fieldtype": "Section Break",
-   "label": "Payments",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Payments"
   },
   {
    "fieldname": "mode_of_payment",
    "fieldtype": "Link",
    "label": "Mode of Payment",
    "options": "Mode of Payment",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "cash_bank_account",
    "fieldtype": "Link",
    "label": "Cash/Bank Account",
-   "options": "Account",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Account"
   },
   {
    "fieldname": "clearance_date",
@@ -1179,15 +970,11 @@
    "label": "Clearance Date",
    "no_copy": 1,
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "col_br_payments",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "depends_on": "is_paid",
@@ -1196,9 +983,7 @@
    "label": "Paid Amount",
    "no_copy": 1,
    "options": "currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "base_paid_amount",
@@ -1207,9 +992,7 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
@@ -1217,9 +1000,7 @@
    "depends_on": "grand_total",
    "fieldname": "write_off",
    "fieldtype": "Section Break",
-   "label": "Write Off",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Write Off"
   },
   {
    "fieldname": "write_off_amount",
@@ -1227,9 +1008,7 @@
    "label": "Write Off Amount",
    "no_copy": 1,
    "options": "currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "base_write_off_amount",
@@ -1238,15 +1017,11 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_61",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1254,9 +1029,7 @@
    "fieldtype": "Link",
    "label": "Write Off Account",
    "options": "Account",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1264,9 +1037,7 @@
    "fieldtype": "Link",
    "label": "Write Off Cost Center",
    "options": "Cost Center",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
@@ -1276,17 +1047,13 @@
    "label": "Advance Payments",
    "oldfieldtype": "Section Break",
    "options": "fa fa-money",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
    "fieldname": "allocate_advances_automatically",
    "fieldtype": "Check",
-   "label": "Set Advances and Allocate (FIFO)",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Set Advances and Allocate (FIFO)"
   },
   {
    "depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1294,9 +1061,7 @@
    "fieldtype": "Button",
    "label": "Get Advances Paid",
    "oldfieldtype": "Button",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "advances",
@@ -1306,26 +1071,20 @@
    "oldfieldname": "advance_allocation_details",
    "oldfieldtype": "Table",
    "options": "Purchase Invoice Advance",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "eval:(!doc.is_return)",
    "fieldname": "payment_schedule_section",
    "fieldtype": "Section Break",
-   "label": "Payment Terms",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Payment Terms"
   },
   {
    "fieldname": "payment_terms_template",
    "fieldtype": "Link",
    "label": "Payment Terms Template",
-   "options": "Payment Terms Template",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Payment Terms Template"
   },
   {
    "fieldname": "payment_schedule",
@@ -1333,9 +1092,7 @@
    "label": "Payment Schedule",
    "no_copy": 1,
    "options": "Payment Schedule",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
@@ -1343,33 +1100,25 @@
    "fieldname": "terms_section_break",
    "fieldtype": "Section Break",
    "label": "Terms and Conditions",
-   "options": "fa fa-legal",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-legal"
   },
   {
    "fieldname": "tc_name",
    "fieldtype": "Link",
    "label": "Terms",
    "options": "Terms and Conditions",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "terms",
    "fieldtype": "Text Editor",
-   "label": "Terms and Conditions1",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Terms and Conditions1"
   },
   {
    "collapsible": 1,
    "fieldname": "printing_settings",
    "fieldtype": "Section Break",
-   "label": "Printing Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Printing Settings"
   },
   {
    "allow_on_submit": 1,
@@ -1377,9 +1126,7 @@
    "fieldtype": "Link",
    "label": "Letter Head",
    "options": "Letter Head",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "allow_on_submit": 1,
@@ -1387,15 +1134,11 @@
    "fieldname": "group_same_items",
    "fieldtype": "Check",
    "label": "Group same items",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_112",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "allow_on_submit": 1,
@@ -1407,18 +1150,14 @@
    "oldfieldtype": "Link",
    "options": "Print Heading",
    "print_hide": 1,
-   "report_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "report_hide": 1
   },
   {
    "fieldname": "language",
    "fieldtype": "Data",
    "label": "Print Language",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
@@ -1427,9 +1166,7 @@
    "label": "More Information",
    "oldfieldtype": "Section Break",
    "options": "fa fa-file-text",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "credit_to",
@@ -1440,9 +1177,7 @@
    "options": "Account",
    "print_hide": 1,
    "reqd": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "fieldname": "party_account_currency",
@@ -1452,9 +1187,7 @@
    "no_copy": 1,
    "options": "Currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "default": "No",
@@ -1464,9 +1197,7 @@
    "oldfieldname": "is_opening",
    "oldfieldtype": "Select",
    "options": "No\nYes",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "against_expense_account",
@@ -1476,15 +1207,11 @@
    "no_copy": 1,
    "oldfieldname": "against_expense_account",
    "oldfieldtype": "Small Text",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_63",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "Draft",
@@ -1493,9 +1220,7 @@
    "in_standard_filter": 1,
    "label": "Status",
    "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "inter_company_invoice_reference",
@@ -1504,9 +1229,7 @@
    "no_copy": 1,
    "options": "Sales Invoice",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "remarks",
@@ -1515,18 +1238,14 @@
    "no_copy": 1,
    "oldfieldname": "remarks",
    "oldfieldtype": "Text",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
    "fieldname": "subscription_section",
    "fieldtype": "Section Break",
    "label": "Subscription Section",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "allow_on_submit": 1,
@@ -1535,9 +1254,7 @@
    "fieldtype": "Date",
    "label": "From Date",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "allow_on_submit": 1,
@@ -1546,15 +1263,11 @@
    "fieldtype": "Date",
    "label": "To Date",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_114",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "auto_repeat",
@@ -1563,32 +1276,24 @@
    "no_copy": 1,
    "options": "Auto Repeat",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "allow_on_submit": 1,
    "depends_on": "eval: doc.auto_repeat",
    "fieldname": "update_auto_repeat_reference",
    "fieldtype": "Button",
-   "label": "Update Auto Repeat Reference",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Update Auto Repeat Reference"
   },
   {
    "collapsible": 1,
    "fieldname": "accounting_dimensions_section",
    "fieldtype": "Section Break",
-   "label": "Accounting Dimensions ",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Accounting Dimensions "
   },
   {
    "fieldname": "dimension_col_break",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "0",
@@ -1596,9 +1301,7 @@
    "fieldname": "is_internal_supplier",
    "fieldtype": "Check",
    "label": "Is Internal Supplier",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "tax_withholding_category",
@@ -1606,25 +1309,19 @@
    "hidden": 1,
    "label": "Tax Withholding Category",
    "options": "Tax Withholding Category",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "billing_address",
    "fieldtype": "Link",
    "label": "Select Billing Address",
-   "options": "Address",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Address"
   },
   {
    "fieldname": "billing_address_display",
    "fieldtype": "Small Text",
    "label": "Billing Address",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "project",
@@ -1638,9 +1335,7 @@
    "fieldname": "unrealized_profit_loss_account",
    "fieldtype": "Link",
    "label": "Unrealized Profit / Loss Account",
-   "options": "Account",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Account"
   },
   {
    "depends_on": "eval:doc.is_internal_supplier",
@@ -1649,9 +1344,7 @@
    "fieldname": "represents_company",
    "fieldtype": "Link",
    "label": "Represents Company",
-   "options": "Company",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Company"
   },
   {
    "depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
@@ -1663,8 +1356,6 @@
    "options": "Warehouse",
    "print_hide": 1,
    "print_width": "50px",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50px"
   },
   {
@@ -1692,7 +1383,7 @@
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-09 12:30:25.632109",
+ "modified": "2021-06-15 18:20:56.806195",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 0ee0bc7..45d89ad 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -400,6 +400,7 @@
 		# because updating ordered qty in bin depends upon updated ordered qty in PO
 		if self.update_stock == 1:
 			self.update_stock_ledger()
+			self.set_consumed_qty_in_po()
 			from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
 			update_serial_nos_after_submit(self, "items")
 
@@ -998,6 +999,7 @@
 		if self.update_stock == 1:
 			self.update_stock_ledger()
 			self.delete_auto_created_batches()
+			self.set_consumed_qty_in_po()
 
 		self.make_gl_entries_on_cancel()
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 723d151..ff433b9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -621,8 +621,10 @@
 		self.assertEqual(actual_qty_0, get_qty_after_transaction())
 
 	def test_subcontracting_via_purchase_invoice(self):
+		from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
+		update_backflush_based_on('BOM')
 		make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
 		make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
 			qty=100, basic_rate=100)
@@ -632,7 +634,7 @@
 
 		self.assertEqual(len(pi.get("supplied_items")), 2)
 
-		rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
+		rm_supp_cost = sum(d.amount for d in pi.get("supplied_items"))
 		self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
 
 	def test_rejected_serial_no(self):
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 10e1c73..29f7247 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -272,7 +272,7 @@
    "fieldname": "rate",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Rate ",
+   "label": "Rate",
    "oldfieldname": "import_rate",
    "oldfieldtype": "Currency",
    "options": "currency",
@@ -854,7 +854,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-03-30 09:02:39.256602",
+ "modified": "2021-06-16 19:33:51.099386",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index f813425..bb55651 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -5,17 +5,17 @@
 frappe.provide("erpnext.accounts");
 
 
-erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
-	setup: function(doc) {
+erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends erpnext.selling.SellingController {
+	setup(doc) {
 		this.setup_posting_date_time_check();
-		this._super(doc);
-	},
-	company: function() {
+		super.setup(doc);
+	}
+	company() {
 		erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
-	},
-	onload: function() {
+	}
+	onload() {
 		var me = this;
-		this._super();
+		super.onload();
 
 		this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
 		if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
@@ -35,11 +35,11 @@
 		}
 		erpnext.queries.setup_warehouse_query(this.frm);
 		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
-	},
+	}
 
-	refresh: function(doc, dt, dn) {
+	refresh(doc, dt, dn) {
 		const me = this;
-		this._super();
+		super.refresh();
 		if(cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) {
 			// hide new msgbox
 			cur_frm.msgbox.hide();
@@ -138,16 +138,16 @@
 				}, __('Create'));
 			}
 		}
-	},
+	}
 
-	make_maintenance_schedule: function() {
+	make_maintenance_schedule() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
 			frm: cur_frm
 		})
-	},
+	}
 
-	on_submit: function(doc, dt, dn) {
+	on_submit(doc, dt, dn) {
 		var me = this;
 
 		if (frappe.get_route()[0] != 'Form') {
@@ -157,9 +157,9 @@
 		$.each(doc["items"], function(i, row) {
 			if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
 		})
-	},
+	}
 
-	set_default_print_format: function() {
+	set_default_print_format() {
 		// set default print format to POS type or Credit Note
 		if(cur_frm.doc.is_pos) {
 			if(cur_frm.pos_print_format) {
@@ -180,9 +180,9 @@
 				cur_frm.meta._default_print_format = null;
 			}
 		}
-	},
+	}
 
-	sales_order_btn: function() {
+	sales_order_btn() {
 		var me = this;
 		this.$sales_order_btn = this.frm.add_custom_button(__('Sales Order'),
 			function() {
@@ -201,9 +201,9 @@
 					}
 				})
 			}, __("Get Items From"));
-	},
+	}
 
-	quotation_btn: function() {
+	quotation_btn() {
 		var me = this;
 		this.$quotation_btn = this.frm.add_custom_button(__('Quotation'),
 			function() {
@@ -225,9 +225,9 @@
 					}
 				})
 			}, __("Get Items From"));
-	},
+	}
 
-	delivery_note_btn: function() {
+	delivery_note_btn() {
 		var me = this;
 		this.$delivery_note_btn = this.frm.add_custom_button(__('Delivery Note'),
 			function() {
@@ -253,12 +253,12 @@
 					}
 				});
 			}, __("Get Items From"));
-	},
+	}
 
-	tc_name: function() {
+	tc_name() {
 		this.get_terms();
-	},
-	customer: function() {
+	}
+	customer() {
 		if (this.frm.doc.is_pos){
 			var pos_profile = this.frm.doc.pos_profile;
 		}
@@ -289,16 +289,16 @@
 				}
 			});
 		}
-	},
+	}
 
-	make_inter_company_invoice: function() {
+	make_inter_company_invoice() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_inter_company_purchase_invoice",
 			frm: me.frm
 		});
-	},
+	}
 
-	debit_to: function() {
+	debit_to() {
 		var me = this;
 		if(this.frm.doc.debit_to) {
 			me.frm.call({
@@ -316,14 +316,14 @@
 				}
 			});
 		}
-	},
+	}
 
-	allocated_amount: function() {
+	allocated_amount() {
 		this.calculate_total_advance();
 		this.frm.refresh_fields();
-	},
+	}
 
-	write_off_outstanding_amount_automatically: function() {
+	write_off_outstanding_amount_automatically() {
 		if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
 			frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
 			// this will make outstanding amount 0
@@ -338,39 +338,39 @@
 
 		this.calculate_outstanding_amount(false);
 		this.frm.refresh_fields();
-	},
+	}
 
-	write_off_amount: function() {
+	write_off_amount() {
 		this.set_in_company_currency(this.frm.doc, ["write_off_amount"]);
 		this.write_off_outstanding_amount_automatically();
-	},
+	}
 
-	items_add: function(doc, cdt, cdn) {
+	items_add(doc, cdt, cdn) {
 		var row = frappe.get_doc(cdt, cdn);
 		this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "cost_center"]);
-	},
+	}
 
-	set_dynamic_labels: function() {
-		this._super();
+	set_dynamic_labels() {
+		super.set_dynamic_labels();
 		this.frm.events.hide_fields(this.frm)
-	},
+	}
 
-	items_on_form_rendered: function() {
+	items_on_form_rendered() {
 		erpnext.setup_serial_or_batch_no();
-	},
+	}
 
-	packed_items_on_form_rendered: function(doc, grid_row) {
+	packed_items_on_form_rendered(doc, grid_row) {
 		erpnext.setup_serial_or_batch_no();
-	},
+	}
 
-	make_sales_return: function() {
+	make_sales_return() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_sales_return",
 			frm: cur_frm
 		})
-	},
+	}
 
-	asset: function(frm, cdt, cdn) {
+	asset(frm, cdt, cdn) {
 		var row = locals[cdt][cdn];
 		if(row.asset) {
 			frappe.call({
@@ -384,18 +384,18 @@
 				}
 			})
 		}
-	},
+	}
 
-	is_pos: function(frm){
+	is_pos(frm){
 		this.set_pos_data();
-	},
+	}
 
-	pos_profile: function() {
+	pos_profile() {
 		this.frm.doc.taxes = []
 		this.set_pos_data();
-	},
+	}
 
-	set_pos_data: function() {
+	set_pos_data() {
 		if(this.frm.doc.is_pos) {
 			this.frm.set_value("allocate_advances_automatically", 0);
 			if(!this.frm.doc.company) {
@@ -425,13 +425,13 @@
 			}
 		}
 		else this.frm.trigger("refresh");
-	},
+	}
 
-	amount: function(){
+	amount(){
 		this.write_off_outstanding_amount_automatically()
-	},
+	}
 
-	change_amount: function(){
+	change_amount(){
 		if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
 			this.calculate_write_off_amount();
 		}else {
@@ -440,18 +440,18 @@
 		}
 
 		this.frm.refresh_fields();
-	},
+	}
 
-	loyalty_amount: function(){
+	loyalty_amount(){
 		this.calculate_outstanding_amount();
 		this.frm.refresh_field("outstanding_amount");
 		this.frm.refresh_field("paid_amount");
 		this.frm.refresh_field("base_paid_amount");
 	}
-});
+};
 
 // for backward compatibility: combine new and previous states
-$.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm}));
 
 cur_frm.cscript['Make Delivery Note'] = function() {
 	frappe.model.open_mapped_doc({
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index 0cea761..dd26be7 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -112,7 +112,7 @@
 		si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
 		si.submit()
 
-		tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
+		tcs_charged = sum(d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC')
 		self.assertEqual(tcs_charged, 500)
 		invoices.append(si)
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index d4b2494..59009ae 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -143,7 +143,7 @@
 		validate_expense_against_budget(args)
 
 def validate_cwip_accounts(gl_map):
-	cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
+	cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting"))
 
 	if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
 			cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
index ff87276..c11c153 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -32,7 +32,7 @@
 		join `tabCash Flow Mapping` cfm on cfma.parent=cfm.name
 		where cfma.parent in (%s)
 		order by cfm.is_working_capital
-	''', (', '.join(['"%s"' % d for d in mapping_names])))
+	''', (', '.join('"%s"' % d for d in mapping_names)))
 
 
 def setup_mappers(mappers):
@@ -83,8 +83,8 @@
 
 		account_types_labels = sorted(
 			set(
-				[(d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense'])
-					for d in account_types]
+				(d['label'], d['is_working_capital'], d['is_income_tax_liability'], d['is_income_tax_expense'])
+					for d in account_types
 			),
 			key=lambda x: x[1]
 		)
@@ -375,7 +375,7 @@
 	total = 0
 	for period in period_list:
 		start_date = get_start_date(period, accumulated_values, company)
-		accounts = ', '.join(['"%s"' % d for d in account_names])
+		accounts = ', '.join('"%s"' % d for d in account_names)
 
 		if opening_balances:
 			date_info = dict(date=start_date)
diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
index 10b32fe..c79d740 100644
--- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
+++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
@@ -145,7 +145,7 @@
 		out = []
 		for party, row in iteritems(self.party_data):
 			if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount:
-				total_party_adjustment = sum([amount for amount in itervalues(self.party_adjustment_details.get(party, {}))])
+				total_party_adjustment = sum(amount for amount in itervalues(self.party_adjustment_details.get(party, {})))
 				row.paid_amount -= total_party_adjustment
 
 				adjustments = self.party_adjustment_details.get(party, {})
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index d20ddbd..39ff804 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -369,7 +369,7 @@
 
 	if accounts:
 		additional_conditions += " and account in ({})"\
-			.format(", ".join([frappe.db.escape(d) for d in accounts]))
+			.format(", ".join(frappe.db.escape(d) for d in accounts))
 
 		gl_filters = {
 			"company": company,
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index cb4d9b4..685419a 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -334,7 +334,7 @@
 
 def get_purchase_receipts_against_purchase_order(item_list):
 	po_pr_map = frappe._dict()
-	po_item_rows = list(set([d.po_detail for d in item_list]))
+	po_item_rows = list(set(d.po_detail for d in item_list))
 
 	if po_item_rows:
 		purchase_receipts = frappe.db.sql("""
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 928b373..2e794da 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -23,7 +23,7 @@
 	if item_list:
 		itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
 
-	mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list]))
+	mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
 	so_dn_map = get_delivery_notes_against_sales_order(item_list)
 
 	data = []
diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py
index cfbd7fd..6a42bb4 100644
--- a/erpnext/accounts/report/pos_register/pos_register.py
+++ b/erpnext/accounts/report/pos_register/pos_register.py
@@ -77,14 +77,14 @@
 		), filters, as_dict=1)
 
 def concat_mode_of_payments(pos_entries):
-	mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries]))
+	mode_of_payments = get_mode_of_payments(set(d.pos_invoice for d in pos_entries))
 	for entry in pos_entries:
 		if mode_of_payments.get(entry.pos_invoice):
 			entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, []))
 
 def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
-	grand_total = sum([d.grand_total for d in group_invoices])
-	paid_amount = sum([d.paid_amount for d in group_invoices])
+	grand_total = sum(d.grand_total for d in group_invoices)
+	paid_amount = sum(d.paid_amount for d in group_invoices)
 	data.append({
 		group_by_field: group_by_value,
 		"grand_total": grand_total,
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 60e675f..48bd730 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -168,21 +168,24 @@
 			"label": _("Income"),
 			"fieldtype": "Currency",
 			"options": "currency",
-			"width": 120
+			"width": 305
+
 		},
 		{
 			"fieldname": "expense",
 			"label": _("Expense"),
 			"fieldtype": "Currency",
 			"options": "currency",
-			"width": 120
+			"width": 305
+
 		},
 		{
 			"fieldname": "gross_profit_loss",
 			"label": _("Gross Profit / Loss"),
 			"fieldtype": "Currency",
 			"options": "currency",
-			"width": 120
+			"width": 307
+
 		}
 	]
 
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py
index 8ac749d..10edd41 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.py
+++ b/erpnext/accounts/report/purchase_register/purchase_register.py
@@ -26,7 +26,7 @@
 	invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
 		invoice_expense_map, expense_accounts)
 	invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
-	suppliers = list(set([d.supplier for d in invoice_list]))
+	suppliers = list(set(d.supplier for d in invoice_list))
 	supplier_details = get_supplier_details(suppliers)
 
 	company_currency = frappe.get_cached_value('Company',  filters.company,  "default_currency")
@@ -120,13 +120,13 @@
 			and docstatus = 1 and (account_head is not null and account_head != '')
 			and category in ('Total', 'Valuation and Total')
 			and parent in (%s) order by account_head""" %
-			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
+			', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
 
 		unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
 			from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
 			and ifnull(unrealized_profit_loss_account, '') != ''
 			order by unrealized_profit_loss_account""" %
-			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
+			', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
 
 	expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
 	unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
@@ -208,7 +208,7 @@
 		from `tabPurchase Invoice Item`
 		where parent in (%s)
 		group by parent, expense_account
-	""" % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+	""" % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	invoice_expense_map = {}
 	for d in expense_details:
@@ -221,7 +221,7 @@
 	unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
 		base_net_total as amount from `tabPurchase Invoice` where name in (%s)
 		and is_internal_supplier = 1 and company = represents_company""" %
-		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+		', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	internal_invoice_map = {}
 	for d in unrealized_amount_details:
@@ -238,7 +238,7 @@
 		where parent in (%s) and category in ('Total', 'Valuation and Total')
 			and base_tax_amount_after_discount_amount != 0
 		group by parent, account_head, add_deduct_tax
-	""" % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+	""" % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	invoice_tax_map = {}
 	for d in tax_details:
@@ -258,7 +258,7 @@
 		select parent, purchase_order, purchase_receipt, po_detail, project
 		from `tabPurchase Invoice Item`
 		where parent in (%s)
-	""" % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+	""" % ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	invoice_po_pr_map = {}
 	for d in pi_items:
diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
index c234da0..ff77468 100644
--- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
+++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
@@ -158,7 +158,7 @@
 def get_mode_of_payments(filters):
 	mode_of_payments = {}
 	invoice_list = get_invoices(filters)
-	invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
+	invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list)
 	if invoice_list:
 		inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment
 			from `tabSales Invoice` a, `tabSales Invoice Payment` b
@@ -197,7 +197,7 @@
 def get_mode_of_payment_details(filters):
 	mode_of_payment_details = {}
 	invoice_list = get_invoices(filters)
-	invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
+	invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list)
 	if invoice_list:
 		inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date,
 			ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py
index cb2c98b..9099593 100644
--- a/erpnext/accounts/report/sales_register/sales_register.py
+++ b/erpnext/accounts/report/sales_register/sales_register.py
@@ -248,19 +248,19 @@
 		income_accounts = frappe.db.sql_list("""select distinct income_account
 			from `tabSales Invoice Item` where docstatus = 1 and parent in (%s)
 			order by income_account""" %
-			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
+			', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
 
 		tax_accounts = 	frappe.db.sql_list("""select distinct account_head
 			from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
 			and docstatus = 1 and base_tax_amount_after_discount_amount != 0
 			and parent in (%s) order by account_head""" %
-			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
+			', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
 
 		unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
 			from `tabSales Invoice` where docstatus = 1 and name in (%s)
 			and ifnull(unrealized_profit_loss_account, '') != ''
 			order by unrealized_profit_loss_account""" %
-			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
+			', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
 
 	for account in income_accounts:
 		income_columns.append({
@@ -406,7 +406,7 @@
 def get_invoice_income_map(invoice_list):
 	income_details = frappe.db.sql("""select parent, income_account, sum(base_net_amount) as amount
 		from `tabSales Invoice Item` where parent in (%s) group by parent, income_account""" %
-		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+		', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	invoice_income_map = {}
 	for d in income_details:
@@ -419,7 +419,7 @@
 	unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
 		base_net_total as amount from `tabSales Invoice` where name in (%s)
 		and is_internal_customer = 1 and company = represents_company""" %
-		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+		', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	internal_invoice_map = {}
 	for d in unrealized_amount_details:
@@ -432,7 +432,7 @@
 	tax_details = frappe.db.sql("""select parent, account_head,
 		sum(base_tax_amount_after_discount_amount) as tax_amount
 		from `tabSales Taxes and Charges` where parent in (%s) group by parent, account_head""" %
-		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+		', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	invoice_tax_map = {}
 	for d in tax_details:
@@ -451,7 +451,7 @@
 	si_items = frappe.db.sql("""select parent, sales_order, delivery_note, so_detail
 		from `tabSales Invoice Item` where parent in (%s)
 		and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')""" %
-		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+		', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	invoice_so_dn_map = {}
 	for d in si_items:
@@ -475,7 +475,7 @@
 	si_items = frappe.db.sql("""select parent, cost_center, warehouse
 		from `tabSales Invoice Item` where parent in (%s)
 		and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')""" %
-		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+		', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list), as_dict=1)
 
 	invoice_cc_wh_map = {}
 	for d in si_items:
diff --git a/erpnext/accounts/report/tax_detail/__init__.py b/erpnext/accounts/report/tax_detail/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/tax_detail/__init__.py
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.js b/erpnext/accounts/report/tax_detail/tax_detail.js
new file mode 100644
index 0000000..ed6fac4
--- /dev/null
+++ b/erpnext/accounts/report/tax_detail/tax_detail.js
@@ -0,0 +1,451 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+// Contributed by Case Solved and sponsored by Nulight Studios
+/* eslint-disable */
+
+frappe.provide('frappe.query_reports');
+
+frappe.query_reports["Tax Detail"] = {
+	filters: [
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("company"),
+			reqd: 1
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date",
+			default: frappe.datetime.month_start(frappe.datetime.get_today()),
+			reqd: 1,
+			width: "60px"
+		},
+		{
+			fieldname: "to_date",
+			label: __("To Date"),
+			fieldtype: "Date",
+			default: frappe.datetime.month_end(frappe.datetime.get_today()),
+			reqd: 1,
+			width: "60px"
+		},
+		{
+			fieldname: "report_name",
+			label: __("Report Name"),
+			fieldtype: "Read Only",
+			default: frappe.query_report.report_name,
+			hidden: 1,
+			reqd: 1
+		},
+		{
+			fieldname: "mode",
+			label: __("Mode"),
+			fieldtype: "Read Only",
+			default: "edit",
+			hidden: 1,
+			reqd: 1
+		}
+	],
+	onload: function onload(report) {
+		// Remove Add Column and Save from menu
+		report.page.add_inner_button(__("New Report"), () => new_report(), __("Custom Report"));
+		report.page.add_inner_button(__("Load Report"), () => load_report(), __("Custom Report"));
+		hide_filters(report);
+	}
+};
+
+function hide_filters(report) {
+	report.page.page_form[0].querySelectorAll('.form-group.frappe-control').forEach(function setHidden(field) {
+		if (field.dataset.fieldtype == "Read Only") {
+			field.classList.add("hidden");
+		}
+	});
+}
+
+erpnext.TaxDetail = class TaxDetail {
+	constructor() {
+		this.patch();
+		this.load_report();
+	}
+	// Monkey patch the QueryReport class
+	patch() {
+		this.qr = frappe.query_report;
+		this.super = {
+			refresh_report: this.qr.refresh_report,
+			show_footer_message: this.qr.show_footer_message
+		}
+		this.qr.refresh_report = () => this.refresh_report();
+		this.qr.show_footer_message = () => this.show_footer_message();
+	}
+	show_footer_message() {
+		// The last thing to run after datatable_render in refresh()
+		this.super.show_footer_message.apply(this.qr);
+		if (this.qr.report_name !== 'Tax Detail') {
+			this.show_help();
+			if (this.loading) {
+				this.set_section('');
+			} else {
+				this.reload_component('');
+			}
+		}
+		this.loading = false;
+	}
+	refresh_report() {
+		// Infrequent report build (onload), load filters & data
+		// super function runs a refresh() serially
+		// already run within frappe.run_serially
+		this.loading = true;
+		this.super.refresh_report.apply(this.qr);
+		if (this.qr.report_name !== 'Tax Detail') {
+			frappe.call({
+				method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
+				args: {name: this.qr.report_name}
+			}).then((r) => {
+				const data = JSON.parse(r.message[this.qr.report_name]['json']);
+				this.create_controls();
+				this.sections = data.sections || {};
+				this.controls['show_detail'].set_input(data.show_detail);
+			});
+		}
+	}
+	load_report() {
+		// One-off report build like titles, menu, etc
+		// Run when this object is created which happens in qr.load_report
+		this.qr.menu_items = this.get_menu_items();
+	}
+	get_menu_items() {
+		// Replace Save action
+		let new_items = [];
+		const save = __('Save');
+
+		for (let item of this.qr.menu_items) {
+			if (item.label === save) {
+				new_items.push({
+					label: save,
+					action: () => this.save_report(),
+					standard: false
+				});
+			} else {
+				new_items.push(item);
+			}
+		}
+		return new_items;
+	}
+	save_report() {
+		this.check_datatable();
+		if (this.qr.report_name !== 'Tax Detail') {
+			frappe.call({
+				method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
+				args: {
+					reference_report: 'Tax Detail',
+					report_name: this.qr.report_name,
+					data: {
+						columns: this.qr.get_visible_columns(),
+						sections: this.sections,
+						show_detail: this.controls['show_detail'].get_input_value()
+					}
+				},
+				freeze: true
+			}).then((r) => {
+				this.set_section('');
+			});
+		}
+	}
+	check_datatable() {
+		if (!this.qr.datatable) {
+			frappe.throw(__('Please change the date range to load data first'));
+		}
+	}
+	set_section(name) {
+		// Sets the given section name and then reloads the data
+		if (name && !this.sections[name]) {
+			this.sections[name] = {};
+		}
+		let options = Object.keys(this.sections);
+		options.unshift('');
+		this.controls['section_name'].$wrapper.find("select").empty().add_options(options);
+		const org_mode = this.qr.get_filter_value('mode');
+		let refresh = false;
+		if (name) {
+			this.controls['section_name'].set_input(name);
+			this.qr.set_filter_value('mode', 'edit');
+			if (org_mode === 'run') {
+				refresh = true;
+			}
+		} else {
+			this.controls['section_name'].set_input('');
+			this.qr.set_filter_value('mode', 'run');
+			if (org_mode === 'edit') {
+				refresh = true;
+			}
+		}
+		if (refresh) {
+			this.qr.refresh();
+		}
+		this.reload_component('');
+	}
+	reload_component(component_name) {
+		const section_name = this.controls['section_name'].get_input_value();
+		if (section_name) {
+			const section = this.sections[section_name];
+			const component_names = Object.keys(section);
+			component_names.unshift('');
+			this.controls['component'].$wrapper.find("select").empty().add_options(component_names);
+			this.controls['component'].set_input(component_name);
+			if (component_name) {
+				this.controls['component_type'].set_input(section[component_name].type);
+			}
+		} else {
+			this.controls['component'].$wrapper.find("select").empty();
+			this.controls['component'].set_input('');
+		}
+		this.set_table_filters();
+	}
+	set_table_filters() {
+		let filters = {};
+		const section_name = this.controls['section_name'].get_input_value();
+		const component_name = this.controls['component'].get_input_value();
+		if (section_name && component_name) {
+			const component_type = this.sections[section_name][component_name].type;
+			if (component_type === 'filter') {
+				filters = this.sections[section_name][component_name]['filters'];
+			}
+		}
+		this.setAppliedFilters(filters);
+	}
+	setAppliedFilters(filters) {
+		if (this.qr.datatable) {
+			Array.from(this.qr.datatable.header.querySelectorAll('.dt-filter')).map(function setFilters(input) {
+				let idx = input.dataset.colIndex;
+				if (filters[idx]) {
+					input.value = filters[idx];
+				} else {
+					input.value = null;
+				}
+			});
+			this.qr.datatable.columnmanager.applyFilter(filters);
+		}
+	}
+	delete(name, type) {
+		if (type === 'section') {
+			delete this.sections[name];
+			const new_section = Object.keys(this.sections)[0] || '';
+			this.set_section(new_section);
+		}
+		if (type === 'component') {
+			const cur_section = this.controls['section_name'].get_input_value();
+			delete this.sections[cur_section][name];
+			this.reload_component('');
+		}
+	}
+	create_controls() {
+		let controls = {};
+		// SELECT in data.js
+		controls['section_name'] = this.qr.page.add_field({
+			label: __('Section'),
+			fieldtype: 'Select',
+			fieldname: 'section_name',
+			change: (e) => {
+				this.set_section(this.controls['section_name'].get_input_value());
+			}
+		});
+		// BUTTON in button.js
+		controls['new_section'] = this.qr.page.add_field({
+			label: __('New Section'),
+			fieldtype: 'Button',
+			fieldname: 'new_section',
+			click: () => {
+				frappe.prompt({
+					label: __('Section Name'),
+					fieldname: 'name',
+					fieldtype: 'Data'
+				}, (values) => {
+					this.set_section(values.name);
+				});
+			}
+		});
+		controls['delete_section'] = this.qr.page.add_field({
+			label: __('Delete Section'),
+			fieldtype: 'Button',
+			fieldname: 'delete_section',
+			click: () => {
+				let cur_section = this.controls['section_name'].get_input_value();
+				if (cur_section) {
+					frappe.confirm(__('Are you sure you want to delete section') + ' ' + cur_section + '?',
+					() => {this.delete(cur_section, 'section')});
+				}
+			}
+		});
+		controls['component'] = this.qr.page.add_field({
+			label: __('Component'),
+			fieldtype: 'Select',
+			fieldname: 'component',
+			change: (e) => {
+				this.reload_component(this.controls['component'].get_input_value());
+			}
+		});
+		controls['component_type'] = this.qr.page.add_field({
+			label: __('Component Type'),
+			fieldtype: 'Select',
+			fieldname: 'component_type',
+			default: 'filter',
+			options: [
+				{label: __('Filtered Row Subtotal'), value: 'filter'},
+				{label: __('Section Subtotal'), value: 'section'}
+			]
+		});
+		controls['add_component'] = this.qr.page.add_field({
+			label: __('Add Component'),
+			fieldtype: 'Button',
+			fieldname: 'add_component',
+			click: () => {
+				this.check_datatable();
+				let section_name = this.controls['section_name'].get_input_value();
+				if (section_name) {
+					const component_type = this.controls['component_type'].get_input_value();
+					let idx = 0;
+					const names = Object.keys(this.sections[section_name]);
+					if (names.length > 0) {
+						const idxs = names.map((key) => parseInt(key.match(/\d+$/)) || 0);
+						idx = Math.max(...idxs) + 1;
+					}
+					const filters = this.qr.datatable.columnmanager.getAppliedFilters();
+					if (component_type === 'filter') {
+						const name = 'Filter' + idx.toString();
+						let data = {
+							type: component_type,
+							filters: filters
+						}
+						this.sections[section_name][name] = data;
+						this.reload_component(name);
+					} else if (component_type === 'section') {
+						if (filters && Object.keys(filters).length !== 0) {
+							frappe.show_alert({
+								message: __('Column filters ignored'),
+								indicator: 'yellow'
+							});
+						}
+						let data = {
+							type: component_type
+						}
+						frappe.prompt({
+							label: __('Section'),
+							fieldname: 'section',
+							fieldtype: 'Select',
+							options: Object.keys(this.sections)
+						}, (values) => {
+							this.sections[section_name][values.section] = data;
+							this.reload_component(values.section);
+						});
+					} else {
+						frappe.throw(__('Please select the Component Type first'));
+					}
+				} else {
+					frappe.throw(__('Please select the Section first'));
+				}
+			}
+		});
+		controls['delete_component'] = this.qr.page.add_field({
+			label: __('Delete Component'),
+			fieldtype: 'Button',
+			fieldname: 'delete_component',
+			click: () => {
+				const component = this.controls['component'].get_input_value();
+				if (component) {
+					frappe.confirm(__('Are you sure you want to delete component') + ' ' + component + '?',
+					() => {this.delete(component, 'component')});
+				}
+			}
+		});
+		controls['save'] = this.qr.page.add_field({
+			label: __('Save & Run'),
+			fieldtype: 'Button',
+			fieldname: 'save',
+			click: () => {
+				this.save_report();
+			}
+		});
+		controls['show_detail'] = this.qr.page.add_field({
+			label: __('Show Detail'),
+			fieldtype: 'Check',
+			fieldname: 'show_detail',
+			default: 1
+		});
+		this.controls = controls;
+	}
+	show_help() {
+		const help = __('Your custom report is built from General Ledger Entries within the date range. You can add multiple sections to the report using the New Section button. Each component added to a section adds a subset of the data into the specified section. Beware of duplicated data rows. The Filtered Row component type saves the datatable column filters to specify the added data. The Section component type refers to the data in a previously defined section, but it cannot refer to its parent section. The Amount column is summed to give the section subtotal. Use the Show Detail box to see the data rows included in each section in the final report. Once finished, hit Save & Run. Report contributed by');
+		this.qr.$report_footer.append('<div class="col-md-12"><strong>' + __('Help') + `: </strong>${help}<a href="https://www.casesolved.co.uk"> Case Solved</a></div>`);
+	}
+}
+
+if (!window.taxdetail) {
+	window.taxdetail = new erpnext.TaxDetail();
+}
+
+function get_reports(cb) {
+	frappe.call({
+		method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
+		freeze: true
+	}).then((r) => {
+		cb(r.message);
+	})
+}
+
+function new_report() {
+	const dialog = new frappe.ui.Dialog({
+		title: __('New Report'),
+		fields: [
+			{
+				fieldname: 'report_name',
+				label: __('Report Name'),
+				fieldtype: 'Data',
+				default: 'VAT Return'
+			}
+		],
+		primary_action_label: __('Create'),
+		primary_action: function new_report_pa(values) {
+			frappe.call({
+				method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
+				args: {
+					reference_report: 'Tax Detail',
+					report_name: values.report_name,
+					data: {
+						columns: [],
+						sections: {},
+						show_detail: 1
+					}
+				},
+				freeze: true
+			}).then((r) => {
+				frappe.set_route('query-report', values.report_name);
+			});
+			dialog.hide();
+		}
+	});
+	dialog.show();
+}
+
+function load_report() {
+	get_reports(function load_report_cb(reports) {
+		const dialog = new frappe.ui.Dialog({
+			title: __('Load Report'),
+			fields: [
+				{
+					fieldname: 'report_name',
+					label: __('Report Name'),
+					fieldtype: 'Select',
+					options: Object.keys(reports)
+				}
+			],
+			primary_action_label: __('Load'),
+			primary_action: function load_report_pa(values) {
+				dialog.hide();
+				frappe.set_route('query-report', values.report_name);
+			}
+		});
+		dialog.show();
+	});
+}
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.json b/erpnext/accounts/report/tax_detail/tax_detail.json
new file mode 100644
index 0000000..d52ffd0
--- /dev/null
+++ b/erpnext/accounts/report/tax_detail/tax_detail.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-02-19 16:44:21.175113",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-02-19 16:44:21.175113",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Tax Detail",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "Tax Detail",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Accounts Manager"
+  },
+  {
+   "role": "Auditor"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.py b/erpnext/accounts/report/tax_detail/tax_detail.py
new file mode 100644
index 0000000..18436de
--- /dev/null
+++ b/erpnext/accounts/report/tax_detail/tax_detail.py
@@ -0,0 +1,296 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+# Contributed by Case Solved and sponsored by Nulight Studios
+
+from __future__ import unicode_literals
+import frappe
+import json
+from frappe import _
+
+# NOTE: Payroll is implemented using Journal Entries which are included as GL Entries
+
+# field lists in multiple doctypes will be coalesced
+required_sql_fields = {
+	("GL Entry", 1): ["posting_date"],
+	("Account",): ["root_type", "account_type"],
+	("GL Entry", 2): ["account", "voucher_type", "voucher_no", "debit", "credit"],
+	("Purchase Invoice Item", "Sales Invoice Item"): ["base_net_amount", "item_tax_rate", "item_tax_template", "item_group", "item_name"],
+	("Purchase Invoice", "Sales Invoice"): ["taxes_and_charges", "tax_category"],
+}
+
+
+def execute(filters=None):
+	if not filters:
+		return [], []
+
+	fieldlist = required_sql_fields
+	fieldstr = get_fieldstr(fieldlist)
+
+	gl_entries = frappe.db.sql("""
+		select {fieldstr}
+		from `tabGL Entry` ge
+		inner join `tabAccount` a on
+			ge.account=a.name and ge.company=a.company
+		left join `tabSales Invoice` si on
+			ge.company=si.company and ge.voucher_type='Sales Invoice' and ge.voucher_no=si.name
+		left join `tabSales Invoice Item` sii on
+			a.root_type='Income' and si.name=sii.parent
+		left join `tabPurchase Invoice` pi on
+			ge.company=pi.company and ge.voucher_type='Purchase Invoice' and ge.voucher_no=pi.name
+		left join `tabPurchase Invoice Item` pii on
+			a.root_type='Expense' and pi.name=pii.parent
+		where
+			ge.company=%(company)s and
+			ge.posting_date>=%(from_date)s and
+			ge.posting_date<=%(to_date)s
+		order by ge.posting_date, ge.voucher_no
+		""".format(fieldstr=fieldstr), filters, as_dict=1)
+
+	report_data = modify_report_data(gl_entries)
+	summary = None
+	if filters['mode'] == 'run' and filters['report_name'] != 'Tax Detail':
+		report_data, summary = run_report(filters['report_name'], report_data)
+
+	# return columns, data, message, chart, report_summary
+	return get_columns(fieldlist), report_data, None, None, summary
+
+def run_report(report_name, data):
+	"Applies the sections and filters saved in the custom report"
+	report_config = json.loads(frappe.get_doc('Report', report_name).json)
+	# Columns indexed from 1 wrt colno
+	columns = report_config.get('columns')
+	sections = report_config.get('sections', {})
+	show_detail = report_config.get('show_detail', 1)
+	report = {}
+	new_data = []
+	summary = []
+	for section_name, section in sections.items():
+		report[section_name] = {'rows': [], 'subtotal': 0.0}
+		for component_name, component in section.items():
+			if component['type'] == 'filter':
+				for row in data:
+					matched = True
+					for colno, filter_string in component['filters'].items():
+						filter_field = columns[int(colno) - 1]['fieldname']
+						if not filter_match(row[filter_field], filter_string):
+							matched = False
+							break
+					if matched:
+						report[section_name]['rows'] += [row]
+						report[section_name]['subtotal'] += row['amount']
+			if component['type'] == 'section':
+				if component_name == section_name:
+					frappe.throw(_("A report component cannot refer to its parent section") + ": " + section_name)
+				try:
+					report[section_name]['rows'] += report[component_name]['rows']
+					report[section_name]['subtotal'] += report[component_name]['subtotal']
+				except KeyError:
+					frappe.throw(_("A report component can only refer to an earlier section") + ": " + section_name)
+
+		if show_detail:
+			new_data += report[section_name]['rows']
+		new_data += [{'voucher_no': section_name, 'amount': report[section_name]['subtotal']}]
+		summary += [{'label': section_name, 'datatype': 'Currency', 'value': report[section_name]['subtotal']}]
+		if show_detail:
+			new_data += [{}]
+	return new_data or data, summary or None
+
+def filter_match(value, string):
+	"Approximation to datatable filters"
+	import datetime
+	if string == '':
+		return True
+	if value is None:
+		value = -999999999999999
+	elif isinstance(value, datetime.date):
+		return True
+
+	if isinstance(value, str):
+		value = value.lower()
+		string = string.lower()
+		if string[0] == '<':
+			return True if string[1:].strip() else False
+		elif string[0] == '>':
+			return False if string[1:].strip() else True
+		elif string[0] == '=':
+			return string[1:] in value if string[1:] else False
+		elif string[0:2] == '!=':
+			return string[2:] not in value
+		elif len(string.split(':')) == 2:
+			pre, post = string.split(':')
+			return (True if not pre.strip() and post.strip() in value else False)
+		else:
+			return string in value
+	else:
+		if string[0] in ['<', '>', '=']:
+			operator = string[0]
+			if operator == '=':
+				operator = '=='
+			string = string[1:].strip()
+		elif string[0:2] == '!=':
+			operator = '!='
+			string = string[2:].strip()
+		elif len(string.split(':')) == 2:
+			pre, post = string.split(':')
+			try:
+				return (True if float(pre) <= value and float(post) >= value else False)
+			except ValueError:
+				return (False if pre.strip() else True)
+		else:
+			return string in str(value)
+
+	try:
+		num = float(string) if string.strip() else 0
+		return frappe.safe_eval(f'{value} {operator} {num}')
+	except ValueError:
+		if operator == '<':
+			return True
+		return False
+
+
+def abbrev(dt):
+	return ''.join(l[0].lower() for l in dt.split(' ')) + '.'
+
+def doclist(dt, dfs):
+	return [abbrev(dt) + f for f in dfs]
+
+def as_split(fields):
+	for field in fields:
+		split = field.split(' as ')
+		yield (split[0], split[1] if len(split) > 1 else split[0])
+
+def coalesce(doctypes, fields):
+	coalesce = []
+	for name, new_name in as_split(fields):
+		sharedfields = ', '.join(abbrev(dt) + name for dt in doctypes)
+		coalesce += [f'coalesce({sharedfields}) as {new_name}']
+	return coalesce
+
+def get_fieldstr(fieldlist):
+	fields = []
+	for doctypes, docfields in fieldlist.items():
+		if len(doctypes) == 1 or isinstance(doctypes[1], int):
+			fields += doclist(doctypes[0], docfields)
+		else:
+			fields += coalesce(doctypes, docfields)
+	return ', '.join(fields)
+
+def get_columns(fieldlist):
+	columns = {}
+	for doctypes, docfields in fieldlist.items():
+		fieldmap = {name: new_name for name, new_name in as_split(docfields)}
+		for doctype in doctypes:
+			if isinstance(doctype, int):
+				break
+			meta = frappe.get_meta(doctype)
+			# get column field metadata from the db
+			fieldmeta = {}
+			for field in meta.get('fields'):
+				if field.fieldname in fieldmap.keys():
+					new_name = fieldmap[field.fieldname]
+					fieldmeta[new_name] = {
+						"label": _(field.label),
+						"fieldname": new_name,
+						"fieldtype": field.fieldtype,
+						"options": field.options
+					}
+			# edit the columns to match the modified data
+			for field in fieldmap.values():
+				col = modify_report_columns(doctype, field, fieldmeta[field])
+				if col:
+					columns[col["fieldname"]] = col
+	# use of a dict ensures duplicate columns are removed
+	return list(columns.values())
+
+def modify_report_columns(doctype, field, column):
+	"Because data is rearranged into other columns"
+	if doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
+		if field in ["item_tax_rate", "base_net_amount"]:
+			return None
+
+	if doctype == "GL Entry" and field in ["debit", "credit"]:
+		column.update({"label": _("Amount"), "fieldname": "amount"})
+
+	if field == "taxes_and_charges":
+		column.update({"label": _("Taxes and Charges Template")})
+	return column
+
+def modify_report_data(data):
+	import json
+	new_data = []
+	for line in data:
+		if line.debit:
+			line.amount = -line.debit
+		else:
+			line.amount = line.credit
+		# Remove Invoice GL Tax Entries and generate Tax entries from the invoice lines
+		if "Invoice" in line.voucher_type:
+			if line.account_type not in ("Tax", "Round Off"):
+				new_data += [line]
+				if line.item_tax_rate:
+					tax_rates = json.loads(line.item_tax_rate)
+					for account, rate in tax_rates.items():
+						tax_line = line.copy()
+						tax_line.account_type = "Tax"
+						tax_line.account = account
+						if line.voucher_type == "Sales Invoice":
+							line.amount = line.base_net_amount
+							tax_line.amount = line.base_net_amount * (rate / 100)
+						if line.voucher_type == "Purchase Invoice":
+							line.amount = -line.base_net_amount
+							tax_line.amount = -line.base_net_amount * (rate / 100)
+						new_data += [tax_line]
+		else:
+			new_data += [line]
+	return new_data
+
+
+# JS client utilities
+
+custom_report_dict = {
+	'ref_doctype': 'GL Entry',
+	'report_type': 'Custom Report',
+	'reference_report': 'Tax Detail'
+}
+
+@frappe.whitelist()
+def get_custom_reports(name=None):
+	filters = custom_report_dict.copy()
+	if name:
+		filters['name'] = name
+	reports = frappe.get_list('Report',
+		filters = filters,
+		fields = ['name', 'json'],
+		as_list=False
+	)
+	reports_dict = {rep.pop('name'): rep for rep in reports}
+	# Prevent custom reports with the same name
+	reports_dict['Tax Detail'] = {'json': None}
+	return reports_dict
+
+@frappe.whitelist()
+def save_custom_report(reference_report, report_name, data):
+	if reference_report != 'Tax Detail':
+		frappe.throw(_("The wrong report is referenced."))
+	if report_name == 'Tax Detail':
+		frappe.throw(_("The parent report cannot be overwritten."))
+
+	doc = {
+		'doctype': 'Report',
+		'report_name': report_name,
+		'is_standard': 'No',
+		'module': 'Accounts',
+		'json': data
+	}
+	doc.update(custom_report_dict)
+
+	try:
+		newdoc = frappe.get_doc(doc)
+		newdoc.insert()
+		frappe.msgprint(_("Report created successfully"))
+	except frappe.exceptions.DuplicateEntryError:
+		dbdoc = frappe.get_doc('Report', report_name)
+		dbdoc.update(doc)
+		dbdoc.save()
+		frappe.msgprint(_("Report updated successfully"))
+	return report_name
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.json b/erpnext/accounts/report/tax_detail/test_tax_detail.json
new file mode 100644
index 0000000..3a4b175
--- /dev/null
+++ b/erpnext/accounts/report/tax_detail/test_tax_detail.json
@@ -0,0 +1,840 @@
+[
+ {
+  "account_manager": null,
+  "accounts": [],
+  "companies": [],
+  "credit_limits": [],
+  "customer_details": null,
+  "customer_group": "All Customer Groups",
+  "customer_name": "_Test Customer",
+  "customer_pos_id": null,
+  "customer_primary_address": null,
+  "customer_primary_contact": null,
+  "customer_type": "Company",
+  "default_bank_account": null,
+  "default_commission_rate": 0.0,
+  "default_currency": null,
+  "default_price_list": null,
+  "default_sales_partner": null,
+  "disabled": 0,
+  "dn_required": 0,
+  "docstatus": 0,
+  "doctype": "Customer",
+  "email_id": null,
+  "gender": null,
+  "image": null,
+  "industry": null,
+  "is_frozen": 0,
+  "is_internal_customer": 0,
+  "language": "en",
+  "lead_name": null,
+  "loyalty_program": null,
+  "loyalty_program_tier": null,
+  "market_segment": null,
+  "mobile_no": null,
+  "modified": "2021-02-15 05:18:03.624724",
+  "name": "_Test Customer",
+  "naming_series": "CUST-.YYYY.-",
+  "pan": null,
+  "parent": null,
+  "parentfield": null,
+  "parenttype": null,
+  "payment_terms": null,
+  "primary_address": null,
+  "represents_company": "",
+  "sales_team": [],
+  "salutation": null,
+  "so_required": 0,
+  "tax_category": null,
+  "tax_id": null,
+  "tax_withholding_category": null,
+  "territory": "All Territories",
+  "website": null
+ },{
+  "accounts": [],
+  "allow_purchase_invoice_creation_without_purchase_order": 0,
+  "allow_purchase_invoice_creation_without_purchase_receipt": 0,
+  "companies": [],
+  "country": "United Kingdom",
+  "default_bank_account": null,
+  "default_currency": null,
+  "default_price_list": null,
+  "disabled": 0,
+  "docstatus": 0,
+  "doctype": "Supplier",
+  "hold_type": "",
+  "image": null,
+  "is_frozen": 0,
+  "is_internal_supplier": 0,
+  "is_transporter": 0,
+  "language": "en",
+  "modified": "2021-03-31 16:47:10.109316",
+  "name": "_Test Supplier",
+  "naming_series": "SUP-.YYYY.-",
+  "on_hold": 0,
+  "pan": null,
+  "parent": null,
+  "parentfield": null,
+  "parenttype": null,
+  "payment_terms": null,
+  "prevent_pos": 0,
+  "prevent_rfqs": 0,
+  "release_date": null,
+  "represents_company": null,
+  "supplier_details": null,
+  "supplier_group": "Raw Material",
+  "supplier_name": "_Test Supplier",
+  "supplier_type": "Company",
+  "tax_category": null,
+  "tax_id": null,
+  "tax_withholding_category": null,
+  "warn_pos": 0,
+  "warn_rfqs": 0,
+  "website": null
+ },{
+  "account_currency": "GBP",
+  "account_name": "Debtors",
+  "account_number": "",
+  "account_type": "Receivable",
+  "balance_must_be": "",
+  "company": "_T",
+  "disabled": 0,
+  "docstatus": 0,
+  "doctype": "Account",
+  "freeze_account": "No",
+  "include_in_gross": 0,
+  "inter_company_account": 0,
+  "is_group": 0,
+  "lft": 58,
+  "modified": "2021-03-26 04:44:19.955468",
+  "name": "Debtors - _T",
+  "old_parent": null,
+  "parent": null,
+  "parent_account": "Application of Funds (Assets) - _T",
+  "parentfield": null,
+  "parenttype": null,
+  "report_type": "Balance Sheet",
+  "rgt": 59,
+  "root_type": "Asset",
+  "tax_rate": 0.0
+ },{
+  "account_currency": "GBP",
+  "account_name": "Sales",
+  "account_number": "",
+  "account_type": "Income Account",
+  "balance_must_be": "",
+  "company": "_T",
+  "disabled": 0,
+  "docstatus": 0,
+  "doctype": "Account",
+  "freeze_account": "No",
+  "include_in_gross": 0,
+  "inter_company_account": 0,
+  "is_group": 0,
+  "lft": 291,
+  "modified": "2021-03-26 04:50:21.697703",
+  "name": "Sales - _T",
+  "old_parent": null,
+  "parent": null,
+  "parent_account": "Income - _T",
+  "parentfield": null,
+  "parenttype": null,
+  "report_type": "Profit and Loss",
+  "rgt": 292,
+  "root_type": "Income",
+  "tax_rate": 0.0
+ },{
+  "account_currency": "GBP",
+  "account_name": "VAT on Sales",
+  "account_number": "",
+  "account_type": "Tax",
+  "balance_must_be": "",
+  "company": "_T",
+  "disabled": 0,
+  "docstatus": 0,
+  "doctype": "Account",
+  "freeze_account": "No",
+  "include_in_gross": 0,
+  "inter_company_account": 0,
+  "is_group": 0,
+  "lft": 317,
+  "modified": "2021-03-26 04:50:21.697703",
+  "name": "VAT on Sales - _T",
+  "old_parent": null,
+  "parent": null,
+  "parent_account": "Source of Funds (Liabilities) - _T",
+  "parentfield": null,
+  "parenttype": null,
+  "report_type": "Balance Sheet",
+  "rgt": 318,
+  "root_type": "Liability",
+  "tax_rate": 0.0
+ },{
+  "account_currency": "GBP",
+  "account_name": "Cost of Goods Sold",
+  "account_number": "",
+  "account_type": "Cost of Goods Sold",
+  "balance_must_be": "",
+  "company": "_T",
+  "disabled": 0,
+  "docstatus": 0,
+  "doctype": "Account",
+  "freeze_account": "No",
+  "include_in_gross": 0,
+  "inter_company_account": 0,
+  "is_group": 0,
+  "lft": 171,
+  "modified": "2021-03-26 04:44:19.994857",
+  "name": "Cost of Goods Sold - _T",
+  "old_parent": null,
+  "parent": null,
+  "parent_account": "Expenses - _T",
+  "parentfield": null,
+  "parenttype": null,
+  "report_type": "Profit and Loss",
+  "rgt": 172,
+  "root_type": "Expense",
+  "tax_rate": 0.0
+ },{
+  "account_currency": "GBP",
+  "account_name": "VAT on Purchases",
+  "account_number": "",
+  "account_type": "Tax",
+  "balance_must_be": "",
+  "company": "_T",
+  "disabled": 0,
+  "docstatus": 0,
+  "doctype": "Account",
+  "freeze_account": "No",
+  "include_in_gross": 0,
+  "inter_company_account": 0,
+  "is_group": 0,
+  "lft": 80,
+  "modified": "2021-03-26 04:44:19.961983",
+  "name": "VAT on Purchases - _T",
+  "old_parent": null,
+  "parent": null,
+  "parent_account": "Application of Funds (Assets) - _T",
+  "parentfield": null,
+  "parenttype": null,
+  "report_type": "Balance Sheet",
+  "rgt": 81,
+  "root_type": "Asset",
+  "tax_rate": 0.0
+ },{
+  "account_currency": "GBP",
+  "account_name": "Creditors",
+  "account_number": "",
+  "account_type": "Payable",
+  "balance_must_be": "",
+  "company": "_T",
+  "disabled": 0,
+  "docstatus": 0,
+  "doctype": "Account",
+  "freeze_account": "No",
+  "include_in_gross": 0,
+  "inter_company_account": 0,
+  "is_group": 0,
+  "lft": 302,
+  "modified": "2021-03-26 04:50:21.697703",
+  "name": "Creditors - _T",
+  "old_parent": null,
+  "parent": null,
+  "parent_account": "Source of Funds (Liabilities) - _T",
+  "parentfield": null,
+  "parenttype": null,
+  "report_type": "Balance Sheet",
+  "rgt": 303,
+  "root_type": "Liability",
+  "tax_rate": 0.0
+ },{
+  "additional_discount_percentage": 0.0,
+  "address_display": null,
+  "adjust_advance_taxes": 0,
+  "advances": [],
+  "against_expense_account": "Cost of Goods Sold - _T",
+  "allocate_advances_automatically": 0,
+  "amended_from": null,
+  "apply_discount_on": "Grand Total",
+  "apply_tds": 0,
+  "auto_repeat": null,
+  "base_discount_amount": 0.0,
+  "base_grand_total": 511.68,
+  "base_in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
+  "base_net_total": 426.4,
+  "base_paid_amount": 0.0,
+  "base_rounded_total": 511.68,
+  "base_rounding_adjustment": 0.0,
+  "base_taxes_and_charges_added": 85.28,
+  "base_taxes_and_charges_deducted": 0.0,
+  "base_total": 426.4,
+  "base_total_taxes_and_charges": 85.28,
+  "base_write_off_amount": 0.0,
+  "bill_date": null,
+  "bill_no": null,
+  "billing_address": null,
+  "billing_address_display": null,
+  "buying_price_list": "Standard Buying",
+  "cash_bank_account": null,
+  "clearance_date": null,
+  "company": "_T",
+  "contact_display": null,
+  "contact_email": null,
+  "contact_mobile": null,
+  "contact_person": null,
+  "conversion_rate": 1.0,
+  "cost_center": null,
+  "credit_to": "Creditors - _T",
+  "currency": "GBP",
+  "disable_rounded_total": 0,
+  "discount_amount": 0.0,
+  "docstatus": 0,
+  "doctype": "Purchase Invoice",
+  "due_date": null,
+  "from_date": null,
+  "grand_total": 511.68,
+  "group_same_items": 0,
+  "hold_comment": null,
+  "ignore_pricing_rule": 0,
+  "in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
+  "inter_company_invoice_reference": null,
+  "is_internal_supplier": 0,
+  "is_opening": "No",
+  "is_paid": 0,
+  "is_return": 0,
+  "is_subcontracted": "No",
+  "items": [
+   {
+    "allow_zero_valuation_rate": 0,
+    "amount": 426.4,
+    "asset_category": null,
+    "asset_location": null,
+    "base_amount": 426.4,
+    "base_net_amount": 426.4,
+    "base_net_rate": 5.33,
+    "base_price_list_rate": 5.33,
+    "base_rate": 5.33,
+    "base_rate_with_margin": 0.0,
+    "batch_no": null,
+    "bom": null,
+    "brand": null,
+    "conversion_factor": 0.0,
+    "cost_center": "Main - _T",
+    "deferred_expense_account": null,
+    "description": "<div class=\"ql-editor read-mode\"><p>Fluid to make widgets</p></div>",
+    "discount_amount": 0.0,
+    "discount_percentage": 0.0,
+    "enable_deferred_expense": 0,
+    "expense_account": "Cost of Goods Sold - _T",
+    "from_warehouse": null,
+    "image": null,
+    "include_exploded_items": 0,
+    "is_fixed_asset": 0,
+    "is_free_item": 0,
+    "item_code": null,
+    "item_group": null,
+    "item_name": "Widget Fluid 1Litre",
+    "item_tax_amount": 0.0,
+    "item_tax_rate": "{\"VAT on Purchases - _T\": 20.0}",
+    "item_tax_template": null,
+    "landed_cost_voucher_amount": 0.0,
+    "manufacturer": null,
+    "manufacturer_part_no": null,
+    "margin_rate_or_amount": 0.0,
+    "margin_type": "",
+    "net_amount": 426.4,
+    "net_rate": 5.33,
+    "page_break": 0,
+    "parent": null,
+    "parentfield": "items",
+    "parenttype": "Purchase Invoice",
+    "po_detail": null,
+    "pr_detail": null,
+    "price_list_rate": 5.33,
+    "pricing_rules": null,
+    "project": null,
+    "purchase_invoice_item": null,
+    "purchase_order": null,
+    "purchase_receipt": null,
+    "qty": 80.0,
+    "quality_inspection": null,
+    "rate": 5.33,
+    "rate_with_margin": 0.0,
+    "received_qty": 0.0,
+    "rejected_qty": 0.0,
+    "rejected_serial_no": null,
+    "rejected_warehouse": null,
+    "rm_supp_cost": 0.0,
+    "sales_invoice_item": null,
+    "serial_no": null,
+    "service_end_date": null,
+    "service_start_date": null,
+    "service_stop_date": null,
+    "stock_qty": 0.0,
+    "stock_uom": "Nos",
+    "stock_uom_rate": 0.0,
+    "total_weight": 0.0,
+    "uom": "Nos",
+    "valuation_rate": 0.0,
+    "warehouse": null,
+    "weight_per_unit": 0.0,
+    "weight_uom": null
+   }
+  ],
+  "language": "en",
+  "letter_head": null,
+  "mode_of_payment": null,
+  "modified": "2021-04-03 03:33:09.180453",
+  "name": null,
+  "naming_series": "ACC-PINV-.YYYY.-",
+  "net_total": 426.4,
+  "on_hold": 0,
+  "other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Purchases</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Widget Fluid 1Litre</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 426.40\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 85.28\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
+  "outstanding_amount": 511.68,
+  "paid_amount": 0.0,
+  "parent": null,
+  "parentfield": null,
+  "parenttype": null,
+  "party_account_currency": "GBP",
+  "payment_schedule": [],
+  "payment_terms_template": null,
+  "plc_conversion_rate": 1.0,
+  "posting_date": null,
+  "posting_time": "16:59:56.789522",
+  "price_list_currency": "GBP",
+  "pricing_rules": [],
+  "project": null,
+  "rejected_warehouse": null,
+  "release_date": null,
+  "remarks": "No Remarks",
+  "represents_company": null,
+  "return_against": null,
+  "rounded_total": 511.68,
+  "rounding_adjustment": 0.0,
+  "scan_barcode": null,
+  "select_print_heading": null,
+  "set_from_warehouse": null,
+  "set_posting_time": 0,
+  "set_warehouse": null,
+  "shipping_address": null,
+  "shipping_address_display": "",
+  "shipping_rule": null,
+  "status": "Unpaid",
+  "supplied_items": [],
+  "supplier": "_Test Supplier",
+  "supplier_address": null,
+  "supplier_name": "_Test Supplier",
+  "supplier_warehouse": "Stores - _T",
+  "tax_category": null,
+  "tax_id": null,
+  "tax_withholding_category": null,
+  "taxes": [
+   {
+    "account_head": "VAT on Purchases - _T",
+    "add_deduct_tax": "Add",
+    "base_tax_amount": 85.28,
+    "base_tax_amount_after_discount_amount": 85.28,
+    "base_total": 511.68,
+    "category": "Total",
+    "charge_type": "On Net Total",
+    "cost_center": "Main - _T",
+    "description": "VAT on Purchases",
+    "included_in_print_rate": 0,
+    "item_wise_tax_detail": "{\"Widget Fluid 1Litre\":[20.0,85.28]}",
+    "parent": null,
+    "parentfield": "taxes",
+    "parenttype": "Purchase Invoice",
+    "rate": 0.0,
+    "row_id": null,
+    "tax_amount": 85.28,
+    "tax_amount_after_discount_amount": 85.28,
+    "total": 511.68
+   }
+  ],
+  "taxes_and_charges": null,
+  "taxes_and_charges_added": 85.28,
+  "taxes_and_charges_deducted": 0.0,
+  "tc_name": null,
+  "terms": null,
+  "title": "_Purchase Invoice",
+  "to_date": null,
+  "total": 426.4,
+  "total_advance": 0.0,
+  "total_net_weight": 0.0,
+  "total_qty": 80.0,
+  "total_taxes_and_charges": 85.28,
+  "unrealized_profit_loss_account": null,
+  "update_stock": 0,
+  "write_off_account": null,
+  "write_off_amount": 0.0,
+  "write_off_cost_center": null
+ },{
+  "account_for_change_amount": null,
+  "additional_discount_percentage": 0.0,
+  "address_display": null,
+  "advances": [],
+  "against_income_account": "Sales - _T",
+  "allocate_advances_automatically": 0,
+  "amended_from": null,
+  "apply_discount_on": "Grand Total",
+  "auto_repeat": null,
+  "base_change_amount": 0.0,
+  "base_discount_amount": 0.0,
+  "base_grand_total": 868.25,
+  "base_in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
+  "base_net_total": 825.0,
+  "base_paid_amount": 0.0,
+  "base_rounded_total": 868.25,
+  "base_rounding_adjustment": 0.0,
+  "base_total": 825.0,
+  "base_total_taxes_and_charges": 43.25,
+  "base_write_off_amount": 0.0,
+  "c_form_applicable": "No",
+  "c_form_no": null,
+  "campaign": null,
+  "cash_bank_account": null,
+  "change_amount": 0.0,
+  "commission_rate": 0.0,
+  "company": "_T",
+  "company_address": null,
+  "company_address_display": null,
+  "company_tax_id": null,
+  "contact_display": null,
+  "contact_email": null,
+  "contact_mobile": null,
+  "contact_person": null,
+  "conversion_rate": 1.0,
+  "cost_center": null,
+  "currency": "GBP",
+  "customer": "_Test Customer",
+  "customer_address": null,
+  "customer_group": "All Customer Groups",
+  "customer_name": "_Test Customer",
+  "debit_to": "Debtors - _T",
+  "discount_amount": 0.0,
+  "docstatus": 0,
+  "doctype": "Sales Invoice",
+  "due_date": null,
+  "from_date": null,
+  "grand_total": 868.25,
+  "group_same_items": 0,
+  "ignore_pricing_rule": 0,
+  "in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
+  "inter_company_invoice_reference": null,
+  "is_consolidated": 0,
+  "is_discounted": 0,
+  "is_internal_customer": 0,
+  "is_opening": "No",
+  "is_pos": 0,
+  "is_return": 0,
+  "items": [
+   {
+    "actual_batch_qty": 0.0,
+    "actual_qty": 0.0,
+    "allow_zero_valuation_rate": 0,
+    "amount": 200.0,
+    "asset": null,
+    "barcode": null,
+    "base_amount": 200.0,
+    "base_net_amount": 200.0,
+    "base_net_rate": 50.0,
+    "base_price_list_rate": 0.0,
+    "base_rate": 50.0,
+    "base_rate_with_margin": 0.0,
+    "batch_no": null,
+    "brand": null,
+    "conversion_factor": 1.0,
+    "cost_center": "Main - _T",
+    "customer_item_code": null,
+    "deferred_revenue_account": null,
+    "delivered_by_supplier": 0,
+    "delivered_qty": 0.0,
+    "delivery_note": null,
+    "description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
+    "discount_amount": 0.0,
+    "discount_percentage": 0.0,
+    "dn_detail": null,
+    "enable_deferred_revenue": 0,
+    "expense_account": null,
+    "finance_book": null,
+    "image": null,
+    "income_account": "Sales - _T",
+    "incoming_rate": 0.0,
+    "is_fixed_asset": 0,
+    "is_free_item": 0,
+    "item_code": null,
+    "item_group": null,
+    "item_name": "Dunlop tyres",
+    "item_tax_rate": "{\"VAT on Sales - _T\": 20.0}",
+    "item_tax_template": null,
+    "margin_rate_or_amount": 0.0,
+    "margin_type": "",
+    "net_amount": 200.0,
+    "net_rate": 50.0,
+    "page_break": 0,
+    "parent": null,
+    "parentfield": "items",
+    "parenttype": "Sales Invoice",
+    "price_list_rate": 0.0,
+    "pricing_rules": null,
+    "project": null,
+    "qty": 4.0,
+    "quality_inspection": null,
+    "rate": 50.0,
+    "rate_with_margin": 0.0,
+    "sales_invoice_item": null,
+    "sales_order": null,
+    "serial_no": null,
+    "service_end_date": null,
+    "service_start_date": null,
+    "service_stop_date": null,
+    "so_detail": null,
+    "stock_qty": 4.0,
+    "stock_uom": "Nos",
+    "stock_uom_rate": 50.0,
+    "target_warehouse": null,
+    "total_weight": 0.0,
+    "uom": "Nos",
+    "warehouse": null,
+    "weight_per_unit": 0.0,
+    "weight_uom": null
+   },
+   {
+    "actual_batch_qty": 0.0,
+    "actual_qty": 0.0,
+    "allow_zero_valuation_rate": 0,
+    "amount": 65.0,
+    "asset": null,
+    "barcode": null,
+    "base_amount": 65.0,
+    "base_net_amount": 65.0,
+    "base_net_rate": 65.0,
+    "base_price_list_rate": 0.0,
+    "base_rate": 65.0,
+    "base_rate_with_margin": 0.0,
+    "batch_no": null,
+    "brand": null,
+    "conversion_factor": 1.0,
+    "cost_center": "Main - _T",
+    "customer_item_code": null,
+    "deferred_revenue_account": null,
+    "delivered_by_supplier": 0,
+    "delivered_qty": 0.0,
+    "delivery_note": null,
+    "description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
+    "discount_amount": 0.0,
+    "discount_percentage": 0.0,
+    "dn_detail": null,
+    "enable_deferred_revenue": 0,
+    "expense_account": null,
+    "finance_book": null,
+    "image": null,
+    "income_account": "Sales - _T",
+    "incoming_rate": 0.0,
+    "is_fixed_asset": 0,
+    "is_free_item": 0,
+    "item_code": "",
+    "item_group": null,
+    "item_name": "Continental tyres",
+    "item_tax_rate": "{\"VAT on Sales - _T\": 5.0}",
+    "item_tax_template": null,
+    "margin_rate_or_amount": 0.0,
+    "margin_type": "",
+    "net_amount": 65.0,
+    "net_rate": 65.0,
+    "page_break": 0,
+    "parent": null,
+    "parentfield": "items",
+    "parenttype": "Sales Invoice",
+    "price_list_rate": 0.0,
+    "pricing_rules": null,
+    "project": null,
+    "qty": 1.0,
+    "quality_inspection": null,
+    "rate": 65.0,
+    "rate_with_margin": 0.0,
+    "sales_invoice_item": null,
+    "sales_order": null,
+    "serial_no": null,
+    "service_end_date": null,
+    "service_start_date": null,
+    "service_stop_date": null,
+    "so_detail": null,
+    "stock_qty": 1.0,
+    "stock_uom": null,
+    "stock_uom_rate": 65.0,
+    "target_warehouse": null,
+    "total_weight": 0.0,
+    "uom": "Nos",
+    "warehouse": null,
+    "weight_per_unit": 0.0,
+    "weight_uom": null
+   },
+   {
+    "actual_batch_qty": 0.0,
+    "actual_qty": 0.0,
+    "allow_zero_valuation_rate": 0,
+    "amount": 560.0,
+    "asset": null,
+    "barcode": null,
+    "base_amount": 560.0,
+    "base_net_amount": 560.0,
+    "base_net_rate": 70.0,
+    "base_price_list_rate": 0.0,
+    "base_rate": 70.0,
+    "base_rate_with_margin": 0.0,
+    "batch_no": null,
+    "brand": null,
+    "conversion_factor": 1.0,
+    "cost_center": "Main - _T",
+    "customer_item_code": null,
+    "deferred_revenue_account": null,
+    "delivered_by_supplier": 0,
+    "delivered_qty": 0.0,
+    "delivery_note": null,
+    "description": "<div class=\"ql-editor read-mode\"><p>New</p></div>",
+    "discount_amount": 0.0,
+    "discount_percentage": 0.0,
+    "dn_detail": null,
+    "enable_deferred_revenue": 0,
+    "expense_account": null,
+    "finance_book": null,
+    "image": null,
+    "income_account": "Sales - _T",
+    "incoming_rate": 0.0,
+    "is_fixed_asset": 0,
+    "is_free_item": 0,
+    "item_code": null,
+    "item_group": null,
+    "item_name": "Toyo tyres",
+    "item_tax_rate": "{\"VAT on Sales - _T\": 0.0}",
+    "item_tax_template": null,
+    "margin_rate_or_amount": 0.0,
+    "margin_type": "",
+    "net_amount": 560.0,
+    "net_rate": 70.0,
+    "page_break": 0,
+    "parent": null,
+    "parentfield": "items",
+    "parenttype": "Sales Invoice",
+    "price_list_rate": 0.0,
+    "pricing_rules": null,
+    "project": null,
+    "qty": 8.0,
+    "quality_inspection": null,
+    "rate": 70.0,
+    "rate_with_margin": 0.0,
+    "sales_invoice_item": null,
+    "sales_order": null,
+    "serial_no": null,
+    "service_end_date": null,
+    "service_start_date": null,
+    "service_stop_date": null,
+    "so_detail": null,
+    "stock_qty": 8.0,
+    "stock_uom": null,
+    "stock_uom_rate": 70.0,
+    "target_warehouse": null,
+    "total_weight": 0.0,
+    "uom": "Nos",
+    "warehouse": null,
+    "weight_per_unit": 0.0,
+    "weight_uom": null
+   }
+  ],
+  "language": "en",
+  "letter_head": null,
+  "loyalty_amount": 0.0,
+  "loyalty_points": 0,
+  "loyalty_program": null,
+  "loyalty_redemption_account": null,
+  "loyalty_redemption_cost_center": null,
+  "modified": "2021-02-16 05:18:59.755144",
+  "name": null,
+  "naming_series": "ACC-SINV-.YYYY.-",
+  "net_total": 825.0,
+  "other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Sales</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Dunlop tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 200.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 40.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Continental tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 65.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(5.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 3.25\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Toyo tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 560.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(0.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 0.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
+  "outstanding_amount": 868.25,
+  "packed_items": [],
+  "paid_amount": 0.0,
+  "parent": null,
+  "parentfield": null,
+  "parenttype": null,
+  "party_account_currency": "GBP",
+  "payment_schedule": [],
+  "payment_terms_template": null,
+  "payments": [],
+  "plc_conversion_rate": 1.0,
+  "po_date": null,
+  "po_no": "",
+  "pos_profile": null,
+  "posting_date": null,
+  "posting_time": "5:19:02.994077",
+  "price_list_currency": "GBP",
+  "pricing_rules": [],
+  "project": null,
+  "redeem_loyalty_points": 0,
+  "remarks": "No Remarks",
+  "represents_company": "",
+  "return_against": null,
+  "rounded_total": 868.25,
+  "rounding_adjustment": 0.0,
+  "sales_partner": null,
+  "sales_team": [],
+  "scan_barcode": null,
+  "select_print_heading": null,
+  "selling_price_list": "Standard Selling",
+  "set_posting_time": 0,
+  "set_target_warehouse": null,
+  "set_warehouse": null,
+  "shipping_address": null,
+  "shipping_address_name": "",
+  "shipping_rule": null,
+  "source": null,
+  "status": "Overdue",
+  "tax_category": "",
+  "tax_id": null,
+  "taxes": [
+   {
+    "account_head": "VAT on Sales - _T",
+    "base_tax_amount": 43.25,
+    "base_tax_amount_after_discount_amount": 43.25,
+    "base_total": 868.25,
+    "charge_type": "On Net Total",
+    "cost_center": "Main - _T",
+    "description": "VAT on Sales",
+    "included_in_print_rate": 0,
+    "item_wise_tax_detail": "{\"Dunlop tyres\":[20.0,40.0],\"Continental tyres\":[5.0,3.25],\"Toyo tyres\":[0.0,0.0]}",
+    "parent": null,
+    "parentfield": "taxes",
+    "parenttype": "Sales Invoice",
+    "rate": 0.0,
+    "row_id": null,
+    "tax_amount": 43.25,
+    "tax_amount_after_discount_amount": 43.25,
+    "total": 868.25
+   }
+  ],
+  "taxes_and_charges": null,
+  "tc_name": null,
+  "terms": null,
+  "territory": "All Territories",
+  "timesheets": [],
+  "title": "_Sales Invoice",
+  "to_date": null,
+  "total": 825.0,
+  "total_advance": 0.0,
+  "total_billing_amount": 0.0,
+  "total_commission": 0.0,
+  "total_net_weight": 0.0,
+  "total_qty": 13.0,
+  "total_taxes_and_charges": 43.25,
+  "unrealized_profit_loss_account": null,
+  "update_billed_amount_in_sales_order": 0,
+  "update_stock": 0,
+  "write_off_account": null,
+  "write_off_amount": 0.0,
+  "write_off_cost_center": null,
+  "write_off_outstanding_amount_automatically": 0
+ }
+]
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.py b/erpnext/accounts/report/tax_detail/test_tax_detail.py
new file mode 100644
index 0000000..743ddba
--- /dev/null
+++ b/erpnext/accounts/report/tax_detail/test_tax_detail.py
@@ -0,0 +1,176 @@
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+import datetime
+import json
+import os
+from frappe.utils import getdate, add_to_date, get_first_day, get_last_day, get_year_start, get_year_ending
+from .tax_detail import filter_match, save_custom_report
+
+class TestTaxDetail(unittest.TestCase):
+	def load_testdocs(self):
+		from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
+		datapath, _ = os.path.splitext(os.path.realpath(__file__))
+		with open(datapath + '.json', 'r') as fp:
+			docs = json.load(fp)
+
+		now = getdate()
+		self.from_date = get_first_day(now)
+		self.to_date = get_last_day(now)
+
+		try:
+			get_fiscal_year(now, company="_T")
+		except FiscalYearError:
+			docs = [{
+				"companies": [{
+					"company": "_T",
+					"parent": "_Test Fiscal",
+					"parentfield": "companies",
+					"parenttype": "Fiscal Year"
+				}],
+				"doctype": "Fiscal Year",
+				"year": "_Test Fiscal",
+				"year_end_date": get_year_ending(now),
+				"year_start_date": get_year_start(now)
+			}] + docs
+
+		docs = [{
+			"abbr": "_T",
+			"company_name": "_T",
+			"country": "United Kingdom",
+			"default_currency": "GBP",
+			"doctype": "Company",
+			"name": "_T"
+		}] + docs
+
+		for doc in docs:
+			try:
+				db_doc = frappe.get_doc(doc)
+				if 'Invoice' in db_doc.doctype:
+					db_doc.due_date = add_to_date(now, days=1)
+					db_doc.insert()
+					# Create GL Entries:
+					db_doc.submit()
+				else:
+					db_doc.insert()
+			except frappe.exceptions.DuplicateEntryError:
+				pass
+
+	def load_defcols(self):
+		self.company = frappe.get_doc('Company', '_T')
+		custom_report = frappe.get_doc('Report', 'Tax Detail')
+		self.default_columns, _ = custom_report.run_query_report(
+			filters={
+				'from_date': '2021-03-01',
+				'to_date': '2021-03-31',
+				'company': self.company.name,
+				'mode': 'run',
+				'report_name': 'Tax Detail'
+			}, user=frappe.session.user)
+
+	def rm_testdocs(self):
+		"Remove the Company and all data"
+		from erpnext.setup.doctype.company.company import create_transaction_deletion_request
+		create_transaction_deletion_request(self.company.name)
+
+	def test_report(self):
+		self.load_testdocs()
+		self.load_defcols()
+		report_name = save_custom_report(
+			'Tax Detail',
+			'_Test Tax Detail',
+			json.dumps({
+				'columns': self.default_columns,
+				'sections': {
+					'Box1':{'Filter0':{'type':'filter','filters':{'4':'VAT on Sales'}}},
+					'Box2':{'Filter0':{'type':'filter','filters':{'4':'Acquisition'}}},
+					'Box3':{'Box1':{'type':'section'},'Box2':{'type':'section'}},
+					'Box4':{'Filter0':{'type':'filter','filters':{'4':'VAT on Purchases'}}},
+					'Box5':{'Box3':{'type':'section'},'Box4':{'type':'section'}},
+					'Box6':{'Filter0':{'type':'filter','filters':{'3':'!=Tax','4':'Sales'}}},
+					'Box7':{'Filter0':{'type':'filter','filters':{'2':'Expense','3':'!=Tax'}}},
+					'Box8':{'Filter0':{'type':'filter','filters':{'3':'!=Tax','4':'Sales','12':'EU'}}},
+					'Box9':{'Filter0':{'type':'filter','filters':{'2':'Expense','3':'!=Tax','12':'EU'}}}
+				},
+				'show_detail': 1
+			}))
+		data = frappe.desk.query_report.run(report_name,
+			filters={
+				'from_date': self.from_date,
+				'to_date': self.to_date,
+				'company': self.company.name,
+				'mode': 'run',
+				'report_name': report_name
+			}, user=frappe.session.user)
+
+		self.assertListEqual(data.get('columns'), self.default_columns)
+		expected = (('Box1', 43.25), ('Box2', 0.0), ('Box3', 43.25), ('Box4', -85.28), ('Box5', -42.03),
+			('Box6', 825.0), ('Box7', -426.40), ('Box8', 0.0), ('Box9', 0.0))
+		exrow = iter(expected)
+		for row in data.get('result'):
+			if row.get('voucher_no') and not row.get('posting_date'):
+				label, value = next(exrow)
+				self.assertDictEqual(row, {'voucher_no': label, 'amount': value})
+		self.assertListEqual(data.get('report_summary'),
+			[{'label': label, 'datatype': 'Currency', 'value': value} for label, value in expected])
+
+		self.rm_testdocs()
+
+	def test_filter_match(self):
+		# None - treated as -inf number except range
+		self.assertTrue(filter_match(None, '!='))
+		self.assertTrue(filter_match(None, '<'))
+		self.assertTrue(filter_match(None, '<jjj'))
+		self.assertTrue(filter_match(None, '  :  '))
+		self.assertTrue(filter_match(None, ':56'))
+		self.assertTrue(filter_match(None, ':de'))
+		self.assertFalse(filter_match(None, '3.4'))
+		self.assertFalse(filter_match(None, '='))
+		self.assertFalse(filter_match(None, '=3.4'))
+		self.assertFalse(filter_match(None, '>3.4'))
+		self.assertFalse(filter_match(None, '   <'))
+		self.assertFalse(filter_match(None, 'ew'))
+		self.assertFalse(filter_match(None, ' '))
+		self.assertFalse(filter_match(None, ' f :'))
+
+		# Numbers
+		self.assertTrue(filter_match(3.4, '3.4'))
+		self.assertTrue(filter_match(3.4, '.4'))
+		self.assertTrue(filter_match(3.4, '3'))
+		self.assertTrue(filter_match(-3.4, '< -3'))
+		self.assertTrue(filter_match(-3.4, '> -4'))
+		self.assertTrue(filter_match(3.4, '= 3.4 '))
+		self.assertTrue(filter_match(3.4, '!=4.5'))
+		self.assertTrue(filter_match(3.4, ' 3 : 4 '))
+		self.assertTrue(filter_match(0.0, '  :  '))
+		self.assertFalse(filter_match(3.4, '=4.5'))
+		self.assertFalse(filter_match(3.4, ' = 3.4 '))
+		self.assertFalse(filter_match(3.4, '!=3.4'))
+		self.assertFalse(filter_match(3.4, '>6'))
+		self.assertFalse(filter_match(3.4, '<-4.5'))
+		self.assertFalse(filter_match(3.4, '4.5'))
+		self.assertFalse(filter_match(3.4, '5:9'))
+
+		# Strings
+		self.assertTrue(filter_match('ACC-SINV-2021-00001', 'SINV'))
+		self.assertTrue(filter_match('ACC-SINV-2021-00001', 'sinv'))
+		self.assertTrue(filter_match('ACC-SINV-2021-00001', '-2021'))
+		self.assertTrue(filter_match(' ACC-SINV-2021-00001', ' acc'))
+		self.assertTrue(filter_match('ACC-SINV-2021-00001', '=2021'))
+		self.assertTrue(filter_match('ACC-SINV-2021-00001', '!=zz'))
+		self.assertTrue(filter_match('ACC-SINV-2021-00001', '<   zzz  '))
+		self.assertTrue(filter_match('ACC-SINV-2021-00001', '  :  sinv  '))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', '  sinv  :'))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', ' acc'))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', '= 2021 '))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', '!=sinv'))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', ' >'))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', '>aa'))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', ' <'))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', '<   '))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', ' ='))
+		self.assertFalse(filter_match('ACC-SINV-2021-00001', '='))
+
+		# Date - always match
+		self.assertTrue(filter_match(datetime.date(2021, 3, 19), ' kdsjkldfs '))
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index a8280c1..e15715d 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -78,7 +78,7 @@
 			and company=%s and posting_date between %s and %s
 	""", (supplier, company, from_date, to_date), as_dict=1)
 
-	supplier_credit_amount = flt(sum([d.credit for d in entries]))
+	supplier_credit_amount = flt(sum(d.credit for d in entries))
 
 	vouchers = [d.voucher_no for d in entries]
 	vouchers += get_advance_vouchers([supplier], company=company,
@@ -91,7 +91,7 @@
 			from `tabGL Entry`
 			where account=%s and posting_date between %s and %s
 				and company=%s and credit > 0 and voucher_no in ({0})
-		""".format(', '.join(["'%s'" % d for d in vouchers])),
+		""".format(', '.join("'%s'" % d for d in vouchers)),
 			(account, from_date, to_date, company))[0][0])
 
 	date_range_filter = [fiscal_year, from_date, to_date]
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index b020d0a..ba461ed 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -139,6 +139,6 @@
 	gross_profit_data = GrossProfitGenerator(filters)
 	result = gross_profit_data.grouped_data
 	if not with_item_data:
-		result = sum([d.gross_profit for d in result])
+		result = sum(d.gross_profit for d in result)
 
 	return result
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 5a64e27..66a9b60 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -635,7 +635,7 @@
 			'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
 			as_dict=1
 		)
-		held_invoices = set([d['name'] for d in held_invoices])
+		held_invoices = set(d['name'] for d in held_invoices)
 
 	return held_invoices
 
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index 10a4001..821fa4d 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -438,10 +438,21 @@
    "dependencies": "GL Entry",
    "hidden": 0,
    "is_query_report": 1,
+   "label": "Tax Detail",
+   "link_to": "Tax Detail",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "GL Entry",
+   "hidden": 0,
+   "is_query_report": 1,
    "label": "DATEV Export",
    "link_to": "DATEV",
    "link_type": "Report",
    "onboard": 0,
+   "only_for": "Germany",
    "type": "Link"
   },
   {
@@ -452,6 +463,7 @@
    "link_to": "UAE VAT 201",
    "link_type": "Report",
    "onboard": 0,
+   "only_for": "United Arab Emirates",
    "type": "Link"
   },
   {
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 3cd4b80..8845f24 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -470,7 +470,7 @@
 		})
 		asset.insert()
 		accumulated_depreciation_after_full_schedule = \
-			max([d.accumulated_depreciation_amount for d in asset.get("schedules")])
+			max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
 
 		asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) -
 			flt(accumulated_depreciation_after_full_schedule))
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 1430827..2f6b5ee 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -92,7 +92,7 @@
 			d.value_after_depreciation = asset_value
 
 			if d.depreciation_method in ("Straight Line", "Manual"):
-				end_date = max([s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx])
+				end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx)
 				total_days = date_diff(end_date, self.date)
 				rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
 				from_date = self.date
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 630a1dc..838a9ab 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -9,13 +9,14 @@
   "supp_master_name",
   "supplier_group",
   "buying_price_list",
+  "maintain_same_rate_action",
+  "role_to_override_stop_action",
   "column_break_3",
   "po_required",
   "pr_required",
   "maintain_same_rate",
-  "maintain_same_rate_action",
-  "role_to_override_stop_action",
   "allow_multiple_items",
+  "bill_for_rejected_quantity_in_purchase_invoice",
   "subcontract",
   "backflush_raw_materials_of_subcontract_based_on",
   "column_break_11",
@@ -108,6 +109,13 @@
    "fieldtype": "Link",
    "label": "Role Allowed to Override Stop Action",
    "options": "Role"
+  },
+  {
+   "default": "1",
+   "description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
+   "fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
+   "fieldtype": "Check",
+   "label": "Bill for Rejected Quantity in Purchase Invoice"
   }
  ],
  "icon": "fa fa-cog",
@@ -115,7 +123,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-04-04 20:01:44.087066",
+ "modified": "2021-06-23 19:40:00.120822",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 0f6d927..521432d 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -53,6 +53,39 @@
 		} else {
 			frm.set_value("tax_withholding_category", frm.supplier_tds);
 		}
+	},
+
+	refresh: function(frm) {
+		frm.trigger('get_materials_from_supplier');
+	},
+
+	get_materials_from_supplier: function(frm) {
+		let po_details = [];
+
+		if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
+			frm.doc.supplied_items.forEach(d => {
+				if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
+					po_details.push(d.name)
+				}
+			});
+		}
+
+		if (po_details && po_details.length) {
+			frm.add_custom_button(__('Return of Components'), () => {
+				frm.call({
+					method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier',
+					freeze: true,
+					freeze_message: __('Creating Stock Entry'),
+					args: { purchase_order: frm.doc.name, po_details: po_details },
+					callback: function(r) {
+						if (r && r.message) {
+							const doc = frappe.model.sync(r.message);
+							frappe.set_route("Form", doc[0].doctype, doc[0].name);
+						}
+					}
+				});
+			}, __('Create'));
+		}
 	}
 });
 
@@ -69,8 +102,8 @@
 	}
 });
 
-erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
-	setup: function() {
+erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends erpnext.buying.BuyingController {
+	setup() {
 		this.frm.custom_make_buttons = {
 			'Purchase Receipt': 'Purchase Receipt',
 			'Purchase Invoice': 'Purchase Invoice',
@@ -78,13 +111,13 @@
 			'Payment Entry': 'Payment',
 		}
 
-		this._super();
+		super.setup();
 
-	},
+	}
 
-	refresh: function(doc, cdt, cdn) {
+	refresh(doc, cdt, cdn) {
 		var me = this;
-		this._super();
+		super.refresh();
 		var allow_receipt = false;
 		var is_drop_ship = false;
 
@@ -190,9 +223,9 @@
 		} else if(doc.docstatus===0) {
 			cur_frm.cscript.add_from_mappers();
 		}
-	},
+	}
 
-	get_items_from_open_material_requests: function() {
+	get_items_from_open_material_requests() {
 		erpnext.utils.map_current_doc({
 			method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order_based_on_supplier",
 			args: {
@@ -210,17 +243,17 @@
 			},
 			get_query_method: "erpnext.stock.doctype.material_request.material_request.get_material_requests_based_on_supplier"
 		});
-	},
+	}
 
-	validate: function() {
+	validate() {
 		set_schedule_date(this.frm);
-	},
+	}
 
-	has_unsupplied_items: function() {
-		return this.frm.doc['supplied_items'].some(item => item.required_qty != item.supplied_qty)
-	},
+	has_unsupplied_items() {
+		return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
+	}
 
-	make_stock_entry: function() {
+	make_stock_entry() {
 		var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
 		var me = this;
 
@@ -335,9 +368,9 @@
 			me.dialog.hide();
 		});
 
-	},
+	}
 
-	_make_rm_stock_entry: function(rm_items) {
+	_make_rm_stock_entry(rm_items) {
 		frappe.call({
 			method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry",
 			args: {
@@ -350,31 +383,31 @@
 				frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
 			}
 		});
-	},
+	}
 
-	make_inter_company_order: function(frm) {
+	make_inter_company_order(frm) {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order",
 			frm: frm
 		});
-	},
+	}
 
-	make_purchase_receipt: function() {
+	make_purchase_receipt() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
 			frm: cur_frm,
 			freeze_message: __("Creating Purchase Receipt ...")
 		})
-	},
+	}
 
-	make_purchase_invoice: function() {
+	make_purchase_invoice() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice",
 			frm: cur_frm
 		})
-	},
+	}
 
-	add_from_mappers: function() {
+	add_from_mappers() {
 		var me = this;
 		this.frm.add_custom_button(__('Material Request'),
 			function() {
@@ -480,13 +513,13 @@
 				}
 			});
 		}, __("Tools"));
-	},
+	}
 
-	tc_name: function() {
+	tc_name() {
 		this.get_terms();
-	},
+	}
 
-	items_add: function(doc, cdt, cdn) {
+	items_add(doc, cdt, cdn) {
 		var row = frappe.get_doc(cdt, cdn);
 		if(doc.schedule_date) {
 			row.schedule_date = doc.schedule_date;
@@ -494,13 +527,13 @@
 		} else {
 			this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]);
 		}
-	},
+	}
 
-	unhold_purchase_order: function(){
+	unhold_purchase_order(){
 		cur_frm.cscript.update_status("Resume", "Draft")
-	},
+	}
 
-	hold_purchase_order: function(){
+	hold_purchase_order(){
 		var me = this;
 		var d = new frappe.ui.Dialog({
 			title: __('Reason for Hold'),
@@ -513,12 +546,14 @@
 			],
 			primary_action: function() {
 				var data = d.get_values();
+				let reason_for_hold = 'Reason for hold: ' + data.reason_for_hold;
+
 				frappe.call({
 					method: "frappe.desk.form.utils.add_comment",
 					args: {
 						reference_doctype: me.frm.doctype,
 						reference_name: me.frm.docname,
-						content: __('Reason for hold:') + " " +data.reason_for_hold,
+						content: __(reason_for_hold),
 						comment_email: frappe.session.user,
 						comment_by: frappe.session.user_fullname
 					},
@@ -532,31 +567,31 @@
 			}
 		});
 		d.show();
-	},
+	}
 
-	unclose_purchase_order: function(){
+	unclose_purchase_order(){
 		cur_frm.cscript.update_status('Re-open', 'Submitted')
-	},
+	}
 
-	close_purchase_order: function(){
+	close_purchase_order(){
 		cur_frm.cscript.update_status('Close', 'Closed')
-	},
+	}
 
-	delivered_by_supplier: function(){
+	delivered_by_supplier(){
 		cur_frm.cscript.update_status('Deliver', 'Delivered')
-	},
+	}
 
-	items_on_form_rendered: function() {
-		set_schedule_date(this.frm);
-	},
-
-	schedule_date: function() {
+	items_on_form_rendered() {
 		set_schedule_date(this.frm);
 	}
-});
+
+	schedule_date() {
+		set_schedule_date(this.frm);
+	}
+};
 
 // for backward compatibility: combine new and previous states
-$.extend(cur_frm.cscript, new erpnext.buying.PurchaseOrderController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.buying.PurchaseOrderController({frm: cur_frm}));
 
 cur_frm.cscript.update_status= function(label, status){
 	frappe.call({
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 41668c6..bb0ad60 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -609,6 +609,7 @@
    "fieldname": "supplied_items",
    "fieldtype": "Table",
    "label": "Supplied Items",
+   "no_copy": 1,
    "oldfieldname": "po_raw_material_details",
    "oldfieldtype": "Table",
    "options": "Purchase Order Item Supplied",
@@ -1377,7 +1378,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 00:55:30.781375",
+ "modified": "2021-05-30 15:17:53.663648",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 782593a..eaa502f 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -14,12 +14,11 @@
 from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status
 from erpnext.stock.utils import get_bin
 from erpnext.accounts.party import get_party_account_currency
-from six import string_types
 from erpnext.stock.doctype.item.item import get_item_defaults
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
 from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
-from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
-	unlink_inter_company_doc
+from erpnext.accounts.doctype.sales_invoice.sales_invoice import (validate_inter_company_party,
+	update_linked_doc, unlink_inter_company_doc)
 
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
@@ -139,7 +138,7 @@
 
 	def validate_minimum_order_qty(self):
 		if not self.get("items"): return
-		items = list(set([d.item_code for d in self.get("items")]))
+		items = list(set(d.item_code for d in self.get("items")))
 
 		itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty
 			from tabItem where name in ({0})""".format(", ".join(["%s"] * len(items))), items))
@@ -326,10 +325,10 @@
 			so.notify_update()
 
 	def has_drop_ship_item(self):
-		return any([d.delivered_by_supplier for d in self.items])
+		return any(d.delivered_by_supplier for d in self.items)
 
 	def is_against_so(self):
-		return any([d.sales_order for d in self.items if d.sales_order])
+		return any(d.sales_order for d in self.items if d.sales_order)
 
 	def set_received_qty_for_drop_ship_items(self):
 		for item in self.items:
@@ -503,9 +502,11 @@
 
 @frappe.whitelist()
 def make_rm_stock_entry(purchase_order, rm_items):
-	if isinstance(rm_items, string_types):
+	rm_items_list = rm_items
+
+	if isinstance(rm_items, str):
 		rm_items_list = json.loads(rm_items)
-	else:
+	elif not rm_items:
 		frappe.throw(_("No Items available for transfer"))
 
 	if rm_items_list:
@@ -543,6 +544,8 @@
 							'qty': rm_item_data["qty"],
 							'from_warehouse': rm_item_data["warehouse"],
 							'stock_uom': rm_item_data["stock_uom"],
+							'serial_no': rm_item_data.get('serial_no'),
+							'batch_no': rm_item_data.get('batch_no'),
 							'main_item_code': rm_item_data["item_code"],
 							'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
 						}
@@ -582,3 +585,58 @@
 def make_inter_company_sales_order(source_name, target_doc=None):
 	from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
 	return make_inter_company_transaction("Purchase Order", source_name, target_doc)
+
+@frappe.whitelist()
+def get_materials_from_supplier(purchase_order, po_details):
+	if isinstance(po_details, str):
+		po_details = json.loads(po_details)
+
+	doc = frappe.get_cached_doc('Purchase Order', purchase_order)
+	doc.initialized_fields()
+	doc.purchase_orders = [doc.name]
+	doc.get_available_materials()
+
+	if not doc.available_materials:
+		frappe.throw(_('Materials are already received against the purchase order {0}')
+			.format(purchase_order))
+
+	return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details)
+
+def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details):
+	ste_doc = frappe.new_doc('Stock Entry')
+	ste_doc.purpose = 'Material Transfer'
+	ste_doc.purchase_order = po_doc.name
+	ste_doc.company = po_doc.company
+	ste_doc.is_return = 1
+
+	for key, value in available_materials.items():
+		if not value.qty:
+			continue
+
+		if value.batch_no:
+			for batch_no, qty in value.batch_no.items():
+				if qty > 0:
+					add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no)
+		else:
+			add_items_in_ste(ste_doc, value, value.qty, po_details)
+
+	ste_doc.set_stock_entry_type()
+	ste_doc.calculate_rate_and_amount()
+
+	return ste_doc
+
+def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
+	item = ste_doc.append('items', row.item_details)
+
+	po_detail = list(set(row.po_details).intersection(po_details))
+	item.update({
+		'qty': qty,
+		'batch_no': batch_no,
+		'basic_rate': row.item_details['rate'],
+		'po_detail': po_detail[0] if po_detail else '',
+		's_warehouse': row.item_details['t_warehouse'],
+		't_warehouse': row.item_details['s_warehouse'],
+		'item_code': row.item_details['rm_item_code'],
+		'subcontracted_item': row.item_details['main_item_code'],
+		'serial_no': '\n'.join(row.serial_no) if row.serial_no else ''
+	})
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 3917196..8563b97 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -20,7 +20,6 @@
 from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
 
 from erpnext.stock.doctype.batch.test_batch import make_new_batch
-from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials
 
 class TestPurchaseOrder(unittest.TestCase):
 	def test_make_purchase_receipt(self):
@@ -359,7 +358,7 @@
 		update_child_qty_rate('Purchase Order', trans_item, po.name)
 		po.reload()
 
-		total_reqd_qty_after_change = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
+		total_reqd_qty_after_change = sum(d.get("required_qty") for d in po.as_dict().get("supplied_items"))
 
 		self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
 
@@ -771,7 +770,7 @@
 		self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
 
 	def test_exploded_items_in_subcontracted(self):
-		item_code = "_Test Subcontracted FG Item 1"
+		item_code = "_Test Subcontracted FG Item 11"
 		make_subcontracted_item(item_code=item_code)
 
 		po = create_purchase_order(item_code=item_code, qty=1,
@@ -848,79 +847,6 @@
 		for item in rm_items:
 			transferred_rm_map[item.get('rm_item_code')] = item
 
-		for item in pr.get('supplied_items'):
-			self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
-
-		update_backflush_based_on("BOM")
-
-	def test_backflushed_based_on_for_multiple_batches(self):
-		item_code = "_Test Subcontracted FG Item 2"
-		make_item('Sub Contracted Raw Material 2', {
-			'is_stock_item': 1,
-			'is_sub_contracted_item': 1
-		})
-
-		make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1,
-			raw_materials=["Sub Contracted Raw Material 2"])
-
-		update_backflush_based_on("Material Transferred for Subcontract")
-
-		order_qty = 500
-		po = create_purchase_order(item_code=item_code, qty=order_qty,
-			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
-
-		make_stock_entry(target="_Test Warehouse - _TC",
-			item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100)
-
-		rm_items = [
-			{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item",
-				"qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}]
-
-		rm_item_string = json.dumps(rm_items)
-		se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
-		se.submit()
-
-		for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]:
-			make_new_batch(batch_id=batch, item_code=item_code)
-
-		pr = make_purchase_receipt(po.name)
-
-		# partial receipt
-		pr.get('items')[0].qty = 30
-		pr.get('items')[0].batch_no = "ABCD1"
-
-		purchase_order = po.name
-		purchase_order_item = po.items[0].name
-
-		for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items():
-			pr.append("items", {
-				"item_code": pr.get('items')[0].item_code,
-				"item_name": pr.get('items')[0].item_name,
-				"uom": pr.get('items')[0].uom,
-				"stock_uom": pr.get('items')[0].stock_uom,
-				"warehouse": pr.get('items')[0].warehouse,
-				"conversion_factor": pr.get('items')[0].conversion_factor,
-				"cost_center": pr.get('items')[0].cost_center,
-				"rate": pr.get('items')[0].rate,
-				"qty": qty,
-				"batch_no": batch_no,
-				"purchase_order": purchase_order,
-				"purchase_order_item": purchase_order_item
-			})
-
-		pr.submit()
-
-		pr1 = make_purchase_receipt(po.name)
-		pr1.get('items')[0].qty = 300
-		pr1.get('items')[0].batch_no = "ABCD1"
-		pr1.save()
-
-		pr_key = ("Sub Contracted Raw Material 2", po.name)
-		consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key)
-
-		self.assertTrue(pr1.supplied_items[0].consumed_qty > 0)
-		self.assertTrue(pr1.supplied_items[0].consumed_qty,  flt(552.0) - flt(consumed_qty))
-
 		update_backflush_based_on("BOM")
 
 	def test_supplied_qty_against_subcontracted_po(self):
@@ -1117,22 +1043,29 @@
 	po.conversion_factor = args.conversion_factor or 1
 	po.supplier_warehouse = args.supplier_warehouse or None
 
-	po.append("items", {
-		"item_code": args.item or args.item_code or "_Test Item",
-		"warehouse": args.warehouse or "_Test Warehouse - _TC",
-		"qty": args.qty or 10,
-		"rate": args.rate or 500,
-		"schedule_date": add_days(nowdate(), 1),
-		"include_exploded_items": args.get('include_exploded_items', 1),
-		"against_blanket_order": args.against_blanket_order
-	})
+	if args.rm_items:
+		for row in args.rm_items:
+			po.append("items", row)
+	else:
+		po.append("items", {
+			"item_code": args.item or args.item_code or "_Test Item",
+			"warehouse": args.warehouse or "_Test Warehouse - _TC",
+			"qty": args.qty or 10,
+			"rate": args.rate or 500,
+			"schedule_date": add_days(nowdate(), 1),
+			"include_exploded_items": args.get('include_exploded_items', 1),
+			"against_blanket_order": args.against_blanket_order
+		})
+
+	po.set_missing_values()
 	if not args.do_not_save:
 		po.insert()
 		if not args.do_not_submit:
 			if po.is_subcontracted == "Yes":
 				supp_items = po.get("supplied_items")
 				for d in supp_items:
-					d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
+					if not d.reserve_warehouse:
+						d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
 			po.submit()
 
 	return po
diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
index d7ea9c1..60247bd 100644
--- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
+++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
@@ -6,21 +6,25 @@
  "engine": "InnoDB",
  "field_order": [
   "main_item_code",
-  "bom_detail_no",
+  "rm_item_code",
+  "column_break_3",
   "stock_uom",
+  "reserve_warehouse",
   "conversion_factor",
   "column_break_6",
-  "rm_item_code",
+  "bom_detail_no",
   "reference_name",
-  "reserve_warehouse",
   "section_break2",
   "rate",
   "col_break2",
   "amount",
   "section_break1",
   "required_qty",
+  "supplied_qty",
   "col_break1",
-  "supplied_qty"
+  "consumed_qty",
+  "returned_qty",
+  "total_supplied_qty"
  ],
  "fields": [
   {
@@ -125,6 +129,8 @@
    "fieldtype": "Float",
    "in_list_view": 1,
    "label": "Supplied Qty",
+   "no_copy": 1,
+   "print_hide": 1,
    "read_only": 1
   },
   {
@@ -142,13 +148,42 @@
   {
    "fieldname": "col_break2",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "consumed_qty",
+   "fieldtype": "Float",
+   "label": "Consumed Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "returned_qty",
+   "fieldtype": "Float",
+   "label": "Returned Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "total_supplied_qty",
+   "fieldtype": "Float",
+   "hidden": 1,
+   "label": "Total Supplied Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
   }
  ],
  "hide_toolbar": 1,
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-06-09 15:17:58.128242",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item Supplied",
diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
index dc00bca..f9cd720 100644
--- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
+++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
@@ -6,10 +6,11 @@
  "engine": "InnoDB",
  "field_order": [
   "main_item_code",
-  "description",
+  "rm_item_code",
+  "item_name",
   "bom_detail_no",
   "col_break1",
-  "rm_item_code",
+  "description",
   "stock_uom",
   "conversion_factor",
   "reference_name",
@@ -25,7 +26,8 @@
   "secbreak_3",
   "batch_no",
   "col_break4",
-  "serial_no"
+  "serial_no",
+  "purchase_order"
  ],
  "fields": [
   {
@@ -52,7 +54,6 @@
    "fieldname": "description",
    "fieldtype": "Text Editor",
    "in_global_search": 1,
-   "in_list_view": 1,
    "label": "Description",
    "oldfieldname": "description",
    "oldfieldtype": "Data",
@@ -81,18 +82,20 @@
    "fieldname": "required_qty",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Required Qty",
+   "label": "Available Qty For Consumption",
    "oldfieldname": "required_qty",
    "oldfieldtype": "Currency",
+   "print_hide": 1,
    "read_only": 1
   },
   {
+   "columns": 2,
    "fieldname": "consumed_qty",
    "fieldtype": "Float",
-   "label": "Consumed Qty",
+   "in_list_view": 1,
+   "label": "Qty to Be Consumed",
    "oldfieldname": "consumed_qty",
    "oldfieldtype": "Currency",
-   "read_only": 1,
    "reqd": 1
   },
   {
@@ -183,12 +186,28 @@
   {
    "fieldname": "col_break4",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "purchase_order",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Purchase Order",
+   "no_copy": 1,
+   "options": "Purchase Order",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-06-19 19:33:04.431213",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Receipt Item Supplied",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index b76c378..bde00cb 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -205,10 +205,10 @@
 
 })
 
-erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
-	refresh: function() {
+erpnext.buying.RequestforQuotationController = class RequestforQuotationController extends erpnext.buying.BuyingController {
+	refresh() {
 		var me = this;
-		this._super();
+		super.refresh();
 		if (this.frm.doc.docstatus===0) {
 			this.frm.add_custom_button(__('Material Request'),
 				function() {
@@ -302,17 +302,17 @@
 					me.get_suppliers_button(me.frm);
 				}, __("Tools"));
 		}
-	},
+	}
 
-	calculate_taxes_and_totals: function() {
+	calculate_taxes_and_totals() {
 		return;
-	},
+	}
 
-	tc_name: function() {
+	tc_name() {
 		this.get_terms();
-	},
+	}
 
-	get_suppliers_button: function (frm) {
+	get_suppliers_button (frm) {
 		var doc = frm.doc;
 		var dialog = new frappe.ui.Dialog({
 			title: __("Get Suppliers"),
@@ -410,8 +410,8 @@
 		});
 
 		dialog.show();
-	},
-});
+	}
+};
 
 // for backward compatibility: combine new and previous states
-$.extend(cur_frm.cscript, new erpnext.buying.RequestforQuotationController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.buying.RequestforQuotationController({frm: cur_frm}));
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 180ba93..a4ce84e 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -317,19 +317,21 @@
 			create_rfq_items(sq_doc, supplier, data)
 
 def create_rfq_items(sq_doc, supplier, data):
-	sq_doc.append('items', {
-		"item_code": data.item_code,
-		"item_name": data.item_name,
-		"description": data.description,
-		"qty": data.qty,
-		"rate": data.rate,
-		"conversion_factor": data.conversion_factor if data.conversion_factor else None,
-		"supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"),
-		"warehouse": data.warehouse or '',
+	args = {}
+
+	for field in ['item_code', 'item_name', 'description', 'qty', 'rate', 'conversion_factor',
+		'warehouse', 'material_request', 'material_request_item', 'stock_qty']:
+		args[field] = data.get(field)
+
+	args.update({
 		"request_for_quotation_item": data.name,
-		"request_for_quotation": data.parent
+		"request_for_quotation": data.parent,
+		"supplier_part_no": frappe.db.get_value("Item Supplier",
+			{'parent': data.item_code, 'supplier': supplier}, "supplier_part_no")
 	})
 
+	sq_doc.append('items', args)
+
 @frappe.whitelist()
 def get_pdf(doctype, name, supplier):
 	doc = get_rfq_doc(doctype, name, supplier)
@@ -391,7 +393,7 @@
 def get_supplier_tag():
 	if not frappe.cache().hget("Supplier", "Tags"):
 		filters = {"document_type": "Supplier"}
-		tags = list(set([tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag]))
+		tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag))
 		frappe.cache().hset("Supplier", "Tags", tags)
 
 	return frappe.cache().hget("Supplier", "Tags")
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index a0187b0..dc9c590 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -4,19 +4,19 @@
 // attach required files
 {% include 'erpnext/public/js/controllers/buying.js' %};
 
-erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({
-	setup: function() {
+erpnext.buying.SupplierQuotationController = class SupplierQuotationController extends erpnext.buying.BuyingController {
+	setup() {
 		this.frm.custom_make_buttons = {
 			'Purchase Order': 'Purchase Order',
 			'Quotation': 'Quotation'
 		}
 
-		this._super();
-	},
+		super.setup();
+	}
 
-	refresh: function() {
+	refresh() {
 		var me = this;
-		this._super();
+		super.refresh();
 
 		if (this.frm.doc.__islocal && !this.frm.doc.valid_till) {
 			this.frm.set_value('valid_till', frappe.datetime.add_months(this.frm.doc.transaction_date, 1));
@@ -77,25 +77,25 @@
 				})
 			}, __("Get Items From"));
 		}
-	},
+	}
 
-	make_purchase_order: function() {
+	make_purchase_order() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_purchase_order",
 			frm: cur_frm
 		})
-	},
-	make_quotation: function() {
+	}
+	make_quotation() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_quotation",
 			frm: cur_frm
 		})
 
 	}
-});
+};
 
 // for backward compatibility: combine new and previous states
-$.extend(cur_frm.cscript, new erpnext.buying.SupplierQuotationController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.buying.SupplierQuotationController({frm: cur_frm}));
 
 cur_frm.fields_dict['items'].grid.get_field('project').get_query =
 	function(doc, cdt, cdn) {
diff --git a/erpnext/buying/report/subcontract_order_summary/__init__.py b/erpnext/buying/report/subcontract_order_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/__init__.py
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
new file mode 100644
index 0000000..5ba52f1
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
@@ -0,0 +1,45 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Subcontract Order Summary"] = {
+	"filters": [
+		{
+			label: __("Company"),
+			fieldname: "company",
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			label: __("From Date"),
+			fieldname:"from_date",
+			fieldtype: "Date",
+			default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+			reqd: 1
+		},
+		{
+			label: __("To Date"),
+			fieldname:"to_date",
+			fieldtype: "Date",
+			default: frappe.datetime.get_today(),
+			reqd: 1
+		},
+		{
+			label: __("Purchase Order"),
+			fieldname: "name",
+			fieldtype: "Link",
+			options: "Purchase Order",
+			get_query: function() {
+				return {
+					filters: {
+						docstatus: 1,
+						is_subcontracted: 'Yes',
+						company: frappe.query_report.get_filter_value('company')
+					}
+				}
+			}
+		}
+	]
+};
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json
new file mode 100644
index 0000000..526a8d8
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-05-31 14:43:32.417694",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-05-31 14:43:32.417694",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Subcontract Order Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Purchase Order",
+ "report_name": "Subcontract Order Summary",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Stock User"
+  },
+  {
+   "role": "Purchase Manager"
+  },
+  {
+   "role": "Purchase User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
new file mode 100644
index 0000000..0c0d4f0
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
@@ -0,0 +1,152 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+	columns, data = [], []
+	columns = get_columns()
+	data = get_data(filters)
+
+	return columns, data
+
+def get_data(report_filters):
+	data = []
+	orders = get_subcontracted_orders(report_filters)
+
+	if orders:
+		supplied_items = get_supplied_items(orders, report_filters)
+		po_details = prepare_subcontracted_data(orders, supplied_items)
+		get_subcontracted_data(po_details, data)
+
+	return data
+
+def get_subcontracted_orders(report_filters):
+	fields = ['`tabPurchase Order Item`.`parent` as po_id', '`tabPurchase Order Item`.`item_code`',
+		'`tabPurchase Order Item`.`item_name`', '`tabPurchase Order Item`.`qty`', '`tabPurchase Order Item`.`name`',
+		'`tabPurchase Order Item`.`received_qty`', '`tabPurchase Order`.`status`']
+
+	filters = get_filters(report_filters)
+
+	return frappe.get_all('Purchase Order', fields = fields, filters=filters) or []
+
+def get_filters(report_filters):
+	filters = [['Purchase Order', 'docstatus', '=', 1], ['Purchase Order', 'is_subcontracted', '=', 'Yes'],
+		['Purchase Order', 'transaction_date', 'between', (report_filters.from_date, report_filters.to_date)]]
+
+	for field in ['name', 'company']:
+		if report_filters.get(field):
+			filters.append(['Purchase Order', field, '=', report_filters.get(field)])
+
+	return filters
+
+def get_supplied_items(orders, report_filters):
+	if not orders:
+		return []
+
+	fields = ['parent', 'main_item_code', 'rm_item_code', 'required_qty',
+		'supplied_qty', 'returned_qty', 'total_supplied_qty', 'consumed_qty', 'reference_name']
+
+	filters = {'parent': ('in', [d.po_id for d in orders]), 'docstatus': 1}
+
+	supplied_items = {}
+	for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters):
+		new_key = (row.parent, row.reference_name, row.main_item_code)
+
+		supplied_items.setdefault(new_key, []).append(row)
+
+	return supplied_items
+
+def prepare_subcontracted_data(orders, supplied_items):
+	po_details = {}
+	for row in orders:
+		key = (row.po_id, row.name, row.item_code)
+		if key not in po_details:
+			po_details.setdefault(key, frappe._dict({'po_item': row, 'supplied_items': []}))
+
+		details = po_details[key]
+
+		if supplied_items.get(key):
+			for supplied_item in supplied_items[key]:
+				details['supplied_items'].append(supplied_item)
+
+	return po_details
+
+def get_subcontracted_data(po_details, data):
+	for key, details in po_details.items():
+		res = details.po_item
+		for index, row in enumerate(details.supplied_items):
+			if index != 0:
+				res = {}
+
+			res.update(row)
+			data.append(res)
+
+def get_columns():
+	return [
+		{
+			"label": _("Purchase Order"),
+			"fieldname": "po_id",
+			"fieldtype": "Link",
+			"options": "Purchase Order",
+			"width": 100
+		},
+		{
+			"label": _("Status"),
+			"fieldname": "status",
+			"fieldtype": "Data",
+			"width": 80
+		},
+		{
+			"label": _("Subcontracted Item"),
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 160
+		},
+		{
+			"label": _("Order Qty"),
+			"fieldname": "qty",
+			"fieldtype": "Float",
+			"width": 90
+		},
+		{
+			"label": _("Received Qty"),
+			"fieldname": "received_qty",
+			"fieldtype": "Float",
+			"width": 110
+		},
+		{
+			"label": _("Supplied Item"),
+			"fieldname": "rm_item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 160
+		},
+		{
+			"label": _("Required Qty"),
+			"fieldname": "required_qty",
+			"fieldtype": "Float",
+			"width": 110
+		},
+		{
+			"label": _("Supplied Qty"),
+			"fieldname": "supplied_qty",
+			"fieldtype": "Float",
+			"width": 110
+		},
+		{
+			"label": _("Consumed Qty"),
+			"fieldname": "consumed_qty",
+			"fieldtype": "Float",
+			"width": 120
+		},
+		{
+			"label": _("Returned Qty"),
+			"fieldname": "returned_qty",
+			"fieldtype": "Float",
+			"width": 110
+		}
+	]
\ No newline at end of file
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
index de2ae8f..68426ab 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
@@ -9,10 +9,10 @@
 	if filters.from_date >= filters.to_date:
 		frappe.msgprint(_("To Date must be greater than From Date"))
 
-	data = []
 	columns = get_columns()
-	get_data(data , filters)
-	return columns, data
+	data = get_data(filters)
+
+	return columns, data or []
 
 def get_columns():
 	return [
@@ -21,13 +21,12 @@
 			"fieldtype": "Link",
 			"fieldname": "purchase_order",
 			"options": "Purchase Order",
-			"width": 150
+			"width": 200
 		},
 		{
 			"label": _("Date"),
 			"fieldtype": "Date",
 			"fieldname": "date",
-			"hidden": 1,
 			"width": 150
 		},
 		{
@@ -41,97 +40,58 @@
 			"label": _("Item Code"),
 			"fieldtype": "Data",
 			"fieldname": "rm_item_code",
-			"width": 100
+			"width": 150
 		},
 		{
 			"label": _("Required Quantity"),
 			"fieldtype": "Float",
-			"fieldname": "r_qty",
-			"width": 100
+			"fieldname": "reqd_qty",
+			"width": 150
 		},
 		{
 			"label": _("Transferred Quantity"),
 			"fieldtype": "Float",
-			"fieldname": "t_qty",
-			"width": 100
+			"fieldname": "transferred_qty",
+			"width": 200
 		},
 		{
 			"label": _("Pending Quantity"),
 			"fieldtype": "Float",
 			"fieldname": "p_qty",
-			"width": 100
+			"width": 150
 		}
 	]
 
-def get_data(data, filters):
-	po = get_po(filters)
-	po_transferred_qty_map = frappe._dict(get_transferred_quantity([v.name for v in po]))
+def get_data(filters):
+	po_rm_item_details = get_po_items_to_supply(filters)
 
-	sub_items = get_purchase_order_item_supplied([v.name for v in po])
+	data = []
+	for row in po_rm_item_details:
+		transferred_qty = row.get("transferred_qty") or 0
+		if transferred_qty < row.get("reqd_qty", 0):
+			pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty)
+			row.p_qty = pending_qty if pending_qty > 0 else 0
+			data.append(row)
 
-	for order in po:
-		for item in sub_items:
-			if order.name == item.parent and order.name in po_transferred_qty_map and \
-				item.required_qty != po_transferred_qty_map.get(order.name).get(item.rm_item_code):
-				transferred_qty = po_transferred_qty_map.get(order.name).get(item.rm_item_code) \
-				if po_transferred_qty_map.get(order.name).get(item.rm_item_code) else 0
-				row ={
-					'purchase_order': item.parent,
-					'date': order.transaction_date,
-					'supplier': order.supplier,
-					'rm_item_code': item.rm_item_code,
-					'r_qty': item.required_qty,
-					't_qty':transferred_qty,
-					'p_qty':item.required_qty - transferred_qty
-				}
+	return data
 
-				data.append(row)
-
-	return(data)
-
-def get_po(filters):
-	record_filters = [
-			["is_subcontracted", "=", "Yes"],
-			["supplier", "=", filters.supplier],
-			["transaction_date", "<=", filters.to_date],
-			["transaction_date", ">=", filters.from_date],
-			["docstatus", "=", 1]
-		]
-	return frappe.get_all("Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"])
-
-def get_transferred_quantity(po_name):
-	stock_entries = get_stock_entry(po_name)
-	stock_entries_detail = get_stock_entry_detail([v.name for v in stock_entries])
-	po_transferred_qty_map = {}
-
-
-	for entry in stock_entries:
-		for details in stock_entries_detail:
-			if details.parent == entry.name:
-				details["Purchase_order"] = entry.purchase_order
-				if entry.purchase_order not in po_transferred_qty_map:
-					po_transferred_qty_map[entry.purchase_order] = {}
-					po_transferred_qty_map[entry.purchase_order][details.item_code] = details.qty
-				else:
-					po_transferred_qty_map[entry.purchase_order][details.item_code] = po_transferred_qty_map[entry.purchase_order].get(details.item_code, 0) + details.qty
-
-	return po_transferred_qty_map
-
-
-def get_stock_entry(po):
-	return frappe.get_all("Stock Entry", filters=[
-			('purchase_order', 'IN', po),
-			('stock_entry_type', '=', 'Send to Subcontractor'),
-			('docstatus', '=', 1)
-	], fields=["name", "purchase_order"])
-
-def get_stock_entry_detail(se):
-	return frappe.get_all("Stock Entry Detail", filters=[
-			["parent", "in", se]
+def get_po_items_to_supply(filters):
+	return frappe.db.get_all(
+		"Purchase Order",
+		fields=[
+			"name as purchase_order",
+			"transaction_date as date",
+			"supplier as supplier",
+			"`tabPurchase Order Item Supplied`.rm_item_code as rm_item_code",
+			"`tabPurchase Order Item Supplied`.required_qty as reqd_qty",
+			"`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty"
 		],
-		fields=["parent", "item_code", "qty"])
-
-def get_purchase_order_item_supplied(po):
-	return frappe.get_all("Purchase Order Item Supplied", filters=[
-			('parent', 'IN', po)
-	], fields=['parent', 'rm_item_code', 'required_qty'])
+		filters = [
+			["Purchase Order", "per_received", "<", "100"],
+			["Purchase Order", "is_subcontracted", "=", "Yes"],
+			["Purchase Order", "supplier", "=", filters.supplier],
+			["Purchase Order", "transaction_date", "<=", filters.to_date],
+			["Purchase Order", "transaction_date", ">=", filters.from_date],
+			["Purchase Order", "docstatus", "=", 1]
+		]
+	)
\ No newline at end of file
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
index c1fc6fb..2448e17 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
@@ -12,34 +12,80 @@
 class TestSubcontractedItemToBeTransferred(unittest.TestCase):
 
 	def test_pending_and_transferred_qty(self):
-		po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
+		po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		# Material Receipt of RMs
 		make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
 		make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
-		transfer_subcontracted_raw_materials(po.name)
-		col, data = execute(filters=frappe._dict({'supplier': po.supplier,
-		   'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
-		   'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))}))
-		self.assertEqual(data[0]['purchase_order'], po.name)
-		self.assertIn(data[0]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
-		self.assertIn(data[0]['p_qty'], [9, 18])
-		self.assertIn(data[0]['t_qty'], [1, 2])
 
-		self.assertEqual(data[1]['purchase_order'], po.name)
-		self.assertIn(data[1]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
-		self.assertIn(data[1]['p_qty'], [9, 18])
-		self.assertIn(data[1]['t_qty'], [1, 2])
+		se = transfer_subcontracted_raw_materials(po)
 
+		col, data = execute(filters=frappe._dict(
+			{
+				'supplier': po.supplier,
+				'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
+				'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))
+			}
+		))
+		po.reload()
+
+		po_data = [row for row in data if row.get('purchase_order') == po.name]
+		# Alphabetically sort to be certain of order
+		po_data = sorted(po_data, key = lambda i: i['rm_item_code'])
+
+		self.assertEqual(len(po_data), 2)
+		self.assertEqual(po_data[0]['purchase_order'], po.name)
+
+		self.assertEqual(po_data[0]['rm_item_code'], '_Test Item')
+		self.assertEqual(po_data[0]['p_qty'], 8)
+		self.assertEqual(po_data[0]['transferred_qty'], 2)
+
+		self.assertEqual(po_data[1]['rm_item_code'], '_Test Item Home Desktop 100')
+		self.assertEqual(po_data[1]['p_qty'], 19)
+		self.assertEqual(po_data[1]['transferred_qty'], 1)
+
+		se.cancel()
+		po.cancel()
 
 def transfer_subcontracted_raw_materials(po):
+	# Order of supplied items fetched in PO is flaky
+	transfer_qty_map = {
+		'_Test Item': 2,
+		'_Test Item Home Desktop 100': 1
+	}
+
+	item_1 = po.supplied_items[0].rm_item_code
+	item_2 = po.supplied_items[1].rm_item_code
+
 	rm_item = [
-	 {'item_code': '_Test Item', 'rm_item_code': '_Test Item', 'item_name': '_Test Item', 'qty': 1,
-		'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100, 'stock_uom': 'Nos'},
-	 {'item_code': '_Test Item Home Desktop 100', 'rm_item_code': '_Test Item Home Desktop 100', 'item_name': '_Test Item Home Desktop 100', 'qty': 2,
-		'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
+		{
+			'name': po.supplied_items[0].name,
+			'item_code': item_1,
+			'rm_item_code': item_1,
+			'item_name': item_1,
+			'qty': transfer_qty_map[item_1],
+			'warehouse': '_Test Warehouse - _TC',
+			'rate': 100,
+			'amount': 100 * transfer_qty_map[item_1],
+			'stock_uom': 'Nos'
+		},
+		{
+			'name': po.supplied_items[1].name,
+			'item_code': item_2,
+			'rm_item_code': item_2,
+			'item_name': item_2,
+			'qty': transfer_qty_map[item_2],
+			'warehouse': '_Test Warehouse - _TC',
+			'rate': 100,
+			'amount': 100 * transfer_qty_map[item_2],
+			'stock_uom': 'Nos'
+		}
+	]
 	rm_item_string = json.dumps(rm_item)
-	se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string))
-	se.from_warehouse = '_Test Warehouse 1 - _TC'
-	se.to_warehouse = '_Test Warehouse 1 - _TC'
+	se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
+	se.from_warehouse = '_Test Warehouse - _TC'
+	se.to_warehouse = '_Test Warehouse - _TC'
 	se.stock_entry_type = 'Send to Subcontractor'
 	se.save()
 	se.submit()
+	return se
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index e455982..1c086e9 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -610,8 +610,8 @@
 			order_field = "purchase_order"
 			order_doctype = "Purchase Order"
 
-		order_list = list(set([d.get(order_field)
-			for d in self.get("items") if d.get(order_field)]))
+		order_list = list(set(d.get(order_field)
+			for d in self.get("items") if d.get(order_field)))
 
 		journal_entries = get_advance_journal_entries(party_type, party, party_account,
 			amount_field, order_doctype, order_list, include_unallocated)
@@ -635,8 +635,8 @@
 
 	def validate_advance_entries(self):
 		order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
-		order_list = list(set([d.get(order_field)
-			for d in self.get("items") if d.get(order_field)]))
+		order_list = list(set(d.get(order_field)
+			for d in self.get("items") if d.get(order_field)))
 
 		if not order_list: return
 
@@ -828,8 +828,14 @@
 					role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
 
 					if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
-						frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
-							.format(item.item_code, item.idx, max_allowed_amt))
+						if self.doctype != "Purchase Invoice":
+							self.throw_overbill_exception(item, max_allowed_amt)
+						elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
+							self.throw_overbill_exception(item, max_allowed_amt)
+
+	def throw_overbill_exception(self, item, max_allowed_amt):
+		frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
+			.format(item.item_code, item.idx, max_allowed_amt))
 
 	def get_company_default(self, fieldname):
 		from erpnext.accounts.utils import get_company_default
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 3f2d339..6a550e0 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -11,16 +11,17 @@
 from erpnext.stock.get_item_details import get_conversion_factor
 from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
 from erpnext.stock.stock_ledger import get_valuation_rate
-from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items
 from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos, get_serial_nos
 from frappe.contacts.doctype.address.address import get_address_display
 
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
-from erpnext.controllers.stock_controller import StockController
 from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
 from erpnext.stock.utils import get_incoming_rate
 
-class BuyingController(StockController):
+from erpnext.controllers.stock_controller import StockController
+from erpnext.controllers.subcontracting import Subcontracting
+
+class BuyingController(StockController, Subcontracting):
 
 	def get_feed(self):
 		if self.get("supplier_name"):
@@ -57,6 +58,11 @@
 		if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
 			self.update_valuation_rate()
 
+	def onload(self):
+		super(BuyingController, self).onload()
+		self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings',
+			'backflush_raw_materials_of_subcontract_based_on'))
+
 	def set_missing_values(self, for_validate=False):
 		super(BuyingController, self).set_missing_values(for_validate)
 
@@ -171,18 +177,19 @@
 
 			TODO: rename item_tax_amount to valuation_tax_amount
 		"""
+		stock_and_asset_items = []
 		stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
 
 		stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
 		last_item_idx = 1
 		for d in self.get("items"):
-			if d.item_code and d.item_code in stock_and_asset_items:
+			if (d.item_code and d.item_code in stock_and_asset_items):
 				stock_and_asset_items_qty += flt(d.qty)
 				stock_and_asset_items_amount += flt(d.base_net_amount)
 				last_item_idx = d.idx
 
-		total_valuation_amount = sum([flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
-			if d.category in ["Valuation", "Valuation and Total"]])
+		total_valuation_amount = sum(flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
+			if d.category in ["Valuation", "Valuation and Total"])
 
 		valuation_amount_adjustment = total_valuation_amount
 		for i, item in enumerate(self.get("items")):
@@ -255,7 +262,7 @@
 		supplied_items_cost = 0.0
 		for d in self.get("supplied_items"):
 			if d.reference_name == item_row_id:
-				if reset_outgoing_rate and frappe.db.get_value('Item', d.rm_item_code, 'is_stock_item'):
+				if reset_outgoing_rate and frappe.get_cached_value('Item', d.rm_item_code, 'is_stock_item'):
 					rate = get_incoming_rate({
 						"item_code": d.rm_item_code,
 						"warehouse": self.supplier_warehouse,
@@ -285,11 +292,13 @@
 				if item in self.sub_contracted_items and not item.bom:
 					frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
 
-			if self.doctype == "Purchase Order":
-				for supplied_item in self.get("supplied_items"):
-					if not supplied_item.reserve_warehouse:
-						frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code)))
+			if self.doctype != "Purchase Order":
+				return
 
+			for row in self.get("supplied_items"):
+				if not row.reserve_warehouse:
+					msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied"
+					frappe.throw(_(msg))
 		else:
 			for item in self.get("items"):
 				if item.bom:
@@ -297,23 +306,7 @@
 
 	def create_raw_materials_supplied(self, raw_material_table):
 		if self.is_subcontracted=="Yes":
-			parent_items = []
-			backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings",
-				"backflush_raw_materials_of_subcontract_based_on")
-			if (self.doctype == 'Purchase Receipt' and
-				backflush_raw_materials_based_on != 'BOM'):
-				self.update_raw_materials_supplied_based_on_stock_entries()
-			else:
-				for item in self.get("items"):
-					if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
-						item.rm_supp_cost = 0.0
-					if item.bom and item.item_code in self.sub_contracted_items:
-						self.update_raw_materials_supplied_based_on_bom(item, raw_material_table)
-
-						if [item.item_code, item.name] not in parent_items:
-							parent_items.append([item.item_code, item.name])
-
-				self.cleanup_raw_materials_supplied(parent_items, raw_material_table)
+			self.set_materials_for_subcontracted_items(raw_material_table)
 
 		elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
 			for item in self.get("items"):
@@ -322,176 +315,6 @@
 		if self.is_subcontracted == "No" and self.get("supplied_items"):
 			self.set('supplied_items', [])
 
-	def update_raw_materials_supplied_based_on_stock_entries(self):
-		self.set('supplied_items', [])
-
-		purchase_orders = set([d.purchase_order for d in self.items])
-
-		# qty of raw materials backflushed (for each item per purchase order)
-		backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
-
-		# qty of "finished good" item yet to be received
-		qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
-
-		for item in self.get('items'):
-			if not item.purchase_order:
-				continue
-
-			# reset raw_material cost
-			item.rm_supp_cost = 0
-
-			# qty of raw materials transferred to the supplier
-			transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
-
-			non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
-
-			item_key = '{}{}'.format(item.item_code, item.purchase_order)
-
-			fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
-
-			if not fg_yet_to_be_received:
-				frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}")
-					.format(item.idx, frappe.bold(item.item_code),
-						frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)),
-					title=_("Limit Crossed"))
-
-			transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
-			# backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
-
-			for raw_material in transferred_raw_materials + non_stock_items:
-				rm_item_key = (raw_material.rm_item_code, item.item_code, item.purchase_order)
-				raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
-
-				consumed_qty = raw_material_data.get('qty', 0)
-				consumed_serial_nos = raw_material_data.get('serial_no', '')
-				consumed_batch_nos = raw_material_data.get('batch_nos', '')
-
-				transferred_qty = raw_material.qty
-
-				rm_qty_to_be_consumed = transferred_qty - consumed_qty
-
-				# backflush all remaining transferred qty in the last Purchase Receipt
-				if fg_yet_to_be_received == item.qty:
-					qty = rm_qty_to_be_consumed
-				else:
-					qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
-
-					if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
-						qty = frappe.utils.ceil(qty)
-
-				if qty > rm_qty_to_be_consumed:
-					qty = rm_qty_to_be_consumed
-
-				if not qty: continue
-
-				if raw_material.serial_nos:
-					set_serial_nos(raw_material, consumed_serial_nos, qty)
-
-				if raw_material.batch_nos:
-					backflushed_batch_qty_map = raw_material_data.get('consumed_batch', {})
-
-					batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
-						qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
-
-					for batch_data in batches_qty:
-						qty = batch_data['qty']
-						raw_material.batch_no = batch_data['batch']
-						if qty > 0:
-							self.append_raw_material_to_be_backflushed(item, raw_material, qty)
-				else:
-					self.append_raw_material_to_be_backflushed(item, raw_material, qty)
-
-	def append_raw_material_to_be_backflushed(self, fg_item_row, raw_material_data, qty):
-		rm = self.append('supplied_items', {})
-		rm.update(raw_material_data)
-
-		if not rm.main_item_code:
-			rm.main_item_code = fg_item_row.item_code
-
-		rm.reference_name = fg_item_row.name
-		rm.required_qty = qty
-		rm.consumed_qty = qty
-
-	def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
-		exploded_item = 1
-		if hasattr(item, 'include_exploded_items'):
-			exploded_item = item.get('include_exploded_items')
-
-		bom_items = get_items_from_bom(item.item_code, item.bom, exploded_item)
-
-		used_alternative_items = []
-		if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order:
-			used_alternative_items = get_used_alternative_items(purchase_order = item.purchase_order)
-
-		raw_materials_cost = 0
-		items = list(set([d.item_code for d in bom_items]))
-		item_wh = frappe._dict(frappe.db.sql("""select i.item_code, id.default_warehouse
-			from `tabItem` i, `tabItem Default` id
-			where id.parent=i.name and id.company=%s and i.name in ({0})"""
-			.format(", ".join(["%s"] * len(items))), [self.company] + items))
-
-		for bom_item in bom_items:
-			if self.doctype == "Purchase Order":
-				reserve_warehouse = bom_item.source_warehouse or item_wh.get(bom_item.item_code)
-				if frappe.db.get_value("Warehouse", reserve_warehouse, "company") != self.company:
-					reserve_warehouse = None
-
-			conversion_factor = item.conversion_factor
-			if (self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order and
-				bom_item.item_code in used_alternative_items):
-				alternative_item_data = used_alternative_items.get(bom_item.item_code)
-				bom_item.item_code = alternative_item_data.item_code
-				bom_item.item_name = alternative_item_data.item_name
-				bom_item.stock_uom = alternative_item_data.stock_uom
-				conversion_factor = alternative_item_data.conversion_factor
-				bom_item.description = alternative_item_data.description
-
-			# check if exists
-			exists = 0
-			for d in self.get(raw_material_table):
-				if d.main_item_code == item.item_code and d.rm_item_code == bom_item.item_code \
-					and d.reference_name == item.name:
-						rm, exists = d, 1
-						break
-
-			if not exists:
-				rm = self.append(raw_material_table, {})
-
-			required_qty = flt(flt(bom_item.qty_consumed_per_unit) * (flt(item.qty) + getattr(item, 'rejected_qty', 0)) *
-				flt(conversion_factor), rm.precision("required_qty"))
-			rm.reference_name = item.name
-			rm.bom_detail_no = bom_item.name
-			rm.main_item_code = item.item_code
-			rm.rm_item_code = bom_item.item_code
-			rm.stock_uom = bom_item.stock_uom
-			rm.required_qty = required_qty
-			rm.rate = bom_item.rate
-			rm.conversion_factor = conversion_factor
-
-			if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
-				rm.consumed_qty = required_qty
-				rm.description = bom_item.description
-				if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no:
-					rm.batch_no = item.batch_no
-			elif not rm.reserve_warehouse:
-				rm.reserve_warehouse = reserve_warehouse
-
-	def cleanup_raw_materials_supplied(self, parent_items, raw_material_table):
-		"""Remove all those child items which are no longer present in main item table"""
-		delete_list = []
-		for d in self.get(raw_material_table):
-			if [d.main_item_code, d.reference_name] not in parent_items:
-				# mark for deletion from doclist
-				delete_list.append(d)
-
-		# delete from doclist
-		if delete_list:
-			rm_supplied_details = self.get(raw_material_table)
-			self.set(raw_material_table, [])
-			for d in rm_supplied_details:
-				if d not in delete_list:
-					self.append(raw_material_table, d)
-
 	@property
 	def sub_contracted_items(self):
 		if not hasattr(self, "_sub_contracted_items"):
@@ -683,7 +506,8 @@
 			self.process_fixed_asset()
 			self.update_fixed_asset(field)
 
-		update_last_purchase_rate(self, is_submit = 1)
+		if self.doctype in ['Purchase Order', 'Purchase Receipt']:
+			update_last_purchase_rate(self, is_submit = 1)
 
 	def on_cancel(self):
 		super(BuyingController, self).on_cancel()
@@ -691,7 +515,9 @@
 		if self.get('is_return'):
 			return
 
-		update_last_purchase_rate(self, is_submit = 0)
+		if self.doctype in ['Purchase Order', 'Purchase Receipt']:
+			update_last_purchase_rate(self, is_submit = 0)
+
 		if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
 			field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
 
@@ -863,104 +689,6 @@
 		else:
 			validate_item_type(self, "is_purchase_item", "purchase")
 
-
-def get_items_from_bom(item_code, bom, exploded_item=1):
-	doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
-
-	bom_items = frappe.db.sql("""select t2.item_code, t2.name,
-			t2.rate, t2.stock_uom, t2.source_warehouse, t2.description,
-			t2.stock_qty / ifnull(t1.quantity, 1) as qty_consumed_per_unit
-		from
-			`tabBOM` t1, `tab{0}` t2, tabItem t3
-		where
-			t2.parent = t1.name and t1.item = %s
-			and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
-			and t2.sourced_by_supplier = 0
-			and t2.item_code = t3.name""".format(doctype),
-			(item_code, bom), as_dict=1)
-
-	if not bom_items:
-		msgprint(_("Specified BOM {0} does not exist for Item {1}").format(bom, item_code), raise_exception=1)
-
-	return bom_items
-
-def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
-	common_query = """
-		SELECT
-			sed.item_code AS rm_item_code,
-			SUM(sed.qty) AS qty,
-			sed.description,
-			sed.stock_uom,
-			sed.subcontracted_item AS main_item_code,
-			{serial_no_concat_syntax} AS serial_nos,
-			{batch_no_concat_syntax} AS batch_nos
-		FROM `tabStock Entry` se,`tabStock Entry Detail` sed
-		WHERE
-			se.name = sed.parent
-			AND se.docstatus=1
-			AND se.purpose='Send to Subcontractor'
-			AND se.purchase_order = %s
-			AND IFNULL(sed.t_warehouse, '') != ''
-			AND IFNULL(sed.subcontracted_item, '') in ('', %s)
-		GROUP BY sed.item_code, sed.subcontracted_item
-	"""
-	raw_materials = frappe.db.multisql({
-		'mariadb': common_query.format(
-			serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
-			batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
-		),
-		'postgres': common_query.format(
-			serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
-			batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
-		)
-	}, (purchase_order, fg_item), as_dict=1)
-
-	return raw_materials
-
-def get_backflushed_subcontracted_raw_materials(purchase_orders):
-	purchase_receipts = frappe.get_all("Purchase Receipt Item",
-		fields = ["purchase_order", "item_code", "name", "parent"],
-		filters={"docstatus": 1, "purchase_order": ("in", list(purchase_orders))})
-
-	distinct_purchase_receipts = {}
-	for pr in purchase_receipts:
-		key = (pr.purchase_order, pr.item_code, pr.parent)
-		distinct_purchase_receipts.setdefault(key, []).append(pr.name)
-
-	backflushed_raw_materials_map = frappe._dict()
-	for args, references in iteritems(distinct_purchase_receipts):
-		purchase_receipt_supplied_items = get_supplied_items(args[1], args[2], references)
-
-		for data in purchase_receipt_supplied_items:
-			pr_key = (data.rm_item_code, data.main_item_code, args[0])
-			if pr_key not in backflushed_raw_materials_map:
-				backflushed_raw_materials_map.setdefault(pr_key, frappe._dict({
-					"qty": 0.0,
-					"serial_no": [],
-					"batch_no": [],
-					"consumed_batch": {}
-				}))
-
-			row = backflushed_raw_materials_map.get(pr_key)
-			row.qty += data.consumed_qty
-
-			for field in ["serial_no", "batch_no"]:
-				if data.get(field):
-					row[field].append(data.get(field))
-
-			if data.get("batch_no"):
-				if data.get("batch_no") in row.consumed_batch:
-					row.consumed_batch[data.get("batch_no")] += data.consumed_qty
-				else:
-					row.consumed_batch[data.get("batch_no")] = data.consumed_qty
-
-	return backflushed_raw_materials_map
-
-def get_supplied_items(item_code, purchase_receipt, references):
-	return frappe.get_all("Purchase Receipt Item Supplied",
-		fields=["rm_item_code", "main_item_code", "consumed_qty", "serial_no", "batch_no"],
-		filters={"main_item_code": item_code, "parent": purchase_receipt, "reference_name": ("in", references)})
-
 def get_asset_item_details(asset_items):
 	asset_items_data = {}
 	for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
@@ -992,135 +720,3 @@
 			error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
 
 		frappe.throw(error_message)
-
-def get_qty_to_be_received(purchase_orders):
-	return frappe._dict(frappe.db.sql("""
-		SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
-		SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
-		FROM `tabPurchase Order Item` poi
-		WHERE
-			poi.`parent` in %s
-		GROUP BY poi.`item_code`, poi.`parent`
-		HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
-	""", (purchase_orders)))
-
-def get_non_stock_items(purchase_order, fg_item_code):
-	return frappe.db.sql("""
-		SELECT
-			pois.main_item_code,
-			pois.rm_item_code,
-			item.description,
-			pois.required_qty AS qty,
-			pois.rate,
-			1 as non_stock_item,
-			pois.stock_uom
-		FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
-		WHERE
-			pois.`rm_item_code` = item.`name`
-			AND item.is_stock_item = 0
-			AND pois.`parent` = %s
-			AND pois.`main_item_code` = %s
-	""", (purchase_order, fg_item_code), as_dict=1)
-
-
-def set_serial_nos(raw_material, consumed_serial_nos, qty):
-	serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
-		set(get_serial_nos(consumed_serial_nos))
-	if serial_nos and qty <= len(serial_nos):
-		raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
-
-def get_transferred_batch_qty_map(purchase_order, fg_item):
-	# returns
-	# {
-	# 	(item_code, fg_code): {
-	# 		batch1: 10, # qty
-	# 		batch2: 16
-	# 	},
-	# }
-	transferred_batch_qty_map = {}
-	transferred_batches = frappe.db.sql("""
-		SELECT
-			sed.batch_no,
-			SUM(sed.qty) AS qty,
-			sed.item_code,
-			sed.subcontracted_item
-		FROM `tabStock Entry` se,`tabStock Entry Detail` sed
-		WHERE
-			se.name = sed.parent
-			AND se.docstatus=1
-			AND se.purpose='Send to Subcontractor'
-			AND se.purchase_order = %s
-			AND ifnull(sed.subcontracted_item, '') in ('', %s)
-			AND sed.batch_no IS NOT NULL
-		GROUP BY
-			sed.batch_no,
-			sed.item_code
-	""", (purchase_order, fg_item), as_dict=1)
-
-	for batch_data in transferred_batches:
-		key = ((batch_data.item_code, fg_item)
-			if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
-		transferred_batch_qty_map.setdefault(key, OrderedDict())
-		transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
-
-	return transferred_batch_qty_map
-
-def get_backflushed_batch_qty_map(purchase_order, fg_item):
-	# returns
-	# {
-	# 	(item_code, fg_code): {
-	# 		batch1: 10, # qty
-	# 		batch2: 16
-	# 	},
-	# }
-	backflushed_batch_qty_map = {}
-	backflushed_batches = frappe.db.sql("""
-		SELECT
-			pris.batch_no,
-			SUM(pris.consumed_qty) AS qty,
-			pris.rm_item_code AS item_code
-		FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
-		WHERE
-			pr.name = pri.parent
-			AND pri.parent = pris.parent
-			AND pri.purchase_order = %s
-			AND pri.item_code = pris.main_item_code
-			AND pr.docstatus = 1
-			AND pris.main_item_code = %s
-			AND pris.batch_no IS NOT NULL
-		GROUP BY
-			pris.rm_item_code, pris.batch_no
-	""", (purchase_order, fg_item), as_dict=1)
-
-	for batch_data in backflushed_batches:
-		backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
-		backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
-
-	return backflushed_batch_qty_map
-
-def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batches, po):
-	# Returns available batches to be backflushed based on requirements
-	transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
-	if not transferred_batches:
-		transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
-
-	available_batches = []
-
-	for (batch, transferred_qty) in transferred_batches.items():
-		backflushed_qty = backflushed_batches.get(batch, 0)
-		available_qty = transferred_qty - backflushed_qty
-
-		if available_qty >= required_qty:
-			available_batches.append({'batch': batch, 'qty': required_qty})
-			break
-		elif available_qty != 0:
-			available_batches.append({'batch': batch, 'qty': available_qty})
-			required_qty -= available_qty
-
-	for row in available_batches:
-		if backflushed_batches.get(row.get('batch'), 0) > 0:
-			backflushed_batches[row.get('batch')] += row.get('qty')
-		else:
-			backflushed_batches[row.get('batch')] = row.get('qty')
-
-	return available_batches
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 81ac234..2803193 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -19,7 +19,7 @@
 	fields = get_fields("Employee", ["name", "employee_name"])
 
 	return frappe.db.sql("""select {fields} from `tabEmployee`
-		where status = 'Active'
+		where status in ('Active', 'Suspended')
 			and docstatus < 2
 			and ({key} like %(txt)s
 				or employee_name like %(txt)s)
@@ -88,7 +88,7 @@
 	fields = get_fields("Customer", fields)
 
 	searchfields = frappe.get_meta("Customer").get_search_fields()
-	searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
+	searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
 
 	return frappe.db.sql("""select {fields} from `tabCustomer`
 		where docstatus < 2
@@ -315,7 +315,7 @@
 	return frappe.db.sql("""select {fields} from `tabProject`
 		where
 			`tabProject`.status not in ("Completed", "Cancelled")
-			and {cond} {match_cond} {scond}
+			and {cond} {scond} {match_cond}
 		order by
 			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
 			idx desc,
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 54156f37..7f28289 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -428,7 +428,7 @@
 		self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
 
 	def get_po_nos(self, ref_doctype, ref_fieldname, po_nos):
-		doc_list = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)]))
+		doc_list = list(set(d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)))
 		if doc_list:
 			po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')]
 
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 83d4c33..943f7aa 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -299,8 +299,8 @@
 			args['name'] = self.get(args['percent_join_field_parent'])
 			self._update_percent_field(args, update_modified)
 		else:
-			distinct_transactions = set([d.get(args['percent_join_field'])
-				for d in self.get_all_children(args['source_dt'])])
+			distinct_transactions = set(d.get(args['percent_join_field'])
+				for d in self.get_all_children(args['source_dt']))
 
 			for name in distinct_transactions:
 				if name:
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 0da723d..35097b9 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -313,7 +313,7 @@
 
 	def get_serialized_items(self):
 		serialized_items = []
-		item_codes = list(set([d.item_code for d in self.get("items")]))
+		item_codes = list(set(d.item_code for d in self.get("items")))
 		if item_codes:
 			serialized_items = frappe.db.sql_list("""select name from `tabItem`
 				where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
@@ -324,8 +324,8 @@
 	def validate_warehouse(self):
 		from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
 
-		warehouses = list(set([d.warehouse for d in
-			self.get("items") if getattr(d, "warehouse", None)]))
+		warehouses = list(set(d.warehouse for d in
+			self.get("items") if getattr(d, "warehouse", None)))
 
 		target_warehouses = list(set([d.target_warehouse for d in
 			self.get("items") if getattr(d, "target_warehouse", None)]))
@@ -501,7 +501,6 @@
 			check_if_stock_and_account_balance_synced(self.posting_date,
 				self.company, self.doctype, self.name)
 
-
 @frappe.whitelist()
 def make_quality_inspections(doctype, docname, items):
 	if isinstance(items, str):
@@ -533,21 +532,75 @@
 
 	return inspections
 
-
 def is_reposting_pending():
 	return frappe.db.exists("Repost Item Valuation",
 		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
 
+def future_sle_exists(args, sl_entries=None):
+	key = (args.voucher_type, args.voucher_no)
 
-def future_sle_exists(args):
-	sl_entries = frappe.get_all("Stock Ledger Entry",
+	if validate_future_sle_not_exists(args, key, sl_entries):
+		return False
+	elif get_cached_data(args, key):
+		return True
+
+	if not sl_entries:
+		sl_entries = get_sle_entries_against_voucher(args)
+		if not sl_entries:
+			return
+
+	or_conditions = get_conditions_to_validate_future_sle(sl_entries)
+
+	data = frappe.db.sql("""
+		select item_code, warehouse, count(name) as total_row
+		from `tabStock Ledger Entry`
+		where
+			({})
+			and timestamp(posting_date, posting_time)
+				>= timestamp(%(posting_date)s, %(posting_time)s)
+			and voucher_no != %(voucher_no)s
+			and is_cancelled = 0
+		GROUP BY
+			item_code, warehouse
+		""".format(" or ".join(or_conditions)), args, as_dict=1)
+
+	for d in data:
+		frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
+
+	return len(data)
+
+def validate_future_sle_not_exists(args, key, sl_entries=None):
+	item_key = ''
+	if args.get('item_code'):
+		item_key = (args.get('item_code'), args.get('warehouse'))
+
+	if not sl_entries and hasattr(frappe.local, 'future_sle'):
+		if (not frappe.local.future_sle.get(key) or
+			(item_key and item_key not in frappe.local.future_sle.get(key))):
+			return True
+
+def get_cached_data(args, key):
+	if not hasattr(frappe.local, 'future_sle'):
+		frappe.local.future_sle = {}
+
+	if key not in frappe.local.future_sle:
+		frappe.local.future_sle[key] = frappe._dict({})
+
+	if args.get('item_code'):
+		item_key = (args.get('item_code'), args.get('warehouse'))
+		count = frappe.local.future_sle[key].get(item_key)
+
+		return True if (count or count == 0) else False
+	else:
+		return frappe.local.future_sle[key]
+
+def get_sle_entries_against_voucher(args):
+	return frappe.get_all("Stock Ledger Entry",
 		filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
 		fields=["item_code", "warehouse"],
 		order_by="creation asc")
 
-	if not sl_entries:
-		return
-
+def get_conditions_to_validate_future_sle(sl_entries):
 	warehouse_items_map = {}
 	for entry in sl_entries:
 		if entry.warehouse not in warehouse_items_map:
@@ -558,23 +611,10 @@
 	or_conditions = []
 	for warehouse, items in warehouse_items_map.items():
 		or_conditions.append(
-			"warehouse = '{}' and item_code in ({})".format(
-				warehouse,
-				", ".join(frappe.db.escape(item) for item in items)
-			)
-		)
+			f"""warehouse = {frappe.db.escape(warehouse)}
+				and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""")
 
-	return frappe.db.sql("""
-		select name
-		from `tabStock Ledger Entry`
-		where
-			({})
-			and timestamp(posting_date, posting_time)
-				>= timestamp(%(posting_date)s, %(posting_time)s)
-			and voucher_no != %(voucher_no)s
-			and is_cancelled = 0
-		limit 1
-		""".format(" or ".join(or_conditions)), args)
+	return or_conditions
 
 def create_repost_item_valuation_entry(args):
 	args = frappe._dict(args)
diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py
new file mode 100644
index 0000000..36ae110
--- /dev/null
+++ b/erpnext/controllers/subcontracting.py
@@ -0,0 +1,393 @@
+import frappe
+import copy
+from frappe import _
+from frappe.utils import flt, cint, get_link_to_form
+from collections import defaultdict
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+class Subcontracting():
+	def set_materials_for_subcontracted_items(self, raw_material_table):
+		if self.doctype == 'Purchase Invoice' and not self.update_stock:
+			return
+
+		self.raw_material_table = raw_material_table
+		self.__identify_change_in_item_table()
+		self.__prepare_supplied_items()
+		self.__validate_supplied_items()
+
+	def __prepare_supplied_items(self):
+		self.initialized_fields()
+		self.__get_purchase_orders()
+		self.__get_pending_qty_to_receive()
+		self.get_available_materials()
+		self.__remove_changed_rows()
+		self.__set_supplied_items()
+
+	def initialized_fields(self):
+		self.available_materials = frappe._dict()
+		self.__transferred_items = frappe._dict()
+		self.alternative_item_details = frappe._dict()
+		self.__get_backflush_based_on()
+
+	def __get_backflush_based_on(self):
+		self.backflush_based_on = frappe.db.get_single_value("Buying Settings",
+			"backflush_raw_materials_of_subcontract_based_on")
+
+	def __get_purchase_orders(self):
+		self.purchase_orders = []
+
+		if self.doctype == 'Purchase Order':
+			return
+
+		self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order]
+
+	def __identify_change_in_item_table(self):
+		self.__changed_name = []
+		self.__reference_name = []
+
+		if self.doctype == 'Purchase Order' or self.is_new():
+			self.set(self.raw_material_table, [])
+			return
+
+		item_dict = self.__get_data_before_save()
+		if not item_dict:
+			return True
+
+		for n_row in self.items:
+			self.__reference_name.append(n_row.name)
+			if (n_row.name not in item_dict) or (n_row.item_code, n_row.qty) != item_dict[n_row.name]:
+				self.__changed_name.append(n_row.name)
+
+			if item_dict.get(n_row.name):
+				del item_dict[n_row.name]
+
+		self.__changed_name.extend(item_dict.keys())
+
+	def __get_data_before_save(self):
+		item_dict = {}
+		if self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self._doc_before_save:
+			for row in self._doc_before_save.get('items'):
+				item_dict[row.name] = (row.item_code, row.qty)
+
+		return item_dict
+
+	def get_available_materials(self):
+		''' Get the available raw materials which has been transferred to the supplier.
+			available_materials = {
+				(item_code, subcontracted_item, purchase_order): {
+					'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
+				}
+			}
+		'''
+		if not self.purchase_orders:
+			return
+
+		for row in self.__get_transferred_items():
+			key = (row.rm_item_code, row.main_item_code, row.purchase_order)
+
+			if key not in self.available_materials:
+				self.available_materials.setdefault(key, frappe._dict({'qty': 0, 'serial_no': [],
+					'batch_no': defaultdict(float), 'item_details': row, 'po_details': []})
+				)
+
+			details = self.available_materials[key]
+			details.qty += row.qty
+			details.po_details.append(row.po_detail)
+
+			if row.serial_no:
+				details.serial_no.extend(get_serial_nos(row.serial_no))
+
+			if row.batch_no:
+				details.batch_no[row.batch_no] += row.qty
+
+			self.__set_alternative_item_details(row)
+
+		self.__transferred_items = copy.deepcopy(self.available_materials)
+		for doctype in ['Purchase Receipt', 'Purchase Invoice']:
+			self.__update_consumed_materials(doctype)
+
+	def __update_consumed_materials(self, doctype, return_consumed_items=False):
+		'''Deduct the consumed materials from the available materials.'''
+
+		pr_items = self.__get_received_items(doctype)
+		if not pr_items:
+			return ([], {}) if return_consumed_items else None
+
+		pr_items = {d.name: d.get(self.get('po_field') or 'purchase_order') for d in pr_items}
+		consumed_materials = self.__get_consumed_items(doctype, pr_items.keys())
+
+		if return_consumed_items:
+			return (consumed_materials, pr_items)
+
+		for row in consumed_materials:
+			key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
+			if not self.available_materials.get(key):
+				continue
+
+			self.available_materials[key]['qty'] -= row.consumed_qty
+			if row.serial_no:
+				self.available_materials[key]['serial_no'] = list(
+					set(self.available_materials[key]['serial_no']) - set(get_serial_nos(row.serial_no))
+				)
+
+			if row.batch_no:
+				self.available_materials[key]['batch_no'][row.batch_no] -= row.consumed_qty
+
+	def __get_transferred_items(self):
+		fields = ['`tabStock Entry`.`purchase_order`']
+		alias_dict = {'item_code': 'rm_item_code', 'subcontracted_item': 'main_item_code', 'basic_rate': 'rate'}
+
+		child_table_fields = ['item_code', 'item_name', 'description', 'qty', 'basic_rate', 'amount',
+			'serial_no', 'uom', 'subcontracted_item', 'stock_uom', 'batch_no', 'conversion_factor',
+			's_warehouse', 't_warehouse', 'item_group', 'po_detail']
+
+		if self.backflush_based_on == 'BOM':
+			child_table_fields.append('original_item')
+
+		for field in child_table_fields:
+			fields.append(f'`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}')
+
+		filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purpose', '=', 'Send to Subcontractor'],
+			['Stock Entry', 'purchase_order', 'in', self.purchase_orders]]
+
+		return frappe.get_all('Stock Entry', fields = fields, filters=filters)
+
+	def __get_received_items(self, doctype):
+		fields = []
+		self.po_field = 'purchase_order'
+
+		for field in ['name', self.po_field, 'parent']:
+			fields.append(f'`tab{doctype} Item`.`{field}`')
+
+		filters = [[doctype, 'docstatus', '=', 1], [f'{doctype} Item', self.po_field, 'in', self.purchase_orders]]
+		if doctype == 'Purchase Invoice':
+			filters.append(['Purchase Invoice', 'update_stock', "=", 1])
+
+		return frappe.get_all(f'{doctype}', fields = fields, filters = filters)
+
+	def __get_consumed_items(self, doctype, pr_items):
+		return frappe.get_all('Purchase Receipt Item Supplied',
+			fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'],
+			filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items)), 'parenttype': doctype})
+
+	def __set_alternative_item_details(self, row):
+		if row.get('original_item'):
+			self.alternative_item_details[row.get('original_item')] = row
+
+	def __get_pending_qty_to_receive(self):
+		'''Get qty to be received against the purchase order.'''
+
+		self.qty_to_be_received = defaultdict(float)
+
+		if self.doctype != 'Purchase Order' and self.backflush_based_on != 'BOM' and self.purchase_orders:
+			for row in frappe.get_all('Purchase Order Item',
+				fields = ['item_code', '(qty - received_qty) as qty', 'parent', 'name'],
+				filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}):
+
+				self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
+
+	def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
+		doctype = 'BOM Item' if not exploded_item else 'BOM Explosion Item'
+		fields = [f'`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit']
+
+		alias_dict = {'item_code': 'rm_item_code', 'name': 'bom_detail_no', 'source_warehouse': 'reserve_warehouse'}
+		for field in ['item_code', 'name', 'rate', 'stock_uom',
+			'source_warehouse', 'description', 'item_name', 'stock_uom']:
+			fields.append(f'`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}')
+
+		filters = [[doctype, 'parent', '=', bom_no], [doctype, 'docstatus', '=', 1],
+			['BOM', 'item', '=', item_code], [doctype, 'sourced_by_supplier', '=', 0]]
+
+		return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or []
+
+	def __remove_changed_rows(self):
+		if not self.__changed_name:
+			return
+
+		i=1
+		self.set(self.raw_material_table, [])
+		for d in self._doc_before_save.supplied_items:
+			if d.reference_name in self.__changed_name:
+				continue
+
+			if (d.reference_name not in self.__reference_name):
+				continue
+
+			d.idx = i
+			self.append('supplied_items', d)
+
+			i += 1
+
+	def __set_supplied_items(self):
+		self.bom_items = {}
+
+		has_supplied_items = True if self.get(self.raw_material_table) else False
+		for row in self.items:
+			if (self.doctype != 'Purchase Order' and ((self.__changed_name and row.name not in self.__changed_name)
+				or (has_supplied_items and not self.__changed_name))):
+				continue
+
+			if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM':
+				for bom_item in self.__get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')):
+					qty = (flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor)
+					bom_item.main_item_code = row.item_code
+					self.__update_reserve_warehouse(bom_item, row)
+					self.__set_alternative_item(bom_item)
+					self.__add_supplied_item(row, bom_item, qty)
+
+			elif self.backflush_based_on != 'BOM':
+				for key, transfer_item in self.available_materials.items():
+					if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0:
+						qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
+						transfer_item.qty -= qty
+						self.__add_supplied_item(row, transfer_item.get('item_details'), qty)
+
+				if self.qty_to_be_received:
+					self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty
+
+	def __update_reserve_warehouse(self, row, item):
+		if self.doctype == 'Purchase Order':
+			row.reserve_warehouse = (self.set_reserve_warehouse or item.warehouse)
+
+	def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
+		key = (item_row.item_code, item_row.purchase_order)
+
+		if self.qty_to_be_received == item_row.qty:
+			return transfer_item.qty
+
+		if self.qty_to_be_received:
+			qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
+			transfer_item.item_details.required_qty = transfer_item.qty
+
+			if (transfer_item.serial_no or frappe.get_cached_value('UOM',
+				transfer_item.item_details.stock_uom, 'must_be_whole_number')):
+				return frappe.utils.ceil(qty)
+
+			return qty
+
+	def __set_alternative_item(self, bom_item):
+		if self.alternative_item_details.get(bom_item.rm_item_code):
+			bom_item.update(self.alternative_item_details[bom_item.rm_item_code])
+
+	def __add_supplied_item(self, item_row, bom_item, qty):
+		bom_item.conversion_factor = item_row.conversion_factor
+		rm_obj = self.append(self.raw_material_table, bom_item)
+		rm_obj.reference_name = item_row.name
+
+		if self.doctype == 'Purchase Order':
+			rm_obj.required_qty = qty
+		else:
+			rm_obj.consumed_qty = 0
+			rm_obj.purchase_order = item_row.purchase_order
+			self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
+
+	def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
+		key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
+
+		if (self.available_materials.get(key) and self.available_materials[key]['batch_no']):
+			new_rm_obj = None
+			for batch_no, batch_qty in self.available_materials[key]['batch_no'].items():
+				if batch_qty >= qty:
+					self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
+					self.available_materials[key]['batch_no'][batch_no] -= qty
+					return
+
+				elif qty > 0 and batch_qty > 0:
+					qty -= batch_qty
+					new_rm_obj = self.append(self.raw_material_table, bom_item)
+					new_rm_obj.reference_name = item_row.name
+					self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
+					self.available_materials[key]['batch_no'][batch_no] = 0
+
+			if abs(qty) > 0 and not new_rm_obj:
+				self.__set_consumed_qty(rm_obj, qty)
+		else:
+			self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
+			self.__set_serial_nos(item_row, rm_obj)
+
+	def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
+		rm_obj.required_qty = required_qty
+		rm_obj.consumed_qty = consumed_qty
+
+	def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
+		rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no,
+			'required_qty': qty, 'purchase_order': item_row.purchase_order})
+
+		self.__set_serial_nos(item_row, rm_obj)
+
+	def __set_serial_nos(self, item_row, rm_obj):
+		key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
+		if (self.available_materials.get(key) and self.available_materials[key]['serial_no']):
+			used_serial_nos = self.available_materials[key]['serial_no'][0: cint(rm_obj.consumed_qty)]
+			rm_obj.serial_no = '\n'.join(used_serial_nos)
+
+			# Removed the used serial nos from the list
+			for sn in used_serial_nos:
+				self.available_materials[key]['serial_no'].remove(sn)
+
+	def set_consumed_qty_in_po(self):
+		# Update consumed qty back in the purchase order
+		if self.is_subcontracted != 'Yes':
+			return
+
+		self.__get_purchase_orders()
+		itemwise_consumed_qty = defaultdict(float)
+		for doctype in ['Purchase Receipt', 'Purchase Invoice']:
+			consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True)
+
+			for row in consumed_items:
+				key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
+				itemwise_consumed_qty[key] += row.consumed_qty
+
+		self.__update_consumed_qty_in_po(itemwise_consumed_qty)
+
+	def __update_consumed_qty_in_po(self, itemwise_consumed_qty):
+		fields = ['main_item_code', 'rm_item_code', 'parent', 'supplied_qty', 'name']
+		filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}
+
+		for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters, order_by='idx'):
+			key = (row.rm_item_code, row.main_item_code, row.parent)
+			consumed_qty = itemwise_consumed_qty.get(key, 0)
+
+			if row.supplied_qty < consumed_qty:
+				consumed_qty = row.supplied_qty
+
+			itemwise_consumed_qty[key] -= consumed_qty
+			frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty)
+
+	def __validate_supplied_items(self):
+		if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
+			return
+
+		for row in self.get(self.raw_material_table):
+			self.__validate_consumed_qty(row)
+
+			key = (row.rm_item_code, row.main_item_code, row.purchase_order)
+			if not self.__transferred_items or not self.__transferred_items.get(key):
+				return
+
+			self.__validate_batch_no(row, key)
+			self.__validate_serial_no(row, key)
+
+	def __validate_consumed_qty(self, row):
+		if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0:
+			msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
+
+			frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
+
+	def __validate_batch_no(self, row, key):
+		if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
+			link = get_link_to_form('Purchase Order', row.purchase_order)
+			msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
+			frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
+
+	def __validate_serial_no(self, row, key):
+		if row.get('serial_no'):
+			serial_nos = get_serial_nos(row.get('serial_no'))
+			incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no'))
+
+			if incorrect_sn:
+				incorrect_sn = "\n".join(incorrect_sn)
+				link = get_link_to_form('Purchase Order', row.purchase_order)
+				msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
+				frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
\ No newline at end of file
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 0b4fb3a..56da5b7 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -378,10 +378,10 @@
 
 	def manipulate_grand_total_for_inclusive_tax(self):
 		# if fully inclusive taxes and diff
-		if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
+		if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
 			last_tax = self.doc.get("taxes")[-1]
-			non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
-				for d in self.doc.get("taxes") if not d.included_in_print_rate])
+			non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
+				for d in self.doc.get("taxes") if not d.included_in_print_rate)
 
 			diff = self.doc.total + non_inclusive_tax_amount \
 				- flt(last_tax.total, last_tax.precision("total"))
@@ -521,8 +521,8 @@
 
 	def calculate_total_advance(self):
 		if self.doc.docstatus < 2:
-			total_allocated_amount = sum([flt(adv.allocated_amount, adv.precision("allocated_amount"))
-				for adv in self.doc.get("advances")])
+			total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
+				for adv in self.doc.get("advances"))
 
 			self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
 
@@ -622,7 +622,7 @@
 
 		if self.doc.doctype == "Sales Invoice" \
 			and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
-			and any([d.type == "Cash" for d in self.doc.payments]):
+			and any(d.type == "Cash" for d in self.doc.payments):
 			grand_total = self.doc.rounded_total or self.doc.grand_total
 			base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
 
@@ -658,7 +658,13 @@
 					item.margin_type = None
 					item.margin_rate_or_amount = 0.0
 
-			if item.margin_type and item.margin_rate_or_amount:
+			if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
+				item.margin_type = "Amount"
+				item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
+						item.precision("margin_rate_or_amount"))
+				item.rate_with_margin = item.rate
+
+			elif item.margin_type and item.margin_rate_or_amount:
 				margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
 				rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
 				base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py
index 66459fd..7a4b2d3 100644
--- a/erpnext/controllers/tests/test_mapper.py
+++ b/erpnext/controllers/tests/test_mapper.py
@@ -26,8 +26,8 @@
 
 		# Assert that all inserted items are present in updated sales order
 		src_items = item_list_1 + item_list_2 + item_list_3
-		self.assertEqual(set([d for d in src_items]),
-			set([d.item_code for d in updated_so.items]))
+		self.assertEqual(set(d for d in src_items),
+			set(d.item_code for d in updated_so.items))
 
 
 	def make_quotation(self, item_list, customer):
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index ecf041e..7c072e4 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -113,7 +113,7 @@
 			doc.set_indicator()
 
 		doc.status_display = ", ".join(doc.status_display)
-		doc.items_preview = ", ".join([d.item_name for d in doc.items if d.item_name])
+		doc.items_preview = ", ".join(d.item_name for d in doc.items if d.item_name)
 		result.append(doc)
 
 	return result
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 0c88d28..ebe8524 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -4,8 +4,8 @@
 frappe.provide("erpnext");
 cur_frm.email_field = "email_id";
 
-erpnext.LeadController = frappe.ui.form.Controller.extend({
-	setup: function () {
+erpnext.LeadController = class LeadController extends frappe.ui.form.Controller {
+	setup () {
 		this.frm.make_methods = {
 			'Customer': this.make_customer,
 			'Quotation': this.make_quotation,
@@ -13,9 +13,9 @@
 		};
 
 		this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
-	},
+	}
 
-	onload: function () {
+	onload () {
 		this.frm.set_query("customer", function (doc, cdt, cdn) {
 			return { query: "erpnext.controllers.queries.customer_query" }
 		});
@@ -27,9 +27,9 @@
 		this.frm.set_query("contact_by", function (doc, cdt, cdn) {
 			return { query: "frappe.core.doctype.user.user.user_query" }
 		});
-	},
+	}
 
-	refresh: function () {
+	refresh () {
 		let doc = this.frm.doc;
 		erpnext.toggle_naming_series();
 		frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' }
@@ -45,47 +45,47 @@
 		} else {
 			frappe.contacts.clear_address_and_contact(this.frm);
 		}
-	},
+	}
 
-	make_customer: function () {
+	make_customer () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_customer",
 			frm: cur_frm
 		})
-	},
+	}
 
-	make_opportunity: function () {
+	make_opportunity () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_opportunity",
 			frm: cur_frm
 		})
-	},
+	}
 
-	make_quotation: function () {
+	make_quotation () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_quotation",
 			frm: cur_frm
 		})
-	},
+	}
 
-	organization_lead: function () {
+	organization_lead () {
 		this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
 		this.frm.toggle_reqd("company_name", this.frm.doc.organization_lead);
-	},
+	}
 
-	company_name: function () {
+	company_name () {
 		if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) {
 			this.frm.set_value("lead_name", this.frm.doc.company_name);
 		}
-	},
+	}
 
-	contact_date: function () {
+	contact_date () {
 		if (this.frm.doc.contact_date) {
 			let d = moment(this.frm.doc.contact_date);
 			d.add(1, "day");
 			this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat));
 		}
 	}
-});
+};
 
-$.extend(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));
+extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index ac374a9..43e1b99 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -145,8 +145,8 @@
 })
 
 // TODO commonify this code
-erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
-	onload: function() {
+erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller {
+	onload() {
 
 		if(!this.frm.doc.status) {
 			frm.set_value('status', 'Open');
@@ -159,9 +159,9 @@
 		}
 
 		this.setup_queries();
-	},
+	}
 
-	setup_queries: function() {
+	setup_queries() {
 		var me = this;
 
 		if(this.frm.fields_dict.contact_by.df.options.match(/^User/)) {
@@ -185,17 +185,17 @@
 		else if (me.frm.doc.opportunity_from == "Customer") {
 			me.frm.set_query('party_name', erpnext.queries['customer']);
 		}
-	},
+	}
 
-	create_quotation: function() {
+	create_quotation() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation",
 			frm: cur_frm
 		})
 	}
-});
+};
 
-$.extend(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm}));
 
 cur_frm.cscript.item_code = function(doc, cdt, cdn) {
 	var d = locals[cdt][cdn];
@@ -213,4 +213,4 @@
 			}
 		})
 	}
-}
\ No newline at end of file
+}
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
index 3f5c95a..fe5707a 100644
--- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
@@ -22,10 +22,10 @@
 	get_chart_data: function (_columns, result) {
 		return {
 			data: {
-				labels: result.map(d => d[0]),
+				labels: result.map(d => d.creation_date),
 				datasets: [{
 					name: "First Response Time",
-					values: result.map(d => d[1])
+					values: result.map(d => d.first_response_time)
 				}]
 			},
 			type: "line",
@@ -35,8 +35,7 @@
 						hide_days: 0,
 						hide_seconds: 0
 					};
-					value = frappe.utils.get_formatted_duration(d, duration_options);
-					return value;
+					return frappe.utils.get_formatted_duration(d, duration_options);
 				}
 			}
 		}
diff --git a/erpnext/education/doctype/assessment_result/assessment_result.js b/erpnext/education/doctype/assessment_result/assessment_result.js
index 617a873..c35f607 100644
--- a/erpnext/education/doctype/assessment_result/assessment_result.js
+++ b/erpnext/education/doctype/assessment_result/assessment_result.js
@@ -6,7 +6,8 @@
 		if (!frm.doc.__islocal) {
 			frm.trigger('setup_chart');
 		}
-		frm.set_df_property('details', 'read_only', 1);
+
+		frm.get_field('details').grid.cannot_add_rows = true;
 
 		frm.set_query('course', function() {
 			return {
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py
index 2b3acf1..ce88990 100644
--- a/erpnext/education/doctype/course_enrollment/course_enrollment.py
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py
@@ -91,4 +91,4 @@
 	if activity:
 		return activity[0].name
 	else:
-		return None
\ No newline at end of file
+		return None
diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js
index b59d848..68e7780 100644
--- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js
+++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js
@@ -72,8 +72,8 @@
 });
 
 
-education.StudentsEditor = Class.extend({
-	init: function(frm, wrapper, students) {
+education.StudentsEditor = class StudentsEditor {
+	constructor(frm, wrapper, students) {
 		this.wrapper = wrapper;
 		this.frm = frm;
 		if(students.length > 0) {
@@ -81,8 +81,8 @@
 		} else {
 			this.show_empty_state();
 		}
-	},
-	make: function(frm, students) {
+	}
+	make(frm, students) {
 		var me = this;
 
 		$(this.wrapper).empty();
@@ -173,13 +173,13 @@
 		});
 
 		$(htmls.join("")).appendTo(me.wrapper);
-	},
+	}
 
-	show_empty_state: function() {
+	show_empty_state() {
 		$(this.wrapper).html(
 			`<div class="text-center text-muted" style="line-height: 100px;">
 				${__("No Students in")} ${this.frm.doc.student_group}
 			</div>`
 		);
 	}
-});
+};
diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.js b/erpnext/healthcare/doctype/exercise_type/exercise_type.js
index b49b00e..0614604 100644
--- a/erpnext/healthcare/doctype/exercise_type/exercise_type.js
+++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.js
@@ -9,14 +9,14 @@
 	}
 });
 
-erpnext.ExerciseEditor = Class.extend({
-	init: function(frm, wrapper) {
+erpnext.ExerciseEditor = class ExerciseEditor {
+	constructor(frm, wrapper) {
 		this.wrapper = wrapper;
 		this.frm = frm;
 		this.make(frm, wrapper);
-	},
+	}
 
-	make: function(frm, wrapper) {
+	make(frm, wrapper) {
 		$(this.wrapper).empty();
 
 		this.exercise_toolbar = $('<p>\
@@ -38,9 +38,9 @@
 			this.make_cards(frm);
 			this.make_buttons(frm);
 		}
-	},
+	}
 
-	make_cards: function(frm) {
+	make_cards(frm) {
 		var me = this;
 		$(me.exercise_cards).empty();
 
@@ -60,9 +60,9 @@
 					</div>
 			</div>`, {image_src: step.image, title: step.title, description: step.description, col_id: "col-"+i, card_id: "card-"+i, id: i})).appendTo(me.row);
 		});
-	},
+	}
 
-	make_buttons: function(frm) {
+	make_buttons(frm) {
 		let me = this;
 		$('.btn-edit').on('click', function() {
 			let id = $(this).attr('data-id');
@@ -82,9 +82,9 @@
 				frm.dirty();
 			}, 300);
 		});
-	},
+	}
 
-	show_add_card_dialog: function(frm) {
+	show_add_card_dialog(frm) {
 		let me = this;
 		let d = new frappe.ui.Dialog({
 			title: __('Add Exercise Step'),
@@ -137,9 +137,9 @@
 			primary_action_label: __('Add')
 		});
 		d.show();
-	},
+	}
 
-	show_edit_card_dialog: function(frm, id) {
+	show_edit_card_dialog(frm, id) {
 		let new_dialog = new frappe.ui.Dialog({
 			title: __("Edit Exercise Step"),
 			fields: [
@@ -183,4 +183,4 @@
 		});
 		new_dialog.show();
 	}
-});
+};
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index 789d452..56a3400 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -33,21 +33,20 @@
 		self.reload() # self.notify_update()
 
 	def on_update(self):
-		if self.customer:
-			customer = frappe.get_doc('Customer', self.customer)
-			if self.customer_group:
-				customer.customer_group = self.customer_group
-			if self.territory:
-				customer.territory = self.territory
-
-			customer.customer_name = self.patient_name
-			customer.default_price_list = self.default_price_list
-			customer.default_currency = self.default_currency
-			customer.language = self.language
-			customer.ignore_mandatory = True
-			customer.save(ignore_permissions=True)
-		else:
-			if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
+		if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
+			if self.customer:
+				customer = frappe.get_doc('Customer', self.customer)
+				if self.customer_group:
+					customer.customer_group = self.customer_group
+				if self.territory:
+					customer.territory = self.territory
+				customer.customer_name = self.patient_name
+				customer.default_price_list = self.default_price_list
+				customer.default_currency = self.default_currency
+				customer.language = self.language
+				customer.ignore_mandatory = True
+				customer.save(ignore_permissions=True)
+			else:
 				create_customer(self)
 
 	def set_full_name(self):
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 8ad77a1..3da606b 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -15,10 +15,11 @@
 
 develop_version = '13.x.x-develop'
 
-app_include_js = "/assets/js/erpnext.min.js"
-app_include_css = "/assets/css/erpnext.css"
-web_include_js = "/assets/js/erpnext-web.min.js"
-web_include_css = "/assets/css/erpnext-web.css"
+app_include_js = "erpnext.bundle.js"
+app_include_css = "erpnext.bundle.css"
+web_include_js = "erpnext-web.bundle.js"
+web_include_css = "erpnext-web.bundle.css"
+email_css = "email_erpnext.bundle.css"
 
 doctype_js = {
 	"Address": "public/js/address.js",
@@ -227,6 +228,7 @@
 
 doc_events = {
 	"*": {
+		"validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
 		"on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record",
 		"on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record",
 		"on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record"
@@ -241,6 +243,9 @@
 		"on_update": ["erpnext.hr.doctype.employee.employee.update_user_permissions",
 			"erpnext.portal.utils.set_default_role"]
 	},
+	"Communication": {
+		"on_update": "erpnext.support.doctype.service_level_agreement.service_level_agreement.update_hold_time"
+	},
 	("Sales Taxes and Charges Template", 'Price List'): {
 		"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
 	},
@@ -331,8 +336,8 @@
 		"erpnext.projects.doctype.project.project.hourly_reminder",
 		"erpnext.projects.doctype.project.project.collect_project_status",
 		"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
-		"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
-		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders"
+		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
+		"erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance"
 	],
 	"hourly_long": [
 		"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js
index c21d4b8..5639cc9 100755
--- a/erpnext/hr/doctype/employee/employee.js
+++ b/erpnext/hr/doctype/employee/employee.js
@@ -2,8 +2,8 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.provide("erpnext.hr");
-erpnext.hr.EmployeeController = frappe.ui.form.Controller.extend({
-	setup: function() {
+erpnext.hr.EmployeeController = class EmployeeController extends frappe.ui.form.Controller {
+	setup() {
 		this.frm.fields_dict.user_id.get_query = function(doc, cdt, cdn) {
 			return {
 				query: "frappe.core.doctype.user.user.user_query",
@@ -12,30 +12,30 @@
 		}
 		this.frm.fields_dict.reports_to.get_query = function(doc, cdt, cdn) {
 			return { query: "erpnext.controllers.queries.employee_query"} }
-	},
+	}
 
-	refresh: function() {
+	refresh() {
 		var me = this;
 		erpnext.toggle_naming_series();
-	},
+	}
 
-	date_of_birth: function() {
+	date_of_birth() {
 		return cur_frm.call({
 			method: "get_retirement_date",
 			args: {date_of_birth: this.frm.doc.date_of_birth}
 		});
-	},
+	}
 
-	salutation: function() {
+	salutation() {
 		if(this.frm.doc.salutation) {
 			this.frm.set_value("gender", {
 				"Mr": "Male",
 				"Ms": "Female"
 			}[this.frm.doc.salutation]);
 		}
-	},
+	}
 
-});
+};
 frappe.ui.form.on('Employee',{
 	setup: function(frm) {
 		frm.set_query("leave_policy", function() {
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 5442ed5..d592a9c 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -207,7 +207,7 @@
    "label": "Status",
    "oldfieldname": "status",
    "oldfieldtype": "Select",
-   "options": "Active\nInactive\nLeft",
+   "options": "Active\nInactive\nSuspended\nLeft",
    "reqd": 1,
    "search_index": 1
   },
@@ -813,7 +813,7 @@
  "idx": 24,
  "image_field": "image",
  "links": [],
- "modified": "2021-06-12 11:31:37.730760",
+ "modified": "2021-06-17 11:31:37.730760",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index bc56942..fa017d9 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -4,7 +4,7 @@
 from __future__ import unicode_literals
 import frappe
 
-from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr
+from frappe.utils import getdate, validate_email_address, today, add_years, cstr
 from frappe.model.naming import set_name_by_naming_series
 from frappe import throw, _, scrub
 from frappe.permissions import add_user_permission, remove_user_permission, \
@@ -12,7 +12,6 @@
 from frappe.model.document import Document
 from erpnext.utilities.transaction_base import delete_events
 from frappe.utils.nestedset import NestedSet
-from erpnext.hr.doctype.job_offer.job_offer import get_staffing_plan_detail
 
 class EmployeeUserDisabledError(frappe.ValidationError): pass
 class EmployeeLeftValidationError(frappe.ValidationError): pass
@@ -37,7 +36,7 @@
 
 	def validate(self):
 		from erpnext.controllers.status_updater import validate_status
-		validate_status(self.status, ["Active", "Inactive", "Left"])
+		validate_status(self.status, ["Active", "Inactive", "Suspended", "Left"])
 
 		self.employee = self.name
 		self.set_employee_name()
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index 285374d..e853bee 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -7,7 +7,8 @@
 		'heatmap_message': _('This is based on the attendance of this Employee'),
 		'fieldname': 'employee',
 		'non_standard_fieldnames': {
-			'Bank Account': 'party'
+			'Bank Account': 'party',
+			'Employee Grievance': 'raised_by'
 		},
 		'transactions': [
 			{
@@ -20,7 +21,7 @@
 			},
 			{
 				'label': _('Lifecycle'),
-				'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation']
+				'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation', 'Employee Grievance']
 			},
 			{
 				'label': _('Shift'),
diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js
index 6679e31..d37e149 100644
--- a/erpnext/hr/doctype/employee/employee_list.js
+++ b/erpnext/hr/doctype/employee/employee_list.js
@@ -3,7 +3,7 @@
 	filters: [["status","=", "Active"]],
 	get_indicator: function(doc) {
 		var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
-		indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray"}[doc.status];
+		indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray", "Suspended": "orange"}[doc.status];
 		return indicator;
 	}
 };
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
index 3205a92..ab965d5 100644
--- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
@@ -68,13 +68,13 @@
 	}
 }
 
-erpnext.MarkedEmployee = Class.extend({
-	init: function(frm, wrapper, employee) {
+erpnext.MarkedEmployee = class MarkedEmployee {
+	constructor(frm, wrapper, employee) {
 		this.wrapper = wrapper;
 		this.frm = frm;
 		this.make(frm, employee);
-	},
-	make: function(frm, employee) {
+	}
+	make(frm, employee) {
 		var me = this;
 		$(this.wrapper).empty();
 
@@ -104,16 +104,16 @@
 				})).appendTo(row);
 		});
 	}
-});
+};
 
 
-erpnext.EmployeeSelector = Class.extend({
-	init: function(frm, wrapper, employee) {
+erpnext.EmployeeSelector = class EmployeeSelector {
+	constructor(frm, wrapper, employee) {
 		this.wrapper = wrapper;
 		this.frm = frm;
 		this.make(frm, employee);
-	},
-	make: function(frm, employee) {
+	}
+	make(frm, employee) {
 		var me = this;
 
 		$(this.wrapper).empty();
@@ -266,6 +266,6 @@
 
 		mark_employee_toolbar.appendTo($(this.wrapper));
 	}
-});
+};
 
 
diff --git a/erpnext/hr/doctype/employee_grievance/__init__.py b/erpnext/hr/doctype/employee_grievance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/__init__.py
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.js b/erpnext/hr/doctype/employee_grievance/employee_grievance.js
new file mode 100644
index 0000000..25c5bad
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.js
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Employee Grievance', {
+	setup: function(frm) {
+		frm.set_query('grievance_against_party', function() {
+			return {
+				filters: {
+					name: ['in', [
+						'Company', 'Department', 'Employee Group', 'Employee Grade', 'Employee']
+					]
+				}
+			};
+		});
+		frm.set_query('associated_document_type', function() {
+			let ignore_modules = ["Setup", "Core", "Integrations", "Automation", "Website",
+				"Utilities", "Event Streaming", "Social", "Chat", "Data Migration", "Printing", "Desk", "Custom"];
+			return {
+				filters: {
+					istable: 0,
+					issingle: 0,
+					module: ["Not In", ignore_modules]
+				}
+			};
+		});
+	},
+
+	grievance_against_party: function(frm) {
+		let filters = {};
+		if (frm.doc.grievance_against_party == 'Employee' && frm.doc.raised_by) {
+			filters.name =  ["!=", frm.doc.raised_by];
+		}
+		frm.set_query('grievance_against', function() {
+			return {
+				filters: filters
+			};
+		});
+	},
+});
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.json b/erpnext/hr/doctype/employee_grievance/employee_grievance.json
new file mode 100644
index 0000000..5a91856
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.json
@@ -0,0 +1,261 @@
+{
+ "actions": [],
+ "autoname": "HR-GRIEV-.YYYY.-.#####",
+ "creation": "2021-05-11 13:41:51.485295",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "subject",
+  "raised_by",
+  "employee_name",
+  "designation",
+  "column_break_3",
+  "date",
+  "status",
+  "reports_to",
+  "grievance_details_section",
+  "grievance_against_party",
+  "grievance_against",
+  "grievance_type",
+  "column_break_11",
+  "associated_document_type",
+  "associated_document",
+  "section_break_14",
+  "description",
+  "investigation_details_section",
+  "cause_of_grievance",
+  "resolution_details_section",
+  "resolved_by",
+  "resolution_date",
+  "employee_responsible",
+  "column_break_16",
+  "resolution_detail",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "grievance_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Grievance Type",
+   "options": "Grievance Type",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Date ",
+   "reqd": 1
+  },
+  {
+   "default": "Open",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "options": "Open\nInvestigated\nResolved\nInvalid",
+   "reqd": 1
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Text",
+   "label": "Description",
+   "reqd": 1
+  },
+  {
+   "fieldname": "cause_of_grievance",
+   "fieldtype": "Text",
+   "label": "Cause of Grievance",
+   "mandatory_depends_on": "eval: doc.status == \"Investigated\" || doc.status ==  \"Resolved\""
+  },
+  {
+   "fieldname": "resolution_details_section",
+   "fieldtype": "Section Break",
+   "label": "Resolution Details"
+  },
+  {
+   "fieldname": "resolved_by",
+   "fieldtype": "Link",
+   "label": "Resolved By",
+   "mandatory_depends_on": "eval: doc.status == \"Resolved\"",
+   "options": "User"
+  },
+  {
+   "fieldname": "employee_responsible",
+   "fieldtype": "Link",
+   "label": "Employee Responsible ",
+   "options": "Employee"
+  },
+  {
+   "fieldname": "resolution_detail",
+   "fieldtype": "Small Text",
+   "label": "Resolution Details",
+   "mandatory_depends_on": "eval: doc.status == \"Resolved\""
+  },
+  {
+   "fieldname": "column_break_16",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "resolution_date",
+   "fieldtype": "Date",
+   "label": "Resolution Date",
+   "mandatory_depends_on": "eval: doc.status == \"Resolved\""
+  },
+  {
+   "fieldname": "grievance_against",
+   "fieldtype": "Dynamic Link",
+   "label": "Grievance Against",
+   "options": "grievance_against_party",
+   "reqd": 1
+  },
+  {
+   "fieldname": "raised_by",
+   "fieldtype": "Link",
+   "label": "Raised By",
+   "options": "Employee",
+   "reqd": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Employee Grievance",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fetch_from": "raised_by.designation",
+   "fieldname": "designation",
+   "fieldtype": "Link",
+   "label": "Designation",
+   "options": "Designation",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "raised_by.reports_to",
+   "fieldname": "reports_to",
+   "fieldtype": "Link",
+   "label": "Reports To",
+   "options": "Employee",
+   "read_only": 1
+  },
+  {
+   "fieldname": "grievance_details_section",
+   "fieldtype": "Section Break",
+   "label": "Grievance Details"
+  },
+  {
+   "fieldname": "column_break_11",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_14",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "grievance_against_party",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Grievance Against Party",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "fieldname": "associated_document_type",
+   "fieldtype": "Link",
+   "label": "Associated Document Type",
+   "options": "DocType"
+  },
+  {
+   "fieldname": "associated_document",
+   "fieldtype": "Dynamic Link",
+   "label": "Associated Document",
+   "options": "associated_document_type"
+  },
+  {
+   "fieldname": "investigation_details_section",
+   "fieldtype": "Section Break",
+   "label": "Investigation Details"
+  },
+  {
+   "fetch_from": "raised_by.employee_name",
+   "fieldname": "employee_name",
+   "fieldtype": "Data",
+   "label": "Employee Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "subject",
+   "fieldtype": "Data",
+   "label": "Subject",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-06-21 12:51:01.499486",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Grievance",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "select": 1,
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "select": 1,
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "search_fields": "subject,raised_by,grievance_against_party",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "subject",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
new file mode 100644
index 0000000..503b5ea
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _, bold
+from frappe.model.document import Document
+
+class EmployeeGrievance(Document):
+	def on_submit(self):
+		if self.status not in ["Invalid", "Resolved"]:
+			frappe.throw(_("Only Employee Grievance with status {0} or {1} can be submitted").format(
+				bold("Invalid"),
+				bold("Resolved"))
+			)
+
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
new file mode 100644
index 0000000..fc08e21
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
@@ -0,0 +1,12 @@
+frappe.listview_settings["Employee Grievance"] = {
+	has_indicator_for_draft: 1,
+	get_indicator: function(doc) {
+		var colors = {
+			"Open": "red",
+			"Investigated": "orange",
+			"Resolved": "green",
+			"Invalid": "grey"
+		};
+		return [__(doc.status), colors[doc.status], "status,=," + doc.status];
+	}
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
new file mode 100644
index 0000000..a615b20
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+import unittest
+from frappe.utils import today
+from erpnext.hr.doctype.employee.test_employee import make_employee
+class TestEmployeeGrievance(unittest.TestCase):
+	def test_create_employee_grievance(self):
+		create_employee_grievance()
+
+def create_employee_grievance():
+	grievance_type = create_grievance_type()
+	emp_1 = make_employee("test_emp_grievance_@example.com", company="_Test Company")
+	emp_2 = make_employee("testculprit@example.com", company="_Test Company")
+
+	grievance = frappe.new_doc("Employee Grievance")
+	grievance.subject = "Test Employee Grievance"
+	grievance.raised_by = emp_1
+	grievance.date = today()
+	grievance.grievance_type = grievance_type
+	grievance.grievance_against_party = "Employee"
+	grievance.grievance_against = emp_2
+	grievance.description = "test descrip"
+
+	#set cause
+	grievance.cause_of_grievance = "test cause"
+
+	#resolution details
+	grievance.resolution_date = today()
+	grievance.resolution_detail = "test resolution detail"
+	grievance.resolved_by = "test_emp_grievance_@example.com"
+	grievance.employee_responsible = emp_2
+	grievance.status = "Resolved"
+
+	grievance.save()
+	grievance.submit()
+
+	return grievance
+
+
+def create_grievance_type():
+	if frappe.db.exists("Grievance Type", "Employee Abuse"):
+		return frappe.get_doc("Grievance Type", "Employee Abuse")
+	grievance_type = frappe.new_doc("Grievance Type")
+	grievance_type.name = "Employee Abuse"
+	grievance_type.description = "Test"
+	grievance_type.save()
+
+	return grievance_type.name
+
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.json b/erpnext/hr/doctype/employee_referral/employee_referral.json
index bfd404b..3ae73a9 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.json
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.json
@@ -9,16 +9,18 @@
   "first_name",
   "last_name",
   "full_name",
-  "email",
-  "contact_no",
-  "resume",
-  "resume_link",
   "column_break_6",
   "date",
   "status",
   "for_designation",
+  "referral_details_section",
+  "email",
+  "contact_no",
+  "resume_link",
+  "column_break_12",
   "current_employer",
   "current_job_title",
+  "resume",
   "referrer_details_section",
   "referrer",
   "referrer_name",
@@ -189,12 +191,21 @@
    "label": "Referral Bonus Payment Status",
    "options": "\nUnpaid\nPaid",
    "read_only": 1
+  },
+  {
+   "fieldname": "referral_details_section",
+   "fieldtype": "Section Break",
+   "label": "Referral Details"
+  },
+  {
+   "fieldname": "column_break_12",
+   "fieldtype": "Column Break"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-26 21:21:38.094086",
+ "modified": "2021-05-04 17:03:26.134560",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Referral",
diff --git a/erpnext/hr/doctype/grievance_type/__init__.py b/erpnext/hr/doctype/grievance_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/__init__.py
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.js b/erpnext/hr/doctype/grievance_type/grievance_type.js
new file mode 100644
index 0000000..425f2fd
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Grievance Type', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.json b/erpnext/hr/doctype/grievance_type/grievance_type.json
new file mode 100644
index 0000000..1dce00a
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "autoname": "Prompt",
+ "creation": "2021-05-11 12:41:50.256071",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "section_break_5",
+  "description"
+ ],
+ "fields": [
+  {
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Text",
+   "label": "Description"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-06-21 12:54:37.764712",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Grievance Type",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.py b/erpnext/hr/doctype/grievance_type/grievance_type.py
new file mode 100644
index 0000000..618cf0a
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class GrievanceType(Document):
+	pass
diff --git a/erpnext/hr/doctype/grievance_type/test_grievance_type.py b/erpnext/hr/doctype/grievance_type/test_grievance_type.py
new file mode 100644
index 0000000..a02a34d
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/test_grievance_type.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestGrievanceType(unittest.TestCase):
+	pass
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index 8af8cea..f65e6e1 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -23,7 +23,7 @@
 		last_idx = max([cint(d.idx) for d in self.get("holidays")] or [0,])
 		for i, d in enumerate(date_list):
 			ch = self.append('holidays', {})
-			ch.description = self.weekly_off
+			ch.description = _(self.weekly_off)
 			ch.holiday_date = d
 			ch.weekly_off = 1
 			ch.idx = last_idx + i + 1
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_list.js b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
index 3b9141b..2ad0d59 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_list.js
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
@@ -2,7 +2,7 @@
 // MIT License. See license.txt
 
 frappe.listview_settings['Job Applicant'] = {
-	add_fields: ["company", "designation", "job_applicant", "status"],
+	add_fields: ["status"],
 	get_indicator: function (doc) {
 		if (doc.status == "Accepted") {
 			return [__(doc.status), "green", "status,=," + doc.status];
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index ae02c51..ae009ba 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -110,6 +110,7 @@
    "label": "Allocation"
   },
   {
+    "allow_on_submit": 1,
    "bold": 1,
    "fieldname": "new_leaves_allocated",
    "fieldtype": "Float",
@@ -235,7 +236,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-14 15:28:26.335104",
+ "modified": "2021-06-03 15:28:26.335104",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Allocation",
@@ -277,4 +278,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "timeline_field": "employee"
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 11302ca..4757cd3 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -8,6 +8,7 @@
 from frappe.model.document import Document
 from erpnext.hr.utils import set_employee_name, get_leave_period
 from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry
+from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period
 
 class OverlapError(frappe.ValidationError): pass
 class BackDatedAllocationError(frappe.ValidationError): pass
@@ -55,6 +56,43 @@
 		if self.carry_forward:
 			self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
 
+	def on_update_after_submit(self):
+		if self.has_value_changed("new_leaves_allocated"):
+			self.validate_against_leave_applications()
+			leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count()
+			args = {
+				"leaves": leaves_to_be_added,
+				"from_date": self.from_date,
+				"to_date": self.to_date,
+				"is_carry_forward": 0
+			}
+			create_leave_ledger_entry(self, args, True)
+
+	def get_existing_leave_count(self):
+		ledger_entries = frappe.get_all("Leave Ledger Entry",
+								filters={
+									"transaction_type": "Leave Allocation",
+									"transaction_name": self.name,
+									"employee": self.employee,
+									"company": self.company,
+									"leave_type": self.leave_type
+								},
+								pluck="leaves")
+		total_existing_leaves = 0
+		for entry in ledger_entries:
+			total_existing_leaves += entry
+
+		return total_existing_leaves
+
+	def validate_against_leave_applications(self):
+		leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
+			self.from_date, self.to_date)
+		if flt(leaves_taken) > flt(self.total_leaves_allocated):
+			if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
+				frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
+			else:
+				frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
+
 	def update_leave_policy_assignments_when_no_allocations_left(self):
 		allocations = frappe.db.get_list("Leave Allocation", filters = {
 			"docstatus": 1,
@@ -225,4 +263,4 @@
 
 def validate_carry_forward(leave_type):
 	if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
-		frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
\ No newline at end of file
+		frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 6e7ae87..49dd701 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -1,10 +1,10 @@
 from __future__ import unicode_literals
 import frappe
+import erpnext
 import unittest
 from frappe.utils import nowdate, add_months, getdate, add_days
 from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
 from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation
-
 class TestLeaveAllocation(unittest.TestCase):
 	@classmethod
 	def setUpClass(cls):
@@ -164,6 +164,53 @@
 		leave_allocation.cancel()
 		self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
 
+
+	def test_leave_addition_after_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+		leave_allocation = create_leave_allocation()
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+		leave_allocation.new_leaves_allocated = 40
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 40)
+
+	def test_leave_subtraction_after_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+		leave_allocation = create_leave_allocation()
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+		leave_allocation.new_leaves_allocated = 10
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 10)
+
+	def test_against_leave_application_validation_after_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+		leave_allocation = create_leave_allocation()
+		leave_allocation.submit()
+		self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+		leave_application = frappe.get_doc({
+			"doctype": 'Leave Application',
+			"employee": employee.name,
+			"leave_type": "_Test Leave Type",
+			"from_date": nowdate(),
+			"to_date": add_days(nowdate(), 10),
+			"company": erpnext.get_default_company() or "_Test Company",
+			"docstatus": 1,
+			"status": "Approved",
+			"leave_approver": 'test@example.com'
+		})
+		leave_application.submit()
+		leave_allocation.new_leaves_allocated = 8
+		leave_allocation.total_leaves_allocated = 8
+		self.assertRaises(frappe.ValidationError, leave_allocation.submit)
+
 def create_leave_allocation(**args):
 	args = frappe._dict(args)
 
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
index 04af232..228391b 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
@@ -103,4 +103,4 @@
 		})
 		frm.set_value('total_estimated_budget', estimated_budget);
 	}
-}
\ No newline at end of file
+};
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 533149a..e6c783a 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -41,7 +41,7 @@
 
 			detail.total_estimated_cost = 0
 			if detail.number_of_positions > 0:
-				if detail.vacancies > 0 and detail.estimated_cost_per_position:
+				if detail.vacancies and detail.estimated_cost_per_position:
 					detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position)
 
 			self.total_estimated_budget += detail.total_estimated_cost
@@ -76,12 +76,12 @@
 		if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or \
 			flt(staffing_plan_detail.total_estimated_cost) > flt(parent_plan_details[0].total_estimated_cost):
 			frappe.throw(_("You can only plan for upto {0} vacancies and budget {1} \
-				for {2} as per staffing plan {3} for parent company {4}."
-				.format(cint(parent_plan_details[0].vacancies),
+				for {2} as per staffing plan {3} for parent company {4}.").format(
+					cint(parent_plan_details[0].vacancies),
 					parent_plan_details[0].total_estimated_cost,
 					frappe.bold(staffing_plan_detail.designation),
 					parent_plan_details[0].name,
-					parent_company)), ParentCompanyError)
+					parent_company), ParentCompanyError)
 
 		#Get vacanices already planned for all companies down the hierarchy of Parent Company
 		lft, rgt = frappe.get_cached_value('Company',  parent_company,  ["lft", "rgt"])
@@ -98,14 +98,14 @@
 			(flt(parent_plan_details[0].total_estimated_cost) < \
 			(flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost))):
 			frappe.throw(_("{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
-				You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}."
-				.format(cint(all_sibling_details.vacancies),
+				You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}.").format(
+					cint(all_sibling_details.vacancies),
 					all_sibling_details.total_estimated_cost,
 					frappe.bold(staffing_plan_detail.designation),
 					parent_company,
 					cint(parent_plan_details[0].vacancies),
 					parent_plan_details[0].total_estimated_cost,
-					parent_plan_details[0].name)))
+					parent_plan_details[0].name))
 
 	def validate_with_subsidiary_plans(self, staffing_plan_detail):
 		#Valdate this plan with all child company plan
@@ -121,11 +121,11 @@
 			cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies) or \
 			flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost):
 			frappe.throw(_("Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
-				Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
-				.format(self.company,
+				Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies").format(
+					self.company,
 					cint(children_details.vacancies),
 					children_details.total_estimated_cost,
-					frappe.bold(staffing_plan_detail.designation))), SubsidiaryCompanyError)
+					frappe.bold(staffing_plan_detail.designation)), SubsidiaryCompanyError)
 
 @frappe.whitelist()
 def get_designation_counts(designation, company):
@@ -170,4 +170,4 @@
 				designation, from_date, to_date)
 
 	# Only a single staffing plan can be active for a designation on given date
-	return staffing_plan if staffing_plan else None
\ No newline at end of file
+	return staffing_plan if staffing_plan else None
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.js b/erpnext/hr/doctype/upload_attendance/upload_attendance.js
index 29aa854..bbafc82 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.js
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.js
@@ -5,19 +5,19 @@
 
 frappe.provide("erpnext.hr");
 
-erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({
-	onload: function() {
+erpnext.hr.AttendanceControlPanel = class AttendanceControlPanel extends frappe.ui.form.Controller {
+	onload() {
 		this.frm.set_value("att_fr_date", frappe.datetime.get_today());
 		this.frm.set_value("att_to_date", frappe.datetime.get_today());
-	},
+	}
 
-	refresh: function() {
+	refresh() {
 		this.frm.disable_save();
 		this.show_upload();
 		this.setup_import_progress();
-	},
+	}
 
-	get_template:function() {
+	get_template() {
 		if(!this.frm.doc.att_fr_date || !this.frm.doc.att_to_date) {
 			frappe.msgprint(__("Attendance From Date and Attendance To Date is mandatory"));
 			return;
@@ -28,7 +28,7 @@
 			from_date: this.frm.doc.att_fr_date,
 			to_date: this.frm.doc.att_to_date,
 		});
-	},
+	}
 
 	show_upload() {
 		var $wrapper = $(cur_frm.fields_dict.upload_html.wrapper).empty();
@@ -36,7 +36,7 @@
 			wrapper: $wrapper,
 			method: 'erpnext.hr.doctype.upload_attendance.upload_attendance.upload'
 		});
-	},
+	}
 
 	setup_import_progress() {
 		var $log_wrapper = $(this.frm.fields_dict.import_log.wrapper).empty();
@@ -64,6 +64,6 @@
 			}
 		});
 	}
-})
+}
 
 cur_frm.cscript = new erpnext.hr.AttendanceControlPanel({frm: cur_frm});
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json
index e49541e..f365003 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.json
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json
@@ -11,8 +11,8 @@
  "event": "Submit",
  "idx": 0,
  "is_standard": 1,
- "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n    <tr height=\"10\"></tr>\n    <tr>\n        <td width=\"15\"></td>\n        <td>\n            <div class=\"text-medium text-muted\">\n                <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n            </div>\n        </td>\n        <td width=\"15\"></td>\n    </tr>\n    <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n    <tr height=\"10\"></tr>\n    <tr>\n        <td width=\"15\"></td>\n        <td>\n            <div>\n                {{ doc.introduction }}\n                <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n                    <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n                    {% set start = frappe.utils.get_datetime(doc.start_time) %}\n                    {% set end = frappe.utils.get_datetime(doc.end_time) %}\n                    {% if start.date() == end.date() %}\n                    <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n                    <li>\n                        {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n                    </li>\n                    {% else %}\n                    <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n                    </li>\n                    <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n                    </li>\n                    {% endif %}\n                    <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n                    {% if doc.is_mandatory %}\n                    <li>Note: This Training Event is mandatory</li>\n                    {% endif %}\n                </ul>\n            </div>\n        </td>\n        <td width=\"15\"></td>\n    </tr>\n    <tr height=\"10\"></tr>\n</table>",
- "modified": "2021-05-24 16:29:13.165930",
+ "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n    <tr height=\"10\"></tr>\n    <tr>\n        <td width=\"15\"></td>\n        <td>\n            <div class=\"text-medium text-muted\">\n                <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n            </div>\n        </td>\n        <td width=\"15\"></td>\n    </tr>\n    <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n    <tr height=\"10\"></tr>\n    <tr>\n        <td width=\"15\"></td>\n        <td>\n            <div>\n                {{ doc.introduction }}\n                <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n                    <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n                    {% set start = frappe.utils.get_datetime(doc.start_time) %}\n                    {% set end = frappe.utils.get_datetime(doc.end_time) %}\n                    {% if start.date() == end.date() %}\n                        <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n                        <li>\n                            {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n                        </li>\n                    {% else %}\n                        <li>\n                            {{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n                        </li>\n                        <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b></li>\n                    {% endif %}\n                    <li>{{ _(\"Event Link\") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n                    {% if doc.is_mandatory %}\n                        <li>{{ _(\"Note: This Training Event is mandatory\") }}</li>\n                    {% endif %}\n                </ul>\n            </div>\n        </td>\n        <td width=\"15\"></td>\n    </tr>\n    <tr height=\"10\"></tr>\n</table>",
+ "modified": "2021-06-16 14:08:12.933367",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Training Scheduled",
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md
index 418fd49..b9ba846 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.md
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md
@@ -24,19 +24,19 @@
                     {% set start = frappe.utils.get_datetime(doc.start_time) %}
                     {% set end = frappe.utils.get_datetime(doc.end_time) %}
                     {% if start.date() == end.date() %}
-                    <li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
-                    <li>
-                        {{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
-                    </li>
+                        <li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
+                        <li>
+                            {{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
+                        </li>
                     {% else %}
-                    <li>{{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
-                    </li>
-                    <li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
-                    </li>
+                        <li>
+                            {{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
+                        </li>
+                        <li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b></li>
                     {% endif %}
-                    <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
+                    <li>{{ _("Event Link") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
                     {% if doc.is_mandatory %}
-                    <li>Note: This Training Event is mandatory</li>
+                        <li>{{ _("Note: This Training Event is mandatory") }}</li>
                     {% endif %}
                 </ul>
             </div>
@@ -44,4 +44,4 @@
         <td width="15"></td>
     </tr>
     <tr height="10"></tr>
-</table>
\ No newline at end of file
+</table>
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 4dd4570..b8953b3 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -178,7 +178,7 @@
 			is_carry_forward, is_expired
 		FROM `tabLeave Ledger Entry`
 		WHERE employee=%(employee)s AND leave_type=%(leave_type)s
-			AND docstatus=1 AND leaves>0
+			AND docstatus=1
 			AND (from_date between %(from_date)s AND %(to_date)s
 				OR to_date between %(from_date)s AND %(to_date)s
 				OR (from_date < %(from_date)s AND to_date > %(to_date)s))
diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
index 92715d3..e86fa2b 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
@@ -47,7 +47,7 @@
 	user = frappe.session.user
 	conditions = get_conditions(filters)
 
-	active_employees = frappe.get_all("Employee",
+	active_employees = frappe.get_list("Employee",
 		filters=conditions,
 		fields=["name", "employee_name", "department", "user_id", "leave_approver"])
 
@@ -72,4 +72,4 @@
 
 			data.append(row)
 
-	return data
\ No newline at end of file
+	return data
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index c5929c6..bcb0ee4 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -57,10 +57,10 @@
 
 	data = []
 
+	leave_types = frappe.db.get_list("Leave Type")
 	leave_list = None
 	if filters.summarized_view:
-		leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True)
-		leave_list = [d[0] + ":Float:120" for d in leave_types]
+		leave_list = [d.name + ":Float:120" for d in leave_types]
 		columns.extend(leave_list)
 		columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
 
@@ -72,11 +72,11 @@
 			if (att_map_set & emp_map_set):
 				parameter_row = ["<b>"+ parameter + "</b>"] + ['' for day in range(filters["total_days_in_month"] + 2)]
 				data.append(parameter_row)
-				record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
+				record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
 				emp_att_map.update(emp_att_data)
 				data += record
 	else:
-		record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
+		record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
 		data += record
 
 	chart_data = get_chart_data(emp_att_map, days)
@@ -126,7 +126,7 @@
 
 	return chart
 
-def add_data(employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=None):
+def add_data(employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=None):
 
 	record = []
 	emp_att_map = {}
@@ -204,9 +204,9 @@
 				else:
 					leaves[d.leave_type] = d.count
 
-			for d in leave_list:
-				if d in leaves:
-					row.append(leaves[d])
+			for d in leave_types:
+				if d.name in leaves:
+					row.append(leaves[d.name])
 				else:
 					row.append("0.0")
 
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index c5201c2..4500ba4 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -154,6 +154,24 @@
    "type": "Link"
   },
   {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Grievance Type",
+   "link_to": "Grievance Type",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Employee Grievance",
+   "link_to": "Employee Grievance",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "Employee",
    "hidden": 0,
    "is_query_report": 0,
@@ -823,7 +841,7 @@
    "type": "Link"
   }
  ],
- "modified": "2021-04-26 13:36:15.413819",
+ "modified": "2021-05-13 17:19:40.524444",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 69d11a8..ff7fbbd 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -60,8 +60,9 @@
 			self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
 
 	def check_sanctioned_amount_limit(self):
-		total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 		sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
+		if sanctioned_amount_limit:
+			total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 
 		if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
 			frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
@@ -155,9 +156,29 @@
 	frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
 
 def get_total_loan_amount(applicant_type, applicant, company):
-	return frappe.db.get_value('Loan',
-		{'applicant_type': applicant_type, 'company': company, 'applicant': applicant, 'docstatus': 1},
-		'sum(loan_amount)')
+	pending_amount = 0
+	loan_details = frappe.db.get_all("Loan",
+		filters={"applicant_type": applicant_type, "company": company, "applicant": applicant, "docstatus": 1,
+			"status": ("!=", "Closed")},
+		fields=["status", "total_payment", "disbursed_amount", "total_interest_payable", "total_principal_paid",
+			"written_off_amount"])
+
+	interest_amount = flt(frappe.db.get_value("Loan Interest Accrual", {"applicant_type": applicant_type,
+		"company": company, "applicant": applicant, "docstatus": 1}, "sum(interest_amount - paid_interest_amount)"))
+
+	for loan in loan_details:
+		if loan.status in ("Disbursed", "Loan Closure Requested"):
+			pending_amount += flt(loan.total_payment) - flt(loan.total_interest_payable) \
+				- flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+		elif loan.status == "Partially Disbursed":
+			pending_amount += flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
+				- flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+		elif loan.status == "Sanctioned":
+			pending_amount += flt(loan.total_payment)
+
+	pending_amount += interest_amount
+
+	return pending_amount
 
 def get_sanctioned_amount_limit(applicant_type, applicant, company):
 	return frappe.db.get_value('Sanctioned Loan Amount',
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index fa4707c..314f58d 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -49,7 +49,11 @@
 		if not frappe.db.exists("Customer", "_Test Loan Customer"):
 			frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
 
-		self.applicant2 = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
+		if not frappe.db.exists("Customer", "_Test Loan Customer 1"):
+			frappe.get_doc(get_customer_dict("_Test Loan Customer 1")).insert(ignore_permissions=True)
+
+		self.applicant2 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name")
+		self.applicant3 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer 1"}, "name")
 
 		create_loan(self.applicant1, "Personal Loan", 280000, "Repay Over Number of Periods", 20)
 
@@ -125,6 +129,38 @@
 		self.assertTrue(gl_entries1)
 		self.assertTrue(gl_entries2)
 
+	def test_sanctioned_amount_limit(self):
+		# Clear loan docs before checking
+		frappe.db.sql("DELETE FROM `tabLoan` where applicant = '_Test Loan Customer 1'")
+		frappe.db.sql("DELETE FROM `tabLoan Application` where applicant = '_Test Loan Customer 1'")
+		frappe.db.sql("DELETE FROM `tabLoan Security Pledge` where applicant = '_Test Loan Customer 1'")
+
+		if not frappe.db.get_value("Sanctioned Loan Amount", filters={"applicant_type": "Customer",
+			"applicant": "_Test Loan Customer 1", "company": "_Test Company"}):
+			frappe.get_doc({
+				"doctype": "Sanctioned Loan Amount",
+				"applicant_type": "Customer",
+				"applicant": "_Test Loan Customer 1",
+				"sanctioned_amount_limit": 1500000,
+				"company": "_Test Company"
+			}).insert(ignore_permissions=True)
+
+		# Make First Loan
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+		loan = create_demand_loan(self.applicant3, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		# Make second loan greater than the sanctioned amount
+		loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge,
+			do_not_save=True)
+		self.assertRaises(frappe.ValidationError, loan_application.save)
+
 	def test_regular_loan_repayment(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
@@ -367,7 +403,7 @@
 		unpledge_request.load_from_db()
 		self.assertEqual(unpledge_request.docstatus, 1)
 
-	def test_santined_loan_security_unpledge(self):
+	def test_sanctioned_loan_security_unpledge(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
 			"qty": 4000.00
@@ -858,7 +894,7 @@
 	return lr
 
 def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None,
-	repayment_periods=None, posting_date=None):
+	repayment_periods=None, posting_date=None, do_not_save=False):
 	loan_application = frappe.new_doc('Loan Application')
 	loan_application.applicant_type = 'Customer'
 	loan_application.company = company
@@ -874,6 +910,9 @@
 	for pledge in proposed_pledges:
 		loan_application.append('proposed_pledges', pledge)
 
+	if do_not_save:
+		return loan_application
+
 	loan_application.save()
 	loan_application.submit()
 
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index 9c0147e..d8f3577 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -46,9 +46,11 @@
 			frappe.throw(_("Loan Amount exceeds maximum loan amount of {0} as per proposed securities").format(self.maximum_loan_amount))
 
 	def check_sanctioned_amount_limit(self):
-		total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 		sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
 
+		if sanctioned_amount_limit:
+			total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
+
 		if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
 			frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
 
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 3d99b1f..b8b1a40 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -235,70 +235,71 @@
 		else:
 			remarks = _("Repayment against Loan: ") + self.against_loan
 
-		if self.total_penalty_paid:
+		if not loan_details.repay_from_salary:
+			if self.total_penalty_paid:
+				gle_map.append(
+					self.get_gl_dict({
+						"account": loan_details.loan_account,
+						"against": loan_details.payment_account,
+						"debit": self.total_penalty_paid,
+						"debit_in_account_currency": self.total_penalty_paid,
+						"against_voucher_type": "Loan",
+						"against_voucher": self.against_loan,
+						"remarks": _("Penalty against loan:") + self.against_loan,
+						"cost_center": self.cost_center,
+						"party_type": self.applicant_type,
+						"party": self.applicant,
+						"posting_date": getdate(self.posting_date)
+					})
+				)
+
+				gle_map.append(
+					self.get_gl_dict({
+						"account": loan_details.penalty_income_account,
+						"against": loan_details.payment_account,
+						"credit": self.total_penalty_paid,
+						"credit_in_account_currency": self.total_penalty_paid,
+						"against_voucher_type": "Loan",
+						"against_voucher": self.against_loan,
+						"remarks": _("Penalty against loan:") + self.against_loan,
+						"cost_center": self.cost_center,
+						"posting_date": getdate(self.posting_date)
+					})
+				)
+
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.loan_account,
-					"against": loan_details.payment_account,
-					"debit": self.total_penalty_paid,
-					"debit_in_account_currency": self.total_penalty_paid,
+					"account": loan_details.payment_account,
+					"against": loan_details.loan_account + ", " + loan_details.interest_income_account
+							+ ", " + loan_details.penalty_income_account,
+					"debit": self.amount_paid,
+					"debit_in_account_currency": self.amount_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
-					"remarks": _("Penalty against loan:") + self.against_loan,
+					"remarks": remarks,
 					"cost_center": self.cost_center,
-					"party_type": self.applicant_type,
-					"party": self.applicant,
 					"posting_date": getdate(self.posting_date)
 				})
 			)
 
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.penalty_income_account,
+					"account": loan_details.loan_account,
+					"party_type": loan_details.applicant_type,
+					"party": loan_details.applicant,
 					"against": loan_details.payment_account,
-					"credit": self.total_penalty_paid,
-					"credit_in_account_currency": self.total_penalty_paid,
+					"credit": self.amount_paid,
+					"credit_in_account_currency": self.amount_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
-					"remarks": _("Penalty against loan:") + self.against_loan,
+					"remarks": remarks,
 					"cost_center": self.cost_center,
 					"posting_date": getdate(self.posting_date)
 				})
 			)
 
-		gle_map.append(
-			self.get_gl_dict({
-				"account": loan_details.payment_account,
-				"against": loan_details.loan_account + ", " + loan_details.interest_income_account
-						+ ", " + loan_details.penalty_income_account,
-				"debit": self.amount_paid,
-				"debit_in_account_currency": self.amount_paid,
-				"against_voucher_type": "Loan",
-				"against_voucher": self.against_loan,
-				"remarks": remarks,
-				"cost_center": self.cost_center,
-				"posting_date": getdate(self.posting_date)
-			})
-		)
-
-		gle_map.append(
-			self.get_gl_dict({
-				"account": loan_details.loan_account,
-				"party_type": loan_details.applicant_type,
-				"party": loan_details.applicant,
-				"against": loan_details.payment_account,
-				"credit": self.amount_paid,
-				"credit_in_account_currency": self.amount_paid,
-				"against_voucher_type": "Loan",
-				"against_voucher": self.against_loan,
-				"remarks": remarks,
-				"cost_center": self.cost_center,
-				"posting_date": getdate(self.posting_date)
-			})
-		)
-
-		if gle_map:
-			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
+			if gle_map:
+				make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
 
 def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
 	payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None):
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
index 44712d5..546a68f 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
@@ -41,9 +41,9 @@
 })
 
 // TODO commonify this code
-erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({
-	refresh: function () {
-		frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' };
+erpnext.maintenance.MaintenanceSchedule = class MaintenanceSchedule extends frappe.ui.form.Controller {
+	refresh() {
+		frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
 
 		var me = this;
 
@@ -138,33 +138,29 @@
 				}, __('Create'));
 			}
 		}
-	},
+	}
 
-	start_date: function (doc, cdt, cdn) {
+	start_date(doc, cdt, cdn) {
 		this.set_no_of_visits(doc, cdt, cdn);
-	},
+	}
 
-	end_date: function (doc, cdt, cdn) {
+	end_date(doc, cdt, cdn) {
 		this.set_no_of_visits(doc, cdt, cdn);
-	},
+	}
 
-	periodicity: function (doc, cdt, cdn) {
+	periodicity(doc, cdt, cdn) {
 		this.set_no_of_visits(doc, cdt, cdn);
+	}
 
-	},
-	no_of_visits: function (doc, cdt, cdn) {
-		this.set_no_of_visits(doc, cdt, cdn);
-	},
-
-	set_no_of_visits: function (doc, cdt, cdn) {
+	set_no_of_visits(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		let me = this;
 		if (item.start_date && item.periodicity) {
 			me.frm.call('validate_end_date_visits');
 			
 		}
-	},
-});
+	}
+};
 
-$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({ frm: cur_frm }));
+extend_cscript(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({frm: cur_frm}));
 
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
index d6105c6..53ecdf5 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
@@ -64,9 +64,9 @@
 })
 
 // TODO commonify this code
-erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
-	refresh: function () {
-		frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' };
+erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui.form.Controller {
+	refresh() {
+		frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
 
 		var me = this;
 
@@ -119,7 +119,7 @@
 					})
 				}, __("Get Items From"));
 		}
-	},
-});
+	}
+};
 
-$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({ frm: cur_frm }));
\ No newline at end of file
+extend_cscript(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({frm: cur_frm}));
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index a09a5e3..44f841f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -360,16 +360,16 @@
 	}
 });
 
-erpnext.bom.BomController = erpnext.TransactionController.extend({
-	conversion_rate: function(doc) {
+erpnext.bom.BomController = class BomController extends erpnext.TransactionController {
+	conversion_rate(doc) {
 		if(this.frm.doc.currency === this.get_company_currency()) {
 			this.frm.set_value("conversion_rate", 1.0);
 		} else {
 			erpnext.bom.update_cost(doc);
 		}
-	},
+	}
 
-	item_code: function(doc, cdt, cdn){
+	item_code(doc, cdt, cdn){
 		var scrap_items = false;
 		var child = locals[cdt][cdn];
 		if (child.doctype == 'BOM Scrap Item') {
@@ -381,19 +381,19 @@
 		}
 
 		get_bom_material_detail(doc, cdt, cdn, scrap_items);
-	},
+	}
 
-	buying_price_list: function(doc) {
+	buying_price_list(doc) {
 		this.apply_price_list();
-	},
+	}
 
-	plc_conversion_rate: function(doc) {
+	plc_conversion_rate(doc) {
 		if (!this.in_apply_price_list) {
 			this.apply_price_list(null, true);
 		}
-	},
+	}
 
-	conversion_factor: function(doc, cdt, cdn) {
+	conversion_factor(doc, cdt, cdn) {
 		if (frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
 			var item = frappe.get_doc(cdt, cdn);
 			frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
@@ -402,10 +402,10 @@
 			this.toggle_conversion_factor(item);
 			this.frm.events.update_cost(this.frm);
 		}
-	},
-});
+	}
+};
 
-$.extend(cur_frm.cscript, new erpnext.bom.BomController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.bom.BomController({frm: cur_frm}));
 
 cur_frm.cscript.hour_rate = function(doc) {
 	erpnext.bom.calculate_op_cost(doc);
@@ -662,4 +662,4 @@
 		frm.set_value("operations", []);
 	}
 	toggle_operations(frm);
-});
\ No newline at end of file
+});
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index e1cca9e..42b23f2 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -12,6 +12,7 @@
 from six import string_types
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+from erpnext.tests.test_subcontracting import set_backflush_based_on
 
 test_records = frappe.get_test_records('BOM')
 
@@ -160,6 +161,7 @@
 
 	def test_subcontractor_sourced_item(self):
 		item_code = "_Test Subcontracted FG Item 1"
+		set_backflush_based_on('Material Transferred for Subcontract')
 
 		if not frappe.db.exists('Item', item_code):
 			make_item(item_code, {
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index d764db3..cdc4518 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -317,7 +317,7 @@
 						'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id')
 
 					if job_cards:
-						qty = min([d.qty for d in job_cards])
+						qty = min(d.qty for d in job_cards)
 
 			doc.db_set('material_transferred_for_manufacturing', qty)
 
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 3e5a72d..8088d93 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -704,6 +704,8 @@
 	stop_work_order: function(frm, status) {
 		frappe.call({
 			method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop",
+			freeze: true,
+			freeze_message: __("Updating Work Order status"),
 			args: {
 				work_order: frm.doc.name,
 				status: status
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ed6fefd..339e7f9 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -286,5 +286,7 @@
 erpnext.patches.v13_0.set_pos_closing_as_failed
 execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True)
 erpnext.patches.v13_0.update_timesheet_changes
+erpnext.patches.v13_0.add_doctype_to_sla #14-06-2021
 erpnext.patches.v13_0.set_training_event_attendance
+erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
 erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py
new file mode 100644
index 0000000..e2c7fd2
--- /dev/null
+++ b/erpnext/patches/v13_0/add_doctype_to_sla.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+	frappe.reload_doc('support', 'doctype', 'sla_fulfilled_on_status')
+	frappe.reload_doc('support', 'doctype', 'service_level_agreement')
+	if frappe.db.has_column('Service Level Agreement', 'enable'):
+		rename_field('Service Level Agreement', 'enable', 'enabled')
+
+	for sla in frappe.get_all('Service Level Agreement'):
+		agreement = frappe.get_doc('Service Level Agreement', sla.name)
+		agreement.document_type = 'Issue'
+		agreement.apply_sla_for_resolution = 1
+		agreement.append('sla_fulfilled_on', {'status': 'Resolved'})
+		agreement.append('sla_fulfilled_on', {'status': 'Closed'})
+		agreement.save()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py
new file mode 100644
index 0000000..7de9fa1
--- /dev/null
+++ b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doctype("Buying Settings")
+	buying_settings = frappe.get_single("Buying Settings")
+	buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0
+	buying_settings.save()
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js
index d1ed91f..24ffce5 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.js
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js
@@ -12,8 +12,12 @@
 				}
 			};
 		});
+	},
 
-		frm.trigger('set_earning_component');
+	onload: function(frm) {
+		if (frm.doc.type) {
+			frm.trigger('set_component_query');
+		}
 	},
 
 	employee: function(frm) {
@@ -46,14 +50,19 @@
 	},
 
 	company: function(frm) {
-		frm.trigger('set_earning_component');
+		frm.set_value("type", "");
+		frm.trigger('set_component_query');
 	},
 
-	set_earning_component: function(frm) {
+	set_component_query: function(frm) {
 		if (!frm.doc.company) return;
+		let filters = {company: frm.doc.company};
+		if (frm.doc.type) {
+			filters.type = frm.doc.type;
+		}
 		frm.set_query("salary_component", function() {
 			return {
-				filters: {type: ["in", ["earning", "deduction"]], company: frm.doc.company}
+				filters: filters
 			};
 		});
 	},
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 3953b46..e71d81f 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -11,6 +11,7 @@
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 from frappe.desk.reportview import get_match_cond, get_filters_cond
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
 
 class PayrollEntry(Document):
 	def onload(self):
@@ -41,7 +42,7 @@
 				emp_with_sal_slip.append(employee_details.employee)
 
 		if len(emp_with_sal_slip):
-			frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip)))
+			frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip)))
 
 	def on_cancel(self):
 		frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
@@ -211,7 +212,7 @@
 		return account_dict
 
 	def make_accrual_jv_entry(self):
-		self.check_permission('write')
+		self.check_permission("write")
 		earnings = self.get_salary_component_total(component_type = "earnings") or {}
 		deductions = self.get_salary_component_total(component_type = "deductions") or {}
 		payroll_payable_account = self.payroll_payable_account
@@ -219,12 +220,13 @@
 		precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
 
 		if earnings or deductions:
-			journal_entry = frappe.new_doc('Journal Entry')
-			journal_entry.voucher_type = 'Journal Entry'
-			journal_entry.user_remark = _('Accrual Journal Entry for salaries from {0} to {1}')\
+			journal_entry = frappe.new_doc("Journal Entry")
+			journal_entry.voucher_type = "Journal Entry"
+			journal_entry.user_remark = _("Accrual Journal Entry for salaries from {0} to {1}")\
 				.format(self.start_date, self.end_date)
 			journal_entry.company = self.company
 			journal_entry.posting_date = self.posting_date
+			accounting_dimensions = get_accounting_dimensions() or []
 
 			accounts = []
 			currencies = []
@@ -236,37 +238,34 @@
 			for acc_cc, amount in earnings.items():
 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount += flt(amount, precision)
-				accounts.append({
+				accounts.append(self.update_accounting_dimensions({
 					"account": acc_cc[0],
 					"debit_in_account_currency": flt(amt, precision),
 					"exchange_rate": flt(exchange_rate),
-					"party_type": '',
 					"cost_center": acc_cc[1] or self.cost_center,
 					"project": self.project
-				})
+				}, accounting_dimensions))
 
 			# Deductions
 			for acc_cc, amount in deductions.items():
 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount -= flt(amount, precision)
-				accounts.append({
+				accounts.append(self.update_accounting_dimensions({
 					"account": acc_cc[0],
 					"credit_in_account_currency": flt(amt, precision),
 					"exchange_rate": flt(exchange_rate),
 					"cost_center": acc_cc[1] or self.cost_center,
-					"party_type": '',
 					"project": self.project
-				})
+				}, accounting_dimensions))
 
 			# Payable amount
 			exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
-			accounts.append({
+			accounts.append(self.update_accounting_dimensions({
 				"account": payroll_payable_account,
 				"credit_in_account_currency": flt(payable_amt, precision),
 				"exchange_rate": flt(exchange_rate),
-				"party_type": '',
 				"cost_center": self.cost_center
-			})
+			}, accounting_dimensions))
 
 			journal_entry.set("accounts", accounts)
 			if len(currencies) > 1:
@@ -286,6 +285,12 @@
 
 		return jv_name
 
+	def update_accounting_dimensions(self, row, accounting_dimensions):
+		for dimension in accounting_dimensions:
+			row.update({dimension: self.get(dimension)})
+
+		return row
+
 	def get_amount_and_exchange_rate_for_journal_entry(self, account, amount, company_currency, currencies):
 		conversion_rate = 1
 		exchange_rate = self.exchange_rate
@@ -665,6 +670,8 @@
 		emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date)
 		return emp_list
 
+	return []
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def employee_query(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 877503b..c55bec8 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -667,16 +667,13 @@
 
 			component_row = self.append(component_type)
 			for attr in (
-				'depends_on_payment_days', 'salary_component',
+				'depends_on_payment_days', 'salary_component', 'abbr',
 				'do_not_include_in_total', 'is_tax_applicable',
 				'is_flexible_benefit', 'variable_based_on_taxable_salary',
 				'exempted_from_income_tax'
 			):
 				component_row.set(attr, component_data.get(attr))
 
-			abbr = component_data.get('abbr') or component_data.get('salary_component_abbr')
-			component_row.set('abbr', abbr)
-
 		if additional_salary:
 			component_row.default_amount = 0
 			component_row.additional_amount = amount
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 9e7db97..ce88cc3 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -481,6 +481,7 @@
 	if not salary_structure:
 		salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
 
+
 	employee = frappe.db.get_value("Employee", {"user_id": user})
 	salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
 	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index dce6b7a..e7d123c 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -124,8 +124,8 @@
 			"doctype": "Salary Structure",
 			"name": salary_structure,
 			"company": company or erpnext.get_default_company(),
-			"earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
-			"deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
+			"earnings": make_earning_salary_component(setup=True,  test_tax=test_tax, company_list=["_Test Company"]),
+			"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
 			"payroll_frequency": payroll_frequency,
 			"payment_account": get_random("Account", filters={'account_currency': currency}),
 			"currency": currency
diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py
index b717491..e646775 100644
--- a/erpnext/portal/doctype/homepage/test_homepage.py
+++ b/erpnext/portal/doctype/homepage/test_homepage.py
@@ -6,12 +6,12 @@
 import frappe
 import unittest
 from frappe.utils import set_request
-from frappe.website.render import render
+from frappe.website.serve import get_response
 
 class TestHomepage(unittest.TestCase):
 	def test_homepage_load(self):
 		set_request(method='GET', path='home')
-		response = render()
+		response = get_response()
 
 		self.assertEqual(response.status_code, 200)
 
diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
index f0aa554..5bb9682 100644
--- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py
+++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
@@ -7,7 +7,7 @@
 import unittest
 from bs4 import BeautifulSoup
 from frappe.utils import set_request
-from frappe.website.render import render
+from frappe.website.serve import get_response
 
 class TestHomepageSection(unittest.TestCase):
 	def test_homepage_section_card(self):
@@ -26,7 +26,7 @@
 			pass
 
 		set_request(method='GET', path='home')
-		response = render()
+		response = get_response()
 
 		self.assertEqual(response.status_code, 200)
 
@@ -59,7 +59,7 @@
 		}).insert()
 
 		set_request(method='GET', path='home')
-		response = render()
+		response = get_response()
 
 		self.assertEqual(response.status_code, 200)
 
diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
index daaba67..8aa0734 100644
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ b/erpnext/portal/product_configurator/test_product_configurator.py
@@ -2,10 +2,8 @@
 
 from bs4 import BeautifulSoup
 import frappe, unittest
-from frappe.utils import set_request, get_html_for_route
-from frappe.website.render import render
+from frappe.utils import get_html_for_route
 from erpnext.portal.product_configurator.utils import get_products_for_website
-from erpnext.stock.doctype.item.test_item import make_item_variant
 
 test_dependencies = ["Item"]
 
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index d1583f1..39a6024 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -232,7 +232,7 @@
 	meta = frappe.get_meta(doctype)
 	searchfields = meta.get_search_fields()
 	search_columns = ", " + ", ".join(searchfields) if searchfields else ''
-	search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
+	search_cond = " or " + " or ".join(field + " like %(txt)s" for field in searchfields)
 
 	return frappe.db.sql(""" select name {search_columns} from `tabProject`
 		where %(key)s like %(txt)s
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index a3e4577..c8bd80f 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -87,8 +87,8 @@
 
 	def set_dates(self):
 		if self.docstatus < 2 and self.time_logs:
-			start_date = min([getdate(d.from_time) for d in self.time_logs])
-			end_date = max([getdate(d.to_time) for d in self.time_logs])
+			start_date = min(getdate(d.from_time) for d in self.time_logs)
+			end_date = max(getdate(d.to_time) for d in self.time_logs)
 
 			if start_date and end_date:
 				self.start_date = getdate(start_date)
diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py
index ea6bdb5..180926f 100644
--- a/erpnext/projects/report/project_profitability/test_project_profitability.py
+++ b/erpnext/projects/report/project_profitability/test_project_profitability.py
@@ -1,7 +1,7 @@
 from __future__ import unicode_literals
 import unittest
 import frappe
-from frappe.utils import getdate, nowdate
+from frappe.utils import getdate, nowdate, add_days
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet
 from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
@@ -16,17 +16,22 @@
 		make_salary_structure_for_timesheet(emp, company='_Test Company')
 		self.timesheet = make_timesheet(emp, simulate = True, is_billable=1)
 		self.salary_slip = make_salary_slip(self.timesheet.name)
+		holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate())
+		if holidays:
+			frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1)
+
 		self.salary_slip.submit()
 		self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
 		self.sales_invoice.due_date = nowdate()
 		self.sales_invoice.submit()
 
 		frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
+		frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 0)
 
 	def test_project_profitability(self):
 		filters = {
 			'company': '_Test Company',
-			'start_date': getdate(),
+			'start_date': add_days(getdate(), -3),
 			'end_date': getdate()
 		}
 
diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py
index 2c7bb49..98dd617 100644
--- a/erpnext/projects/report/project_summary/project_summary.py
+++ b/erpnext/projects/report/project_summary/project_summary.py
@@ -122,7 +122,7 @@
 	if not data:
 		return None
 
-	avg_completion = sum([project.percent_complete for project in data]) / len(data)
+	avg_completion = sum(project.percent_complete for project in data) / len(data)
 	total = sum([project.total_tasks for project in data])
 	total_overdue = sum([project.overdue_tasks for project in data])
 	completed = sum([project.completed_tasks for project in data])
diff --git a/erpnext/public/css/email.css b/erpnext/public/css/email.css
deleted file mode 100644
index 8cf1a31..0000000
--- a/erpnext/public/css/email.css
+++ /dev/null
@@ -1,29 +0,0 @@
-.panel-header {
-  background-color: #fafbfc;
-  border: 1px solid #d1d8dd;
-  border-radius: 3px 3px 0 0;
-}
-.panel-body {
-  background-color: #fff;
-  border: 1px solid #d1d8dd;
-  border-top: none;
-  border-radius: 0 0 3px 3px;
-  overflow-wrap: break-word;
-}
-.sender-avatar {
-  width: 24px;
-  height: 24px;
-  border-radius: 3px;
-  vertical-align: middle;
-}
-.sender-avatar-placeholder {
-  width: 24px;
-  height: 24px;
-  border-radius: 3px;
-  vertical-align: middle;
-  line-height: 24px;
-  text-align: center;
-  color: #d1d8dd;
-  border: 1px solid #d1d8dd;
-  background-color: #fff;
-}
diff --git a/erpnext/public/css/erpnext.css b/erpnext/public/css/erpnext.css
deleted file mode 100644
index 6e4efcb..0000000
--- a/erpnext/public/css/erpnext.css
+++ /dev/null
@@ -1,408 +0,0 @@
-.erpnext-footer {
-  margin: 11px auto;
-  text-align: center;
-}
-.show-all-reports {
-  margin-top: 5px;
-  font-size: 11px;
-}
-/* toolbar */
-.toolbar-splash {
-  width: 32px;
-  height: 32px;
-  margin: -10px auto;
-}
-.erpnext-icon {
-  width: 24px;
-  margin-right: 0px;
-  margin-top: -3px;
-}
-.dashboard-list-item {
-  background-color: inherit;
-  padding: 5px 0px;
-  border-bottom: 1px solid #d1d8dd;
-}
-#page-stock-balance .dashboard-list-item {
-  padding: 5px 15px;
-}
-.dashboard-list-item:last-child {
-  border-bottom: none;
-}
-.frappe-control[data-fieldname='result_html'] {
-  overflow: scroll;
-}
-.assessment-result-tool {
-  table-layout: fixed;
-}
-.assessment-result-tool input {
-  width: 100%;
-  border: 0;
-  outline: none;
-  text-align: right;
-}
-.assessment-result-tool th {
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-.assessment-result-tool .total-score,
-.assessment-result-tool .grade,
-.assessment-result-tool .score {
-  text-align: right;
-}
-/* pos */
-body[data-route="pos"] .pos-bill-toolbar {
-  padding: 10px 0px;
-  height: 51px;
-}
-body[data-route="pos"] .pos-bill-item:hover,
-body[data-route="pos"] .list-customers-table > .pos-list-row:hover {
-  background-color: #f5f7fa;
-  cursor: pointer;
-}
-body[data-route="pos"] .pos-item-qty {
-  display: inline-block;
-}
-body[data-route="pos"] .pos-qty-row > div {
-  padding: 5px 0px;
-}
-body[data-route="pos"] .pos-qty-btn {
-  margin-top: 3px;
-  cursor: pointer;
-  font-size: 120%;
-}
-body[data-route="pos"] .search-area .form-group {
-  max-width: 100% !important;
-}
-body[data-route="pos"] .tax-table {
-  margin-bottom: 10px;
-}
-body[data-route="pos"] .discount-field-col {
-  padding-left: 24px;
-}
-body[data-route="pos"] .discount-amount-area .input-group:first-child {
-  margin-bottom: 2px;
-}
-body[data-route="pos"] .payment-toolbar .row {
-  width: 323px;
-  margin: 0 auto;
-}
-body[data-route="pos"] .payment-mode {
-  cursor: pointer;
-  font-family: sans-serif;
-  font-size: 15px;
-}
-body[data-route="pos"] .pos-payment-row .col-xs-6 {
-  padding: 15px;
-}
-body[data-route="pos"] .pos-payment-row {
-  border-bottom: 1px solid #d1d8dd;
-  margin: 2px 0px 5px 0px;
-  height: 60px;
-  margin-top: 0px;
-  margin-bottom: 0px;
-}
-body[data-route="pos"] .pos-payment-row:hover,
-body[data-route="pos"] .pos-keyboard-key:hover {
-  background-color: #fafbfc;
-  cursor: pointer;
-}
-body[data-route="pos"] .pos-keyboard-key,
-body[data-route="pos"] .delete-btn {
-  border: 1px solid #d1d8dd;
-  height: 85px;
-  width: 85px;
-  margin: 10px 10px;
-  font-size: 24px;
-  font-weight: 200;
-  background-color: #FDFDFD;
-  border-color: #e8e8e8;
-}
-body[data-route="pos"] .numeric-keypad {
-  border: 1px solid #d1d8dd;
-  height: 69px;
-  width: 69px;
-  font-size: 20px;
-  font-weight: 200;
-  background-color: #FDFDFD;
-  border-color: #e8e8e8;
-  margin-left: -4px;
-}
-body[data-route="pos"] .pos-pay {
-  height: 69px;
-  width: 69px;
-  font-size: 17px;
-  font-weight: 200;
-  margin-left: -4px;
-}
-body[data-route="pos"] .numeric-keypad {
-  height: 60px;
-  width: 60px;
-  font-size: 20px;
-  font-weight: 200;
-  border-radius: 0;
-  background-color: #fff;
-  margin-left: -4px;
-}
-@media (max-width: 1199px) {
-  body[data-route="pos"] .numeric-keypad {
-    height: 45px;
-    width: 45px;
-    font-size: 14px;
-  }
-}
-@media (max-width: 991px) {
-  body[data-route="pos"] .numeric-keypad {
-    height: 40px;
-    width: 40px;
-  }
-}
-body[data-route="pos"] .numeric_keypad {
-  margin-left: -15px;
-}
-body[data-route="pos"] .numeric_keypad > .row > button {
-  border: none;
-  border-right: 1px solid #d1d8dd;
-  border-bottom: 1px solid #d1d8dd;
-}
-body[data-route="pos"] .numeric_keypad > .row > button:first-child {
-  border-left: 1px solid #d1d8dd;
-}
-body[data-route="pos"] .numeric_keypad > .row:first-child > button {
-  border-top: 1px solid #d1d8dd;
-}
-body[data-route="pos"] .pos-pay {
-  background-color: #5E64FF;
-  border: none;
-}
-body[data-route="pos"] .multimode-payments {
-  padding-left: 30px;
-}
-body[data-route="pos"] .payment-toolbar {
-  padding-right: 30px;
-}
-body[data-route="pos"] .list-row-head.pos-invoice-list {
-  border-top: 1px solid #d1d8dd;
-}
-body[data-route="pos"] .modal-dialog {
-  width: 750px;
-}
-@media (max-width: 767px) {
-  body[data-route="pos"] .modal-dialog {
-    width: auto;
-  }
-  body[data-route="pos"] .modal-dialog .modal-content {
-    height: auto;
-  }
-}
-@media (max-width: 767px) {
-  body[data-route="pos"] .amount-row h3 {
-    font-size: 15px;
-  }
-  body[data-route="pos"] .pos-keyboard-key,
-  body[data-route="pos"] .delete-btn {
-    height: 50px;
-  }
-  body[data-route="pos"] .multimode-payments {
-    padding-left: 15px;
-  }
-  body[data-route="pos"] .payment-toolbar {
-    padding-right: 15px;
-  }
-}
-body[data-route="pos"] .amount-label {
-  font-size: 16px;
-}
-body[data-route="pos"] .selected-payment-mode {
-  background-color: #fafbfc;
-  cursor: pointer;
-}
-body[data-route="pos"] .pos-invoice-list {
-  padding: 15px 10px;
-}
-body[data-route="pos"] .write_off_amount,
-body[data-route="pos"] .change_amount {
-  margin: 15px;
-  width: 130px;
-}
-body[data-route="pos"] .pos-list-row {
-  display: table;
-  table-layout: fixed;
-  width: 100%;
-  padding: 9px 15px;
-  font-size: 12px;
-  margin: 0px;
-  border-bottom: 1px solid #d1d8dd;
-}
-body[data-route="pos"] .pos-list-row .cell {
-  display: table-cell;
-  vertical-align: middle;
-}
-body[data-route="pos"] .pos-list-row .cell.price-cell {
-  width: 50%;
-}
-body[data-route="pos"] .pos-list-row .subject {
-  width: 40%;
-}
-body[data-route="pos"] .pos-list-row .list-row-checkbox,
-body[data-route="pos"] .pos-list-row .list-select-all {
-  margin-right: 7px;
-}
-body[data-route="pos"] .pos-bill-header {
-  background-color: #f5f7fa;
-  border: 1px solid #d1d8dd;
-  padding: 13px 15px;
-}
-body[data-route="pos"] .pos-list-row.active {
-  background-color: #fffce7;
-}
-body[data-route="pos"] .totals-area {
-  border-right: 1px solid #d1d8dd;
-  border-left: 1px solid #d1d8dd;
-  margin-bottom: 15px;
-}
-body[data-route="pos"] .tax-area .pos-list-row {
-  border: none;
-}
-body[data-route="pos"] .item-cart-items {
-  height: calc(100vh - 526px);
-  overflow: auto;
-  border: 1px solid #d1d8dd;
-  border-top: none;
-}
-@media (max-width: 767px) {
-  body[data-route="pos"] .item-cart-items {
-    height: 30vh;
-  }
-}
-body[data-route="pos"] .no-items-message {
-  min-height: 200px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  height: 100%;
-}
-body[data-route="pos"] .pos-list-row:last-child {
-  border-bottom: none;
-}
-body[data-route="pos"] .form-section-heading {
-  padding: 0;
-}
-body[data-route="pos"] .item-list {
-  border: 1px solid #d1d8dd;
-  border-top: none;
-  max-height: calc(100vh - 190px);
-  overflow: auto;
-}
-@media (max-width: 767px) {
-  body[data-route="pos"] .item-list {
-    max-height: initial;
-  }
-}
-body[data-route="pos"] .item-list .image-field {
-  height: 140px;
-}
-body[data-route="pos"] .item-list .image-field .placeholder-text {
-  font-size: 50px;
-}
-body[data-route="pos"] .item-list .pos-item-wrapper {
-  position: relative;
-}
-body[data-route="pos"] .pos-bill-toolbar {
-  margin-top: 10px;
-}
-body[data-route="pos"] .search-item .form-group {
-  margin: 0;
-}
-body[data-route="pos"] .item-list-area .pos-bill-header {
-  padding: 5px;
-  padding-left: 15px;
-}
-body[data-route="pos"] .pos-selected-item-action .pos-list-row:first-child {
-  padding-top: 0;
-}
-body[data-route="pos"] .pos-selected-item-action > .pos-list-row {
-  border: none;
-}
-@media (max-width: 1199px) {
-  body[data-route="pos"] .pos-selected-item-action > .pos-list-row {
-    padding: 5px 15px;
-  }
-}
-body[data-route="pos"] .edit-customer-btn {
-  position: absolute;
-  right: 57px;
-  top: 15px;
-  z-index: 100;
-}
-body[data-route="pos"] .btn-more {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  cursor: pointer;
-  background-color: #fafbfc;
-  min-height: 200px;
-}
-body[data-route="pos"] .collapse-btn {
-  cursor: pointer;
-}
-@media (max-width: 767px) {
-  body[data-route="pos"] .page-actions {
-    max-width: 110px;
-  }
-}
-.price-info {
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  margin: 0 0 15px 15px;
-  background-color: rgba(141, 153, 166, 0.6);
-  padding: 5px 9px;
-  border-radius: 3px;
-  color: #fff;
-}
-.leaderboard .result {
-  border-top: 1px solid #d1d8dd;
-}
-.leaderboard .list-item {
-  padding-left: 45px;
-}
-.leaderboard .list-item_content {
-  padding-right: 45px;
-}
-.exercise-card {
-	box-shadow: 0 1px 3px rgba(0,0,0,0.30);
-	border-radius: 2px;
-	padding: 6px 6px 6px 8px;
-	margin-top: 10px;
-	height: 100% !important;
-}
-.exercise-card .card-img-top {
-	width: 100%;
-	height: 15vw;
-	object-fit: cover;
-}
-.exercise-card .btn-edit {
-	position: absolute;
-	bottom: 10px;
-	left: 20px;
-}
-.exercise-card .btn-del {
-	position: absolute;
-	bottom: 10px;
-	left: 50px;
-}
-.exercise-card .card-body {
-		margin-bottom: 10px;
-}
-.exercise-card .card-footer {
-		padding: 10px;
-}
-.exercise-row {
-	height: 100% !important;
-	display: flex;
-	flex-wrap: wrap;
-}
-.exercise-col {
-	padding: 10px;
-}
diff --git a/erpnext/public/css/leaflet/leaflet.css b/erpnext/public/css/leaflet/leaflet.css
deleted file mode 100755
index 979a8bd..0000000
--- a/erpnext/public/css/leaflet/leaflet.css
+++ /dev/null
@@ -1,611 +0,0 @@
-/* required styles */

-

-.leaflet-pane,

-.leaflet-tile,

-.leaflet-marker-icon,

-.leaflet-marker-shadow,

-.leaflet-tile-container,

-.leaflet-map-pane svg,

-.leaflet-map-pane canvas,

-.leaflet-zoom-box,

-.leaflet-image-layer,

-.leaflet-layer {

-    position: absolute;

-    left: 0;

-    top: 0;

-}

-

-.leaflet-container {

-    overflow: hidden;

-    -ms-touch-action: none;

-    touch-action: none;

-}

-

-.leaflet-tile,

-.leaflet-marker-icon,

-.leaflet-marker-shadow {

-    -webkit-user-select: none;

-    -moz-user-select: none;

-    user-select: none;

-    -webkit-user-drag: none;

-}

-

-

-/* Safari renders non-retina tile on retina better with this, but Chrome is worse */

-

-.leaflet-safari .leaflet-tile {

-    image-rendering: -webkit-optimize-contrast;

-}

-

-

-/* hack that prevents hw layers "stretching" when loading new tiles */

-

-.leaflet-safari .leaflet-tile-container {

-    width: 1600px;

-    height: 1600px;

-    -webkit-transform-origin: 0 0;

-}

-

-.leaflet-marker-icon,

-.leaflet-marker-shadow {

-    display: block;

-}

-

-

-/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */

-

-

-/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */

-

-.leaflet-container .leaflet-overlay-pane svg,

-.leaflet-container .leaflet-marker-pane img,

-.leaflet-container .leaflet-tile-pane img,

-.leaflet-container img.leaflet-image-layer {

-    max-width: none !important;

-}

-

-.leaflet-tile {

-    filter: inherit;

-    visibility: hidden;

-}

-

-.leaflet-tile-loaded {

-    visibility: inherit;

-}

-

-.leaflet-zoom-box {

-    width: 0;

-    height: 0;

-    -moz-box-sizing: border-box;

-    box-sizing: border-box;

-    z-index: 800;

-}

-

-

-/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */

-

-.leaflet-overlay-pane svg {

-    -moz-user-select: none;

-}

-

-.leaflet-pane {

-    z-index: 400;

-}

-

-.leaflet-tile-pane {

-    z-index: 200;

-}

-

-.leaflet-overlay-pane {

-    z-index: 400;

-}

-

-.leaflet-shadow-pane {

-    z-index: 500;

-}

-

-.leaflet-marker-pane {

-    z-index: 600;

-}

-

-.leaflet-popup-pane {

-    z-index: 700;

-}

-

-.leaflet-map-pane canvas {

-    z-index: 100;

-}

-

-.leaflet-map-pane svg {

-    z-index: 200;

-}

-

-.leaflet-vml-shape {

-    width: 1px;

-    height: 1px;

-}

-

-.lvml {

-    behavior: url(#default#VML);

-    display: inline-block;

-    position: absolute;

-}

-

-

-/* control positioning */

-

-.leaflet-control {

-    position: relative;

-    z-index: 800;

-    pointer-events: auto;

-}

-

-.leaflet-top,

-.leaflet-bottom {

-    position: absolute;

-    z-index: 1000;

-    pointer-events: none;

-}

-

-.leaflet-top {

-    top: 0;

-}

-

-.leaflet-right {

-    right: 0;

-}

-

-.leaflet-bottom {

-    bottom: 0;

-}

-

-.leaflet-left {

-    left: 0;

-}

-

-.leaflet-control {

-    float: left;

-    clear: both;

-}

-

-.leaflet-right .leaflet-control {

-    float: right;

-}

-

-.leaflet-top .leaflet-control {

-    margin-top: 10px;

-}

-

-.leaflet-bottom .leaflet-control {

-    margin-bottom: 10px;

-}

-

-.leaflet-left .leaflet-control {

-    margin-left: 10px;

-}

-

-.leaflet-right .leaflet-control {

-    margin-right: 10px;

-}

-

-

-/* zoom and fade animations */

-

-.leaflet-fade-anim .leaflet-tile {

-    will-change: opacity;

-}

-

-.leaflet-fade-anim .leaflet-popup {

-    opacity: 0;

-    -webkit-transition: opacity 0.2s linear;

-    -moz-transition: opacity 0.2s linear;

-    -o-transition: opacity 0.2s linear;

-    transition: opacity 0.2s linear;

-}

-

-.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {

-    opacity: 1;

-}

-

-.leaflet-zoom-animated {

-    -webkit-transform-origin: 0 0;

-    -ms-transform-origin: 0 0;

-    transform-origin: 0 0;

-}

-

-.leaflet-zoom-anim .leaflet-zoom-animated {

-    will-change: transform;

-}

-

-.leaflet-zoom-anim .leaflet-zoom-animated {

-    -webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1);

-    -moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1);

-    -o-transition: -o-transform 0.25s cubic-bezier(0, 0, 0.25, 1);

-    transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1);

-}

-

-.leaflet-zoom-anim .leaflet-tile,

-.leaflet-pan-anim .leaflet-tile {

-    -webkit-transition: none;

-    -moz-transition: none;

-    -o-transition: none;

-    transition: none;

-}

-

-.leaflet-zoom-anim .leaflet-zoom-hide {

-    visibility: hidden;

-}

-

-

-/* cursors */

-

-.leaflet-interactive {

-    cursor: pointer;

-}

-

-.leaflet-grab {

-    cursor: -webkit-grab;

-    cursor: -moz-grab;

-}

-

-.leaflet-crosshair,

-.leaflet-crosshair .leaflet-interactive {

-    cursor: crosshair;

-}

-

-.leaflet-popup-pane,

-.leaflet-control {

-    cursor: auto;

-}

-

-.leaflet-dragging .leaflet-grab,

-.leaflet-dragging .leaflet-grab .leaflet-interactive,

-.leaflet-dragging .leaflet-marker-draggable {

-    cursor: move;

-    cursor: -webkit-grabbing;

-    cursor: -moz-grabbing;

-}

-

-

-/* visual tweaks */

-

-.leaflet-container {

-    background: #ddd;

-    outline: 0;

-}

-

-.leaflet-container a {

-    color: #0078A8;

-}

-

-.leaflet-container a.leaflet-active {

-    outline: 2px solid orange;

-}

-

-.leaflet-zoom-box {

-    border: 2px dotted #38f;

-    background: rgba(255, 255, 255, 0.5);

-}

-

-

-/* general typography */

-

-.leaflet-container {

-    font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;

-}

-

-

-/* general toolbar styles */

-

-.leaflet-bar {

-    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);

-    border-radius: 4px;

-}

-

-.leaflet-bar a,

-.leaflet-bar a:hover {

-    background-color: #fff;

-    border-bottom: 1px solid #ccc;

-    width: 26px;

-    height: 26px;

-    line-height: 26px;

-    display: block;

-    text-align: center;

-    text-decoration: none;

-    color: black;

-}

-

-.leaflet-bar a,

-.leaflet-control-layers-toggle {

-    background-position: 50% 50%;

-    background-repeat: no-repeat;

-    display: block;

-}

-

-.leaflet-bar a:hover {

-    background-color: #f4f4f4;

-}

-

-.leaflet-bar a:first-child {

-    border-top-left-radius: 4px;

-    border-top-right-radius: 4px;

-}

-

-.leaflet-bar a:last-child {

-    border-bottom-left-radius: 4px;

-    border-bottom-right-radius: 4px;

-    border-bottom: none;

-}

-

-.leaflet-bar a.leaflet-disabled {

-    cursor: default;

-    background-color: #f4f4f4;

-    color: #bbb;

-}

-

-.leaflet-touch .leaflet-bar a {

-    width: 30px;

-    height: 30px;

-    line-height: 30px;

-}

-

-

-/* zoom control */

-

-.leaflet-control-zoom-in,

-.leaflet-control-zoom-out {

-    font: bold 18px 'Lucida Console', Monaco, monospace;

-    text-indent: 1px;

-}

-

-.leaflet-control-zoom-out {

-    font-size: 20px;

-}

-

-.leaflet-touch .leaflet-control-zoom-in {

-    font-size: 22px;

-}

-

-.leaflet-touch .leaflet-control-zoom-out {

-    font-size: 24px;

-}

-

-

-/* layers control */

-

-.leaflet-control-layers {

-    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);

-    background: #fff;

-    border-radius: 5px;

-}

-

-.leaflet-control-layers-toggle {

-    background-image: url('assets/erpnext/images/leaflet/layers.png');

-    width: 36px;

-    height: 36px;

-}

-

-.leaflet-retina .leaflet-control-layers-toggle {

-    background-image: url('assets/erpnext/images/leaflet/layers-2x.png');

-    background-size: 26px 26px;

-}

-

-.leaflet-touch .leaflet-control-layers-toggle {

-    width: 44px;

-    height: 44px;

-}

-

-.leaflet-control-layers .leaflet-control-layers-list,

-.leaflet-control-layers-expanded .leaflet-control-layers-toggle {

-    display: none;

-}

-

-.leaflet-control-layers-expanded .leaflet-control-layers-list {

-    display: block;

-    position: relative;

-}

-

-.leaflet-control-layers-expanded {

-    padding: 6px 10px 6px 6px;

-    color: #333;

-    background: #fff;

-}

-

-.leaflet-control-layers-scrollbar {

-    overflow-y: scroll;

-    padding-right: 5px;

-}

-

-.leaflet-control-layers-selector {

-    margin-top: 2px;

-    position: relative;

-    top: 1px;

-}

-

-.leaflet-control-layers label {

-    display: block;

-}

-

-.leaflet-control-layers-separator {

-    height: 0;

-    border-top: 1px solid #ddd;

-    margin: 5px -10px 5px -6px;

-}

-

-

-/* attribution and scale controls */

-

-.leaflet-container .leaflet-control-attribution {

-    background: #fff;

-    background: rgba(255, 255, 255, 0.7);

-    margin: 0;

-}

-

-.leaflet-control-attribution,

-.leaflet-control-scale-line {

-    padding: 0 5px;

-    color: #333;

-}

-

-.leaflet-control-attribution a {

-    text-decoration: none;

-}

-

-.leaflet-control-attribution a:hover {

-    text-decoration: underline;

-}

-

-.leaflet-container .leaflet-control-attribution,

-.leaflet-container .leaflet-control-scale {

-    font-size: 11px;

-}

-

-.leaflet-left .leaflet-control-scale {

-    margin-left: 5px;

-}

-

-.leaflet-bottom .leaflet-control-scale {

-    margin-bottom: 5px;

-}

-

-.leaflet-control-scale-line {

-    border: 2px solid #777;

-    border-top: none;

-    line-height: 1.1;

-    padding: 2px 5px 1px;

-    font-size: 11px;

-    white-space: nowrap;

-    overflow: hidden;

-    -moz-box-sizing: border-box;

-    box-sizing: border-box;

-    background: #fff;

-    background: rgba(255, 255, 255, 0.5);

-}

-

-.leaflet-control-scale-line:not(:first-child) {

-    border-top: 2px solid #777;

-    border-bottom: none;

-    margin-top: -2px;

-}

-

-.leaflet-control-scale-line:not(:first-child):not(:last-child) {

-    border-bottom: 2px solid #777;

-}

-

-.leaflet-touch .leaflet-control-attribution,

-.leaflet-touch .leaflet-control-layers,

-.leaflet-touch .leaflet-bar {

-    box-shadow: none;

-}

-

-.leaflet-touch .leaflet-control-layers,

-.leaflet-touch .leaflet-bar {

-    border: 2px solid rgba(0, 0, 0, 0.2);

-    background-clip: padding-box;

-}

-

-

-/* popup */

-

-.leaflet-popup {

-    position: absolute;

-    text-align: center;

-}

-

-.leaflet-popup-content-wrapper {

-    padding: 1px;

-    text-align: left;

-    border-radius: 12px;

-}

-

-.leaflet-popup-content {

-    margin: 13px 19px;

-    line-height: 1.4;

-}

-

-.leaflet-popup-content p {

-    margin: 18px 0;

-}

-

-.leaflet-popup-tip-container {

-    margin: 0 auto;

-    width: 40px;

-    height: 20px;

-    position: relative;

-    overflow: hidden;

-}

-

-.leaflet-popup-tip {

-    width: 17px;

-    height: 17px;

-    padding: 1px;

-    margin: -10px auto 0;

-    -webkit-transform: rotate(45deg);

-    -moz-transform: rotate(45deg);

-    -ms-transform: rotate(45deg);

-    -o-transform: rotate(45deg);

-    transform: rotate(45deg);

-}

-

-.leaflet-popup-content-wrapper,

-.leaflet-popup-tip {

-    background: white;

-    color: #333;

-    box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4);

-}

-

-.leaflet-container a.leaflet-popup-close-button {

-    position: absolute;

-    top: 0;

-    right: 0;

-    padding: 4px 4px 0 0;

-    border: none;

-    text-align: center;

-    width: 18px;

-    height: 14px;

-    font: 16px/14px Tahoma, Verdana, sans-serif;

-    color: #c3c3c3;

-    text-decoration: none;

-    font-weight: bold;

-    background: transparent;

-}

-

-.leaflet-container a.leaflet-popup-close-button:hover {

-    color: #999;

-}

-

-.leaflet-popup-scrolled {

-    overflow: auto;

-    border-bottom: 1px solid #ddd;

-    border-top: 1px solid #ddd;

-}

-

-.leaflet-oldie .leaflet-popup-content-wrapper {

-    zoom: 1;

-}

-

-.leaflet-oldie .leaflet-popup-tip {

-    width: 24px;

-    margin: 0 auto;

-    -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";

-    filter: progid: DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);

-}

-

-.leaflet-oldie .leaflet-popup-tip-container {

-    margin-top: -1px;

-}

-

-.leaflet-oldie .leaflet-control-zoom,

-.leaflet-oldie .leaflet-control-layers,

-.leaflet-oldie .leaflet-popup-content-wrapper,

-.leaflet-oldie .leaflet-popup-tip {

-    border: 1px solid #999;

-}

-

-

-/* div icon */

-

-.leaflet-div-icon {

-    background: #fff;

-    border: 1px solid #666;

-}
\ No newline at end of file
diff --git a/erpnext/public/css/leaflet/leaflet.draw.css b/erpnext/public/css/leaflet/leaflet.draw.css
deleted file mode 100755
index 6fb7db0..0000000
--- a/erpnext/public/css/leaflet/leaflet.draw.css
+++ /dev/null
@@ -1,316 +0,0 @@
-/* ================================================================== */
-
-
-/* Toolbars
-/* ================================================================== */
-
-.leaflet-draw-section {
-    position: relative;
-}
-
-.leaflet-draw-toolbar {
-    margin-top: 12px;
-}
-
-.leaflet-draw-toolbar-top {
-    margin-top: 0;
-}
-
-.leaflet-draw-toolbar-notop a:first-child {
-    border-top-right-radius: 0;
-}
-
-.leaflet-draw-toolbar-nobottom a:last-child {
-    border-bottom-right-radius: 0;
-}
-
-.leaflet-draw-toolbar a {
-    background-image: url('assets/erpnext/images/leaflet/spritesheet.png');
-    background-repeat: no-repeat;
-}
-
-.leaflet-retina .leaflet-draw-toolbar a {
-    background-image: url('assets/erpnext/images/leaflet/spritesheet-2x.png');
-    background-size: 270px 30px;
-}
-
-.leaflet-draw a {
-    display: block;
-    text-align: center;
-    text-decoration: none;
-}
-
-
-/* ================================================================== */
-
-
-/* Toolbar actions menu
-/* ================================================================== */
-
-.leaflet-draw-actions {
-    display: none;
-    list-style: none;
-    margin: 0;
-    padding: 0;
-    position: absolute;
-    left: 26px;
-    /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */
-    top: 0;
-    white-space: nowrap;
-}
-
-.leaflet-right .leaflet-draw-actions {
-    right: 26px;
-    left: auto;
-}
-
-.leaflet-draw-actions li {
-    display: inline-block;
-}
-
-.leaflet-draw-actions li:first-child a {
-    border-left: none;
-}
-
-.leaflet-draw-actions li:last-child a {
-    -webkit-border-radius: 0 4px 4px 0;
-    border-radius: 0 4px 4px 0;
-}
-
-.leaflet-right .leaflet-draw-actions li:last-child a {
-    -webkit-border-radius: 0;
-    border-radius: 0;
-}
-
-.leaflet-right .leaflet-draw-actions li:first-child a {
-    -webkit-border-radius: 4px 0 0 4px;
-    border-radius: 4px 0 0 4px;
-}
-
-.leaflet-draw-actions a {
-    background-color: #919187;
-    border-left: 1px solid #AAA;
-    color: #FFF;
-    font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif;
-    line-height: 28px;
-    text-decoration: none;
-    padding-left: 10px;
-    padding-right: 10px;
-    height: 28px;
-}
-
-.leaflet-draw-actions-bottom {
-    margin-top: 0;
-}
-
-.leaflet-draw-actions-top {
-    margin-top: 1px;
-}
-
-.leaflet-draw-actions-top a,
-.leaflet-draw-actions-bottom a {
-    height: 27px;
-    line-height: 27px;
-}
-
-.leaflet-draw-actions a:hover {
-    background-color: #A0A098;
-}
-
-.leaflet-draw-actions-top.leaflet-draw-actions-bottom a {
-    height: 26px;
-    line-height: 26px;
-}
-
-
-/* ================================================================== */
-
-
-/* Draw toolbar
-/* ================================================================== */
-
-.leaflet-draw-toolbar .leaflet-draw-draw-polyline {
-    background-position: -2px -2px;
-}
-
-.leaflet-draw-toolbar .leaflet-draw-draw-polygon {
-    background-position: -31px -2px;
-}
-
-.leaflet-draw-toolbar .leaflet-draw-draw-rectangle {
-    background-position: -62px -2px;
-}
-
-.leaflet-draw-toolbar .leaflet-draw-draw-circle {
-    background-position: -92px -2px;
-}
-
-.leaflet-draw-toolbar .leaflet-draw-draw-marker {
-    background-position: -122px -2px;
-}
-
-
-/* ================================================================== */
-
-
-/* Edit toolbar
-/* ================================================================== */
-
-.leaflet-draw-toolbar .leaflet-draw-edit-edit {
-    background-position: -152px -2px;
-}
-
-.leaflet-draw-toolbar .leaflet-draw-edit-remove {
-    background-position: -182px -2px;
-}
-
-.leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled {
-    background-position: -212px -2px;
-}
-
-.leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled {
-    background-position: -242px -2px;
-}
-
-
-/* ================================================================== */
-
-
-/* Drawing styles
-/* ================================================================== */
-
-.leaflet-mouse-marker {
-    background-color: #fff;
-    cursor: crosshair;
-}
-
-.leaflet-draw-tooltip {
-    background: rgb(54, 54, 54);
-    background: rgba(0, 0, 0, 0.5);
-    border: 1px solid transparent;
-    -webkit-border-radius: 4px;
-    border-radius: 4px;
-    color: #fff;
-    font: 12px/18px "Helvetica Neue", Arial, Helvetica, sans-serif;
-    margin-left: 20px;
-    margin-top: -21px;
-    padding: 4px 8px;
-    position: absolute;
-    visibility: hidden;
-    white-space: nowrap;
-    z-index: 6;
-}
-
-.leaflet-draw-tooltip:before {
-    border-right: 6px solid black;
-    border-right-color: rgba(0, 0, 0, 0.5);
-    border-top: 6px solid transparent;
-    border-bottom: 6px solid transparent;
-    content: "";
-    position: absolute;
-    top: 7px;
-    left: -7px;
-}
-
-.leaflet-error-draw-tooltip {
-    background-color: #F2DEDE;
-    border: 1px solid #E6B6BD;
-    color: #B94A48;
-}
-
-.leaflet-error-draw-tooltip:before {
-    border-right-color: #E6B6BD;
-}
-
-.leaflet-draw-tooltip-single {
-    margin-top: -12px
-}
-
-.leaflet-draw-tooltip-subtext {
-    color: #f8d5e4;
-}
-
-.leaflet-draw-guide-dash {
-    font-size: 1%;
-    opacity: 0.6;
-    position: absolute;
-    width: 5px;
-    height: 5px;
-}
-
-
-/* ================================================================== */
-
-
-/* Edit styles
-/* ================================================================== */
-
-.leaflet-edit-marker-selected {
-    background: rgba(254, 87, 161, 0.1);
-    border: 4px dashed rgba(254, 87, 161, 0.6);
-    -webkit-border-radius: 4px;
-    border-radius: 4px;
-}
-
-.leaflet-edit-move {
-    cursor: move;
-}
-
-.leaflet-edit-resize {
-    cursor: pointer;
-}
-
-
-/* ================================================================== */
-
-
-/* Old IE styles
-/* ================================================================== */
-
-.leaflet-oldie .leaflet-draw-toolbar {
-    border: 3px solid #999;
-}
-
-.leaflet-oldie .leaflet-draw-toolbar a {
-    background-color: #eee;
-}
-
-.leaflet-oldie .leaflet-draw-toolbar a:hover {
-    background-color: #fff;
-}
-
-.leaflet-oldie .leaflet-draw-actions {
-    left: 32px;
-    margin-top: 3px;
-}
-
-.leaflet-oldie .leaflet-draw-actions li {
-    display: inline;
-    zoom: 1;
-}
-
-.leaflet-oldie .leaflet-edit-marker-selected {
-    border: 4px dashed #fe93c2;
-}
-
-.leaflet-oldie .leaflet-draw-actions a {
-    background-color: #999;
-}
-
-.leaflet-oldie .leaflet-draw-actions a:hover {
-    background-color: #a5a5a5;
-}
-
-.leaflet-oldie .leaflet-draw-actions-top a {
-    margin-top: 1px;
-}
-
-.leaflet-oldie .leaflet-draw-actions-bottom a {
-    height: 28px;
-    line-height: 28px;
-}
-
-.leaflet-oldie .leaflet-draw-actions-top.leaflet-draw-actions-bottom a {
-    height: 27px;
-    line-height: 27px;
-}
\ No newline at end of file
diff --git a/erpnext/public/js/account_tree_grid.js b/erpnext/public/js/account_tree_grid.js
index 757f33e..413a5ee 100644
--- a/erpnext/public/js/account_tree_grid.js
+++ b/erpnext/public/js/account_tree_grid.js
@@ -14,9 +14,9 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.	If not, see <http://www.gnu.org/licenses/>.
 
-erpnext.AccountTreeGrid = frappe.views.TreeGridReport.extend({
-	init: function(wrapper, title) {
-		this._super({
+erpnext.AccountTreeGrid = class AccountTreeGrid extends frappe.views.TreeGridReport {
+	constructor(wrapper, title) {
+		super({
 			title: title,
 			parent: $(wrapper).find('.layout-main'),
 			page: wrapper.page,
@@ -33,8 +33,24 @@
 				}
 			},
 		});
-	},
-	setup_columns: function() {
+
+		this.filters = [
+			{fieldtype: "Select", label: __("Company"), link:"Company", fieldname: "company",
+				default_value: __("Select Company..."),
+				filter: function(val, item, opts, me) {
+					if (item.company == val || val == opts.default_value) {
+						return me.apply_zero_filter(val, item, opts, me);
+					}
+					return false;
+				}},
+			{fieldtype: "Select", label: "Fiscal Year", link:"Fiscal Year", fieldname: "fiscal_year",
+				default_value: __("Select Fiscal Year...")},
+			{fieldtype: "Date", label: __("From Date"), fieldname: "from_date"},
+			{fieldtype: "Label", label: __("To")},
+			{fieldtype: "Date", label: __("To Date"), fieldname: "to_date"}
+		]
+	}
+	setup_columns() {
 		this.columns = [
 			{id: "name", name: __("Account"), field: "name", width: 300, cssClass: "cell-title"},
 			{id: "opening_dr", name: __("Opening (Dr)"), field: "opening_dr", width: 100,
@@ -50,25 +66,10 @@
 			{id: "closing_cr", name: __("Closing (Cr)"), field: "closing_cr", width: 100,
 				formatter: this.currency_formatter}
 		];
+	}
 
-	},
-	filters: [
-		{fieldtype: "Select", label: __("Company"), link:"Company", fieldname: "company",
-			default_value: __("Select Company..."),
-			filter: function(val, item, opts, me) {
-				if (item.company == val || val == opts.default_value) {
-					return me.apply_zero_filter(val, item, opts, me);
-				}
-				return false;
-			}},
-		{fieldtype: "Select", label: "Fiscal Year", link:"Fiscal Year", fieldname: "fiscal_year",
-			default_value: __("Select Fiscal Year...")},
-		{fieldtype: "Date", label: __("From Date"), fieldname: "from_date"},
-		{fieldtype: "Label", label: __("To")},
-		{fieldtype: "Date", label: __("To Date"), fieldname: "to_date"}
-	],
-	setup_filters: function() {
-		this._super();
+	setup_filters() {
+		super.setup_filters();
 		var me = this;
 		// default filters
 		this.filter_inputs.fiscal_year.change(function() {
@@ -83,8 +84,8 @@
 		});
 		me.show_zero_check()
 		if(me.ignore_closing_entry) me.ignore_closing_entry();
-	},
-	prepare_data: function() {
+	}
+	prepare_data() {
 		var me = this;
 		if(!this.primary_data) {
 			// make accounts list
@@ -113,12 +114,12 @@
 		this.set_indent();
 		this.prepare_balances();
 
-	},
-	init_account: function(d) {
+	}
+	init_account(d) {
 		this.reset_item_values(d);
-	},
+	}
 
-	prepare_balances: function() {
+	prepare_balances() {
 		var gl = frappe.report_dump.data['GL Entry'];
 		var me = this;
 
@@ -139,8 +140,8 @@
 		});
 
 		this.update_groups();
-	},
-	update_balances: function(account, posting_date, v) {
+	}
+	update_balances(account, posting_date, v) {
 		// opening
 		if (posting_date < this.opening_date || v.is_opening === "Yes") {
 			if (account.report_type === "Profit and Loss" &&
@@ -161,8 +162,8 @@
 		var closing_bal = flt(account.opening_dr) - flt(account.opening_cr) +
 			flt(account.debit) - flt(account.credit);
 		this.set_debit_or_credit(account, "closing", closing_bal);
-	},
-	set_debit_or_credit: function(account, field, balance) {
+	}
+	set_debit_or_credit(account, field, balance) {
 		if(balance > 0) {
 			account[field+"_dr"] = balance;
 			account[field+"_cr"] = 0;
@@ -170,8 +171,8 @@
 			account[field+"_cr"] = Math.abs(balance);
 			account[field+"_dr"] = 0;
 		}
-	},
-	update_groups: function() {
+	}
+	update_groups() {
 		// update groups
 		var me= this;
 		$.each(this.data, function(i, account) {
@@ -202,9 +203,9 @@
 				}
 			}
 		});
-	},
+	}
 
-	set_fiscal_year: function() {
+	set_fiscal_year() {
 		if (this.opening_date > this.closing_date) {
 			frappe.msgprint(__("Opening Date should be before Closing Date"));
 			return;
@@ -223,9 +224,9 @@
 			frappe.msgprint(__("Opening Date and Closing Date should be within same Fiscal Year"));
 			return;
 		}
-	},
+	}
 
-	show_general_ledger: function(account) {
+	show_general_ledger(account) {
 		frappe.route_options = {
 			account: account,
 			company: this.company,
@@ -234,4 +235,4 @@
 		};
 		frappe.set_route("query-report", "General Ledger");
 	}
-});
+};
diff --git a/erpnext/public/js/bank-reconciliation-tool.bundle.js b/erpnext/public/js/bank-reconciliation-tool.bundle.js
new file mode 100644
index 0000000..636ef18
--- /dev/null
+++ b/erpnext/public/js/bank-reconciliation-tool.bundle.js
@@ -0,0 +1,3 @@
+import "./bank_reconciliation_tool/data_table_manager";
+import "./bank_reconciliation_tool/number_card";
+import "./bank_reconciliation_tool/dialog_manager";
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index e7dcd41..86dadd3 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -9,14 +9,14 @@
 
 cur_frm.email_field = "contact_email";
 
-erpnext.buying.BuyingController = erpnext.TransactionController.extend({
-	setup: function() {
-		this._super();
-	},
+erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController {
+	setup() {
+		super.setup();
+	}
 
-	onload: function(doc, cdt, cdn) {
+	onload(doc, cdt, cdn) {
 		this.setup_queries(doc, cdt, cdn);
-		this._super();
+		super.onload();
 
 		this.frm.set_query('shipping_rule', function() {
 			return {
@@ -48,9 +48,9 @@
 			});
 		}
 		/* eslint-enable */
-	},
+	}
 
-	setup_queries: function(doc, cdt, cdn) {
+	setup_queries(doc, cdt, cdn) {
 		var me = this;
 
 		if(this.frm.fields_dict.buying_price_list) {
@@ -109,9 +109,9 @@
 				return me.set_query_for_item_tax_template(doc, cdt, cdn)
 			});
 		}
-	},
+	}
 
-	refresh: function(doc) {
+	refresh(doc) {
 		frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier'};
 
 		this.frm.toggle_display("supplier_name",
@@ -122,38 +122,49 @@
 			this.set_from_product_bundle();
 		}
 
-		this._super();
-	},
+		this.toggle_subcontracting_fields();
+		super.refresh();
+	}
 
-	supplier: function() {
+	toggle_subcontracting_fields() {
+		if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) {
+			this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
+				'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');
+
+			this.frm.set_df_property('supplied_items', 'cannot_add_rows', 1);
+			this.frm.set_df_property('supplied_items', 'cannot_delete_rows', 1);
+		}
+	}
+
+	supplier() {
 		var me = this;
 		erpnext.utils.get_party_details(this.frm, null, null, function(){
 			me.apply_price_list();
 		});
-	},
+	}
 
-	supplier_address: function() {
+	supplier_address() {
 		erpnext.utils.get_address_display(this.frm);
 		erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address");
-	},
+	}
 
-	buying_price_list: function() {
+	buying_price_list() {
 		this.apply_price_list();
-	},
+	}
 
-	discount_percentage: function(doc, cdt, cdn) {
+	discount_percentage(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		item.discount_amount = 0.0;
 		this.price_list_rate(doc, cdt, cdn);
-	},
+	}
 
-	discount_amount: function(doc, cdt, cdn) {
+	discount_amount(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		item.discount_percentage = 0.0;
 		this.price_list_rate(doc, cdt, cdn);
-	},
+	}
 
-	qty: function(doc, cdt, cdn) {
+	qty(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) {
 			frappe.model.round_floats_in(item, ["qty", "received_qty"]);
@@ -168,22 +179,22 @@
 			item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
 			item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty);
 		}
-		this._super(doc, cdt, cdn);
-	},
+		super.qty(doc, cdt, cdn);
+	}
 
-	batch_no: function(doc, cdt, cdn) {
-		this._super(doc, cdt, cdn);
-	},
+	batch_no(doc, cdt, cdn) {
+		super.batch_no(doc, cdt, cdn);
+	}
 
-	received_qty: function(doc, cdt, cdn) {
+	received_qty(doc, cdt, cdn) {
 		this.calculate_accepted_qty(doc, cdt, cdn)
-	},
+	}
 
-	rejected_qty: function(doc, cdt, cdn) {
+	rejected_qty(doc, cdt, cdn) {
 		this.calculate_accepted_qty(doc, cdt, cdn)
-	},
+	}
 
-	calculate_accepted_qty: function(doc, cdt, cdn){
+	calculate_accepted_qty(doc, cdt, cdn){
 		var item = frappe.get_doc(cdt, cdn);
 		frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]);
 
@@ -191,9 +202,9 @@
 
 		item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item));
 		this.qty(doc, cdt, cdn);
-	},
+	}
 
-	validate_negative_quantity: function(cdt, cdn, item, fieldnames){
+	validate_negative_quantity(cdt, cdn, item, fieldnames){
 		if(!item || !fieldnames) { return }
 
 		var is_negative_qty = false;
@@ -206,9 +217,9 @@
 		}
 
 		return is_negative_qty
-	},
+	}
 
-	warehouse: function(doc, cdt, cdn) {
+	warehouse(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		if(item.item_code && item.warehouse) {
 			return this.frm.call({
@@ -221,9 +232,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	project: function(doc, cdt, cdn) {
+	project(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		if(item.project) {
 			$.each(this.frm.doc["items"] || [],
@@ -234,48 +245,48 @@
 					}
 				});
 		}
-	},
+	}
 
-	rejected_warehouse: function(doc, cdt) {
+	rejected_warehouse(doc, cdt) {
 		// trigger autofill_warehouse only if parent rejected_warehouse field is triggered
 		if (["Purchase Invoice", "Purchase Receipt"].includes(cdt)) {
 			this.autofill_warehouse(doc.items, "rejected_warehouse", doc.rejected_warehouse);
 		}
-	},
+	}
 
-	category: function(doc, cdt, cdn) {
+	category(doc, cdt, cdn) {
 		// should be the category field of tax table
 		if(cdt != doc.doctype) {
 			this.calculate_taxes_and_totals();
 		}
-	},
-	add_deduct_tax: function(doc, cdt, cdn) {
+	}
+	add_deduct_tax(doc, cdt, cdn) {
 		this.calculate_taxes_and_totals();
-	},
+	}
 
-	set_from_product_bundle: function() {
+	set_from_product_bundle() {
 		var me = this;
 		this.frm.add_custom_button(__("Product Bundle"), function() {
 			erpnext.buying.get_items_from_product_bundle(me.frm);
 		}, __("Get Items From"));
-	},
+	}
 
-	shipping_address: function(){
+	shipping_address(){
 		var me = this;
 		erpnext.utils.get_address_display(this.frm, "shipping_address",
 			"shipping_address_display", true);
-	},
+	}
 
-	billing_address: function() {
+	billing_address() {
 		erpnext.utils.get_address_display(this.frm, "billing_address",
 			"billing_address_display", true);
-	},
+	}
 
-	tc_name: function() {
+	tc_name() {
 		this.get_terms();
-	},
+	}
 
-	update_auto_repeat_reference: function(doc) {
+	update_auto_repeat_reference(doc) {
 		if (doc.auto_repeat) {
 			frappe.call({
 				method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
@@ -292,9 +303,9 @@
 				}
 			})
 		}
-	},
+	}
 
-	manufacturer: function(doc, cdt, cdn) {
+	manufacturer(doc, cdt, cdn) {
 		const row = locals[cdt][cdn];
 
 		if(row.manufacturer) {
@@ -311,9 +322,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	manufacturer_part_no: function(doc, cdt, cdn) {
+	manufacturer_part_no(doc, cdt, cdn) {
 		const row = locals[cdt][cdn];
 
 		if (row.manufacturer_part_no) {
@@ -336,7 +347,7 @@
 
 			}
 		}
-});
+};
 
 cur_frm.add_fetch('project', 'cost_center', 'cost_center');
 
@@ -497,4 +508,4 @@
 	});
 
 	dialog.show();
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js
index 87b21b7..d346357 100644
--- a/erpnext/public/js/controllers/stock_controller.js
+++ b/erpnext/public/js/controllers/stock_controller.js
@@ -3,22 +3,22 @@
 
 frappe.provide("erpnext.stock");
 
-erpnext.stock.StockController = frappe.ui.form.Controller.extend({
-	onload: function() {
+erpnext.stock.StockController = class StockController extends frappe.ui.form.Controller {
+	onload() {
 		// warehouse query if company
 		if (this.frm.fields_dict.company) {
 			this.setup_warehouse_query();
 		}
-	},
+	}
 
-	setup_warehouse_query: function() {
+	setup_warehouse_query() {
 		var me = this;
 		erpnext.queries.setup_queries(this.frm, "Warehouse", function() {
 			return erpnext.queries.warehouse(me.frm.doc);
 		});
-	},
+	}
 
-	setup_posting_date_time_check: function() {
+	setup_posting_date_time_check() {
 		// make posting date default and read only unless explictly checked
 		frappe.ui.form.on(this.frm.doctype, 'set_posting_date_and_time_read_only', function(frm) {
 			if(frm.doc.docstatus == 0 && frm.doc.set_posting_time) {
@@ -46,9 +46,9 @@
 				frm.trigger('set_posting_date_and_time_read_only');
 			}
 		});
-	},
+	}
 
-	show_stock_ledger: function() {
+	show_stock_ledger() {
 		var me = this;
 		if(this.frm.doc.docstatus > 0) {
 			cur_frm.add_custom_button(__("Stock Ledger"), function() {
@@ -63,9 +63,9 @@
 			}, __("View"));
 		}
 
-	},
+	}
 
-	show_general_ledger: function() {
+	show_general_ledger() {
 		var me = this;
 		if(this.frm.doc.docstatus > 0) {
 			cur_frm.add_custom_button(__('Accounting Ledger'), function() {
@@ -81,4 +81,4 @@
 			}, __("View"));
 		}
 	}
-});
+};
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index e5a5fcf..cc33b8b 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -1,12 +1,12 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-erpnext.taxes_and_totals = erpnext.payments.extend({
-	setup: function() {
+erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
+	setup() {
 		this.fetch_round_off_accounts();
-	},
+	}
 
-	apply_pricing_rule_on_item: function(item) {
+	apply_pricing_rule_on_item(item) {
 		let effective_item_rate = item.price_list_rate;
 		let item_rate = item.rate;
 		if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
@@ -32,9 +32,9 @@
 		}
 
 		frappe.model.set_value(item.doctype, item.name, "rate", item_rate);
-	},
+	}
 
-	calculate_taxes_and_totals: function(update_paid_amount) {
+	calculate_taxes_and_totals(update_paid_amount) {
 		this.discount_amount_applied = false;
 		this._calculate_taxes_and_totals();
 		this.calculate_discount_amount();
@@ -63,16 +63,16 @@
 		}
 
 		this.frm.refresh_fields();
-	},
+	}
 
-	calculate_discount_amount: function(){
+	calculate_discount_amount(){
 		if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
 			this.set_discount_amount();
 			this.apply_discount_amount();
 		}
-	},
+	}
 
-	_calculate_taxes_and_totals: function() {
+	_calculate_taxes_and_totals() {
 		frappe.run_serially([
 			() => this.validate_conversion_rate(),
 			() => this.calculate_item_values(),
@@ -85,9 +85,9 @@
 			() => this.calculate_totals(),
 			() => this._cleanup()
 		]);
-	},
+	}
 
-	validate_conversion_rate: function() {
+	validate_conversion_rate() {
 		this.frm.doc.conversion_rate = flt(this.frm.doc.conversion_rate, (cur_frm) ? precision("conversion_rate") : 9);
 		var conversion_rate_label = frappe.meta.get_label(this.frm.doc.doctype, "conversion_rate",
 			this.frm.doc.name);
@@ -102,9 +102,9 @@
 				frappe.throw(err_message);
 			}
 		}
-	},
+	}
 
-	calculate_item_values: function() {
+	calculate_item_values() {
 		var me = this;
 		if (!this.discount_amount_applied) {
 			$.each(this.frm.doc["items"] || [], function(i, item) {
@@ -124,16 +124,16 @@
 				me.set_in_company_currency(item, ["price_list_rate", "rate", "amount", "net_rate", "net_amount"]);
 			});
 		}
-	},
+	}
 
-	set_in_company_currency: function(doc, fields) {
+	set_in_company_currency(doc, fields) {
 		var me = this;
 		$.each(fields, function(i, f) {
 			doc["base_"+f] = flt(flt(doc[f], precision(f, doc)) * me.frm.doc.conversion_rate, precision("base_" + f, doc));
 		});
-	},
+	}
 
-	initialize_taxes: function() {
+	initialize_taxes() {
 		var me = this;
 
 		$.each(this.frm.doc["taxes"] || [], function(i, tax) {
@@ -155,9 +155,9 @@
 			}
 			frappe.model.round_floats_in(tax);
 		});
-	},
+	}
 
-	fetch_round_off_accounts: function() {
+	fetch_round_off_accounts() {
 		let me = this;
 		frappe.flags.round_off_applicable_accounts = [];
 
@@ -168,14 +168,16 @@
 					"company": me.frm.doc.company,
 					"account_list": frappe.flags.round_off_applicable_accounts
 				},
-				callback: function(r) {
-					frappe.flags.round_off_applicable_accounts.push(...r.message);
+				callback(r) {
+					if (r.message) {
+						frappe.flags.round_off_applicable_accounts.push(...r.message);
+					}
 				}
 			});
 		}
-	},
+	}
 
-	determine_exclusive_rate: function() {
+	determine_exclusive_rate() {
 		var me = this;
 
 		var has_inclusive_tax = false;
@@ -213,9 +215,9 @@
 				me.set_in_company_currency(item, ["net_rate", "net_amount"]);
 			}
 		});
-	},
+	}
 
-	get_current_tax_fraction: function(tax, item_tax_map) {
+	get_current_tax_fraction(tax, item_tax_map) {
 		// Get tax fraction for calculating tax exclusive amount
 		// from tax inclusive amount
 		var current_tax_fraction = 0.0;
@@ -244,14 +246,14 @@
 			inclusive_tax_amount_per_qty *= -1;
 		}
 		return [current_tax_fraction, inclusive_tax_amount_per_qty];
-	},
+	}
 
-	_get_tax_rate: function(tax, item_tax_map) {
+	_get_tax_rate(tax, item_tax_map) {
 		return (Object.keys(item_tax_map).indexOf(tax.account_head) != -1) ?
 			flt(item_tax_map[tax.account_head], precision("rate", tax)) : tax.rate;
-	},
+	}
 
-	calculate_net_total: function() {
+	calculate_net_total() {
 		var me = this;
 		this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0;
 
@@ -264,9 +266,9 @@
 			});
 
 		frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
-	},
+	}
 
-	update_item_tax_map: function() {
+	update_item_tax_map() {
 		let me = this;
 		let item_codes = [];
 		let item_rates = {};
@@ -303,9 +305,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	add_taxes_from_item_tax_template: function(item_tax_map) {
+	add_taxes_from_item_tax_template(item_tax_map) {
 		let me = this;
 
 		if (item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) {
@@ -323,9 +325,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	calculate_taxes: function() {
+	calculate_taxes() {
 		var me = this;
 		this.frm.doc.rounding_adjustment = 0;
 		var actual_tax_dict = {};
@@ -404,9 +406,9 @@
 				}
 			});
 		});
-	},
+	}
 
-	set_cumulative_total: function(row_idx, tax) {
+	set_cumulative_total(row_idx, tax) {
 		var tax_amount = tax.tax_amount_after_discount_amount;
 		if (tax.category == 'Valuation') {
 			tax_amount = 0;
@@ -419,13 +421,13 @@
 		} else {
 			tax.total = flt(this.frm.doc["taxes"][row_idx-1].total + tax_amount, precision("total", tax));
 		}
-	},
+	}
 
-	_load_item_tax_rate: function(item_tax_rate) {
+	_load_item_tax_rate(item_tax_rate) {
 		return item_tax_rate ? JSON.parse(item_tax_rate) : {};
-	},
+	}
 
-	get_current_tax_amount: function(item, tax, item_tax_map) {
+	get_current_tax_amount(item, tax, item_tax_map) {
 		var tax_rate = this._get_tax_rate(tax, item_tax_map);
 		var current_tax_amount = 0.0;
 
@@ -461,9 +463,9 @@
 		this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
 
 		return current_tax_amount;
-	},
+	}
 
-	set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) {
+	set_item_wise_tax(item, tax, tax_rate, current_tax_amount) {
 		// store tax breakup for each item
 		let tax_detail = tax.item_wise_tax_detail;
 		let key = item.item_code || item.item_name;
@@ -478,9 +480,9 @@
 			item_wise_tax_amount += tax_detail[key][1];
 
 		tax_detail[key] = [tax_rate, flt(item_wise_tax_amount, precision("base_tax_amount", tax))];
-	},
+	}
 
-	round_off_totals: function(tax) {
+	round_off_totals(tax) {
 		if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
 			tax.tax_amount= Math.round(tax.tax_amount);
 			tax.tax_amount_after_discount_amount = Math.round(tax.tax_amount_after_discount_amount);
@@ -488,16 +490,16 @@
 
 		tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax));
 		tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, precision("tax_amount", tax));
-	},
+	}
 
-	round_off_base_values: function(tax) {
+	round_off_base_values(tax) {
 		if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
 			tax.base_tax_amount= Math.round(tax.base_tax_amount);
 			tax.base_tax_amount_after_discount_amount = Math.round(tax.base_tax_amount_after_discount_amount);
 		}
-	},
+	}
 
-	manipulate_grand_total_for_inclusive_tax: function() {
+	manipulate_grand_total_for_inclusive_tax() {
 		var me = this;
 		// if fully inclusive taxes and diff
 		if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) {
@@ -528,10 +530,10 @@
 				}
 			}
 		}
-	},
+	}
 
-	calculate_totals: function() {
-		// Changing sequence can because of rounding adjustment issue and on-screen discrepancy
+	calculate_totals() {
+		// Changing sequence can cause rounding_adjustmentng issue and on-screen discrepency
 		var me = this;
 		var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0;
 		this.frm.doc.grand_total = flt(tax_count
@@ -576,9 +578,9 @@
 
 		// rounded totals
 		this.set_rounded_total();
-	},
+	}
 
-	set_rounded_total: function() {
+	set_rounded_total() {
 		var disable_rounded_total = 0;
 		if(frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total", this.frm.doc.name)) {
 			disable_rounded_total = this.frm.doc.disable_rounded_total;
@@ -600,9 +602,9 @@
 
 			this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
 		}
-	},
+	}
 
-	_cleanup: function() {
+	_cleanup() {
 		this.frm.doc.base_in_words = this.frm.doc.in_words = "";
 
 		if(this.frm.doc["items"] && this.frm.doc["items"].length) {
@@ -631,16 +633,16 @@
 		}
 
 		this.frm.refresh_fields();
-	},
+	}
 
-	set_discount_amount: function() {
+	set_discount_amount() {
 		if(this.frm.doc.additional_discount_percentage) {
 			this.frm.doc.discount_amount = flt(flt(this.frm.doc[frappe.scrub(this.frm.doc.apply_discount_on)])
 				* this.frm.doc.additional_discount_percentage / 100, precision("discount_amount"));
 		}
-	},
+	}
 
-	apply_discount_amount: function() {
+	apply_discount_amount() {
 		var me = this;
 		var distributed_amount = 0.0;
 		this.frm.doc.base_discount_amount = 0.0;
@@ -678,9 +680,9 @@
 				this._calculate_taxes_and_totals();
 			}
 		}
-	},
+	}
 
-	get_total_for_discount_amount: function() {
+	get_total_for_discount_amount() {
 		if(this.frm.doc.apply_discount_on == "Net Total") {
 			return this.frm.doc.net_total;
 		} else {
@@ -704,27 +706,27 @@
 
 			return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
 		}
-	},
+	}
 
-	calculate_total_advance: function(update_paid_amount) {
+	calculate_total_advance(update_paid_amount) {
 		var total_allocated_amount = frappe.utils.sum($.map(this.frm.doc["advances"] || [], function(adv) {
 			return flt(adv.allocated_amount, precision("allocated_amount", adv));
 		}));
 		this.frm.doc.total_advance = flt(total_allocated_amount, precision("total_advance"));
 
 		this.calculate_outstanding_amount(update_paid_amount);
-	},
+	}
 
-	is_internal_invoice: function() {
+	is_internal_invoice() {
 		if (['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
 			if (this.frm.doc.company === this.frm.doc.represents_company) {
 				return true;
 			}
 		}
 		return false;
-	},
+	}
 
-	calculate_outstanding_amount: function(update_paid_amount) {
+	calculate_outstanding_amount(update_paid_amount) {
 		// NOTE:
 		// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
 		// total_advance is only for non POS Invoice
@@ -772,9 +774,9 @@
 			this.frm.doc.outstanding_amount =  flt(total_amount_to_pay - flt(paid_amount) +
 				flt(this.frm.doc.change_amount * this.frm.doc.conversion_rate), precision("outstanding_amount"));
 		}
-	},
+	}
 
-	update_paid_amount_for_return: function() {
+	update_paid_amount_for_return() {
 		var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
 
 		if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
@@ -798,9 +800,9 @@
 		this.frm.refresh_fields();
 
 		this.calculate_paid_amount();
-	},
+	}
 
-	set_default_payment: function(total_amount_to_pay, update_paid_amount) {
+	set_default_payment(total_amount_to_pay, update_paid_amount) {
 		var me = this;
 		var payment_status = true;
 		if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) {
@@ -816,9 +818,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	calculate_paid_amount: function() {
+	calculate_paid_amount() {
 		var me = this;
 		var paid_amount = 0.0;
 		var base_paid_amount = 0.0;
@@ -838,9 +840,9 @@
 
 		this.frm.set_value('paid_amount', flt(paid_amount, precision("paid_amount")));
 		this.frm.set_value('base_paid_amount', flt(base_paid_amount, precision("base_paid_amount")));
-	},
+	}
 
-	calculate_change_amount: function(){
+	calculate_change_amount(){
 		this.frm.doc.change_amount = 0.0;
 		this.frm.doc.base_change_amount = 0.0;
 		if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
@@ -859,9 +861,9 @@
 					precision("base_change_amount"));
 			}
 		}
-	},
+	}
 
-	calculate_write_off_amount: function(){
+	calculate_write_off_amount(){
 		if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
 			this.frm.doc.write_off_amount = flt(this.frm.doc.grand_total - this.frm.doc.paid_amount
 				+ this.frm.doc.change_amount, precision("write_off_amount"));
@@ -873,4 +875,4 @@
 		}
 		this.calculate_outstanding_amount(false);
 	}
-});
+};
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 0f44ad7..8360337 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -3,9 +3,9 @@
 
 frappe.provide('erpnext.accounts.dimensions');
 
-erpnext.TransactionController = erpnext.taxes_and_totals.extend({
-	setup: function() {
-		this._super();
+erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals {
+	setup() {
+		super.setup();
 		let me = this;
 		frappe.flags.hide_serial_batch_dialog = true;
 		frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) {
@@ -220,8 +220,8 @@
 			});
 		}
 
-	},
-	onload: function() {
+	}
+	onload() {
 		var me = this;
 
 		if(this.frm.doc.__islocal) {
@@ -247,15 +247,15 @@
 				}
 			]);
 		}
-	},
+	}
 
-	is_return: function() {
+	is_return() {
 		if(!this.frm.doc.is_return && this.frm.doc.return_against) {
 			this.frm.set_value('return_against', '');
 		}
-	},
+	}
 
-	setup_quality_inspection: function() {
+	setup_quality_inspection() {
 		if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
 			return;
 		}
@@ -296,9 +296,9 @@
 				}
 			}
 		});
-	},
+	}
 
-	make_payment_request: function() {
+	make_payment_request() {
 		var me = this;
 		const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
 			? "Inward" : "Outward";
@@ -320,9 +320,9 @@
 				}
 			}
 		})
-	},
+	}
 
-	onload_post_render: function() {
+	onload_post_render() {
 		if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
 			&& !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
 			frappe.after_ajax(() => this.apply_default_taxes());
@@ -334,9 +334,9 @@
 			this.setup_item_selector();
 			this.frm.get_field("items").grid.set_multiple_add("item_code", "qty");
 		}
-	},
+	}
 
-	refresh: function() {
+	refresh() {
 		erpnext.toggle_naming_series();
 		erpnext.hide_company();
 		this.set_dynamic_labels();
@@ -366,9 +366,9 @@
 					.appendTo($input_group);
 			}
 		}
-	},
+	}
 
-	scan_barcode: function() {
+	scan_barcode() {
 		let scan_barcode_field = this.frm.fields_dict["scan_barcode"];
 
 		let show_description = function(idx, exist = null) {
@@ -440,9 +440,9 @@
 			});
 		}
 		return false;
-	},
+	}
 
-	apply_default_taxes: function() {
+	apply_default_taxes() {
 		var me = this;
 		var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
 			me.frm.doc.name);
@@ -481,22 +481,22 @@
 				}
 			});
 		}
-	},
+	}
 
-	setup_sms: function() {
+	setup_sms() {
 		var me = this;
 		let blacklist = ['Purchase Invoice', 'BOM'];
 		if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status)
 			&& !blacklist.includes(this.frm.doctype)) {
 			this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
 		}
-	},
+	}
 
-	send_sms: function() {
+	send_sms() {
 		var sms_man = new erpnext.SMSManager(this.frm.doc);
-	},
+	}
 
-	barcode: function(doc, cdt, cdn) {
+	barcode(doc, cdt, cdn) {
 		var d = locals[cdt][cdn];
 		if(d.barcode=="" || d.barcode==null) {
 			// barcode cleared, remove item
@@ -505,9 +505,9 @@
 
 		this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
 		this.item_code(doc, cdt, cdn);
-	},
+	}
 
-	item_code: function(doc, cdt, cdn) {
+	item_code(doc, cdt, cdn) {
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
 		var update_stock = 0, show_batch_dialog = 0;
@@ -658,9 +658,9 @@
 				});
 			}
 		}
-	},
+	}
 
-	price_list_rate: function(doc, cdt, cdn) {
+	price_list_rate(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
 
@@ -672,17 +672,17 @@
 				precision("rate", item));
 
 		this.calculate_taxes_and_totals();
-	},
+	}
 
-	margin_rate_or_amount: function(doc, cdt, cdn) {
+	margin_rate_or_amount(doc, cdt, cdn) {
 		// calculated the revised total margin and rate on margin rate changes
 		let item = frappe.get_doc(cdt, cdn);
 		this.apply_pricing_rule_on_item(item);
 		this.calculate_taxes_and_totals();
 		cur_frm.refresh_fields();
-	},
+	}
 
-	margin_type: function(doc, cdt, cdn) {
+	margin_type(doc, cdt, cdn) {
 		// calculate the revised total margin and rate on margin type changes
 		let item = frappe.get_doc(cdt, cdn);
 		if (!item.margin_type) {
@@ -692,9 +692,9 @@
 			this.calculate_taxes_and_totals();
 			cur_frm.refresh_fields();
 		}
-	},
+	}
 
-	get_incoming_rate: function(item, posting_date, posting_time, voucher_type, company) {
+	get_incoming_rate(item, posting_date, posting_time, voucher_type, company) {
 
 		let item_args = {
 			'item_code': item.item_code,
@@ -717,12 +717,36 @@
 				frappe.model.set_value(item.doctype, item.name, 'rate', r.message * item.conversion_factor);
 			}
 		});
-	},
+	}
 
-	serial_no: function(doc, cdt, cdn) {
+	add_taxes_from_item_tax_template(item_tax_map) {
+		let me = this;
+
+		if(item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) {
+			if(typeof (item_tax_map) == "string") {
+				item_tax_map = JSON.parse(item_tax_map);
+			}
+
+			$.each(item_tax_map, function(tax, rate) {
+				let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax);
+				if(!found) {
+					let child = frappe.model.add_child(me.frm.doc, "taxes");
+					child.charge_type = "On Net Total";
+					child.account_head = tax;
+					child.rate = 0;
+				}
+			});
+		}
+	}
+
+	serial_no(doc, cdt, cdn) {
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
 
+		if (item && item.doctype === 'Purchase Receipt Item Supplied') {
+			return;
+		}
+
 		if (item && item.serial_no) {
 			if (!item.item_code) {
 				this.frm.trigger("item_code", cdt, cdn);
@@ -739,9 +763,9 @@
 				}
 			}
 		}
-	},
+	}
 
-	update_qty: function(cdt, cdn) {
+	update_qty(cdt, cdn) {
 		var valid_serial_nos = [];
 		var serialnos = [];
 		var item = frappe.get_doc(cdt, cdn);
@@ -754,17 +778,17 @@
 		frappe.model.set_value(item.doctype, item.name,
 			"qty", valid_serial_nos.length / item.conversion_factor);
 		frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
-	},
+	}
 
-	validate: function() {
+	validate() {
 		this.calculate_taxes_and_totals(false);
-	},
+	}
 
-	update_stock: function() {
+	update_stock() {
 		this.frm.trigger('set_default_internal_warehouse');
-	},
+	}
 
-	set_default_internal_warehouse: function() {
+	set_default_internal_warehouse() {
 		let me = this;
 		if ((this.frm.doc.doctype === 'Sales Invoice' && me.frm.doc.update_stock)
 			|| this.frm.doc.doctype == 'Delivery Note') {
@@ -783,9 +807,9 @@
 				});
 			}
 		}
-	},
+	}
 
-	company: function() {
+	company() {
 		var me = this;
 		var set_pricing = function() {
 			if(me.frm.doc.company && me.frm.fields_dict.currency) {
@@ -864,9 +888,6 @@
 
 		}
 
-		if (this.frm.doc.posting_date) var date = this.frm.doc.posting_date;
-		else var date = this.frm.doc.transaction_date;
-
 		if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
 			in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
 			erpnext.utils.get_shipping_address(this.frm, function(){
@@ -892,16 +913,16 @@
 		if(this.frm.doc.company) {
 			erpnext.last_selected_company = this.frm.doc.company;
 		}
-	},
+	}
 
-	transaction_date: function() {
+	transaction_date() {
 		if (this.frm.doc.transaction_date) {
 			this.frm.transaction_date = this.frm.doc.transaction_date;
 			frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
 		}
-	},
+	}
 
-	posting_date: function() {
+	posting_date() {
 		var me = this;
 		if (this.frm.doc.posting_date) {
 			this.frm.posting_date = this.frm.doc.posting_date;
@@ -930,9 +951,9 @@
 				frappe.ui.form.trigger(me.frm.doc.doctype, "currency");
 			}
 		}
-	},
+	}
 
-	due_date: function() {
+	due_date() {
 		// due_date is to be changed, payment terms template and/or payment schedule must
 		// be removed as due_date is automatically changed based on payment terms
 		if (this.frm.doc.due_date && !this.frm.updating_party_details && !this.frm.doc.is_pos) {
@@ -955,13 +976,13 @@
 				frappe.msgprint(final_message);
 			}
 		}
-	},
+	}
 
-	bill_date: function() {
+	bill_date() {
 		this.posting_date();
-	},
+	}
 
-	recalculate_terms: function() {
+	recalculate_terms() {
 		const doc = this.frm.doc;
 		if (doc.payment_terms_template) {
 			this.payment_terms_template();
@@ -980,17 +1001,17 @@
 				}
 			);
 		}
-	},
+	}
 
-	get_company_currency: function() {
+	get_company_currency() {
 		return erpnext.get_currency(this.frm.doc.company);
-	},
+	}
 
-	contact_person: function() {
+	contact_person() {
 		erpnext.utils.get_contact_details(this.frm);
-	},
+	}
 
-	currency: function() {
+	currency() {
 		/* manqala 19/09/2016: let the translation date be whichever of the transaction_date or posting_date is available */
 		var transaction_date = this.frm.doc.transaction_date || this.frm.doc.posting_date;
 		/* end manqala */
@@ -1012,9 +1033,9 @@
 		} else {
 			this.conversion_rate();
 		}
-	},
+	}
 
-	conversion_rate: function() {
+	conversion_rate() {
 		const me = this.frm;
 		if(this.frm.doc.currency === this.get_company_currency()) {
 			this.frm.set_value("conversion_rate", 1.0);
@@ -1034,9 +1055,9 @@
 		}
 		// Make read only if Accounts Settings doesn't allow stale rates
 		this.frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
-	},
+	}
 
-	shipping_rule: function() {
+	shipping_rule() {
 		var me = this;
 		if(this.frm.doc.shipping_rule) {
 			return this.frm.call({
@@ -1052,9 +1073,9 @@
 		else {
 			me.calculate_taxes_and_totals();
 		}
-	},
+	}
 
-	set_margin_amount_based_on_currency: function(exchange_rate) {
+	set_margin_amount_based_on_currency(exchange_rate) {
 		if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "Purchase Invoice", "Purchase Order", "Purchase Receipt"]), this.frm.doc.doctype) {
 			var me = this;
 			$.each(this.frm.doc.items || [], function(i, d) {
@@ -1064,9 +1085,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	set_actual_charges_based_on_currency: function(exchange_rate) {
+	set_actual_charges_based_on_currency(exchange_rate) {
 		var me = this;
 		$.each(this.frm.doc.taxes || [], function(i, d) {
 			if(d.charge_type == "Actual") {
@@ -1074,9 +1095,9 @@
 					flt(d.tax_amount) / flt(exchange_rate));
 			}
 		});
-	},
+	}
 
-	get_exchange_rate: function(transaction_date, from_currency, to_currency, callback) {
+	get_exchange_rate(transaction_date, from_currency, to_currency, callback) {
 		var args;
 		if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doctype)) {
 			args = "for_selling";
@@ -1100,9 +1121,9 @@
 				callback(flt(r.message));
 			}
 		});
-	},
+	}
 
-	price_list_currency: function() {
+	price_list_currency() {
 		var me=this;
 		this.set_dynamic_labels();
 
@@ -1116,9 +1137,9 @@
 		} else {
 			this.plc_conversion_rate();
 		}
-	},
+	}
 
-	plc_conversion_rate: function() {
+	plc_conversion_rate() {
 		if(this.frm.doc.price_list_currency === this.get_company_currency()) {
 			this.frm.set_value("plc_conversion_rate", 1.0);
 		} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
@@ -1130,9 +1151,9 @@
 		if(!this.in_apply_price_list) {
 			this.apply_price_list(null, true);
 		}
-	},
+	}
 
-	uom: function(doc, cdt, cdn) {
+	uom(doc, cdt, cdn) {
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
 		if(item.item_code && item.uom) {
@@ -1150,9 +1171,9 @@
 			});
 		}
 		me.calculate_stock_uom_rate(doc, cdt, cdn);
-	},
+	}
 
-	conversion_factor: function(doc, cdt, cdn, dont_fetch_price_list_rate) {
+	conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) {
 		if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
 			var item = frappe.get_doc(cdt, cdn);
 			frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
@@ -1177,35 +1198,35 @@
 			}
 			this.calculate_stock_uom_rate(doc, cdt, cdn);
 		}
-	},
+	}
 
-	batch_no: function(doc, cdt, cdn) {
+	batch_no(doc, cdt, cdn) {
 		let item = frappe.get_doc(cdt, cdn);
 		this.apply_price_list(item, true);
-	},
+	}
 
-	toggle_conversion_factor: function(item) {
+	toggle_conversion_factor(item) {
 		// toggle read only property for conversion factor field if the uom and stock uom are same
 		if(this.frm.get_field('items').grid.fields_map.conversion_factor) {
 			this.frm.fields_dict.items.grid.toggle_enable("conversion_factor",
 				((item.uom != item.stock_uom) && !frappe.meta.get_docfield(cur_frm.fields_dict.items.grid.doctype, "conversion_factor").read_only)? true: false);
 		}
 
-	},
+	}
 
-	qty: function(doc, cdt, cdn) {
+	qty(doc, cdt, cdn) {
 		let item = frappe.get_doc(cdt, cdn);
 		this.conversion_factor(doc, cdt, cdn, true);
 		this.calculate_stock_uom_rate(doc, cdt, cdn);
 		this.apply_pricing_rule(item, true);
-	},
+	}
 
-	calculate_stock_uom_rate: function(doc, cdt, cdn) {
+	calculate_stock_uom_rate(doc, cdt, cdn) {
 		let item = frappe.get_doc(cdt, cdn);
 		item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);
 		refresh_field("stock_uom_rate", item.name, item.parentfield);
-	},
-	service_stop_date: function(frm, cdt, cdn) {
+	}
+	service_stop_date(frm, cdt, cdn) {
 		var child = locals[cdt][cdn];
 
 		if(child.service_stop_date) {
@@ -1221,9 +1242,9 @@
 				frappe.throw(__("Service Stop Date cannot be after Service End Date"));
 			}
 		}
-	},
+	}
 
-	service_start_date: function(frm, cdt, cdn) {
+	service_start_date(frm, cdt, cdn) {
 		var child = locals[cdt][cdn];
 
 		if(child.service_start_date) {
@@ -1235,9 +1256,9 @@
 				}
 			})
 		}
-	},
+	}
 
-	calculate_net_weight: function(){
+	calculate_net_weight(){
 		/* Calculate Total Net Weight then further applied shipping rule to calculate shipping charges.*/
 		var me = this;
 		this.frm.doc.total_net_weight= 0.0;
@@ -1247,9 +1268,9 @@
 		});
 		refresh_field("total_net_weight");
 		this.shipping_rule();
-	},
+	}
 
-	set_dynamic_labels: function() {
+	set_dynamic_labels() {
 		// What TODO? should we make price list system non-mandatory?
 		this.frm.toggle_reqd("plc_conversion_rate",
 			!!(this.frm.doc.price_list_name && this.frm.doc.price_list_currency));
@@ -1258,9 +1279,9 @@
 		this.change_form_labels(company_currency);
 		this.change_grid_labels(company_currency);
 		this.frm.refresh_fields();
-	},
+	}
 
-	change_form_labels: function(company_currency) {
+	change_form_labels(company_currency) {
 		var me = this;
 
 		this.frm.set_currency_labels(["base_total", "base_net_total", "base_total_taxes_and_charges",
@@ -1307,9 +1328,9 @@
 		if(frappe.meta.get_docfield(cur_frm.doctype, "base_net_total"))
 			cur_frm.toggle_display("base_net_total", (show && (me.frm.doc.currency != company_currency)));
 
-	},
+	}
 
-	change_grid_labels: function(company_currency) {
+	change_grid_labels(company_currency) {
 		var me = this;
 
 		this.update_item_grid_labels(company_currency);
@@ -1350,9 +1371,9 @@
 		}
 
 		this.update_payment_schedule_grid_labels(company_currency);
-	},
+	}
 
-	update_item_grid_labels: function(company_currency) {
+	update_item_grid_labels(company_currency) {
 		this.frm.set_currency_labels([
 			"base_rate", "base_net_rate", "base_price_list_rate",
 			"base_amount", "base_net_amount", "base_rate_with_margin"
@@ -1362,9 +1383,9 @@
 			"rate", "net_rate", "price_list_rate", "amount",
 			"net_amount", "stock_uom_rate", "rate_with_margin"
 		], this.frm.doc.currency, "items");
-	},
+	}
 
-	update_payment_schedule_grid_labels: function(company_currency) {
+	update_payment_schedule_grid_labels(company_currency) {
 		const me = this;
 		if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) {
 			this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"],
@@ -1378,9 +1399,9 @@
 					schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency);
 			});
 		}
-	},
+	}
 
-	toggle_item_grid_columns: function(company_currency) {
+	toggle_item_grid_columns(company_currency) {
 		const me = this;
 		// toggle columns
 		var item_grid = this.frm.fields_dict["items"].grid;
@@ -1401,21 +1422,21 @@
 			if(frappe.meta.get_docfield(item_grid.doctype, fname))
 				item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency)));
 		});
-	},
+	}
 
-	recalculate: function() {
+	recalculate() {
 		this.calculate_taxes_and_totals();
-	},
+	}
 
-	recalculate_values: function() {
+	recalculate_values() {
 		this.calculate_taxes_and_totals();
-	},
+	}
 
-	calculate_charges: function() {
+	calculate_charges() {
 		this.calculate_taxes_and_totals();
-	},
+	}
 
-	ignore_pricing_rule: function() {
+	ignore_pricing_rule() {
 		if(this.frm.doc.ignore_pricing_rule) {
 			var me = this;
 			var item_list = [];
@@ -1449,9 +1470,9 @@
 		} else {
 			this.apply_pricing_rule();
 		}
-	},
+	}
 
-	apply_pricing_rule: function(item, calculate_taxes_and_totals) {
+	apply_pricing_rule(item, calculate_taxes_and_totals) {
 		var me = this;
 		var args = this._get_args(item);
 		if (!(args.items && args.items.length)) {
@@ -1470,9 +1491,9 @@
 				}
 			}
 		});
-	},
+	}
 
-	_get_args: function(item) {
+	_get_args(item) {
 		var me = this;
 		return {
 			"items": this._get_item_list(item),
@@ -1500,9 +1521,9 @@
 			"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
 			"coupon_code": me.frm.doc.coupon_code
 		};
-	},
+	}
 
-	_get_item_list: function(item) {
+	_get_item_list(item) {
 		var item_list = [];
 		var append_item = function(d) {
 			if (d.item_code) {
@@ -1543,9 +1564,9 @@
 			});
 		}
 		return item_list;
-	},
+	}
 
-	_set_values_for_item_list: function(children) {
+	_set_values_for_item_list(children) {
 		var me = this;
 		var price_list_rate_changed = false;
 		var items_rule_dict = {};
@@ -1585,9 +1606,9 @@
 		me.apply_rule_on_other_items(items_rule_dict);
 
 		if(!price_list_rate_changed) me.calculate_taxes_and_totals();
-	},
+	}
 
-	apply_rule_on_other_items: function(args) {
+	apply_rule_on_other_items(args) {
 		const me = this;
 		const fields = ["discount_percentage", "pricing_rules", "discount_amount", "rate"];
 
@@ -1606,9 +1627,9 @@
 				});
 			}
 		}
-	},
+	}
 
-	apply_product_discount: function(args) {
+	apply_product_discount(args) {
 		const items = this.frm.doc.items.filter(d => (d.is_free_item)) || [];
 
 		const exist_items = items.map(row => (row.item_code, row.pricing_rules));
@@ -1633,9 +1654,9 @@
 		// free_item_data is a temporary variable
 		args.free_item_data = '';
 		refresh_field('items');
-	},
+	}
 
-	apply_price_list: function(item, reset_plc_conversion) {
+	apply_price_list(item, reset_plc_conversion) {
 		// We need to reset plc_conversion_rate sometimes because the call to
 		// `erpnext.stock.get_item_details.apply_price_list` is sensitive to its value
 		if (!reset_plc_conversion) {
@@ -1674,9 +1695,9 @@
 		}).always(() => {
 			me.in_apply_price_list = false;
 		});
-	},
+	}
 
-	remove_pricing_rule: function(item) {
+	remove_pricing_rule(item) {
 		let me = this;
 		const fields = ["discount_percentage",
 			"discount_amount", "margin_rate_or_amount", "rate_with_margin"];
@@ -1710,18 +1731,18 @@
 
 			me.trigger_price_list_rate();
 		}
-	},
+	}
 
-	trigger_price_list_rate: function() {
+	trigger_price_list_rate() {
 		var me  = this;
 
 		this.frm.doc.items.forEach(child_row => {
 			me.frm.script_manager.trigger("price_list_rate",
 				child_row.doctype, child_row.name);
 		})
-	},
+	}
 
-	validate_company_and_party: function() {
+	validate_company_and_party() {
 		var me = this;
 		var valid = true;
 
@@ -1736,9 +1757,9 @@
 			}
 		});
 		return valid;
-	},
+	}
 
-	get_terms: function() {
+	get_terms() {
 		var me = this;
 
 		erpnext.utils.get_terms(this.frm.doc.tc_name, this.frm.doc, function(r) {
@@ -1746,9 +1767,9 @@
 				me.frm.set_value("terms", r.message);
 			}
 		});
-	},
+	}
 
-	taxes_and_charges: function() {
+	taxes_and_charges() {
 		var me = this;
 		if(this.frm.doc.taxes_and_charges) {
 			return this.frm.call({
@@ -1774,9 +1795,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	tax_category: function() {
+	tax_category() {
 		var me = this;
 		if(me.frm.updating_party_details) return;
 
@@ -1784,9 +1805,9 @@
 			() => this.update_item_tax_map(),
 			() => erpnext.utils.set_taxes(this.frm, "tax_category"),
 		]);
-	},
+	}
 
-	item_tax_template: function(doc, cdt, cdn) {
+	item_tax_template(doc, cdt, cdn) {
 		var me = this;
 		if(me.frm.updating_party_details) return;
 
@@ -1811,11 +1832,10 @@
 			item.item_tax_rate = "{}";
 			me.calculate_taxes_and_totals();
 		}
-	},
+	}
 
 
-
-	is_recurring: function() {
+	is_recurring() {
 		// set default values for recurring documents
 		if(this.frm.doc.is_recurring && this.frm.doc.__islocal) {
 			frappe.msgprint(__("Please set recurring after saving"));
@@ -1838,9 +1858,9 @@
 		}
 
 		refresh_many(["notification_email_address", "repeat_on_day_of_month"]);
-	},
+	}
 
-	from_date: function() {
+	from_date() {
 		// set to_date
 		if(this.frm.doc.from_date) {
 			var recurring_type_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6,
@@ -1854,25 +1874,25 @@
 				refresh_field('to_date');
 			}
 		}
-	},
+	}
 
-	set_gross_profit: function(item) {
+	set_gross_profit(item) {
 		if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype) && item.valuation_rate) {
 			var rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1);
 			item.gross_profit = flt(((rate - item.valuation_rate) * item.stock_qty), precision("amount", item));
 		}
-	},
+	}
 
-	setup_item_selector: function() {
+	setup_item_selector() {
 		// TODO: remove item selector
 
 		return;
 		// if(!this.item_selector) {
 		// 	this.item_selector = new erpnext.ItemSelector({frm: this.frm});
 		// }
-	},
+	}
 
-	get_advances: function() {
+	get_advances() {
 		if(!this.frm.is_return) {
 			return this.frm.call({
 				method: "set_advances",
@@ -1882,9 +1902,9 @@
 				}
 			})
 		}
-	},
+	}
 
-	make_payment_entry: function() {
+	make_payment_entry() {
 		return frappe.call({
 			method: cur_frm.cscript.get_method_for_payment(),
 			args: {
@@ -1897,9 +1917,9 @@
 				// cur_frm.refresh_fields()
 			}
 		});
-	},
+	}
 
-	make_quality_inspection: function () {
+	make_quality_inspection() {
 		let data = [];
 		const fields = [
 			{
@@ -2021,9 +2041,9 @@
 		} else {
 			dialog.show();
 		}
-	},
+	}
 
-	get_method_for_payment: function(){
+	get_method_for_payment() {
 		var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
 		if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
 			if(in_list(['Sales Invoice', 'Purchase Invoice'],  cur_frm.doc.doctype)){
@@ -2034,9 +2054,9 @@
 		}
 
 		return method
-	},
+	}
 
-	set_query_for_batch: function(doc, cdt, cdn) {
+	set_query_for_batch(doc, cdt, cdn) {
 		// Show item's batches in the dropdown of batch no
 
 		var me = this;
@@ -2066,9 +2086,9 @@
 				filters: filters
 			}
 		}
-	},
+	}
 
-	set_query_for_item_tax_template: function(doc, cdt, cdn) {
+	set_query_for_item_tax_template(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		if(!item.item_code) {
 			return doc.company ? {filters: {company: doc.company}} : {};
@@ -2088,9 +2108,9 @@
 				filters: filters
 			}
 		}
-	},
+	}
 
-	payment_terms_template: function() {
+	payment_terms_template() {
 		var me = this;
 		const doc = this.frm.doc;
 		if(doc.payment_terms_template && doc.doctype !== 'Delivery Note') {
@@ -2113,9 +2133,9 @@
 				}
 			})
 		}
-	},
+	}
 
-	payment_term: function(doc, cdt, cdn) {
+	payment_term(doc, cdt, cdn) {
 		const me = this;
 		var row = locals[cdt][cdn];
 		if(row.payment_term) {
@@ -2139,17 +2159,17 @@
 				}
 			})
 		}
-	},
+	}
 
-	against_blanket_order: function(doc, cdt, cdn) {
+	against_blanket_order(doc, cdt, cdn) {
 		var item = locals[cdt][cdn];
 		if(!item.against_blanket_order) {
 			frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order", null);
 			frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order_rate", 0.00);
 		}
-	},
+	}
 
-	blanket_order: function(doc, cdt, cdn) {
+	blanket_order(doc, cdt, cdn) {
 		var me = this;
 		var item = locals[cdt][cdn];
 		if (item.blanket_order && (item.parenttype=="Sales Order" || item.parenttype=="Purchase Order")) {
@@ -2177,34 +2197,34 @@
 				}
 			})
 		}
-	},
+	}
 
-	set_reserve_warehouse: function() {
+	set_reserve_warehouse() {
 		this.autofill_warehouse(this.frm.doc.supplied_items, "reserve_warehouse", this.frm.doc.set_reserve_warehouse);
-	},
+	}
 
-	set_warehouse: function() {
+	set_warehouse() {
 		this.autofill_warehouse(this.frm.doc.items, "warehouse", this.frm.doc.set_warehouse);
-	},
+	}
 
-	set_target_warehouse: function() {
+	set_target_warehouse() {
 		this.autofill_warehouse(this.frm.doc.items, "target_warehouse", this.frm.doc.set_target_warehouse);
-	},
+	}
 
-	set_from_warehouse: function() {
+	set_from_warehouse() {
 		this.autofill_warehouse(this.frm.doc.items, "from_warehouse", this.frm.doc.set_from_warehouse);
-	},
+	}
 
-	autofill_warehouse : function (child_table, warehouse_field, warehouse) {
+	autofill_warehouse(child_table, warehouse_field, warehouse) {
 		if (warehouse && child_table && child_table.length) {
 			let doctype = child_table[0].doctype;
 			$.each(child_table || [], function(i, item) {
 				frappe.model.set_value(doctype, item.name, warehouse_field, warehouse);
 			});
 		}
-	},
+	}
 
-	coupon_code: function() {
+	coupon_code() {
 		var me = this;
 		frappe.run_serially([
 			() => this.frm.doc.ignore_pricing_rule=1,
@@ -2213,7 +2233,7 @@
 			() => me.apply_pricing_rule()
 		]);
 	}
-});
+};
 
 erpnext.show_serial_batch_selector = function (frm, d, callback, on_close, show_dialog) {
 	let warehouse, receiving_stock, existing_stock;
diff --git a/erpnext/public/js/erpnext-web.bundle.js b/erpnext/public/js/erpnext-web.bundle.js
new file mode 100644
index 0000000..7db6967
--- /dev/null
+++ b/erpnext/public/js/erpnext-web.bundle.js
@@ -0,0 +1,2 @@
+import "./website_utils";
+import "./shopping_cart";
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
new file mode 100644
index 0000000..519cfca
--- /dev/null
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -0,0 +1,27 @@
+import "./conf";
+import "./utils";
+import "./queries";
+import "./sms_manager";
+import "./utils/party";
+import "./controllers/stock_controller";
+import "./payment/payments";
+import "./controllers/taxes_and_totals";
+import "./controllers/transaction";
+import "./templates/item_selector.html";
+import "./templates/employees_to_mark_attendance.html";
+import "./utils/item_selector";
+import "./help_links";
+import "./agriculture/ternary_plot";
+import "./templates/item_quick_entry.html";
+import "./utils/item_quick_entry";
+import "./utils/customer_quick_entry";
+import "./education/student_button.html";
+import "./education/assessment_result_tool.html";
+import "./hub/hub_factory";
+import "./call_popup/call_popup";
+import "./utils/dimension_tree_filter";
+import "./telephony";
+import "./templates/call_link.html";
+
+// import { sum } from 'frappe/public/utils/util.js'
+
diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js
index 8dab2d6..9c67c1c 100644
--- a/erpnext/public/js/hub/hub_factory.js
+++ b/erpnext/public/js/hub/hub_factory.js
@@ -19,11 +19,7 @@
 	}
 
 	make(page_name) {
-		const assets = [
-			'/assets/js/marketplace.min.js'
-		];
-
-		frappe.require(assets, () => {
+		frappe.require('marketplace.bundle.js', () => {
 			erpnext.hub.marketplace = new erpnext.hub.Marketplace({
 				parent: this.make_page(true, page_name)
 			});
diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.bundle.js
similarity index 100%
rename from erpnext/public/js/hub/marketplace.js
rename to erpnext/public/js/hub/marketplace.bundle.js
diff --git a/erpnext/public/js/item-dashboard.bundle.js b/erpnext/public/js/item-dashboard.bundle.js
new file mode 100644
index 0000000..2d329e2
--- /dev/null
+++ b/erpnext/public/js/item-dashboard.bundle.js
@@ -0,0 +1,5 @@
+import "../../stock/dashboard/item_dashboard.html";
+import "../../stock/dashboard/item_dashboard_list.html";
+import "../../stock/dashboard/item_dashboard.js";
+import "../../stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html";
+import "../../stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html";
diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js
index ddf8706..4c23669 100644
--- a/erpnext/public/js/payment/payments.js
+++ b/erpnext/public/js/payment/payments.js
@@ -1,8 +1,8 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-erpnext.payments = erpnext.stock.StockController.extend({
-	make_payment: function() {
+erpnext.payments = class payments extends erpnext.stock.StockController {
+	make_payment() {
 		var me = this;
 
 		this.dialog = new frappe.ui.Dialog({
@@ -14,15 +14,15 @@
 		this.set_payment_primary_action();
 		this.make_keyboard();
 		this.select_text();
-	},
+	}
 
 	select_text() {
 		$(this.$body).find('.form-control').click(function() {
 			$(this).select();
 		});
-	},
+	}
 
-	set_payment_primary_action: function() {
+	set_payment_primary_action() {
 		var me = this;
 
 		this.dialog.set_primary_action(__("Submit"), function() {
@@ -35,18 +35,18 @@
 				}
 			});
 		})
-	},
+	}
 
-	make_keyboard: function() {
+	make_keyboard(){
 		var me = this;
 		$(this.$body).empty();
 		$(this.$body).html(frappe.render_template('pos_payment', this.frm.doc))
 		this.show_payment_details();
-		this.bind_keyboard_event();
-		this.clear_amount();
-	},
+		this.bind_keyboard_event()
+		this.clear_amount()
+	}
 
-	make_multimode_payment: function() {
+	make_multimode_payment(){
 		var me = this;
 
 		if (this.frm.doc.change_amount > 0) {
@@ -56,9 +56,9 @@
 		this.payments = frappe.model.add_child(this.frm.doc, 'Multi Mode Payment', "payments");
 		this.payments.mode_of_payment = this.dialog.fields_dict.mode_of_payment.get_value();
 		this.payments.amount = flt(this.payment_val);
-	},
+	}
 
-	show_payment_details: function() {
+	show_payment_details(){
 		var me = this;
 		var multimode_payments = $(this.$body).find('.multimode-payments').empty();
 		if (this.frm.doc.payments.length) {
@@ -81,9 +81,9 @@
 		}else{
 			$("<p>No payment mode selected in pos profile</p>").appendTo(multimode_payments)
 		}
-	},
+	}
 
-	set_outstanding_amount: function() {
+	set_outstanding_amount(){
 		this.selected_mode = $(this.$body).find(repl("input[idx='%(idx)s']",{'idx': this.idx}));
 		this.highlight_selected_row();
 		this.payment_val = 0.0;
@@ -98,15 +98,16 @@
 		}
 		this.selected_mode.select()
 		this.bind_amount_change_event();
-	},
+	}
 
-	bind_keyboard_event() {
+	bind_keyboard_event(){
+		var me = this;
 		this.payment_val = '';
 		this.bind_form_control_event();
 		this.bind_numeric_keys_event();
-	},
+	}
 
-	bind_form_control_event: function() {
+	bind_form_control_event() {
 		var me = this;
 		$(this.$body).find('.pos-payment-row').click(function() {
 			me.idx = $(this).attr("idx");
@@ -126,7 +127,7 @@
 		$(this.$body).find('.change_amount').change(function() {
 			me.change_amount(flt($(this).val()), precision("change_amount"));
 		});
-	},
+	}
 
 	highlight_selected_row() {
 		var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']", {'idx': this.idx}));
@@ -134,9 +135,9 @@
 		selected_row.addClass('selected-payment-mode');
 		$(this.$body).find('.amount').attr('disabled', true);
 		this.selected_mode.attr('disabled', false);
-	},
+	}
 
-	bind_numeric_keys_event: function() {
+	bind_numeric_keys_event() {
 		var me = this;
 		$(this.$body).find('.pos-keyboard-key').click(function(){
 			me.payment_val += $(this).text();
@@ -152,7 +153,7 @@
 			me.update_paid_amount();
 		})
 
-	},
+	}
 
 	bind_amount_change_event() {
 		var me = this;
@@ -162,9 +163,9 @@
 			me.idx = me.selected_mode.attr("idx");
 			me.update_payment_amount();
 		});
-	},
+	}
 
-	clear_amount: function() {
+	clear_amount() {
 		var me = this;
 		$(this.$body).find('.clr').click(function(e) {
 			e.stopPropagation();
@@ -175,7 +176,7 @@
 			me.highlight_selected_row();
 			me.update_payment_amount();
 		});
-	},
+	}
 
 	write_off_amount(write_off_amount) {
 		this.frm.doc.write_off_amount = flt(write_off_amount, precision("write_off_amount"));
@@ -183,17 +184,17 @@
 			precision("base_write_off_amount"));
 		this.calculate_outstanding_amount(false);
 		this.show_amounts();
-	},
+	}
 
-	change_amount: function(change_amount) {
+	change_amount(change_amount) {
 		var me = this;
 
 		this.frm.doc.change_amount = flt(change_amount, precision("change_amount"));
 		this.calculate_write_off_amount();
 		this.show_amounts();
-	},
+	}
 
-	update_paid_amount: function(update_write_off) {
+	update_paid_amount(update_write_off) {
 		var me = this;
 		if (in_list(['change_amount', 'write_off_amount'], this.idx)) {
 			var value = me.selected_mode.val();
@@ -208,9 +209,9 @@
 		} else {
 			this.update_payment_amount();
 		}
-	},
+	}
 
-	update_payment_amount: function() {
+	update_payment_amount(){
 		var me = this;
 
 		$.each(this.frm.doc.payments, function(index, data) {
@@ -221,9 +222,9 @@
 
 		this.calculate_outstanding_amount(false);
 		this.show_amounts();
-	},
+	}
 
-	show_amounts: function() {
+	show_amounts(){
 		var me = this;
 		$(this.$body).find(".write_off_amount").val(format_currency(this.frm.doc.write_off_amount, this.frm.doc.currency));
 		$(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency));
@@ -231,4 +232,4 @@
 		$(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency));
 		this.update_invoice();
 	}
-})
\ No newline at end of file
+}
diff --git a/erpnext/public/js/point-of-sale.bundle.js b/erpnext/public/js/point-of-sale.bundle.js
new file mode 100644
index 0000000..fbc4bbb
--- /dev/null
+++ b/erpnext/public/js/point-of-sale.bundle.js
@@ -0,0 +1,8 @@
+import "../../selling/page/point_of_sale/pos_item_selector.js";
+import "../../selling/page/point_of_sale/pos_item_cart.js";
+import "../../selling/page/point_of_sale/pos_item_details.js";
+import "../../selling/page/point_of_sale/pos_number_pad.js";
+import "../../selling/page/point_of_sale/pos_payment.js";
+import "../../selling/page/point_of_sale/pos_past_order_list.js";
+import "../../selling/page/point_of_sale/pos_past_order_summary.js";
+import "../../selling/page/point_of_sale/pos_controller.js";
diff --git a/erpnext/public/js/stock_analytics.js b/erpnext/public/js/stock_analytics.js
index 140c9dc..dfe2c88 100644
--- a/erpnext/public/js/stock_analytics.js
+++ b/erpnext/public/js/stock_analytics.js
@@ -2,8 +2,8 @@
 // License: GNU General Public License v3. See license.txt
 
 
-erpnext.StockAnalytics = erpnext.StockGridReport.extend({
-	init: function(wrapper, opts) {
+erpnext.StockAnalytics = class StockAnalytics extends erpnext.StockGridReport {
+	constructor(wrapper, opts) {
 		var args = {
 			title: __("Stock Analytics"),
 			parent: $(wrapper).find('.layout-main'),
@@ -30,9 +30,33 @@
 
 		if(opts) $.extend(args, opts);
 
-		this._super(args);
-	},
-	setup_columns: function() {
+		super(args);
+
+		this.filters = [
+			{fieldtype:"Select", label: __("Value or Qty"), fieldname: "value_or_qty",
+				options:[{label:__("Value"), value:"Value"}, {label:__("Quantity"), value:"Quantity"}],
+				filter: function(val, item, opts, me) {
+					return me.apply_zero_filter(val, item, opts, me);
+				}},
+			{fieldtype:"Select", label: __("Brand"), link:"Brand", fieldname: "brand",
+				default_value: __("Select Brand..."), filter: function(val, item, opts) {
+					return val == opts.default_value || item.brand == val || item._show;
+				}, link_formatter: {filter_input: "brand"}},
+			{fieldtype:"Select", label: __("Warehouse"), link:"Warehouse", fieldname: "warehouse",
+				default_value: __("Select Warehouse...")},
+			{fieldtype:"Date", label: __("From Date"), fieldname: "from_date"},
+			{fieldtype:"Date", label: __("To Date"), fieldname: "to_date"},
+			{fieldtype:"Select", label: __("Range"), fieldname: "range",
+				options:[
+					{label:__("Daily"), value:"Daily"},
+					{label:__("Weekly"), value:"Weekly"},
+					{label:__("Monthly"), value:"Monthly"},
+					{label:__("Quarterly"), value:"Quarterly"},
+					{label:__("Yearly"), value:"Yearly"},
+				]}
+		];
+	}
+	setup_columns() {
 		var std_columns = [
 			{id: "name", name: __("Item"), field: "name", width: 300},
 			{id: "brand", name: __("Brand"), field: "brand", width: 100},
@@ -43,43 +67,21 @@
 
 		this.make_date_range_columns();
 		this.columns = std_columns.concat(this.columns);
-	},
-	filters: [
-		{fieldtype:"Select", label: __("Value or Qty"), fieldname: "value_or_qty",
-			options:[{label:__("Value"), value:"Value"}, {label:__("Quantity"), value:"Quantity"}],
-			filter: function(val, item, opts, me) {
-				return me.apply_zero_filter(val, item, opts, me);
-			}},
-		{fieldtype:"Select", label: __("Brand"), link:"Brand", fieldname: "brand",
-			default_value: __("Select Brand..."), filter: function(val, item, opts) {
-				return val == opts.default_value || item.brand == val || item._show;
-			}, link_formatter: {filter_input: "brand"}},
-		{fieldtype:"Select", label: __("Warehouse"), link:"Warehouse", fieldname: "warehouse",
-			default_value: __("Select Warehouse...")},
-		{fieldtype:"Date", label: __("From Date"), fieldname: "from_date"},
-		{fieldtype:"Date", label: __("To Date"), fieldname: "to_date"},
-		{fieldtype:"Select", label: __("Range"), fieldname: "range",
-			options:[
-				{label:__("Daily"), value:"Daily"},
-				{label:__("Weekly"), value:"Weekly"},
-				{label:__("Monthly"), value:"Monthly"},
-				{label:__("Quarterly"), value:"Quarterly"},
-				{label:__("Yearly"), value:"Yearly"},
-			]}
-	],
-	setup_filters: function() {
+	}
+
+	setup_filters() {
 		var me = this;
-		this._super();
+		super.setup_filters();
 
 		this.trigger_refresh_on_change(["value_or_qty", "brand", "warehouse", "range"]);
 
 		this.show_zero_check();
-	},
-	init_filter_values: function() {
-		this._super();
+	}
+	init_filter_values() {
+		super.init_filter_values();
 		this.filter_inputs.range && this.filter_inputs.range.val('Monthly');
-	},
-	prepare_data: function() {
+	}
+	prepare_data() {
 		var me = this;
 
 		if(!this.data) {
@@ -112,8 +114,8 @@
 		this.prepare_balances();
 		this.update_groups();
 
-	},
-	prepare_balances: function() {
+	}
+	prepare_balances() {
 		var me = this;
 		var from_date = frappe.datetime.str_to_obj(this.from_date);
 		var to_date = frappe.datetime.str_to_obj(this.to_date);
@@ -164,8 +166,8 @@
 				item.closing_qty_value += diff;
 			}
 		}
-	},
-	update_groups: function() {
+	}
+	update_groups() {
 		var me = this;
 		$.each(this.data, function(i, item) {
 			// update groups
@@ -192,8 +194,8 @@
 				}
 			}
 		});
-	},
-	show_stock_ledger: function(item_code) {
+	}
+	show_stock_ledger(item_code) {
 		frappe.route_options = {
 			item_code: item_code,
 			from_date: this.from_date,
@@ -201,5 +203,5 @@
 		};
 		frappe.set_route("query-report", "Stock Ledger");
 	}
-});
+};
 
diff --git a/erpnext/public/js/stock_grid_report.js b/erpnext/public/js/stock_grid_report.js
index 832fd3e..752fafd 100644
--- a/erpnext/public/js/stock_grid_report.js
+++ b/erpnext/public/js/stock_grid_report.js
@@ -1,16 +1,16 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
-	get_item_warehouse: function(warehouse, item) {
+erpnext.StockGridReport = class StockGridReport extends frappe.views.TreeGridReport {
+	get_item_warehouse(warehouse, item) {
 		if(!this.item_warehouse[item]) this.item_warehouse[item] = {};
 		if(!this.item_warehouse[item][warehouse]) this.item_warehouse[item][warehouse] = {
 			balance_qty: 0.0, balance_value: 0.0, fifo_stack: []
 		};
 		return this.item_warehouse[item][warehouse];
-	},
+	}
 
-	get_value_diff: function(wh, sl, is_fifo) {
+	get_value_diff(wh, sl, is_fifo) {
 		// value
 		if(sl.qty > 0) {
 			// incoming - rate is given
@@ -59,8 +59,8 @@
 		wh.balance_qty += sl.qty;
 		wh.balance_value += value_diff;
 		return value_diff;
-	},
-	get_fifo_value_diff: function(wh, sl) {
+	}
+	get_fifo_value_diff(wh, sl) {
 		// get exact rate from fifo stack
 		var fifo_stack = (wh.fifo_stack || []).reverse();
 		var fifo_value_diff = 0.0;
@@ -89,9 +89,9 @@
 		// reset the updated stack
 		wh.fifo_stack = fifo_stack.reverse();
 		return -fifo_value_diff;
-	},
+	}
 
-	get_serialized_value_diff: function(sl) {
+	get_serialized_value_diff(sl) {
 		var me = this;
 
 		var value_diff = 0.0;
@@ -103,9 +103,9 @@
 		});
 
 		return value_diff;
-	},
+	}
 
-	get_serialized_buying_rates: function() {
+	get_serialized_buying_rates() {
 		var serialized_buying_rates = {};
 
 		if (frappe.report_dump.data["Serial No"]) {
@@ -115,5 +115,5 @@
 		}
 
 		return serialized_buying_rates;
-	},
-});
+	}
+};
diff --git a/erpnext/public/js/telephony.js b/erpnext/public/js/telephony.js
index 9548d6c..1c3e314 100644
--- a/erpnext/public/js/telephony.js
+++ b/erpnext/public/js/telephony.js
@@ -1,19 +1,19 @@
-frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
+frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlData  {
 	make_input() {
-		this._super();
+		super.make_input();
 		if (this.df.options == 'Phone') {
 			this.setup_phone();
 		}
 		if (this.frm && this.frm.fields_dict) {
 			Object.values(this.frm.fields_dict).forEach(function(field) {
-				if (field.df.read_only === 1 && field.df.options === 'Phone' 
+				if (field.df.read_only === 1 && field.df.options === 'Phone'
 					&& field.disp_area.style[0] != 'display' && !field.has_icon) {
 					field.setup_phone();
 					field.has_icon = true;
 				}
 			});
 		}
-	},
+	}
 	setup_phone() {
 		if (frappe.phone_call.handler) {
 			let control = this.df.read_only ? '.control-value' : '.control-input';
@@ -30,4 +30,4 @@
 				});
 		}
 	}
-});
+};
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index ce40ced..db7c034 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -749,6 +749,151 @@
 	}
 });
 
+// Show SLA dashboard
+$(document).on('app_ready', function() {
+	frappe.call({
+		method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_sla_doctypes',
+		callback: function(r) {
+			if (!r.message)
+				return;
+
+			$.each(r.message, function(_i, d) {
+				frappe.ui.form.on(d, {
+					onload: function(frm) {
+						if (!frm.doc.service_level_agreement)
+							return;
+
+						frappe.call({
+							method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
+							args: {
+								doctype: frm.doc.doctype,
+								name: frm.doc.service_level_agreement,
+								customer: frm.doc.customer
+							},
+							callback: function (r) {
+								if (r && r.message) {
+									frm.set_query('priority', function() {
+										return {
+											filters: {
+												'name': ['in', r.message.priority],
+											}
+										};
+									});
+									frm.set_query('service_level_agreement', function() {
+										return {
+											filters: {
+												'name': ['in', r.message.service_level_agreements],
+											}
+										};
+									});
+								}
+							}
+						});
+					},
+
+					refresh: function(frm) {
+						if (frm.doc.status !== 'Closed' && frm.doc.service_level_agreement
+							&& frm.doc.agreement_status === 'Ongoing') {
+							frappe.call({
+								'method': 'frappe.client.get',
+								args: {
+									doctype: 'Service Level Agreement',
+									name: frm.doc.service_level_agreement
+								},
+								callback: function(data) {
+									let statuses = data.message.pause_sla_on;
+									const hold_statuses = [];
+									$.each(statuses, (_i, entry) => {
+										hold_statuses.push(entry.status);
+									});
+									if (hold_statuses.includes(frm.doc.status)) {
+										frm.dashboard.clear_headline();
+										let message = {'indicator': 'orange', 'msg': __('SLA is on hold since {0}', [moment(frm.doc.on_hold_since).fromNow(true)])};
+										frm.dashboard.set_headline_alert(
+											'<div class="row">' +
+												'<div class="col-xs-12">' +
+													'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
+												'</div>' +
+											'</div>'
+										);
+									} else {
+										set_time_to_resolve_and_response(frm, data.message.apply_sla_for_resolution);
+									}
+								}
+							});
+						} else if (frm.doc.service_level_agreement) {
+							frm.dashboard.clear_headline();
+
+							let agreement_status = (frm.doc.agreement_status == 'Fulfilled') ?
+								{'indicator': 'green', 'msg': 'Service Level Agreement has been fulfilled'} :
+								{'indicator': 'red', 'msg': 'Service Level Agreement Failed'};
+
+							frm.dashboard.set_headline_alert(
+								'<div class="row">' +
+									'<div class="col-xs-12">' +
+										'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
+									'</div>' +
+								'</div>'
+							);
+						}
+					},
+				});
+			});
+		}
+	});
+});
+
+function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) {
+	frm.dashboard.clear_headline();
+
+	let time_to_respond = get_status(frm.doc.response_by_variance);
+	if (!frm.doc.first_responded_on && frm.doc.agreement_status === 'Ongoing') {
+		time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status);
+	}
+
+	let alert = `
+		<div class="row">
+			<div class="col-xs-12 col-sm-6">
+				<span class="indicator whitespace-nowrap ${time_to_respond.indicator}">
+					<span>Time to Respond: ${time_to_respond.diff_display}</span>
+				</span>
+			</div>`;
+
+
+	if (apply_sla_for_resolution) {
+		let time_to_resolve = get_status(frm.doc.resolution_by_variance);
+		if (!frm.doc.resolution_date && frm.doc.agreement_status === 'Ongoing') {
+			time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status);
+		}
+
+		alert += `
+			<div class="col-xs-12 col-sm-6">
+				<span class="indicator whitespace-nowrap ${time_to_resolve.indicator}">
+					<span>Time to Resolve: ${time_to_resolve.diff_display}</span>
+				</span>
+			</div>`;
+	}
+
+	alert += '</div>';
+
+	frm.dashboard.set_headline_alert(alert);
+}
+
+function get_time_left(timestamp, agreement_status) {
+	const diff = moment(timestamp).diff(moment());
+	const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed';
+	let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green';
+	return {'diff_display': diff_display, 'indicator': indicator};
+}
+
+function get_status(variance) {
+	if (variance > 0) {
+		return {'diff_display': 'Fulfilled', 'indicator': 'green'};
+	} else {
+		return {'diff_display': 'Failed', 'indicator': 'red'};
+	}
+}
+
 function attach_selector_button(inner_text, append_loction, context, grid_row) {
 	let $btn_div = $("<div>").css({"margin-bottom": "10px", "margin-top": "10px"})
 		.appendTo(append_loction);
diff --git a/erpnext/public/js/utils/customer_quick_entry.js b/erpnext/public/js/utils/customer_quick_entry.js
index ebe6cd9..efb8dd9 100644
--- a/erpnext/public/js/utils/customer_quick_entry.js
+++ b/erpnext/public/js/utils/customer_quick_entry.js
@@ -1,17 +1,17 @@
 frappe.provide('frappe.ui.form');
 
-frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
-	init: function(doctype, after_insert) {
+frappe.ui.form.CustomerQuickEntryForm = class CustomerQuickEntryForm extends frappe.ui.form.QuickEntryForm {
+	constructor(doctype, after_insert) {
+		super(doctype, after_insert);
 		this.skip_redirect_on_error = true;
-		this._super(doctype, after_insert);
-	},
+	}
 
-	render_dialog: function() {
+	render_dialog() {
 		this.mandatory = this.mandatory.concat(this.get_variant_fields());
-		this._super();
-	},
+		super.render_dialog();
+	}
 
-	get_variant_fields: function() {
+	get_variant_fields() {
 		var variant_fields = [{
 			fieldtype: "Section Break",
 			label: __("Primary Contact Details"),
@@ -77,5 +77,5 @@
 		}];
 
 		return variant_fields;
-	},
-})
\ No newline at end of file
+	}
+}
diff --git a/erpnext/public/js/utils/item_quick_entry.js b/erpnext/public/js/utils/item_quick_entry.js
index 27ef107..7e0198d 100644
--- a/erpnext/public/js/utils/item_quick_entry.js
+++ b/erpnext/public/js/utils/item_quick_entry.js
@@ -1,27 +1,27 @@
 frappe.provide('frappe.ui.form');
 
-frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
-	init: function(doctype, after_insert) {
-		this._super(doctype, after_insert);
-	},
+frappe.ui.form.ItemQuickEntryForm = class ItemQuickEntryForm extends frappe.ui.form.QuickEntryForm {
+	constructor(doctype, after_insert) {
+		super(doctype, after_insert);
+	}
 
-	render_dialog: function() {
+	render_dialog() {
 		this.mandatory = this.get_variant_fields().concat(this.mandatory);
 		this.mandatory = this.mandatory.concat(this.get_attributes_fields());
 		this.check_naming_series_based_on();
-		this._super();
+		super.render_dialog();
 		this.init_post_render_dialog_operations();
 		this.preset_fields_for_template();
 		this.dialog.$wrapper.find('.edit-full').text(__('Edit in full page for more options like assets, serial nos, batches etc.'))
-	},
+	}
 
-	check_naming_series_based_on: function() {
+	check_naming_series_based_on() {
 		if (frappe.defaults.get_default("item_naming_by") === "Naming Series") {
 			this.mandatory = this.mandatory.filter(d => d.fieldname !== "item_code");
 		}
-	},
+	}
 
-	init_post_render_dialog_operations: function() {
+	init_post_render_dialog_operations() {
 		this.dialog.fields_dict.attribute_html.$wrapper.append(frappe.render_template("item_quick_entry"));
 		this.init_for_create_variant_trigger();
 		this.init_for_item_template_trigger();
@@ -29,9 +29,9 @@
 		this.toggle_manufacturer_fields();
 		this.dialog.get_field("item_template").df.hidden = 1;
 		this.dialog.get_field("item_template").refresh();
-	},
+	}
 
-	register_primary_action: function() {
+	register_primary_action() {
 		var me = this;
 		this.dialog.set_primary_action(__('Save'), function() {
 			if (me.dialog.working) return;
@@ -59,9 +59,9 @@
 				me.insert(variant_values);
 			}
 		});
-	},
+	}
 
-	insert: function(variant_values) {
+	insert(variant_values) {
 		let me = this;
 		return new Promise(resolve => {
 			frappe.call({
@@ -94,9 +94,9 @@
 				freeze: true
 			});
 		});
-	},
+	}
 
-	open_doc: function() {
+	open_doc() {
 		this.dialog.hide();
 		this.update_doc();
 		if (this.dialog.fields_dict.create_variant.$input.prop("checked")) {
@@ -106,9 +106,9 @@
 		} else {
 			frappe.set_route('Form', this.doctype, this.doc.name);
 		}
-	},
+	}
 
-	get_variant_fields: function() {
+	get_variant_fields() {
 		var variant_fields = [{
 			fieldname: "create_variant",
 			fieldtype: "Check",
@@ -130,9 +130,9 @@
 		}];
 
 		return variant_fields;
-	},
+	}
 
-	get_manufacturing_fields: function() {
+	get_manufacturing_fields() {
 		this.manufacturer_fields = [{
 			fieldtype: 'Link',
 			options: 'Manufacturer',
@@ -148,9 +148,9 @@
 			reqd: 0
 		}];
 		return this.manufacturer_fields;
-	},
+	}
 
-	get_attributes_fields: function() {
+	get_attributes_fields() {
 		var attribute_fields = [{
 			fieldname: 'attribute_html',
 			fieldtype: 'HTML'
@@ -158,18 +158,18 @@
 
 		attribute_fields = attribute_fields.concat(this.get_manufacturing_fields());
 		return attribute_fields;
-	},
+	}
 
-	init_for_create_variant_trigger: function() {
+	init_for_create_variant_trigger() {
 		var me = this;
 
 		this.dialog.fields_dict.create_variant.$input.on("click", function() {
 			me.preset_fields_for_template();
 			me.init_post_template_trigger_operations(false, [], true);
 		});
-	},
+	}
 
-	preset_fields_for_template: function() {
+	preset_fields_for_template() {
 		var for_variant = this.dialog.get_value('create_variant');
 
 		// setup template field, seen and mandatory if variant
@@ -195,9 +195,9 @@
 			f.refresh();
 		});
 
-	},
+	}
 
-	init_for_item_template_trigger: function() {
+	init_for_item_template_trigger() {
 		var me = this;
 
 		me.dialog.fields_dict["item_template"].df.onchange = () => {
@@ -228,9 +228,9 @@
 				me.init_post_template_trigger_operations(false, [], true);
 			}
 		}
-	},
+	}
 
-	init_post_template_trigger_operations: function(is_manufacturer, attributes, attributes_flag) {
+	init_post_template_trigger_operations(is_manufacturer, attributes, attributes_flag) {
 		this.attributes = attributes;
 		this.attribute_values = {};
 		this.attributes_count = attributes.length;
@@ -240,23 +240,23 @@
 		this.toggle_manufacturer_fields();
 		this.dialog.fields_dict.attribute_html.$wrapper.find(".attributes").toggleClass("hide-control", attributes_flag);
 		this.dialog.fields_dict.attribute_html.$wrapper.find(".attributes-header").toggleClass("hide-control", attributes_flag);
-	},
+	}
 
-	toggle_manufacturer_fields: function() {
+	toggle_manufacturer_fields() {
 		var me = this;
 		$.each(this.manufacturer_fields, function(i, dialog_field) {
 			me.dialog.get_field(dialog_field.fieldname).df.hidden = !me.is_manufacturer;
 			me.dialog.get_field(dialog_field.fieldname).df.reqd = dialog_field.fieldname == 'manufacturer' ? me.is_manufacturer : false;
 			me.dialog.get_field(dialog_field.fieldname).refresh();
 		});
-	},
+	}
 
-	initiate_render_attributes: function() {
+	initiate_render_attributes() {
 		this.dialog.fields_dict.attribute_html.$wrapper.find(".attributes").empty();
 		this.render_attributes(this.attributes);
-	},
+	}
 
-	render_attributes: function(attributes) {
+	render_attributes(attributes) {
 		var me = this;
 
 		this.dialog.get_field('attribute_html').toggle(true);
@@ -291,9 +291,9 @@
 				});
 			}
 		});
-	},
+	}
 
-	init_make_control: function(fieldtype, row) {
+	init_make_control(fieldtype, row) {
 		this[row.attribute] = frappe.ui.form.make_control({
 			df: {
 				"fieldtype": fieldtype,
@@ -305,9 +305,9 @@
 			only_input: false
 		});
 		this[row.attribute].make_input();
-	},
+	}
 
-	init_awesomplete_for_attribute: function(row) {
+	init_awesomplete_for_attribute(row) {
 		var me = this;
 
 		this[row.attribute].input.awesomplete = new Awesomplete(this[row.attribute].input, {
@@ -343,9 +343,9 @@
 			me.attribute_values[$(e.target).attr("data-fieldname")] = e.target.value;
 			$(e.target).closest(".frappe-control").toggleClass("has-error", e.target.value ? false : true);
 		});
-	},
+	}
 
-	get_variant_doc: function() {
+	get_variant_doc() {
 		var me = this;
 		var variant_doc = {};
 		var attribute = this.validate_mandatory_attributes();
@@ -381,9 +381,9 @@
 			})
 		}
 		return variant_doc;
-	},
+	}
 
-	validate_mandatory_attributes: function() {
+	validate_mandatory_attributes() {
 		var me = this;
 		var attribute = {};
 		var mandatory = [];
@@ -404,4 +404,4 @@
 		}
 		return attribute;
 	}
-});
\ No newline at end of file
+};
diff --git a/erpnext/public/js/utils/item_selector.js b/erpnext/public/js/utils/item_selector.js
index d04c488..9fc2640 100644
--- a/erpnext/public/js/utils/item_selector.js
+++ b/erpnext/public/js/utils/item_selector.js
@@ -1,5 +1,5 @@
-erpnext.ItemSelector = Class.extend({
-	init: function(opts) {
+erpnext.ItemSelector = class ItemSelector {
+	constructor(opts) {
 		$.extend(this, opts);
 
 		if (!this.item_field) {
@@ -12,9 +12,9 @@
 
 		this.grid = this.frm.get_field("items").grid;
 		this.setup();
-	},
+	}
 
-	setup: function() {
+	setup() {
 		var me = this;
 		if(!this.grid.add_items_button) {
 			this.grid.add_items_button = this.grid.add_custom_button(__('Add Items'), function() {
@@ -26,9 +26,9 @@
 				setTimeout(function() { me.dialog.input.focus(); }, 1000);
 			});
 		}
-	},
+	}
 
-	make_dialog: function() {
+	make_dialog() {
 		this.dialog = new frappe.ui.Dialog({
 			title: __('Add Items')
 		});
@@ -53,9 +53,9 @@
 				me.timeout_id = undefined;
 			}, 500);
 		});
-	},
+	}
 
-	add_item: function(item_code) {
+	add_item(item_code) {
 		// add row or update qty
 		var added = false;
 
@@ -82,9 +82,9 @@
 			]);
 		}
 
-	},
+	}
 
-	render_items: function() {
+	render_items() {
 		let args = {
 			query: this.item_query,
 			filters: {}
@@ -107,4 +107,4 @@
 			me.dialog.results.html(frappe.render_template('item_selector', {'data':r.values}));
 		});
 	}
-});
\ No newline at end of file
+};
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index b5d3981..597d77c 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -1,6 +1,6 @@
 
-erpnext.SerialNoBatchSelector = Class.extend({
-	init: function(opts, show_dialog) {
+erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
+	constructor(opts, show_dialog) {
 		$.extend(this, opts);
 		this.show_dialog = show_dialog;
 		// frm, item, warehouse_details, has_batch, oldest
@@ -12,16 +12,16 @@
 		if(d && d.has_serial_no && !(this.show_dialog == false)) this.has_serial_no = 1;
 
 		this.setup();
-	},
+	}
 
-	setup: function() {
+	setup() {
 		this.item_code = this.item.item_code;
 		this.qty = this.item.qty;
 		this.make_dialog();
 		this.on_close_dialog();
-	},
+	}
 
-	make_dialog: function() {
+	make_dialog() {
 		var me = this;
 
 		this.data = this.oldest ? this.oldest : [];
@@ -186,15 +186,15 @@
 		}
 
 		this.dialog.show();
-	},
+	}
 
-	on_close_dialog: function() {
+	on_close_dialog() {
 		this.dialog.get_close_btn().on('click', () => {
 			this.on_close && this.on_close(this.item);
 		});
-	},
+	}
 
-	validate: function() {
+	validate() {
 		let values = this.values;
 		if(!values.warehouse) {
 			frappe.throw(__("Please select a warehouse"));
@@ -220,7 +220,7 @@
 			}
 			return true;
 		}
-	},
+	}
 
 	update_batch_items() {
 		// clones an items if muliple batches are selected.
@@ -243,14 +243,14 @@
 					'selected_qty', this.values.warehouse);
 			});
 		}
-	},
+	}
 
 	update_serial_no_item() {
 		// just updates serial no for the item
 		if(this.has_serial_no && !this.has_batch) {
 			this.map_row_values(this.item, this.values, 'serial_no', 'qty');
 		}
-	},
+	}
 
 	update_batch_serial_no_items() {
 		// if serial no selected is from different batches, adds new rows for each batch.
@@ -291,14 +291,14 @@
 				});
 			})
 		}
-	},
+	}
 
-	batch_exists: function(batch) {
+	batch_exists(batch) {
 		const batches = this.frm.doc.items.map(data => data.batch_no);
 		return (batches && in_list(batches, batch)) ? true : false;
-	},
+	}
 
-	map_row_values: function(row, values, number, qty_field, warehouse) {
+	map_row_values(row, values, number, qty_field, warehouse) {
 		row.qty = values[qty_field];
 		row.transfer_qty = flt(values[qty_field]) * flt(row.conversion_factor);
 		row[number] = values[number];
@@ -311,9 +311,9 @@
 		}
 
 		this.frm.dirty();
-	},
+	}
 
-	update_total_qty: function() {
+	update_total_qty() {
 		let qty_field = this.dialog.fields_dict.qty;
 		let total_qty = 0;
 
@@ -322,8 +322,9 @@
 		});
 
 		qty_field.set_input(total_qty);
-	},
-	update_pending_qtys: function() {
+	}
+
+	update_pending_qtys() {
 		const pending_qty_field = this.dialog.fields_dict.pending_qty;
 		const total_selected_qty_field = this.dialog.fields_dict.total_selected_qty;
 
@@ -337,8 +338,9 @@
 
 		pending_qty_field.set_input(pending_qty);
 		total_selected_qty_field.set_input(total_selected_qty);
-	},
-	get_batch_fields: function() {
+	}
+
+	get_batch_fields() {
 		var me = this;
 
 		return [
@@ -450,9 +452,9 @@
 				},
 			}
 		];
-	},
+	}
 
-	get_serial_no_fields: function() {
+	get_serial_no_fields() {
 		var me = this;
 		this.serial_list = [];
 
@@ -535,7 +537,7 @@
 			}
 		];
 	}
-});
+};
 
 function get_pending_qty_fields(me) {
 	if (!check_can_calculate_pending_qty(me)) return [];
diff --git a/erpnext/public/less/email.less b/erpnext/public/less/email.less
deleted file mode 100644
index 4077c49..0000000
--- a/erpnext/public/less/email.less
+++ /dev/null
@@ -1,32 +0,0 @@
-@import "../../../../frappe/frappe/public/less/variables.less";
-
-.panel-header {
-	background-color: @light-bg;
-	border: 1px solid @border-color;
-	border-radius: 3px 3px 0 0;
-}
-
-.panel-body {
-	background-color: #fff;
-	border: 1px solid @border-color;
-	border-top: none;
-	border-radius: 0 0 3px 3px;
-	overflow-wrap: break-word;
-}
-
-.sender-avatar {
-	width: 24px;
-	height: 24px;
-	border-radius: 3px;
-	vertical-align: middle;
-}
-
-.sender-avatar-placeholder {
-	.sender-avatar;
-
-	line-height: 24px;
-	text-align: center;
-	color: @border-color;
-	border: 1px solid @border-color;
-	background-color: #fff;
-}
\ No newline at end of file
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
deleted file mode 100644
index 29deada..0000000
--- a/erpnext/public/less/hub.less
+++ /dev/null
@@ -1,375 +0,0 @@
-@import "variables.less";
-@import (reference) "desk.less";
-
-body[data-route*="marketplace"] {
-	.layout-side-section {
-		padding-top: 25px;
-		padding-left: 5px;
-		padding-right: 25px;
-	}
-
-	[data-route], [data-action] {
-		cursor: pointer;
-	}
-
-	.layout-main-section {
-		border: none;
-		font-size: @text-medium;
-		padding-top: 25px;
-
-		@media (max-width: @screen-xs) {
-			padding-left: 20px;
-			padding-right: 20px;
-		}
-	}
-
-	input, textarea {
-		font-size: @text-medium;
-	}
-
-	.hub-image {
-		height: 200px;
-	}
-
-	.hub-image-loading, .hub-image-broken {
-		content: " ";
-		position: absolute;
-		left: 0;
-		height: 100%;
-		width: 100%;
-		background-color: var(--bg-light-gray);
-		display: flex;
-		align-items: center;
-		justify-content: center;
-
-		span {
-			font-size: 32px;
-			color: @text-extra-muted;
-		}
-	}
-
-	.progress-bar {
-		background-color: #89da28;
-	}
-
-	.subpage-title.flex {
-		align-items: flex-start;
-		justify-content: space-between;
-	}
-
-	.hub-card {
-		margin-bottom: 25px;
-		position: relative;
-		border: 1px solid @border-color;
-		border-radius: 4px;
-		overflow: hidden;
-
-		&:hover .hub-card-overlay {
-			display: block;
-		}
-	}
-
-	.hub-card.is-local {
-		&.active {
-			.hub-card-header {
-				background-color: #f4ffe5;
-			}
-
-			.octicon-check {
-				display: inline;
-			}
-		}
-
-		.octicon-check {
-			display: none;
-			position: absolute;
-			font-size: 20px;
-			right: 15px;
-			top: 50%;
-			transform: translateY(-50%);
-		}
-	}
-
-	.hub-card-header {
-		position: relative;
-		padding: 12px 15px;
-		height: 60px;
-		border-bottom: 1px solid @border-color;
-	}
-
-	.hub-card-body {
-		position: relative;
-		height: 200px;
-	}
-
-	.hub-card-overlay {
-		display: none;
-		position: absolute;
-		top: 0;
-		width: 100%;
-		height: 100%;
-		background-color: rgba(0, 0, 0, 0.05);
-	}
-
-	.hub-card-overlay-body {
-		position: relative;
-		height: 100%;
-	}
-
-	.hub-card-overlay-button {
-		position: absolute;
-		right: 15px;
-		bottom: 15px;
-	}
-
-	.hub-card-image {
-		position: relative;
-		width: 100%;
-		height: 100%;
-		object-fit: contain;
-	}
-
-	.hub-search-container {
-		margin-bottom: 20px;
-
-		input {
-			height: 32px;
-		}
-	}
-
-	.hub-sidebar {
-		padding-top: 25px;
-		padding-right: 15px;
-	}
-
-	.hub-sidebar-group {
-		margin-bottom: 10px;
-	}
-
-	.hub-sidebar-item {
-		padding: 5px 8px;
-		margin-bottom: 3px;
-		border-radius: 4px;
-		border: 1px solid transparent;
-
-		&.active, &:hover:not(.is-title) {
-			border-color: @border-color;
-		}
-	}
-
-	.hub-item-image {
-		position: relative;
-		border: 1px solid @border-color;
-		border-radius: 4px;
-		overflow: hidden;
-		height: 200px;
-		width: 200px;
-		display: flex;
-		align-items: center;
-	}
-
-	.hub-item-skeleton-image {
-		border-radius: 4px;
-		background-color: @light-bg;
-		overflow: hidden;
-		height: 200px;
-		width: 200px;
-	}
-
-	.hub-skeleton {
-		background-color: @light-bg;
-		color: @light-bg;
-		max-width: 500px;
-	}
-
-	.hub-item-seller img {
-		width: 50px;
-		height: 50px;
-		border-radius: 4px;
-		border: 1px solid @border-color;
-	}
-
-	.register-title {
-		font-size: @text-regular;
-	}
-
-	.register-form {
-		border: 1px solid @border-color;
-		border-radius: 4px;
-		padding: 15px 25px;
-	}
-
-	.publish-area.filled {
-		.empty-items-container {
-			display: none;
-		}
-	}
-
-	.publish-area.empty {
-		.hub-items-container {
-			display: none;
-		}
-	}
-
-	.publish-area-head {
-		display: flex;
-		justify-content: space-between;
-		margin-bottom: 20px;
-	}
-
-	.hub-list-item {
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-		border: 1px solid @border-color;
-		margin-bottom: -1px;
-		overflow: hidden;
-	}
-
-	.hub-list-item:first-child {
-		border-top-left-radius: 4px;
-		border-top-right-radius: 4px;
-	}
-	.hub-list-item:last-child {
-		border-bottom-left-radius: 4px;
-		border-bottom-right-radius: 4px;
-	}
-
-	.hub-list-left {
-		display: flex;
-		align-items: center;
-		max-width: 90%;
-	}
-
-	.hub-list-right {
-		padding-right: 15px;
-	}
-
-	.hub-list-image {
-		position: relative;
-		width: 58px;
-		height: 58px;
-		border-right: 1px solid @border-color;
-
-		&::after {
-			font-size: 12px;
-		}
-	}
-
-	.hub-list-body {
-		padding: 12px 15px;
-	}
-
-	.hub-list-title {
-		font-weight: bold;
-	}
-
-	.hub-list-subtitle {
-		color: @text-muted;
-	}
-
-	.selling-item-message-card {
-		max-width: 500px;
-		margin-bottom: 15px;
-		border-radius: 3px;
-		border: 1px solid @border-color;
-		.selling-item-detail {
-			overflow: auto;
-			.item-image {
-				float: left;
-				height: 80px;
-				width: 80px;
-				object-fit: contain;
-				margin: 5px;
-			}
-			.item-name {
-				margin-left: 10px;
-			}
-		}
-		.received-message-container {
-			clear: left;
-			background-color: @light-bg;
-			.received-message {
-				border-top: 1px solid @border-color;
-				padding: 10px;
-			}
-			.frappe-timestamp {
-				float: right;
-			}
-		}
-	}
-
-	.form-container {
-		.frappe-control {
-			max-width: 100% !important;
-		}
-	}
-
-	.form-message {
-		padding-top: 0;
-		padding-bottom: 0;
-		border-bottom: none;
-	}
-
-	.hub-items-container {
-		.hub-items-header {
-			justify-content: space-between;
-			align-items: baseline;
-		}
-	}
-
-	.hub-item-container {
-		overflow: hidden;
-	}
-
-	.hub-item-review-container {
-		margin-top: calc(30vh);
-	}
-
-	.hub-item-dropdown {
-		margin-top: 20px;
-	}
-
-	/* messages page */
-
-	.message-list-item {
-		display: flex;
-		align-items: center;
-		padding: 8px 12px;
-
-		&:not(.active) {
-			filter: grayscale(1);
-			color: @text-muted;
-		}
-
-		&:hover {
-			background-color: @light-bg;
-		}
-
-		.list-item-left {
-			width: 30px;
-			border-radius: 4px;
-			overflow: hidden;
-			margin-right: 15px;
-		}
-
-		.list-item-body {
-			font-weight: bold;
-			padding-bottom: 1px;
-		}
-	}
-
-	.message-container {
-		display: flex;
-		flex-direction: column;
-		border: 1px solid @border-color;
-		border-radius: 3px;
-		height: calc(100vh - 300px);
-		justify-content: space-between;
-		padding: 15px;
-	}
-
-	.message-list {
-		overflow: scroll;
-	}
-}
diff --git a/erpnext/public/less/pos.less b/erpnext/public/less/pos.less
deleted file mode 100644
index b081ed4..0000000
--- a/erpnext/public/less/pos.less
+++ /dev/null
@@ -1,229 +0,0 @@
-@import "../../../../frappe/frappe/public/less/variables.less";
-
-[data-route="point-of-sale"] {
-	.layout-main-section-wrapper {
-		margin-bottom: 0;
-	}
-
-	.pos-items-wrapper {
-		max-height: ~"calc(100vh - 210px)";
-	}
-}
-
-.pos {
-	// display: flex;
-	padding: 15px;
-}
-
-.list-item {
-	min-height: 40px;
-	height: auto;
-}
-
-.cart-container {
-	padding: 0 15px;
-	// flex: 2;
-	display: inline-block;
-	width: 39%;
-	vertical-align: top;
-}
-
-.item-container {
-	padding: 0 15px;
-	// flex: 3;
-	display: inline-block;
-	width: 60%;
-	vertical-align: top;
-}
-
-.search-field {
-	width: 60%;
-
-	input::placeholder {
-		font-size: @text-medium;
-	}
-}
-
-.item-group-field {
-	width: 40%;
-	margin-left: 15px;
-}
-
-.cart-wrapper {
-	margin-bottom: 12px;
-	.list-item__content:not(:first-child) {
-		justify-content: flex-end;
-	}
-
-	.list-item--head .list-item__content:nth-child(2) {
-		flex: 1.5;
-	}
-}
-
-.cart-items {
-	height: 150px;
-	overflow: auto;
-
-	.list-item.current-item {
-		background-color: @light-yellow;
-	}
-
-	.list-item.current-item.qty input {
-		border: 1px solid @brand-primary;
-		font-weight: bold;
-	}
-
-	.list-item.current-item.disc .discount {
-		font-weight: bold;
-	}
-
-	.list-item.current-item.rate .rate {
-		font-weight: bold;
-	}
-
-	.list-item .quantity {
-		flex: 1.5;
-	}
-
-	input {
-		text-align: right;
-		height: 22px;
-		font-size: @text-medium;
-	}
-}
-
-.fields {
-	display: flex;
-}
-
-.pos-items-wrapper {
-	max-height: 480px;
-	overflow-y: auto;
-}
-
-.pos-items {
-	overflow: hidden;
-}
-
-.pos-item-wrapper {
-	display: flex;
-	flex-direction: column;
-	position: relative;
-	width: 25%;
-}
-
-.image-view-container {
-	display: block;
-}
-
-.image-view-container .image-field {
-	height: auto;
-}
-
-.empty-state {
-	height: 100%;
-	position: relative;
-
-	span {
-		position: absolute;
-		color: @text-muted;
-		font-size: @text-medium;
-		top: 50%;
-		left: 50%;
-		transform: translate(-50%, -50%);
-	}
-}
-
-@keyframes yellow-fade {
-	0% {background-color: @light-yellow;}
-	100% {background-color: transparent;}
-}
-
-.highlight {
-	animation: yellow-fade 1s ease-in 1;
-}
-
-input[type=number]::-webkit-inner-spin-button,
-input[type=number]::-webkit-outer-spin-button {
-	-webkit-appearance: none;
-	margin: 0;
-}
-
-// number pad
-
-.number-pad {
-	border-collapse: collapse;
-	cursor: pointer;
-	display: table;
-}
-.num-row {
-	display: table-row;
-}
-.num-col {
-	display: table-cell;
-	border: 1px solid @border-color;
-
-	& > div {
-		width: 50px;
-		height: 50px;
-		text-align: center;
-		line-height: 50px;
-	}
-
-	&.active {
-		background-color: @light-yellow;
-	}
-
-	&.brand-primary {
-		background-color: @brand-primary;
-		color: #ffffff;
-	}
-}
-
-// taxes, totals and discount area
-.discount-amount {
-	.discount-inputs {
-		display: flex;
-		flex-direction: column;
-		padding: 15px 0;
-	}
-
-	input:first-child {
-		margin-bottom: 10px;
-	}
-}
-
-.taxes-and-totals {
-	border-top: 1px solid @border-color;
-
-	.taxes {
-		display: flex;
-		flex-direction: column;
-		padding: 15px 0;
-		align-items: flex-end;
-
-		& > div:first-child {
-			margin-bottom: 10px;
-		}
-	}
-}
-
-.grand-total {
-	border-top: 1px solid @border-color;
-
-	.list-item {
-		height: 60px;
-	}
-
-	.grand-total-value {
-		font-size: 18px;
-	}
-}
-
-.rounded-total-value {
-  font-size: 18px;
-}
-
-.quantity-total {
-  font-size: 18px;
-}
\ No newline at end of file
diff --git a/erpnext/public/less/products.less b/erpnext/public/less/products.less
deleted file mode 100644
index 5e744ce..0000000
--- a/erpnext/public/less/products.less
+++ /dev/null
@@ -1,71 +0,0 @@
-@import "variables.less";
-
-.products-list .product-image {
-	display: inline-block;
-	width: 160px;
-	height: 160px;
-	object-fit: contain;
-	margin-right: 1rem;
-}
-
-.product-image.no-image {
-	display: flex;
-	justify-content: center;
-	align-items: center;
-	font-size: 3rem;
-	color: var(--gray);
-	background: var(--light);
-}
-
-.product-image a {
-	text-decoration: none;
-}
-
-.filter-options {
-	margin-left: -5px;
-	padding-left: 5px;
-	max-height: 300px;
-	overflow: auto;
-}
-
-.item-slideshow-image {
-	height: 3rem;
-	width: 3rem;
-	object-fit: contain;
-	padding: 0.5rem;
-	border: 1px solid @border-color;
-	border-radius: 4px;
-	cursor: pointer;
-
-	&:hover, &.active {
-		border-color: var(--primary);
-	}
-}
-
-.address-card {
-	cursor: pointer;
-	position: relative;
-
-	.check {
-		display: none;
-	}
-
-	&.active {
-		border-color: var(--primary);
-
-		.check {
-			display: inline-flex;
-		}
-	}
-}
-
-.check {
-	display: inline-flex;
-    padding: 0.25rem;
-    background: var(--primary);
-    color: white;
-    border-radius: 50%;
-	font-size: 12px;
-	width: 24px;
-	height: 24px;
-}
\ No newline at end of file
diff --git a/erpnext/public/less/website.less b/erpnext/public/less/website.less
deleted file mode 100644
index ac878de..0000000
--- a/erpnext/public/less/website.less
+++ /dev/null
@@ -1,388 +0,0 @@
-@import "variables.less";
-
-.web-long-description {
-	font-size: 18px;
-	line-height: 200%;
-}
-
-.web-page-content {
-	margin-bottom: 30px;
-}
-
-.item-stock {
-	margin-bottom: 10px !important;
-}
-
-.product-link {
-	display: block;
-	text-align: center;
-}
-
-
-.product-image img {
-	max-height: 500px;
-	margin: 0 auto;
-}
-
-@media (max-width: 767px) {
-	.product-image {
-		height: 0px;
-		padding: 0px 0px 100%;
-		overflow: hidden;
-	}
-}
-
-.product-image-square {
-	width: 100%;
-	height: 0;
-	padding: 50% 0px;
-	background-size: cover;
-	background-repeat: no-repeat;
-	background-position: center top;
-}
-
-.product-image.missing-image {
-	.product-image-square;
-	position: relative;
-	background-color: @light-border-color;
-}
-
-.product-image.missing-image .octicon {
-	font-size: 32px;
-	color: @border-color;
-}
-
-.product-search {
-	margin-bottom: 15px;
-}
-
-
-@media (max-width: 767px) {
-	.product-search {
-		width: 100%;
-	}
-}
-
-.borderless td, .borderless th {
-  border-bottom: 1px solid @light-border-color;
-  padding-left:0px !important;
-  line-height: 1.8em !important;
-}
-
-.item-desc {
-	 border-top: 2px solid @light-border-color;
-	 padding-top:10px;
-}
-
-.featured-products {
-	border-top: 1px solid @light-border-color;
-}
-
-.transaction-list-item {
-	.indicator {
-		font-weight: inherit;
-		color: @text-muted;
-	}
-
-	.transaction-time {
-		margin-top: 5px;
-	}
-
-}
-
-// order.html
-.transaction-subheading {
-	.indicator {
-		font-weight: inherit;
-		color: @text-muted;
-	}
-}
-
-.order-container {
-	margin: 50px 0px;
-
-	.order-item-header .h6 {
-		padding: 7px 15px;
-	}
-
-	.order-items {
-		margin: 30px 0px 0px;
-	}
-
-	.order-item-table {
-		margin: 0px -15px;
-	}
-
-	.order-item-header {
-		border-bottom: 1px solid #d1d8dd;
-	}
-
-	.order-image-col {
-		padding-right: 0px;
-	}
-
-	.order-image {
-		max-width: 55px;
-		max-height: 55px;
-		margin-top: -5px;
-	}
-
-	.order-taxes {
-		margin-top: 30px;
-
-		.row {
-			margin-top: 15px;
-		}
-	}
-
-	.tax-grand-total-row {
-		padding-top: 15px;
-		padding-bottom: 30px;
-	}
-
-	.tax-grand-total {
-		display: inline-block;
-		font-size: 16px;
-		font-weight: bold;
-		margin-top: 5px;
-	}
-}
-
-.cart-container {
-	margin: 50px 0px;
-
-	.checkout {
-		margin-bottom:15px;
-	}
-
-	.cart-item-header .h6 {
-		padding: 7px 15px;
-	}
-
-	.cart-items {
-		margin: 30px 0px 0px;
-	}
-
-	.cart-item-table {
-		margin: 0px -15px;
-	}
-
-	.cart-item-header {
-		border-bottom: 1px solid #d1d8dd;
-	}
-
-	.cart-image-col {
-		padding-right: 0px;
-	}
-
-	.cart-image {
-		max-width: 55px;
-		max-height: 55px;
-		margin-top: -5px;
-	}
-
-	.cart-taxes {
-		margin-top: 30px;
-
-		.row {
-			margin-top: 15px;
-		}
-	}
-
-	.tax-grand-total-row {
-		border-top: 1px solid @border-color;
-		padding-top: 15px;
-	}
-
-	.cart-addresses {
-		margin-top: 50px;
-	}
-}
-
-.cart-items-dropdown .cart-dropdown,
-.item_name_dropdown {
-	display: none;
-
-}
-
-.cart-dropdown-container {
-	width: 400px;
-	padding: 15px;
-
-	.item-price {
-		display: block !important;
-		padding-bottom: 10px;
-	}
-
-	.cart-item-header {
-		border-bottom: 1px solid #d1d8dd;
-	}
-
-	.cart-items-dropdown {
-		max-height: 350px;
-	}
-
-	.cart-items-dropdown .cart-dropdown {
-		display:block;
-		margin-top:15px;
-	}
-
-	.item_name_dropdown {
-		display:block;
-	}
-
-	.item-description,
-	.cart-items .checkout,
-	.item_name_and_description {
-		display: none;
-	}
-
-	.checkout-btn {
-		padding-bottom:25px;
-	}
-	.col-name-description {
-		margin-bottom:8px;
-	}
-}
-
-// .number-spinner {
-// 	width:100px;
-// 	margin-top:5px;
-// }
-
-.cart-btn {
-	border-color: #ccc;
-}
-.cart-qty {
-	text-align:center;
-}
-
-.product-list-link {
-	.row {
-		border-bottom: 1px solid @light-border-color;
-	}
-
-	.row:hover {
-		background-color: @light-bg;
-	}
-
-	.row > div {
-		padding-top: 15px;
-		padding-bottom: 15px;
-	}
-}
-
-.product-list-link:first-child .row {
-	border-top: 1px solid @light-border-color;
-}
-
-.item-group-nav-buttons {
-	margin-top: 15px;
-}
-
-.footer-subscribe {
-	.btn-default {
-		background-color: transparent;
-		border: 1px solid @border-color;
-	}
-}
-
-@media (min-width: 992px) {
-	.footer-subscribe {
-		max-width: 350px;
-	}
-}
-
-.item-group-content {
-	margin-top: 30px;
-}
-
-.item-group-slideshow {
-	margin-bottom: 1rem;
-}
-
-.product-image-img {
-	border: 1px solid @light-border-color;
-	border-radius: 3px;
-}
-
-.product-text {
-	word-wrap: break-word;
-	height: 75px;
-	display: block; /* Fallback for non-webkit */
-	display: -webkit-box;
-	max-width: 100%;
-	margin: 0 auto;
-	-webkit-line-clamp: 3;
-	-webkit-box-orient: vertical;
-	overflow: hidden;
-	text-overflow: ellipsis;
-}
-
-.product-image-wrapper {
-	padding-bottom: 40px;
-}
-
-.duration-bar {
-  display: inline-block;
-  color: white;
-  background: #8FD288;
-  padding: 3px;
-}
-
-.duration-invisible {
-  visibility: hidden;
-}
-
-.duration-value {
-  float: right;
-}
-
-.bar-outer-text {
-  color: #8FD288;
-  background: none;
-  float: none;
-  border: none;
-}
-
-.bom-spec {
-	margin-bottom: 20px;
-}
-
-.modal-title {
-	margin-top: 5px;
-}
-
-.modal-header {
-	padding: 10px 15px;
-}
-// For Item Alternate Image
-.item-alternative-image {
-	padding: 5px;
-	margin-bottom: 5px;
-
-	&:hover {
-		border-color: @brand-primary;
-	}
-}
-
-.item-slideshow-image {
-	height: 3rem;
-	width: 3rem;
-	object-fit: contain;
-	padding: 0.5rem;
-	border: 1px solid @border-color;
-	border-radius: 4px;
-	cursor: pointer;
-
-	&:hover, &.active {
-		border-color: @brand-primary;
-	}
-}
-
-.section-products {
-	.card-img-top {
-		max-height: 300px;
-		object-fit: contain;
-	}
-}
\ No newline at end of file
diff --git a/erpnext/public/scss/erpnext-web.bundle.scss b/erpnext/public/scss/erpnext-web.bundle.scss
new file mode 100644
index 0000000..6ef1892
--- /dev/null
+++ b/erpnext/public/scss/erpnext-web.bundle.scss
@@ -0,0 +1,2 @@
+@import "./shopping_cart";
+@import "./website";
diff --git a/erpnext/public/scss/erpnext.bundle.scss b/erpnext/public/scss/erpnext.bundle.scss
new file mode 100644
index 0000000..d3313c7
--- /dev/null
+++ b/erpnext/public/scss/erpnext.bundle.scss
@@ -0,0 +1,3 @@
+@import "./erpnext";
+@import "./call_popup";
+@import "./point-of-sale";
diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/scss/erpnext.scss
similarity index 83%
rename from erpnext/public/less/erpnext.less
rename to erpnext/public/scss/erpnext.scss
index 4076ebe..8ab5973 100644
--- a/erpnext/public/less/erpnext.less
+++ b/erpnext/public/scss/erpnext.scss
@@ -1,5 +1,3 @@
-@import "variables.less";
-
 .erpnext-footer {
 	margin: 11px auto;
 	text-align: center;
@@ -141,7 +139,7 @@
 	}
 
 	.pos-payment-row {
-		border-bottom:1px solid @border-color;
+		border-bottom:1px solid var(--border-color);
 		margin: 2px 0px 5px 0px;
 		height: 60px;
 		margin-top: 0px;
@@ -149,12 +147,12 @@
 	}
 
 	.pos-payment-row:hover, .pos-keyboard-key:hover{
-		background-color: @light-bg;
+		background-color: var(--bg-color);
 		cursor: pointer;
 	}
 
 	.pos-keyboard-key, .delete-btn {
-		border: 1px solid @border-color;
+		border: 1px solid var(--border-color);
 		height:85px;
 		width:85px;
 		margin:10px 10px;
@@ -165,7 +163,7 @@
 	}
 
 	.numeric-keypad {
-		border: 1px solid @border-color;
+		border: 1px solid var(--border-color);
 		height:69px;
 		width:69px;
 		font-size:20px;
@@ -192,13 +190,13 @@
 		background-color: #fff;
 		margin-left:-4px;
 
-		@media (max-width: @screen-md) {
+		@media (max-width: var(--xl-width)) {
 			height: 45px;
 			width: 45px;
 			font-size: 14px;
 		}
 
-		@media (max-width: @screen-sm) {
+		@media (max-width: var(--lg-width)) {
 			height: 40px;
 			width: 40px;
 		}
@@ -209,21 +207,21 @@
 
 		& > .row > button {
 			border: none;
-			border-right: 1px solid @border-color;
-			border-bottom: 1px solid @border-color;
+			border-right: 1px solid var(--border-color);
+			border-bottom: 1px solid var(--border-color);
 
 			&:first-child {
-				border-left: 1px solid @border-color;
+				border-left: 1px solid var(--border-color);
 			}
 		}
 
 		& > .row:first-child > button {
-			border-top: 1px solid @border-color;
+			border-top: 1px solid var(--border-color);
 		}
 	}
 
 	.pos-pay {
-		background-color: @brand-primary;
+		background-color: var(--primary);
 		border: none;
 	}
 
@@ -236,13 +234,13 @@
 	}
 
 	.list-row-head.pos-invoice-list {
-		border-top: 1px solid @border-color;
+		border-top: 1px solid var(--border-color);
 	}
 
 	.modal-dialog {
 		width: 750px;
 
-		@media (max-width: @screen-xs) {
+		@media (max-width: var(--md-width)) {
 			width: auto;
 
 			.modal-content {
@@ -251,7 +249,7 @@
 		}
 	}
 
-	@media (max-width: @screen-xs) {
+	@media (max-width: var(--md-width)) {
 		.amount-row h3 {
 			font-size: 15px;
 		}
@@ -271,7 +269,7 @@
 	}
 
 	.selected-payment-mode {
-		background-color: @light-bg;
+		background-color: var(--bg-color);
 		cursor: pointer;
 	}
 
@@ -291,7 +289,7 @@
 		padding: 9px 15px;
 		font-size: 12px;
 		margin: 0px;
-		border-bottom: 1px solid @border-color;
+		border-bottom: 1px solid var(--border-color);
 
 		.cell {
 			display: table-cell;
@@ -313,17 +311,17 @@
 
 	.pos-bill-header {
 		background-color: #f5f7fa;
-		border: 1px solid @border-color;
+		border: 1px solid var(--border-color);
 		padding: 13px 15px;
 	}
 
 	.pos-list-row.active {
-		background-color: @light-yellow;
+		background-color: var(--fg-hover-color);
 	}
 
 	.totals-area {
-		border-right: 1px solid @border-color;
-		border-left: 1px solid @border-color;
+		border-right: 1px solid var(--border-color);
+		border-left: 1px solid var(--border-color);
 		margin-bottom: 15px;
 	}
 
@@ -332,12 +330,12 @@
 	}
 
 	.item-cart-items {
-		height: ~"calc(100vh - 526px)";
+		height: calc(100vh - 526px);
 		overflow: auto;
-		border: 1px solid @border-color;
+		border: 1px solid var(--border-color);
 		border-top: none;
 
-		@media (max-width: @screen-xs) {
+		@media (max-width: var(--md-width)) {
 			height: 30vh;
 		}
 	}
@@ -359,12 +357,12 @@
 	}
 
 	.item-list {
-		border: 1px solid @border-color;
+		border: 1px solid var(--border-color);
 		border-top: none;
-		max-height: ~"calc(100vh - 190px)";
+		max-height: calc(100vh - 190px);
 		overflow: auto;
 
-		@media (max-width: @screen-xs) {
+		@media (max-width: var(--md-width)) {
 			max-height: initial;
 		}
 
@@ -402,7 +400,7 @@
 		&> .pos-list-row {
 			border: none;
 
-			@media (max-width: @screen-md) {
+			@media (max-width: var(--xl-width)) {
 				padding: 5px 15px;
 			}
 		}
@@ -420,7 +418,7 @@
 		justify-content: center;
 		align-items: center;
 		cursor: pointer;
-		background-color: @light-bg;
+		background-color: var(--bg-color);
 		min-height: 200px;
 	}
 
@@ -428,7 +426,7 @@
 		cursor: pointer;
 	}
 
-	@media (max-width: @screen-xs) {
+	@media (max-width: var(--md-width)) {
 		.page-actions {
 			max-width: 110px;
 		}
@@ -491,4 +489,4 @@
 
 .exercise-col {
 	padding: 10px;
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/scss/erpnext_email.bundle.scss b/erpnext/public/scss/erpnext_email.bundle.scss
new file mode 100644
index 0000000..d94e74a
--- /dev/null
+++ b/erpnext/public/scss/erpnext_email.bundle.scss
@@ -0,0 +1,30 @@
+.panel-header {
+	background-color: var(--bg-color);
+	border: 1px solid var(--border-color);
+	border-radius: 3px 3px 0 0;
+}
+
+.panel-body {
+	background-color: white;
+	border: 1px solid var(--border-color);
+	border-top: none;
+	border-radius: 0 0 3px 3px;
+	overflow-wrap: break-word;
+}
+
+.sender-avatar {
+	width: 24px;
+	height: 24px;
+	border-radius: 3px;
+	vertical-align: middle;
+}
+
+.sender-avatar-placeholder {
+	@extend .sender-avatar;
+
+	line-height: 24px;
+	text-align: center;
+	color: var(--border-color);
+	border: 1px solid var(--border-color);
+	background-color: white;
+}
diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss
index 9bdaa8d..c77b2ce 100644
--- a/erpnext/public/scss/point-of-sale.scss
+++ b/erpnext/public/scss/point-of-sale.scss
@@ -806,6 +806,9 @@
 						display: none;
 						float: right;
 						font-weight: 700;
+						white-space: nowrap;
+						overflow: hidden;
+						text-overflow: ellipsis;
 					}
 
 					> .cash-shortcuts {
@@ -829,6 +832,11 @@
 						}
 					}
 				}
+
+				> .loyalty-card {
+					display: flex;
+					flex-direction: column;
+				}
 			}
 		}
 
@@ -1134,4 +1142,4 @@
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 11ebef7..5d33c1b 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -385,13 +385,16 @@
 	if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
 		frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
 
-	if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1:
+	if abs(
+		flt(value_details['TotInvVal']) + flt(value_details['Discount']) - 
+		flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) -
+		total_item_value) > 1:
 		frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
 
 	calculated_invoice_value = \
 			flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
 			+ flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
-			+ flt(value_details['OthChrg']) - flt(value_details['Discount'])
+			+ flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount'])
 
 	if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
 		frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 229e0c0..3e0b9b7 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -27,6 +27,9 @@
 	add_print_formats()
 
 def add_hsn_sac_codes():
+	if frappe.flags.in_test and frappe.flags.created_hsn_codes:
+		return
+
 	# HSN codes
 	with open(os.path.join(os.path.dirname(__file__), 'hsn_code_data.json'), 'r') as f:
 		hsn_codes = json.loads(f.read())
@@ -38,6 +41,9 @@
 		sac_codes = json.loads(f.read())
 	create_hsn_codes(sac_codes, code_field="sac_code")
 
+	if frappe.flags.in_test:
+		frappe.flags.created_hsn_codes = True
+
 def create_hsn_codes(data, code_field):
 	for d in data:
 		hsn_code = frappe.new_doc('GST HSN Code')
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 51d86ff..818888c 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -75,7 +75,7 @@
 				self.loyalty_program_tier = customer.loyalty_program_tier
 
 		if self.sales_team:
-			if sum([member.allocated_percentage or 0 for member in self.sales_team]) != 100:
+			if sum(member.allocated_percentage or 0 for member in self.sales_team) != 100:
 				frappe.throw(_("Total contribution percentage should be equal to 100"))
 
 	def check_customer_group_change(self):
diff --git a/erpnext/selling/doctype/installation_note/installation_note.js b/erpnext/selling/doctype/installation_note/installation_note.js
index 7fd0877..27a3b35 100644
--- a/erpnext/selling/doctype/installation_note/installation_note.js
+++ b/erpnext/selling/doctype/installation_note/installation_note.js
@@ -30,8 +30,8 @@
 frappe.provide("erpnext.selling");
 
 // TODO commonify this code
-erpnext.selling.InstallationNote = frappe.ui.form.Controller.extend({
-	refresh: function() {
+erpnext.selling.InstallationNote = class InstallationNote extends frappe.ui.form.Controller {
+	refresh() {
 		var me = this;
 		if (this.frm.doc.docstatus===0) {
 			this.frm.add_custom_button(__('From Delivery Note'),
@@ -54,7 +54,7 @@
 				}, "fa fa-download", "btn-default"
 			);
 		}
-	},
-});
+	}
+};
 
-$.extend(cur_frm.cscript, new erpnext.selling.InstallationNote({frm: cur_frm}));
\ No newline at end of file
+extend_cscript(cur_frm.cscript, new erpnext.selling.InstallationNote({frm: cur_frm}));
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 5a0d9c9..1223449 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -36,13 +36,12 @@
 	}
 });
 
-erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
-	onload: function(doc, dt, dn) {
+erpnext.selling.QuotationController = class QuotationController extends erpnext.selling.SellingController {
+	onload(doc, dt, dn) {
 		var me = this;
-		this._super(doc, dt, dn);
-
-	},
-	party_name: function() {
+		super.onload(doc, dt, dn);
+	}
+	party_name() {
 		var me = this;
 		erpnext.utils.get_party_details(this.frm, null, null, function() {
 			me.apply_price_list();
@@ -51,11 +50,14 @@
 		if(me.frm.doc.quotation_to=="Lead" && me.frm.doc.party_name) {
 			me.frm.trigger("get_lead_details");
 		}
-	},
-	refresh: function(doc, dt, dn) {
-		this._super(doc, dt, dn);
-		doctype = doc.quotation_to == 'Customer' ? 'Customer':'Lead';
-		frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'party_name', doctype: doctype}
+	}
+	refresh(doc, dt, dn) {
+		super.refresh(doc, dt, dn);
+		frappe.dynamic_link = {
+			doc: this.frm.doc,
+			fieldname: 'party_name',
+			doctype: doc.quotation_to == 'Customer' ? 'Customer' : 'Lead',
+		};
 
 		var me = this;
 
@@ -121,9 +123,9 @@
 
 		this.toggle_reqd_lead_customer();
 
-	},
+	}
 
-	set_dynamic_field_label: function(){
+	set_dynamic_field_label(){
 		if (this.frm.doc.quotation_to == "Customer")
 		{
 			this.frm.set_df_property("party_name", "label", "Customer");
@@ -138,22 +140,22 @@
 				return{	query: "erpnext.controllers.queries.lead_query" }
 			}
 		}
-	},
+	}
 
-	toggle_reqd_lead_customer: function() {
+	toggle_reqd_lead_customer() {
 		var me = this;
 
 		// to overwrite the customer_filter trigger from queries.js
 		this.frm.toggle_reqd("party_name", this.frm.doc.quotation_to);
 		this.frm.set_query('customer_address', this.address_query);
 		this.frm.set_query('shipping_address_name', this.address_query);
-	},
+	}
 
-	tc_name: function() {
+	tc_name() {
 		this.get_terms();
-	},
+	}
 
-	address_query: function(doc) {
+	address_query(doc) {
 		return {
 			query: 'frappe.contacts.doctype.address.address.address_query',
 			filters: {
@@ -161,20 +163,20 @@
 				link_name: doc.party_name
 			}
 		};
-	},
+	}
 
-	validate_company_and_party: function(party_field) {
+	validate_company_and_party(party_field) {
 		if(!this.frm.doc.quotation_to) {
 			frappe.msgprint(__("Please select a value for {0} quotation_to {1}", [this.frm.doc.doctype, this.frm.doc.name]));
 			return false;
 		} else if (this.frm.doc.quotation_to == "Lead") {
 			return true;
 		} else {
-			return this._super(party_field);
+			return super.validate_company_and_party(party_field);
 		}
-	},
+	}
 
-	get_lead_details: function() {
+	get_lead_details() {
 		var me = this;
 		if(!this.frm.doc.quotation_to === "Lead") {
 			return;
@@ -198,7 +200,7 @@
 			}
 		})
 	}
-});
+};
 
 cur_frm.script_manager.make(erpnext.selling.QuotationController);
 
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 246f923..e4f8a47 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -50,7 +50,7 @@
 			self.customer_name = company_name or lead_name
 
 	def update_opportunity(self, status):
-		for opportunity in list(set([d.prevdoc_docname for d in self.get("items")])):
+		for opportunity in set(d.prevdoc_docname for d in self.get("items")):
 			if opportunity:
 				self.update_opportunity_status(status, opportunity)
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index e3b41e6..b42c615 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -102,14 +102,14 @@
 	}
 });
 
-erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend({
-	onload: function(doc, dt, dn) {
-		this._super();
-	},
+erpnext.selling.SalesOrderController = class SalesOrderController extends erpnext.selling.SellingController {
+	onload(doc, dt, dn) {
+		super.onload(doc, dt, dn);
+	}
 
-	refresh: function(doc, dt, dn) {
+	refresh(doc, dt, dn) {
 		var me = this;
-		this._super();
+		super.refresh();
 		let allow_delivery = false;
 
 		if (doc.docstatus==1) {
@@ -241,14 +241,14 @@
 		}
 
 		this.order_type(doc);
-	},
+	}
 
 	create_pick_list() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.create_pick_list",
 			frm: this.frm
 		})
-	},
+	}
 
 	make_work_order() {
 		var me = this;
@@ -343,33 +343,33 @@
 				}
 			}
 		});
-	},
+	}
 
-	order_type: function() {
+	order_type() {
 		this.toggle_delivery_date();
-	},
+	}
 
-	tc_name: function() {
+	tc_name() {
 		this.get_terms();
-	},
+	}
 
-	make_material_request: function() {
+	make_material_request() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_material_request",
 			frm: this.frm
 		})
-	},
+	}
 
-	skip_delivery_note: function() {
+	skip_delivery_note() {
 		this.toggle_delivery_date();
-	},
+	}
 
-	toggle_delivery_date: function() {
+	toggle_delivery_date() {
 		this.frm.fields_dict.items.grid.toggle_reqd("delivery_date",
 			(this.frm.doc.order_type == "Sales" && !this.frm.doc.skip_delivery_note));
-	},
+	}
 
-	make_raw_material_request: function() {
+	make_raw_material_request() {
 		var me = this;
 		this.frm.call({
 			doc: this.frm.doc,
@@ -390,9 +390,9 @@
 				}
 			}
 		});
-	},
+	}
 
-	make_raw_material_request_dialog: function(r) {
+	make_raw_material_request_dialog(r) {
 		var fields = [
 			{fieldtype:'Check', fieldname:'include_exploded_items',
 				label: __('Include Exploded Items')},
@@ -447,9 +447,9 @@
 			primary_action_label: __('Create')
 		});
 		d.show();
-	},
+	}
 
-	make_delivery_note_based_on_delivery_date: function() {
+	make_delivery_note_based_on_delivery_date() {
 		var me = this;
 
 		var delivery_dates = [];
@@ -509,51 +509,51 @@
 		} else {
 			this.make_delivery_note();
 		}
-	},
+	}
 
-	make_delivery_note: function() {
+	make_delivery_note() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_sales_invoice: function() {
+	make_sales_invoice() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_maintenance_schedule: function() {
+	make_maintenance_schedule() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_project: function() {
+	make_project() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_project",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_inter_company_order: function() {
+	make_inter_company_order() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_inter_company_purchase_order",
 			frm: this.frm
 		});
-	},
+	}
 
-	make_maintenance_visit: function() {
+	make_maintenance_visit() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_purchase_order: function(){
+	make_purchase_order(){
 		let pending_items = this.frm.doc.items.some((item) =>{
 			let pending_qty = flt(item.stock_qty) - flt(item.ordered_qty);
 			return pending_qty > 0;
@@ -690,9 +690,9 @@
 		dialog.get_field("items_for_po").refresh();
 		dialog.wrapper.find('.grid-heading-row .grid-row-check').click();
 		dialog.show();
-	},
+	}
 
-	hold_sales_order: function(){
+	hold_sales_order(){
 		var me = this;
 		var d = new frappe.ui.Dialog({
 			title: __('Reason for Hold'),
@@ -724,11 +724,11 @@
 			}
 		});
 		d.show();
-	},
-	close_sales_order: function(){
+	}
+	close_sales_order(){
 		this.frm.cscript.update_status("Close", "Closed")
-	},
-	update_status: function(label, status){
+	}
+	update_status(label, status){
 		var doc = this.frm.doc;
 		var me = this;
 		frappe.ui.form.is_saving = true;
@@ -743,5 +743,6 @@
 			}
 		});
 	}
-});
-$.extend(cur_frm.cscript, new erpnext.selling.SalesOrderController({frm: cur_frm}));
+};
+
+extend_cscript(cur_frm.cscript, new erpnext.selling.SalesOrderController({frm: cur_frm}));
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index d9e52e1..41f57a3 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -151,7 +151,7 @@
 			frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
 
 	def update_prevdoc_status(self, flag=None):
-		for quotation in list(set([d.prevdoc_docname for d in self.get("items")])):
+		for quotation in set(d.prevdoc_docname for d in self.get("items")):
 			if quotation:
 				doc = frappe.get_doc("Quotation", quotation)
 				if doc.docstatus==2:
@@ -233,7 +233,7 @@
 		# Checks Sales Invoice
 		submit_rv = frappe.db.sql_list("""select t1.name
 			from `tabSales Invoice` t1,`tabSales Invoice Item` t2
-			where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""",
+			where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus < 2""",
 			self.name)
 
 		if submit_rv:
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 9873710..974648d 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1217,6 +1217,19 @@
 		# To test if the SO does NOT have a Blanket Order
 		self.assertEqual(so_doc.items[0].blanket_order, None)
 
+	def test_so_cancellation_when_si_drafted(self):
+		"""
+			Test to check if Sales Order gets cancelled if Sales Invoice is in Draft state
+			Expected result: sales order should not get cancelled 
+		"""
+		so = make_sales_order()
+		so.submit()
+		si = make_sales_invoice(so.name)
+		si.save()
+
+		self.assertRaises(frappe.ValidationError, so.cancel)
+
+
 
 def make_sales_order(**args):
 	so = frappe.new_doc("Sales Order")
diff --git a/erpnext/selling/doctype/sms_center/sms_center.js b/erpnext/selling/doctype/sms_center/sms_center.js
index dda2803..974cfc7 100644
--- a/erpnext/selling/doctype/sms_center/sms_center.js
+++ b/erpnext/selling/doctype/sms_center/sms_center.js
@@ -1,7 +1,7 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-$.extend(cur_frm.cscript, {
+extend_cscript(cur_frm.cscript, {
 	message: function () {
 		var total_characters = this.frm.doc.message.length;
 		var total_msg = 1;
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index e3405e0..6db4150 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -7,7 +7,7 @@
 		single_column: true
 	});
 
-	frappe.require('assets/js/point-of-sale.min.js', function() {
+	frappe.require('point-of-sale.bundle.js', function() {
 		wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
 		window.cur_pos = wrapper.pos;
 	});
@@ -19,4 +19,4 @@
 		wrapper.pos.wrapper.html("");
 		wrapper.pos.check_opening_entry();
 	}
-};
\ No newline at end of file
+};
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 156fb77..c484873 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -481,7 +481,7 @@
 		const amount = doc.loyalty_amount > 0 ? format_currency(doc.loyalty_amount, doc.currency) : '';
 		this.$payment_modes.append(
 			`<div class="payment-mode-wrapper">
-				<div class="mode-of-payment" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
+				<div class="mode-of-payment loyalty-card" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
 					Redeem Loyalty Points
 					<div class="loyalty-amount-amount pay-amount">${amount}</div>
 					<div class="loyalty-amount-name">${loyalty_program}</div>
@@ -563,4 +563,4 @@
 	toggle_component(show) {
 		show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
 	}
-};
\ No newline at end of file
+};
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index f5feb95..8cb2446 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -59,7 +59,7 @@
 			IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
 			soi.qty, soi.delivered_qty,
 			(soi.qty - soi.delivered_qty) AS pending_qty,
-			IFNULL(sii.qty, 0) as billed_qty,
+			IFNULL(SUM(sii.qty), 0) as billed_qty,
 			soi.base_amount as amount,
 			(soi.delivered_qty * soi.base_rate) as delivered_qty_amount,
 			(soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount,
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 0428573..eb02867 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -9,15 +9,15 @@
 cur_frm.email_field = "contact_email";
 
 frappe.provide("erpnext.selling");
-erpnext.selling.SellingController = erpnext.TransactionController.extend({
-	setup: function() {
-		this._super();
+erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController {
+	setup() {
+		super.setup();
 		this.frm.add_fetch("sales_partner", "commission_rate", "commission_rate");
 		this.frm.add_fetch("sales_person", "commission_rate", "commission_rate");
-	},
+	}
 
-	onload: function() {
-		this._super();
+	onload() {
+		super.onload();
 		this.setup_queries();
 		this.frm.set_query('shipping_rule', function() {
 			return {
@@ -26,9 +26,9 @@
 				}
 			};
 		});
-	},
+	}
 
-	setup_queries: function() {
+	setup_queries() {
 		var me = this;
 
 		$.each([["customer", "customer"],
@@ -81,10 +81,10 @@
 			});
 		}
 
-	},
+	}
 
-	refresh: function() {
-		this._super();
+	refresh() {
+		super.refresh();
 
 		frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
 
@@ -95,45 +95,45 @@
 			this.frm.toggle_display("packing_list", packing_list_exists ? true : false);
 		}
 		this.toggle_editable_price_list_rate();
-	},
+	}
 
-	customer: function() {
+	customer() {
 		var me = this;
 		erpnext.utils.get_party_details(this.frm, null, null, function() {
 			me.apply_price_list();
 		});
-	},
+	}
 
-	customer_address: function() {
+	customer_address() {
 		erpnext.utils.get_address_display(this.frm, "customer_address");
 		erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name");
-	},
+	}
 
-	shipping_address_name: function() {
+	shipping_address_name() {
 		erpnext.utils.get_address_display(this.frm, "shipping_address_name", "shipping_address");
 		erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name");
-	},
+	}
 
-	sales_partner: function() {
+	sales_partner() {
 		this.apply_pricing_rule();
-	},
+	}
 
-	campaign: function() {
+	campaign() {
 		this.apply_pricing_rule();
-	},
+	}
 
-	selling_price_list: function() {
+	selling_price_list() {
 		this.apply_price_list();
 		this.set_dynamic_labels();
-	},
+	}
 
-	discount_percentage: function(doc, cdt, cdn) {
+	discount_percentage(doc, cdt, cdn) {
 		var item = frappe.get_doc(cdt, cdn);
 		item.discount_amount = 0.0;
 		this.apply_discount_on_item(doc, cdt, cdn, 'discount_percentage');
-	},
+	}
 
-	discount_amount: function(doc, cdt, cdn) {
+	discount_amount(doc, cdt, cdn) {
 
 		if(doc.name === cdn) {
 			return;
@@ -142,9 +142,9 @@
 		var item = frappe.get_doc(cdt, cdn);
 		item.discount_percentage = 0.0;
 		this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount');
-	},
+	}
 
-	apply_discount_on_item: function(doc, cdt, cdn, field) {
+	apply_discount_on_item(doc, cdt, cdn, field) {
 		var item = frappe.get_doc(cdt, cdn);
 		if(!item.price_list_rate) {
 			item[field] = 0.0;
@@ -152,14 +152,14 @@
 			this.price_list_rate(doc, cdt, cdn);
 		}
 		this.set_gross_profit(item);
-	},
+	}
 
-	commission_rate: function() {
+	commission_rate() {
 		this.calculate_commission();
 		refresh_field("total_commission");
-	},
+	}
 
-	total_commission: function() {
+	total_commission() {
 		if(this.frm.doc.base_net_total) {
 			frappe.model.round_floats_in(this.frm.doc, ["base_net_total", "total_commission"]);
 
@@ -175,9 +175,9 @@
 			this.frm.set_value("commission_rate",
 				flt(this.frm.doc.total_commission * 100.0 / this.frm.doc.base_net_total));
 		}
-	},
+	}
 
-	allocated_percentage: function(doc, cdt, cdn) {
+	allocated_percentage(doc, cdt, cdn) {
 		var sales_person = frappe.get_doc(cdt, cdn);
 		if(sales_person.allocated_percentage) {
 
@@ -193,15 +193,15 @@
 			refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name,
 				sales_person.parentfield);
 		}
-	},
+	}
 
-	sales_person: function(doc, cdt, cdn) {
+	sales_person(doc, cdt, cdn) {
 		var row = frappe.get_doc(cdt, cdn);
 		this.calculate_incentive(row);
 		refresh_field("incentives",row.name,row.parentfield);
-	},
+	}
 
-	warehouse: function(doc, cdt, cdn) {
+	warehouse(doc, cdt, cdn) {
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
 
@@ -239,18 +239,18 @@
 				});
 			}
 		})
-	},
+	}
 
-	toggle_editable_price_list_rate: function() {
+	toggle_editable_price_list_rate() {
 		var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
 		var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
 
 		if(df && editable_price_list_rate) {
 			df.read_only = 0;
 		}
-	},
+	}
 
-	calculate_commission: function() {
+	calculate_commission() {
 		if(this.frm.fields_dict.commission_rate) {
 			if(this.frm.doc.commission_rate > 100) {
 				var msg = __(frappe.meta.get_label(this.frm.doc.doctype, "commission_rate", this.frm.doc.name)) +
@@ -262,9 +262,9 @@
 			this.frm.doc.total_commission = flt(this.frm.doc.base_net_total * this.frm.doc.commission_rate / 100.0,
 				precision("total_commission"));
 		}
-	},
+	}
 
-	calculate_contribution: function() {
+	calculate_contribution() {
 		var me = this;
 		$.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) {
 			frappe.model.round_floats_in(sales_person);
@@ -274,18 +274,18 @@
 					precision("allocated_amount", sales_person));
 			}
 		});
-	},
+	}
 
-	calculate_incentive: function(row) {
+	calculate_incentive(row) {
 		if(row.allocated_amount)
 		{
 			row.incentives = flt(
 					row.allocated_amount * row.commission_rate / 100.0,
 					precision("incentives", row));
 		}
-	},
+	}
 
-	batch_no: function(doc, cdt, cdn) {
+	batch_no(doc, cdt, cdn) {
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
 
@@ -312,14 +312,14 @@
 				});
 			}
 		})
-	},
+	}
 
-	set_dynamic_labels: function() {
-		this._super();
+	set_dynamic_labels() {
+		super.set_dynamic_labels();
 		this.set_product_bundle_help(this.frm.doc);
-	},
+	}
 
-	set_product_bundle_help: function(doc) {
+	set_product_bundle_help(doc) {
 		if(!cur_frm.fields_dict.packing_list) return;
 		if ((doc.packed_items || []).length) {
 			$(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true);
@@ -337,9 +337,9 @@
 			}
 		}
 		refresh_field('product_bundle_help');
-	},
+	}
 
-	company_address: function() {
+	company_address() {
 		var me = this;
 		if(this.frm.doc.company_address) {
 			frappe.call({
@@ -354,42 +354,42 @@
 		} else {
 			this.frm.set_value("company_address_display", "");
 		}
-	},
+	}
 
-	conversion_factor: function(doc, cdt, cdn, dont_fetch_price_list_rate) {
-	    this._super(doc, cdt, cdn, dont_fetch_price_list_rate);
+	conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) {
+	    super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate);
 		if(frappe.meta.get_docfield(cdt, "stock_qty", cdn) &&
 			in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
 				if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
 				this.set_batch_number(cdt, cdn);
 			}
-	},
+	}
 
-	batch_no: function(doc, cdt, cdn) {
-		this._super(doc, cdt, cdn);
-	},
+	batch_no(doc, cdt, cdn) {
+		super.batch_no(doc, cdt, cdn);
+	}
 
-	qty: function(doc, cdt, cdn) {
-		this._super(doc, cdt, cdn);
+	qty(doc, cdt, cdn) {
+		super.qty(doc, cdt, cdn);
 
 		if(in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
 			if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
 			this.set_batch_number(cdt, cdn);
 		}
-	},
+	}
 
 	/* Determine appropriate batch number and set it in the form.
 	* @param {string} cdt - Document Doctype
 	* @param {string} cdn - Document name
 	*/
-	set_batch_number: function(cdt, cdn) {
+	set_batch_number(cdt, cdn) {
 		const doc = frappe.get_doc(cdt, cdn);
 		if (doc && doc.has_batch_no && doc.warehouse) {
 			this._set_batch_number(doc);
 		}
-	},
+	}
 
-	_set_batch_number: function(doc) {
+	_set_batch_number(doc) {
 		let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)};
 		if (doc.has_serial_no && doc.serial_no) {
 			args['serial_no'] = doc.serial_no
@@ -406,9 +406,9 @@
 				}
 			}
 		});
-	},
+	}
 
-	update_auto_repeat_reference: function(doc) {
+	update_auto_repeat_reference(doc) {
 		if (doc.auto_repeat) {
 			frappe.call({
 				method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
@@ -426,7 +426,7 @@
 			})
 		}
 	}
-});
+};
 
 frappe.ui.form.on(cur_frm.doctype,"project", function(frm) {
 	if(in_list(["Delivery Note", "Sales Invoice"], frm.doc.doctype)) {
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 077538d..27e023c 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -54,7 +54,7 @@
 
 	def validate_abbr(self):
 		if not self.abbr:
-			self.abbr = ''.join([c[0] for c in self.company_name.split()]).upper()
+			self.abbr = ''.join(c[0] for c in self.company_name.split()).upper()
 
 		self.abbr = self.abbr.strip()
 
@@ -335,7 +335,7 @@
 		clear_defaults_cache()
 
 	def abbreviate(self):
-		self.abbr = ''.join([c[0].upper() for c in self.company_name.split()])
+		self.abbr = ''.join(c[0].upper() for c in self.company_name.split())
 
 	def on_trash(self):
 		"""
diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.js b/erpnext/setup/doctype/currency_exchange/currency_exchange.js
index a8ea55c..85036a1 100644
--- a/erpnext/setup/doctype/currency_exchange/currency_exchange.js
+++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.js
@@ -1,30 +1,30 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-$.extend(cur_frm.cscript, {
+extend_cscript(cur_frm.cscript, {
 	onload: function() {
 		if(cur_frm.doc.__islocal) {
 			cur_frm.set_value("to_currency", frappe.defaults.get_global_default("currency"));
 		}
 	},
-	
+
 	refresh: function() {
 		cur_frm.cscript.set_exchange_rate_label();
 	},
-	
+
 	from_currency: function() {
 		cur_frm.cscript.set_exchange_rate_label();
 	},
-	
+
 	to_currency: function() {
 		cur_frm.cscript.set_exchange_rate_label();
 	},
-	
+
 	set_exchange_rate_label: function() {
 		if(cur_frm.doc.from_currency && cur_frm.doc.to_currency) {
 			var default_label = __(frappe.meta.docfield_map[cur_frm.doctype]["exchange_rate"].label);
-			cur_frm.fields_dict.exchange_rate.set_label(default_label + 
+			cur_frm.fields_dict.exchange_rate.set_label(default_label +
 				repl(" (1 %(from_currency)s = [?] %(to_currency)s)", cur_frm.doc));
 		}
 	}
-});
\ No newline at end of file
+});
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 6687143..1a83cb6 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -8,7 +8,7 @@
 from frappe.utils import nowdate, cint, cstr
 from frappe.utils.nestedset import NestedSet
 from frappe.website.website_generator import WebsiteGenerator
-from frappe.website.render import clear_cache
+from frappe.website.utils import clear_cache
 from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
 from erpnext.shopping_cart.product_info import set_product_info_for_website
 from erpnext.utilities.product import get_qty_in_stock
@@ -139,7 +139,7 @@
 			# return child item groups if the type is of "Is Group"
 			return get_child_groups_for_list_in_html(item_group, start, limit, search)
 
-	child_groups = ", ".join([frappe.db.escape(i[0]) for i in get_child_groups(product_group)])
+	child_groups = ", ".join(frappe.db.escape(i[0]) for i in get_child_groups(product_group))
 
 	# base query
 	query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,
@@ -239,7 +239,7 @@
 	return frappe.get_template(products_template).render(context)
 
 def get_group_item_count(item_group):
-	child_groups = ", ".join(['"' + i[0] + '"' for i in get_child_groups(item_group)])
+	child_groups = ", ".join('"' + i[0] + '"' for i in get_child_groups(item_group))
 	return frappe.db.sql("""select count(*) from `tabItem`
 		where docstatus = 0 and show_in_website = 1
 		and (item_group in (%s)
diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json
index ec9a6d6..daaa626 100644
--- a/erpnext/setup/setup_wizard/data/country_wise_tax.json
+++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json
@@ -1867,7 +1867,7 @@
 	"South Africa": {
 		"South Africa Tax": {
 			"account_name": "VAT",
-			"tax_rate": 14.00
+			"tax_rate": 15.00
 		}
 	},
 
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py
index d96d803..3eab4ff 100644
--- a/erpnext/shopping_cart/product_query.py
+++ b/erpnext/shopping_cart/product_query.py
@@ -71,7 +71,8 @@
 					],
 					or_filters=self.or_filters,
 					start=start,
-					limit=self.page_length
+					limit=self.page_length,
+					order_by="weightage desc"
 				)
 
 				items_dict = {item.name: item for item in items}
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index a657ecf..37e9e89 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -1,11 +1,11 @@
 frappe.provide('erpnext.stock');
 
-erpnext.stock.ItemDashboard = Class.extend({
-	init: function (opts) {
+erpnext.stock.ItemDashboard = class ItemDashboard {
+	constructor(opts) {
 		$.extend(this, opts);
 		this.make();
-	},
-	make: function () {
+	}
+	make() {
 		var me = this;
 		this.start = 0;
 		if (!this.sort_by) {
@@ -79,9 +79,9 @@
 			me.refresh();
 		});
 
-	},
-	refresh: function () {
-		if (this.before_refresh) {
+	}
+	refresh() {
+		if(this.before_refresh) {
 			this.before_refresh();
 		}
 
@@ -104,9 +104,9 @@
 				me.render(r.message);
 			}
 		});
-	},
-	render: function (data) {
-		if (this.start === 0) {
+	}
+	render(data) {
+		if (this.start===0) {
 			this.max_count = 0;
 			this.result.empty();
 		}
@@ -141,11 +141,11 @@
 			$(`<div class='text-muted' style='margin: 20px 5px;'>
 				${message} </div>`).appendTo(this.result);
 		}
-	},
+	}
 
-	get_item_dashboard_data: function (data, max_count, show_item) {
-		if (!max_count) max_count = 0;
-		if (!data) data = [];
+	get_item_dashboard_data(data, max_count, show_item) {
+		if(!max_count) max_count = 0;
+		if(!data) data = [];
 
 		data.forEach(function (d) {
 			d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
@@ -170,9 +170,9 @@
 			can_write: can_write,
 			show_item: show_item || false
 		};
-	},
+	}
 
-	get_capacity_dashboard_data: function (data) {
+	get_capacity_dashboard_data(data) {
 		if (!data) data = [];
 
 		data.forEach(function (d) {
@@ -189,7 +189,7 @@
 			can_write: can_write,
 		};
 	}
-});
+};
 
 erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) {
 	var dialog = new frappe.ui.Dialog({
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 8fdda56..508e17c 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -304,7 +304,7 @@
 		frappe.throw(_("The serial no {0} does not belong to item {1}")
 			.format(get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code)))
 
-	serial_no_link = ','.join([get_link_to_form("Serial No", sn) for sn in serial_nos])
+	serial_no_link = ','.join(get_link_to_form("Serial No", sn) for sn in serial_nos)
 
 	message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
 	frappe.throw(_("There is no batch found against the {0}: {1}")
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 0514bd2..4364201 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -54,7 +54,7 @@
 		self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty"))
 		self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))
 		self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty"))
-		
+
 		self.set_projected_qty()
 		self.db_update()
 
@@ -115,7 +115,7 @@
 		#Get Transferred Entries
 		materials_transferred = frappe.db.sql("""
 			select
-				ifnull(sum(transfer_qty),0)
+				ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0)
 			from
 				`tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po
 			where
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 7875b9c..c3803f1 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -123,17 +123,17 @@
 	}
 });
 
-erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend({
-	setup: function(doc) {
+erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpnext.selling.SellingController {
+	setup(doc) {
 		this.setup_posting_date_time_check();
-		this._super(doc);
+		super.setup(doc);
 		this.frm.make_methods = {
 			'Delivery Trip': this.make_delivery_trip,
 		};
-	},
-	refresh: function(doc, dt, dn) {
+	}
+	refresh(doc, dt, dn) {
 		var me = this;
-		this._super();
+		super.refresh();
 		if ((!doc.is_return) && (doc.status!="Closed" || this.frm.is_new())) {
 			if (this.frm.doc.docstatus===0) {
 				this.frm.add_custom_button(__('Sales Order'),
@@ -231,64 +231,64 @@
 				erpnext.utils.make_subscription(doc.doctype, doc.name)
 			}, __('Create'))
 		}
-	},
+	}
 
-	make_shipment: function() {
+	make_shipment() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.delivery_note.delivery_note.make_shipment",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_sales_invoice: function() {
+	make_sales_invoice() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_installation_note: function() {
+	make_installation_note() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.delivery_note.delivery_note.make_installation_note",
 			frm: this.frm
 		});
-	},
+	}
 
-	make_sales_return: function() {
+	make_sales_return() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_return",
 			frm: this.frm
 		})
-	},
+	}
 
-	make_delivery_trip: function() {
+	make_delivery_trip() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
 			frm: cur_frm
 		})
-	},
+	}
 
-	tc_name: function() {
+	tc_name() {
 		this.get_terms();
-	},
+	}
 
-	items_on_form_rendered: function(doc, grid_row) {
+	items_on_form_rendered(doc, grid_row) {
 		erpnext.setup_serial_or_batch_no();
-	},
+	}
 
-	packed_items_on_form_rendered: function(doc, grid_row) {
+	packed_items_on_form_rendered(doc, grid_row) {
 		erpnext.setup_serial_or_batch_no();
-	},
+	}
 
-	close_delivery_note: function(doc){
+	close_delivery_note(doc){
 		this.update_status("Closed")
-	},
+	}
 
-	reopen_delivery_note : function() {
+	reopen_delivery_note() {
 		this.update_status("Submitted")
-	},
+	}
 
-	update_status: function(status) {
+	update_status(status) {
 		var me = this;
 		frappe.ui.form.is_saving = true;
 		frappe.call({
@@ -302,10 +302,10 @@
 				frappe.ui.form.is_saving = false;
 			}
 		})
-	},
-});
+	}
+};
 
-$.extend(cur_frm.cscript, new erpnext.stock.DeliveryNoteController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.stock.DeliveryNoteController({frm: cur_frm}));
 
 frappe.ui.form.on('Delivery Note', {
 	setup: function(frm) {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index cce51cb..dd31965 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -264,7 +264,7 @@
 		"""
 			Validate that if packed qty exists, it should be equal to qty
 		"""
-		if not any([flt(d.get('packed_qty')) for d in self.get("items")]):
+		if not any(flt(d.get('packed_qty')) for d in self.get("items")):
 			return
 		has_error = False
 		for d in self.get("items"):
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
index f08125b..0402898 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
@@ -6,8 +6,8 @@
 			return [__("Return"), "gray", "is_return,=,Yes"];
 		} else if (doc.status === "Closed") {
 			return [__("Closed"), "green", "status,=,Closed"];
-		} else if (flt(doc.per_returned, 2) === 100) {
-			return [__("Return Issued"), "grey", "per_returned,=,100"];
+		} else if (doc.status === "Return Issued") {
+			return [__("Return Issued"), "grey", "status,=,Return Issued"];
 		} else if (flt(doc.per_billed, 2) < 100) {
 			return [__("To Bill"), "orange", "per_billed,<,100"];
 		} else if (flt(doc.per_billed, 2) === 100) {
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 81e7301..9ec28d8 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -68,7 +68,7 @@
 			delete (bool, optional): Defaults to `False`. `True` if driver details need to be emptied, else `False`.
 		"""
 
-		delivery_notes = list(set([stop.delivery_note for stop in self.delivery_stops if stop.delivery_note]))
+		delivery_notes = list(set(stop.delivery_note for stop in self.delivery_stops if stop.delivery_note))
 
 		update_fields = {
 			"driver": self.driver,
@@ -136,8 +136,8 @@
 
 				# Include last leg in the final distance calculation
 				self.uom = self.default_distance_uom
-				total_distance = sum([leg.get("distance", {}).get("value", 0.0)
-					for leg in directions.get("legs")])  # in meters
+				total_distance = sum(leg.get("distance", {}).get("value", 0.0)
+					for leg in directions.get("legs"))  # in meters
 				self.total_distance = total_distance * self.uom_conversion_factor
 			else:
 				idx += len(route) - 1
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 8aec893..45e3c21 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -380,7 +380,7 @@
 
 		// Show Stock Levels only if is_stock_item
 		if (frm.doc.is_stock_item) {
-			frappe.require('assets/js/item-dashboard.min.js', function() {
+			frappe.require('item-dashboard.bundle.js', function() {
 				const section = frm.dashboard.add_section('', __("Stock Levels"));
 				erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
 					parent: section,
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index fbd30cf..42cc67c 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -16,7 +16,7 @@
 from frappe.website.doctype.website_slideshow.website_slideshow import \
 	get_slideshow
 
-from frappe.website.render import clear_cache
+from frappe.website.utils import clear_cache
 from frappe.website.website_generator import WebsiteGenerator
 
 
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
index d5700fe..8f76844 100644
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
@@ -18,6 +18,9 @@
 		make_items()
 
 	def test_alternative_item_for_subcontract_rm(self):
+		frappe.db.set_value('Buying Settings', None,
+			'backflush_raw_materials_of_subcontract_based_on', 'BOM')
+
 		create_stock_reconciliation(item_code='Alternate Item For A RW 1', warehouse='_Test Warehouse - _TC',
 			qty=5, rate=2000)
 		create_stock_reconciliation(item_code='Test FG A RW 2', warehouse='_Test Warehouse - _TC',
@@ -65,6 +68,8 @@
 				status = True
 
 		self.assertEqual(status, True)
+		frappe.db.set_value('Buying Settings', None,
+			'backflush_raw_materials_of_subcontract_based_on', 'Material Transferred for Subcontract')
 
 	def test_alternative_item_for_production_rm(self):
 		create_stock_reconciliation(item_code='Alternate Item For A RW 1',
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
index 1abbc35..433f78a 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
@@ -5,8 +5,8 @@
 
 frappe.provide("erpnext.stock");
 
-erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
-	setup: function() {
+erpnext.stock.LandedCostVoucher = class LandedCostVoucher extends erpnext.stock.StockController {
+	setup() {
 		var me = this;
 		this.frm.fields_dict.purchase_receipts.grid.get_field('receipt_document').get_query =
 			function (doc, cdt, cdn) {
@@ -30,9 +30,9 @@
 		this.frm.add_fetch("receipt_document", "supplier", "supplier");
 		this.frm.add_fetch("receipt_document", "posting_date", "posting_date");
 		this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total");
-	},
+	}
 
-	refresh: function() {
+	refresh() {
 		var help_content =
 			`<br><br>
 			<table class="table table-bordered" style="background-color: #f9f9f9;">
@@ -67,9 +67,9 @@
 			let company_currency = frappe.get_doc(":Company", this.frm.doc.company).default_currency;
 			this.frm.set_currency_labels(["total_taxes_and_charges"], company_currency);
 		}
-	},
+	}
 
-	get_items_from_purchase_receipts: function() {
+	get_items_from_purchase_receipts() {
 		var me = this;
 		if(!this.frm.doc.purchase_receipts.length) {
 			frappe.msgprint(__("Please enter Purchase Receipt first"));
@@ -82,22 +82,22 @@
 				}
 			});
 		}
-	},
+	}
 
-	amount: function(frm) {
+	amount(frm) {
 		this.set_total_taxes_and_charges();
 		this.set_applicable_charges_for_item();
-	},
+	}
 
-	set_total_taxes_and_charges: function() {
+	set_total_taxes_and_charges() {
 		var total_taxes_and_charges = 0.0;
 		$.each(this.frm.doc.taxes || [], function(i, d) {
 			total_taxes_and_charges += flt(d.base_amount);
 		});
 		this.frm.set_value("total_taxes_and_charges", total_taxes_and_charges);
-	},
+	}
 
-	set_applicable_charges_for_item: function() {
+	set_applicable_charges_for_item() {
 		var me = this;
 
 		if(this.frm.doc.taxes.length) {
@@ -123,15 +123,15 @@
 				refresh_field("items");
 			}
 		}
-	},
-	distribute_charges_based_on: function (frm) {
+	}
+	distribute_charges_based_on (frm) {
 		this.set_applicable_charges_for_item();
-	},
+	}
 
-	items_remove: () => {
+	items_remove() {
 		this.trigger('set_applicable_charges_for_item');
 	}
-});
+};
 
 cur_frm.script_manager.make(erpnext.stock.LandedCostVoucher);
 
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 8310946..5df4d87 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -78,7 +78,7 @@
 					.format(item.idx, item.item_code))
 
 	def set_total_taxes_and_charges(self):
-		self.total_taxes_and_charges = sum([flt(d.base_amount) for d in self.get("taxes")])
+		self.total_taxes_and_charges = sum(flt(d.base_amount) for d in self.get("taxes"))
 
 	def set_applicable_charges_on_item(self):
 		if self.get('taxes') and self.distribute_charges_based_on != 'Distribute Manually':
@@ -104,15 +104,15 @@
 		based_on = self.distribute_charges_based_on.lower()
 
 		if based_on != 'distribute manually':
-			total = sum([flt(d.get(based_on)) for d in self.get("items")])
+			total = sum(flt(d.get(based_on)) for d in self.get("items"))
 		else:
 			# consider for proportion while distributing manually
-			total = sum([flt(d.get('applicable_charges')) for d in self.get("items")])
+			total = sum(flt(d.get('applicable_charges')) for d in self.get("items"))
 
 		if not total:
 			frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on))
 
-		total_applicable_charges = sum([flt(d.applicable_charges) for d in self.get("items")])
+		total_applicable_charges = sum(flt(d.applicable_charges) for d in self.get("items"))
 
 		precision = get_field_precision(frappe.get_meta("Landed Cost Item").get_field("applicable_charges"),
 		currency=frappe.get_cached_value('Company',  self.company,  "default_currency"))
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 984ae46..32b08f6 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -311,7 +311,7 @@
 
 def distribute_landed_cost_on_items(lcv):
 	based_on = lcv.distribute_charges_based_on.lower()
-	total = sum([flt(d.get(based_on)) for d in lcv.get("items")])
+	total = sum(flt(d.get(based_on)) for d in lcv.get("items"))
 
 	for item in lcv.get("items"):
 		item.applicable_charges = flt(item.get(based_on)) * flt(lcv.total_taxes_and_charges) / flt(total)
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 92c8d21..5f53be0 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -101,7 +101,8 @@
 		}
 
 		if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') {
-			if (flt(frm.doc.per_ordered, 2) < 100) {
+			let precision = frappe.defaults.get_default("float_precision");
+			if (flt(frm.doc.per_ordered, precision) < 100) {
 				let add_create_pick_list_button = () => {
 					frm.add_custom_button(__('Pick List'),
 						() => frm.events.create_pick_list(frm), __('Create'));
@@ -407,33 +408,33 @@
 	}
 });
 
-erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.extend({
-	tc_name: function() {
+erpnext.buying.MaterialRequestController = class MaterialRequestController extends erpnext.buying.BuyingController {
+	tc_name() {
 		this.get_terms();
-	},
+	}
 
-	item_code: function() {
+	item_code() {
 		// to override item code trigger from transaction.js
-	},
+	}
 
-	validate_company_and_party: function() {
+	validate_company_and_party() {
 		return true;
-	},
+	}
 
-	calculate_taxes_and_totals: function() {
+	calculate_taxes_and_totals() {
 		return;
-	},
+	}
 
-	validate: function() {
+	validate() {
 		set_schedule_date(this.frm);
-	},
+	}
 
-	onload: function(doc, cdt, cdn) {
+	onload(doc, cdt, cdn) {
 		this.frm.set_query("item_code", "items", function() {
 			if (doc.material_request_type == "Customer Provided") {
 				return{
 					query: "erpnext.controllers.queries.item_query",
-					filters:{ 
+					filters:{
 						'customer': me.frm.doc.customer,
 						'is_stock_item':1
 					}
@@ -450,9 +451,9 @@
 				}
 			}
 		});
-	},
+	}
 
-	items_add: function(doc, cdt, cdn) {
+	items_add(doc, cdt, cdn) {
 		var row = frappe.get_doc(cdt, cdn);
 		if(doc.schedule_date) {
 			row.schedule_date = doc.schedule_date;
@@ -460,19 +461,19 @@
 		} else {
 			this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]);
 		}
-	},
+	}
 
-	items_on_form_rendered: function() {
-		set_schedule_date(this.frm);
-	},
-
-	schedule_date: function() {
+	items_on_form_rendered() {
 		set_schedule_date(this.frm);
 	}
-});
+
+	schedule_date() {
+		set_schedule_date(this.frm);
+	}
+};
 
 // for backward compatibility: combine new and previous states
-$.extend(cur_frm.cscript, new erpnext.buying.MaterialRequestController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.buying.MaterialRequestController({frm: cur_frm}));
 
 function set_schedule_date(frm) {
 	if(frm.doc.schedule_date){
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index 2008bff..4a843e0 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -88,9 +88,9 @@
 		rows = [d.item_code for d in self.get("items")]
 
 		# also pick custom fields from delivery note
-		custom_fields = ', '.join(['dni.`{0}`'.format(d.fieldname)
+		custom_fields = ', '.join('dni.`{0}`'.format(d.fieldname)
 			for d in frappe.get_meta("Delivery Note Item").get_custom_fields()
-			if d.fieldtype not in no_value_fields])
+			if d.fieldtype not in no_value_fields)
 
 		if custom_fields:
 			custom_fields = ', ' + custom_fields
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index befdad9..0182ed5 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -114,15 +114,15 @@
 	}
 });
 
-erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend({
-	setup: function(doc) {
+erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extends erpnext.buying.BuyingController {
+	setup(doc) {
 		this.setup_posting_date_time_check();
-		this._super(doc);
-	},
+		super.setup(doc);
+	}
 
-	refresh: function() {
+	refresh() {
 		var me = this;
-		this._super();
+		super.refresh();
 		if(this.frm.doc.docstatus > 0) {
 			this.show_stock_ledger();
 			//removed for temporary
@@ -201,31 +201,31 @@
 		}
 
 		this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
-	},
+	}
 
-	make_purchase_invoice: function() {
+	make_purchase_invoice() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
 			frm: cur_frm
 		})
-	},
+	}
 
-	make_purchase_return: function() {
+	make_purchase_return() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_return",
 			frm: cur_frm
 		})
-	},
+	}
 
-	close_purchase_receipt: function() {
+	close_purchase_receipt() {
 		cur_frm.cscript.update_status("Closed");
-	},
+	}
 
-	reopen_purchase_receipt: function() {
+	reopen_purchase_receipt() {
 		cur_frm.cscript.update_status("Submitted");
-	},
+	}
 
-	make_retention_stock_entry: function() {
+	make_retention_stock_entry() {
 		frappe.call({
 			method: "erpnext.stock.doctype.stock_entry.stock_entry.move_sample_to_retention_warehouse",
 			args:{
@@ -242,16 +242,16 @@
 				}
 			}
 		});
-	},
+	}
 
-	apply_putaway_rule: function() {
+	apply_putaway_rule() {
 		if (this.frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(this.frm);
 	}
 
-});
+};
 
 // for backward compatibility: combine new and previous states
-$.extend(cur_frm.cscript, new erpnext.stock.PurchaseReceiptController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.stock.PurchaseReceiptController({frm: cur_frm}));
 
 cur_frm.cscript.update_status = function(status) {
 	frappe.ui.form.is_saving = true;
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index ad350d3..44fb736 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -514,8 +514,7 @@
    "oldfieldname": "pr_raw_material_details",
    "oldfieldtype": "Table",
    "options": "Purchase Receipt Item Supplied",
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "fieldname": "section_break0",
@@ -1149,7 +1148,7 @@
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 01:01:00.754119",
+ "modified": "2021-05-25 00:15:12.239017",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 83ba324..e488b69 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -202,6 +202,7 @@
 
 		self.make_gl_entries()
 		self.repost_future_sle_and_gle()
+		self.set_consumed_qty_in_po()
 
 	def check_next_docstatus(self):
 		submit_rv = frappe.db.sql("""select t1.name
@@ -233,6 +234,7 @@
 		self.repost_future_sle_and_gle()
 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 		self.delete_auto_created_batches()
+		self.set_consumed_qty_in_po()
 
 	@frappe.whitelist()
 	def get_current_stock(self):
@@ -579,7 +581,6 @@
 
 @frappe.whitelist()
 def make_purchase_invoice(source_name, target_doc=None):
-	from frappe.model.mapper import get_mapped_doc
 	from erpnext.accounts.party import get_payment_terms_template
 
 	doc = frappe.get_doc('Purchase Receipt', source_name)
@@ -599,11 +600,16 @@
 
 	def update_item(source_doc, target_doc, source_parent):
 		target_doc.qty, returned_qty = get_pending_qty(source_doc)
+		if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+			target_doc.rejected_qty = 0
 		target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
 		returned_qty_map[source_doc.name] = returned_qty
 
 	def get_pending_qty(item_row):
-		pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
+		qty = item_row.qty
+		if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+			qty = item_row.received_qty
+		pending_qty = qty - invoiced_qty_map.get(item_row.name, 0)
 		returned_qty = flt(returned_qty_map.get(item_row.name, 0))
 		if returned_qty:
 			if returned_qty >= pending_qty:
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 5095a80..99abf3a 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -246,7 +246,7 @@
 		pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes")
 		self.assertEqual(len(pr.get("supplied_items")), 2)
 
-		rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")])
+		rm_supp_cost = sum(d.amount for d in pr.get("supplied_items"))
 		self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
 
 		pr.cancel()
@@ -335,6 +335,10 @@
 		se2.cancel()
 		se3.cancel()
 		po.reload()
+		pr2.load_from_db()
+		pr2.cancel()
+
+		po.load_from_db()
 		po.cancel()
 
 	def test_serial_no_supplier(self):
@@ -417,11 +421,18 @@
 		self.assertEqual(return_pr_2.items[0].qty, -3)
 
 		# Make PI against unreturned amount
+		buying_settings = frappe.get_single("Buying Settings")
+		buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0
+		buying_settings.save()
+
 		pi = make_purchase_invoice(pr.name)
 		pi.submit()
 
 		self.assertEqual(pi.items[0].qty, 3)
 
+		buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 1
+		buying_settings.save()
+
 		pr.load_from_db()
 		# PR should be completed on billing all unreturned amount
 		self.assertEqual(pr.items[0].billed_amt, 150)
@@ -763,8 +774,8 @@
 		pr1.items[0].purchase_receipt_item = pr.items[0].name
 		pr1.submit()
 
-		pi = make_purchase_invoice(pr.name)
-		self.assertEqual(pi.items[0].qty, 3)
+		pi1 = make_purchase_invoice(pr.name)
+		self.assertEqual(pi1.items[0].qty, 3)
 
 		pr1.cancel()
 		pr.reload()
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 5ecc9f8..b236f6a 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -613,7 +613,7 @@
 	batch_nos = filters.get("batch_no")
 	expiry_date = filters.get("expiry_date")
 	if batch_nos:
-		batch_no_condition = """and sr.batch_no in ({}) """.format(', '.join(["'%s'" % d for d in batch_nos]))
+		batch_no_condition = """and sr.batch_no in ({}) """.format(', '.join("'%s'" % d for d in batch_nos))
 
 	if expiry_date:
 		batch_join_selection = "LEFT JOIN `tabBatch` batch on sr.batch_no = batch.name "
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 1a25994..908020d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -793,8 +793,8 @@
 	}
 });
 
-erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
-	setup: function() {
+erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockController {
+	setup() {
 		var me = this;
 
 		this.setup_posting_date_time_check();
@@ -841,9 +841,9 @@
 
 		frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier' }
 		this.frm.set_query("supplier_address", erpnext.queries.address_query)
-	},
+	}
 
-	onload_post_render: function() {
+	onload_post_render() {
 		var me = this;
 		this.set_default_account(function() {
 			if(me.frm.doc.__islocal && me.frm.doc.company && !me.frm.doc.amended_from) {
@@ -852,9 +852,9 @@
 		});
 
 		this.frm.get_field("items").grid.set_multiple_add("item_code", "qty");
-	},
+	}
 
-	refresh: function() {
+	refresh() {
 		var me = this;
 		erpnext.toggle_naming_series();
 		this.toggle_related_fields(this.frm.doc);
@@ -865,22 +865,22 @@
 		}
 		erpnext.hide_company();
 		erpnext.utils.add_item(this.frm);
-	},
+	}
 
-	scan_barcode: function() {
+	scan_barcode() {
 		let transaction_controller= new erpnext.TransactionController({frm:this.frm});
 		transaction_controller.scan_barcode();
-	},
+	}
 
-	on_submit: function() {
+	on_submit() {
 		this.clean_up();
-	},
+	}
 
-	after_cancel: function() {
+	after_cancel() {
 		this.clean_up();
-	},
+	}
 
-	set_default_account: function(callback) {
+	set_default_account(callback) {
 		var me = this;
 
 		if(this.frm.doc.company && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
@@ -900,9 +900,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	clean_up: function() {
+	clean_up() {
 		// Clear Work Order record from locals, because it is updated via Stock Entry
 		if(this.frm.doc.work_order &&
 			in_list(["Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture"],
@@ -910,13 +910,13 @@
 			frappe.model.remove_from_locals("Work Order",
 				this.frm.doc.work_order);
 		}
-	},
+	}
 
-	fg_completed_qty: function() {
+	fg_completed_qty() {
 		this.get_items();
-	},
+	}
 
-	get_items: function() {
+	get_items() {
 		var me = this;
 		if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
 			frappe.throw(__("BOM and Manufacturing Quantity are required"));
@@ -933,9 +933,9 @@
 				}
 			});
 		}
-	},
+	}
 
-	work_order: function() {
+	work_order() {
 		var me = this;
 		this.toggle_enable_bom();
 		if(!me.frm.doc.work_order || me.frm.doc.job_card) {
@@ -968,13 +968,13 @@
 				}
 			}
 		});
-	},
+	}
 
-	toggle_enable_bom: function() {
+	toggle_enable_bom() {
 		this.frm.toggle_enable("bom_no", !!!this.frm.doc.work_order);
-	},
+	}
 
-	add_excise_button: function() {
+	add_excise_button() {
 		if(frappe.boot.sysdefaults.country === "India")
 			this.frm.add_custom_button(__("Excise Invoice"), function() {
 				var excise = frappe.model.make_new_doc_and_get_name('Journal Entry');
@@ -982,9 +982,9 @@
 				excise.voucher_type = 'Excise Entry';
 				frappe.set_route('Form', 'Journal Entry', excise.name);
 			}, __('Create'));
-	},
+	}
 
-	items_add: function(doc, cdt, cdn) {
+	items_add(doc, cdt, cdn) {
 		var row = frappe.get_doc(cdt, cdn);
 
 		if (!(row.expense_account && row.cost_center)) {
@@ -993,27 +993,27 @@
 
 		if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
 		if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
-	},
+	}
 
-	from_warehouse: function(doc) {
+	from_warehouse(doc) {
 		this.frm.trigger('set_transit_warehouse');
 		this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse);
-	},
+	}
 
-	to_warehouse: function(doc) {
+	to_warehouse(doc) {
 		this.set_warehouse_in_children(doc.items, "t_warehouse", doc.to_warehouse);
-	},
+	}
 
-	set_warehouse_in_children: function(child_table, warehouse_field, warehouse) {
+	set_warehouse_in_children(child_table, warehouse_field, warehouse) {
 		let transaction_controller = new erpnext.TransactionController();
 		transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse);
-	},
+	}
 
-	items_on_form_rendered: function(doc, grid_row) {
+	items_on_form_rendered(doc, grid_row) {
 		erpnext.setup_serial_or_batch_no();
-	},
+	}
 
-	toggle_related_fields: function(doc) {
+	toggle_related_fields(doc) {
 		this.frm.toggle_enable("from_warehouse", doc.purpose!='Material Receipt');
 		this.frm.toggle_enable("to_warehouse", doc.purpose!='Material Issue');
 
@@ -1040,12 +1040,12 @@
 			doc.purpose!='Material Issue');
 
 		this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue');
-	},
+	}
 
-	supplier: function(doc) {
+	supplier(doc) {
 		erpnext.utils.get_party_details(this.frm, null, null, null);
 	}
-});
+};
 
 erpnext.stock.select_batch_and_serial_no = (frm, item) => {
 	let get_warehouse_type_and_name = (item) => {
@@ -1079,6 +1079,10 @@
 }
 
 function attach_bom_items(bom_no) {
+	if (!bom_no) {
+		return
+	}
+
 	if (check_should_not_attach_bom_items(bom_no)) return
 	frappe.db.get_doc("BOM",bom_no).then(bom => {
 		const {name, items} = bom
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index a0b5457..523d332 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -74,7 +74,8 @@
   "total_amount",
   "job_card",
   "amended_from",
-  "credit_note"
+  "credit_note",
+  "is_return"
  ],
  "fields": [
   {
@@ -611,6 +612,16 @@
    "fieldname": "apply_putaway_rule",
    "fieldtype": "Check",
    "label": "Apply Putaway Rule"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_return",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Is Return",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
@@ -618,7 +629,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-05-24 11:32:23.904307",
+ "modified": "2021-05-26 17:07:58.015737",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 2f76bc7..66f8b63 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -97,8 +97,7 @@
 		update_serial_nos_after_submit(self, "items")
 		self.update_work_order()
 		self.validate_purchase_order()
-		if self.purchase_order and self.purpose == "Send to Subcontractor":
-			self.update_purchase_order_supplied_items()
+		self.update_purchase_order_supplied_items()
 
 		self.make_gl_entries()
 
@@ -117,9 +116,7 @@
 			self.set_material_request_transfer_status('Completed')
 
 	def on_cancel(self):
-
-		if self.purchase_order and self.purpose == "Send to Subcontractor":
-			self.update_purchase_order_supplied_items()
+		self.update_purchase_order_supplied_items()
 
 		if self.work_order and self.purpose == "Material Consumption for Manufacture":
 			self.validate_work_order_status()
@@ -465,7 +462,7 @@
 		"""
 		# Set rate for outgoing items
 		outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
-		finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item])
+		finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
 
 		# Set basic rate for incoming items
 		for d in self.get('items'):
@@ -1008,10 +1005,12 @@
 					if self.purchase_order and self.purpose == "Send to Subcontractor":
 						#Get PO Supplied Items Details
 						item_wh = frappe._dict(frappe.db.sql("""
-							select rm_item_code, reserve_warehouse
-							from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
-							where po.name = poitemsup.parent
-								and po.name = %s""",self.purchase_order))
+							SELECT
+								rm_item_code, reserve_warehouse
+							FROM
+								`tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
+							WHERE
+								po.name = poitemsup.parent and po.name = %s """,self.purchase_order))
 
 					for item in itervalues(item_dict):
 						if self.pro_doc and cint(self.pro_doc.from_wip_warehouse):
@@ -1294,7 +1293,8 @@
 				item_dict[item]["qty"] = 0
 
 		# delete items with 0 qty
-		for item in item_dict.keys():
+		list_of_items = item_dict.keys()
+		for item in list_of_items:
 			if not item_dict[item]["qty"]:
 				del item_dict[item]
 
@@ -1347,7 +1347,7 @@
 			se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0)
 
 			for field in ["idx", "po_detail", "original_item",
-				"expense_account", "description", "item_name"]:
+				"expense_account", "description", "item_name", "serial_no", "batch_no"]:
 				if item_dict[d].get(field):
 					se_child.set(field, item_dict[d].get(field))
 
@@ -1400,33 +1400,26 @@
 							.format(item.batch_no, item.item_code))
 
 	def update_purchase_order_supplied_items(self):
-		#Get PO Supplied Items Details
-		item_wh = frappe._dict(frappe.db.sql("""
-			select rm_item_code, reserve_warehouse
-			from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
-			where po.name = poitemsup.parent
-			and po.name = %s""", self.purchase_order))
+		if (self.purchase_order and
+			(self.purpose in ['Send to Subcontractor', 'Material Transfer'] or self.is_return)):
 
-		#Update Supplied Qty in PO Supplied Items
+			#Get PO Supplied Items Details
+			item_wh = frappe._dict(frappe.db.sql("""
+				select rm_item_code, reserve_warehouse
+				from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
+				where po.name = poitemsup.parent
+				and po.name = %s""", self.purchase_order))
 
-		frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
-			SET
-				pos.supplied_qty = IFNULL((SELECT ifnull(sum(transfer_qty), 0)
-					FROM
-						`tabStock Entry Detail` sed, `tabStock Entry` se
-					WHERE
-						pos.name = sed.po_detail AND pos.rm_item_code = sed.item_code
-						AND pos.parent = se.purchase_order AND sed.docstatus = 1
-						AND se.name = sed.parent and se.purchase_order = %(po)s
-				), 0)
-			WHERE pos.docstatus = 1 and pos.parent = %(po)s""", {"po": self.purchase_order})
+			supplied_items = get_supplied_items(self.purchase_order)
+			for name, item in supplied_items.items():
+				frappe.db.set_value('Purchase Order Item Supplied', name, item)
 
-		#Update reserved sub contracted quantity in bin based on Supplied Item Details and
-		for d in self.get("items"):
-			item_code = d.get('original_item') or d.get('item_code')
-			reserve_warehouse = item_wh.get(item_code)
-			stock_bin = get_bin(item_code, reserve_warehouse)
-			stock_bin.update_reserved_qty_for_sub_contracting()
+			#Update reserved sub contracted quantity in bin based on Supplied Item Details and
+			for d in self.get("items"):
+				item_code = d.get('original_item') or d.get('item_code')
+				reserve_warehouse = item_wh.get(item_code)
+				stock_bin = get_bin(item_code, reserve_warehouse)
+				stock_bin.update_reserved_qty_for_sub_contracting()
 
 	def update_so_in_serial_number(self):
 		so_name, item_code = frappe.db.get_value("Work Order", self.work_order, ["sales_order", "production_item"])
@@ -1480,7 +1473,7 @@
 				cond += """ WHEN (parent = %s and name = %s) THEN %s
 					""" %(frappe.db.escape(data[0]), frappe.db.escape(data[1]), transferred_qty)
 
-			if cond and stock_entries_child_list:
+			if stock_entries_child_list:
 				frappe.db.sql(""" UPDATE `tabStock Entry Detail`
 					SET
 						transferred_qty = CASE {cond} END
@@ -1751,3 +1744,30 @@
 			format(max_retain_qty, batch_no, item_code), alert=True)
 		sample_quantity = qty_diff
 	return sample_quantity
+
+def get_supplied_items(purchase_order):
+	fields = ['`tabStock Entry Detail`.`transfer_qty`', '`tabStock Entry`.`is_return`',
+			'`tabStock Entry Detail`.`po_detail`', '`tabStock Entry Detail`.`item_code`']
+
+	filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purchase_order', '=', purchase_order]]
+
+	supplied_item_details = {}
+	for row in frappe.get_all('Stock Entry', fields = fields, filters = filters):
+		if not row.po_detail:
+			continue
+
+		key = row.po_detail
+		if key not in supplied_item_details:
+			supplied_item_details.setdefault(key,
+				frappe._dict({'supplied_qty': 0, 'returned_qty':0, 'total_supplied_qty':0}))
+
+		supplied_item = supplied_item_details[key]
+
+		if row.is_return:
+			supplied_item.returned_qty += row.transfer_qty
+		else:
+			supplied_item.supplied_qty += row.transfer_qty
+
+		supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty)
+
+	return supplied_item_details
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index b0e7440..0febcb6 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, date_diff
+from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, cint
 from frappe.model.document import Document
 from datetime import date
 from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
@@ -108,17 +108,18 @@
 		self.stock_uom = item_det.stock_uom
 
 	def check_stock_frozen_date(self):
-		stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
-		if stock_frozen_upto:
-			stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
-			if getdate(self.posting_date) <= getdate(stock_frozen_upto) and not stock_auth_role in frappe.get_roles():
-				frappe.throw(_("Stock transactions before {0} are frozen").format(formatdate(stock_frozen_upto)), StockFreezeError)
+		stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings')
 
-		stock_frozen_upto_days = int(frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto_days') or 0)
+		if stock_settings.stock_frozen_upto:
+			if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto)
+				and stock_settings.stock_auth_role not in frappe.get_roles()):
+				frappe.throw(_("Stock transactions before {0} are frozen")
+					.format(formatdate(stock_settings.stock_frozen_upto)), StockFreezeError)
+
+		stock_frozen_upto_days = cint(stock_settings.stock_frozen_upto_days)
 		if stock_frozen_upto_days:
-			stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
 			older_than_x_days_ago = (add_days(getdate(self.posting_date), stock_frozen_upto_days) <= date.today())
-			if older_than_x_days_ago and not stock_auth_role in frappe.get_roles():
+			if older_than_x_days_ago and stock_settings.stock_auth_role not in frappe.get_roles():
 				frappe.throw(_("Not allowed to update stock transactions older than {0}").format(stock_frozen_upto_days), StockFreezeError)
 
 	def scrub_posting_time(self):
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index ac4ed5e..3badc7e 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -220,8 +220,8 @@
 
 });
 
-erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
-	setup: function() {
+erpnext.stock.StockReconciliation = class StockReconciliation extends erpnext.stock.StockController {
+	setup() {
 		var me = this;
 
 		this.setup_posting_date_time_check();
@@ -249,17 +249,17 @@
 				}
 			}
 		}
-	},
+	}
 
-	refresh: function() {
+	refresh() {
 		if(this.frm.doc.docstatus > 0) {
 			this.show_stock_ledger();
 			if (erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
 				this.show_general_ledger();
 			}
 		}
-	},
+	}
 
-});
+};
 
 cur_frm.cscript = new erpnext.stock.StockReconciliation({frm: cur_frm});
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 306df99..93ab40a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -469,10 +469,17 @@
 	def submit(self):
 		if len(self.items) > 100:
 			msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"))
-			self.queue_action('submit', timeout=2000)
+			self.queue_action('submit', timeout=4600)
 		else:
 			self._submit()
 
+	def cancel(self):
+		if len(self.items) > 100:
+			msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"))
+			self.queue_action('cancel', timeout=2000)
+		else:
+			self._cancel()
+
 @frappe.whitelist()
 def get_items(warehouse, posting_date, posting_time, company):
 	lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index 95478f6..e3981c9 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -11,6 +11,7 @@
 import erpnext
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
+from erpnext.stock.doctype.item.test_item import create_item
 
 test_records = frappe.get_test_records('Warehouse')
 
@@ -92,6 +93,39 @@
 		self.assertTrue(frappe.db.get_value("Warehouse",
 			filters={"account": "Test Warehouse for Merging 2 - TCP1"}))
 
+	def test_unlinking_warehouse_from_item_defaults(self):
+		company = "_Test Company"
+
+		warehouse_names = [f'_Test Warehouse {i} for Unlinking' for i in range(2)]
+		warehouse_ids = []
+		for warehouse in warehouse_names:
+			warehouse_id = create_warehouse(warehouse, company=company)
+			warehouse_ids.append(warehouse_id)
+
+		item_names = [f'_Test Item {i} for Unlinking' for i in range(2)]
+		for item, warehouse in zip(item_names, warehouse_ids):
+			create_item(item, warehouse=warehouse, company=company)
+
+		# Delete warehouses
+		for warehouse in warehouse_ids:
+			frappe.delete_doc("Warehouse", warehouse)
+
+		# Check Item existance
+		for item in item_names:
+			self.assertTrue(
+				bool(frappe.db.exists("Item", item)),
+				f"{item} doesn't exist"
+			)
+
+			item_doc = frappe.get_doc("Item", item)
+			for item_default in item_doc.item_defaults:
+				self.assertNotIn(
+					item_default.default_warehouse,
+					warehouse_ids,
+					f"{item} linked to {item_default.default_warehouse} in {warehouse_ids}."
+				)
+
+
 def create_warehouse(warehouse_name, properties=None, company=None):
 	if not company:
 		company = "_Test Company"
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 2062bdd..3abc139 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -54,6 +54,7 @@
 			throw(_("Child warehouse exists for this warehouse. You can not delete this warehouse."))
 
 		self.update_nsm_model()
+		self.unlink_from_items()
 
 	def check_if_sle_exists(self):
 		return frappe.db.sql("""select name from `tabStock Ledger Entry`
@@ -138,6 +139,12 @@
 			self.save()
 			return 1
 
+	def unlink_from_items(self):
+		frappe.db.sql("""
+				update `tabItem Default`
+				set default_warehouse=NULL
+				where default_warehouse=%s""", self.name)
+
 @frappe.whitelist()
 def get_children(doctype, parent=None, company=None, is_root=False):
 	if is_root:
diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js
index bddffd4..f00dd3e 100644
--- a/erpnext/stock/page/stock_balance/stock_balance.js
+++ b/erpnext/stock/page/stock_balance/stock_balance.js
@@ -62,7 +62,7 @@
 
 	// page.sort_selector.wrapper.css({'margin-right': '15px', 'margin-top': '4px'});
 
-	frappe.require('assets/js/item-dashboard.min.js', function() {
+	frappe.require('item-dashboard.bundle.js', function() {
 		page.item_dashboard = new erpnext.stock.ItemDashboard({
 			parent: page.main,
 			page_length: 20,
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
index b610e7d..c0ffdc9 100644
--- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
@@ -79,7 +79,7 @@
 		}
 	});
 
-	frappe.require('assets/js/item-dashboard.min.js', function() {
+	frappe.require('item-dashboard.bundle.js', function() {
 		$(frappe.render_template('warehouse_capacity_summary_header')).appendTo(page.main);
 
 		page.capacity_dashboard = new erpnext.stock.ItemDashboard({
@@ -117,4 +117,4 @@
 		setup_click('Item');
 		setup_click('Warehouse');
 	});
-};
\ No newline at end of file
+};
diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py
index 5296211..db7498b 100644
--- a/erpnext/stock/report/item_price_stock/item_price_stock.py
+++ b/erpnext/stock/report/item_price_stock/item_price_stock.py
@@ -89,7 +89,7 @@
 		{conditions}"""
 		.format(conditions=conditions), filters, as_dict=1)
 
-	price_list_names = list(set([item.price_list_name for item in item_results]))
+	price_list_names = list(set(item.price_list_name for item in item_results))
 
 	buying_price_map = get_price_map(price_list_names, buying=1)
 	selling_price_map = get_price_map(price_list_names, selling=1)
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index 2f70523..2e13aa0 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -66,7 +66,7 @@
 			purpose is NULL
 			or purpose not in ({})
 		)
-	""".format(', '.join([f"'{p}'" for p in purpose_to_exclude]))
+	""".format(', '.join(f"'{p}'" for p in purpose_to_exclude))
 	condition = condition.replace("posting_date", "sle.posting_date")
 
 	consumed_items = frappe.db.sql("""
diff --git a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
index 276e42e..8fffbcc 100644
--- a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
+++ b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
@@ -141,7 +141,7 @@
 		return []
 
 	item_conditions_sql = ' and sle.item_code in ({})' \
-		.format(', '.join([frappe.db.escape(i) for i in items]))
+		.format(', '.join(frappe.db.escape(i) for i in items))
 
 	conditions = get_sle_conditions(filters)
 
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index bbd73e9..b6a8063 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -157,7 +157,7 @@
 	item_conditions_sql = ''
 	if items:
 		item_conditions_sql = ' and sle.item_code in ({})'\
-			.format(', '.join([frappe.db.escape(i, percent=False) for i in items]))
+			.format(', '.join(frappe.db.escape(i, percent=False) for i in items))
 
 	conditions = get_conditions(filters)
 
@@ -253,7 +253,7 @@
 def get_item_details(items, sle, filters):
 	item_details = {}
 	if not items:
-		items = list(set([d.item_code for d in sle]))
+		items = list(set(d.item_code for d in sle))
 
 	if not items:
 		return item_details
@@ -291,7 +291,7 @@
 			select parent, warehouse, warehouse_reorder_qty, warehouse_reorder_level
 			from `tabItem Reorder`
 			where parent in ({0})
-		""".format(', '.join([frappe.db.escape(i, percent=False) for i in items])), as_dict=1)
+		""".format(', '.join(frappe.db.escape(i, percent=False) for i in items)), as_dict=1)
 
 	return dict((d.parent + d.warehouse, d) for d in item_reorder_details)
 
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 36996e9..8909f21 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -115,7 +115,7 @@
 	item_conditions_sql = ''
 	if items:
 		item_conditions_sql = 'and sle.item_code in ({})'\
-			.format(', '.join([frappe.db.escape(i) for i in items]))
+			.format(', '.join(frappe.db.escape(i) for i in items))
 
 	sl_entries = frappe.db.sql("""
 		SELECT
@@ -169,7 +169,7 @@
 def get_item_details(items, sl_entries, include_uom):
 	item_details = {}
 	if not items:
-		items = list(set([d.item_code for d in sl_entries]))
+		items = list(set(d.item_code for d in sl_entries))
 
 	if not items:
 		return item_details
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index b2825fc..9fe89c3 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -22,6 +22,7 @@
 # _exceptions = []
 
 def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
+	from erpnext.controllers.stock_controller import future_sle_exists
 	if sl_entries:
 		from erpnext.stock.utils import update_bin
 
@@ -30,6 +31,9 @@
 			validate_cancellation(sl_entries)
 			set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
 
+		args = get_args_for_future_sle(sl_entries[0])
+		future_sle_exists(args, sl_entries)
+
 		for sle in sl_entries:
 			if sle.serial_no:
 				validate_serial_no(sle)
@@ -53,6 +57,14 @@
 			args = sle_doc.as_dict()
 			update_bin(args, allow_negative_stock, via_landed_cost_voucher)
 
+def get_args_for_future_sle(row):
+	return frappe._dict({
+		'voucher_type': row.get('voucher_type'),
+		'voucher_no': row.get('voucher_no'),
+		'posting_date': row.get('posting_date'),
+		'posting_time': row.get('posting_time')
+	})
+
 def validate_serial_no(sle):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 	for sn in get_serial_nos(sle.serial_no):
@@ -472,7 +484,7 @@
 			frappe.db.set_value("Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate)
 
 		# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
-		if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"):
+		if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes':
 			doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
 			doc.update_valuation_rate(reset_outgoing_rate=False)
 			for d in (doc.items + doc.supplied_items):
@@ -521,7 +533,7 @@
 			fields=["purchase_rate", "name", "company"],
 			filters = {'name': ('in', serial_nos)})
 
-		incoming_values = sum([flt(d.purchase_rate) for d in all_serial_nos if d.company==sle.company])
+		incoming_values = sum(flt(d.purchase_rate) for d in all_serial_nos if d.company==sle.company)
 
 		# Get rate for serial nos which has been transferred to other company
 		invalid_serial_nos = [d.name for d in all_serial_nos if d.company!=sle.company]
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 034d3eb..8a6a3a3 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -177,7 +177,7 @@
 	return bin_obj
 
 def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
-	is_stock_item = frappe.db.get_value('Item', args.get("item_code"), 'is_stock_item')
+	is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
 	if is_stock_item:
 		bin = get_bin(args.get("item_code"), args.get("warehouse"))
 		bin.update_stock(args, allow_negative_stock, via_landed_cost_voucher)
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index ecc9fcf..9ac1efa 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -17,84 +17,7 @@
 				if (r && r.allow_resetting_service_level_agreement == "0") {
 					frm.set_df_property("reset_service_level_agreement", "hidden", 1);
 				}
-		});
-
-		if (frm.doc.service_level_agreement) {
-			frappe.call({
-				method: "erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters",
-				args: {
-					name: frm.doc.service_level_agreement,
-					customer: frm.doc.customer
-				},
-				callback: function (r) {
-					if (r && r.message) {
-						frm.set_query("priority", function() {
-							return {
-								filters: {
-									"name": ["in", r.message.priority],
-								}
-							};
-						});
-						frm.set_query("service_level_agreement", function() {
-							return {
-								filters: {
-									"name": ["in", r.message.service_level_agreements],
-								}
-							};
-						});
-					}
-				}
 			});
-		}
-	},
-
-	refresh: function(frm) {
-
-		// alert messages
-		if (frm.doc.status !== "Closed" && frm.doc.service_level_agreement
-			&& frm.doc.agreement_status === "Ongoing") {
-			frappe.call({
-				"method": "frappe.client.get",
-				args: {
-					doctype: "Service Level Agreement",
-					name: frm.doc.service_level_agreement
-				},
-				callback: function(data) {
-					let statuses = data.message.pause_sla_on;
-					const hold_statuses = [];
-					$.each(statuses, (_i, entry) => {
-						hold_statuses.push(entry.status);
-					});
-					if (hold_statuses.includes(frm.doc.status)) {
-						frm.dashboard.clear_headline();
-						let message = { "indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)]) };
-						frm.dashboard.set_headline_alert(
-							'<div class="row">' +
-							'<div class="col-xs-12">' +
-							'<span class="indicator whitespace-nowrap ' + message.indicator + '"><span>' + message.msg + '</span></span> ' +
-							'</div>' +
-							'</div>'
-						);
-					} else {
-						set_time_to_resolve_and_response(frm);
-					}
-				}
-			});
-		} else if (frm.doc.service_level_agreement) {
-			frm.dashboard.clear_headline();
-
-			let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
-				{ "indicator": "green", "msg": "Service Level Agreement has been fulfilled" } :
-				{ "indicator": "red", "msg": "Service Level Agreement Failed" };
-
-			frm.dashboard.set_headline_alert(
-				'<div class="row">' +
-				'<div class="col-xs-12">' +
-				'<span class="indicator whitespace-nowrap ' + agreement_status.indicator + '"><span class="hidden-xs">' + agreement_status.msg + '</span></span> ' +
-				'</div>' +
-				'</div>'
-			);
-		}
 
 		// buttons
 		if (frm.doc.status !== "Closed") {
@@ -140,7 +63,7 @@
 					message: __("Resetting Service Level Agreement.")
 				});
 
-				frm.call("reset_service_level_agreement", {
+				frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", {
 					reason: values.reason,
 					user: frappe.session.user_email
 				}, () => {
@@ -222,44 +145,4 @@
 		// 	frm.timeline.wrapper.data("help-article-event-attached", true);
 		// }
 	},
-});
-
-function set_time_to_resolve_and_response(frm) {
-	frm.dashboard.clear_headline();
-
-	var time_to_respond = get_status(frm.doc.response_by_variance);
-	if (!frm.doc.first_responded_on && frm.doc.agreement_status === "Ongoing") {
-		time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status);
-	}
-
-	var time_to_resolve = get_status(frm.doc.resolution_by_variance);
-	if (!frm.doc.resolution_date && frm.doc.agreement_status === "Ongoing") {
-		time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status);
-	}
-
-	frm.dashboard.set_headline_alert(
-		'<div class="row">' +
-			'<div class="col-xs-12 col-sm-6">' +
-				'<span class="indicator whitespace-nowrap '+ time_to_respond.indicator +'"><span>Time to Respond: '+ time_to_respond.diff_display +'</span></span> ' +
-			'</div>' +
-			'<div class="col-xs-12 col-sm-6">' +
-				'<span class="indicator whitespace-nowrap '+ time_to_resolve.indicator +'"><span>Time to Resolve: '+ time_to_resolve.diff_display +'</span></span> ' +
-			'</div>' +
-		'</div>'
-	);
-}
-
-function get_time_left(timestamp, agreement_status) {
-	const diff = moment(timestamp).diff(moment());
-	const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed";
-	let indicator = (diff_display == "Failed" && agreement_status != "Fulfilled") ? "red" : "green";
-	return {"diff_display": diff_display, "indicator": indicator};
-}
-
-function get_status(variance) {
-	if (variance > 0) {
-		return {"diff_display": "Fulfilled", "indicator": "green"};
-	} else {
-		return {"diff_display": "Failed", "indicator": "red"};
-	}
-}
+});
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index b068363..dd6d647 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -7,11 +7,10 @@
 from frappe import _
 from frappe import utils
 from frappe.model.document import Document
-from frappe.utils import cint, now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
+from frappe.utils import now_datetime
 from datetime import datetime, timedelta
 from frappe.model.mapper import get_mapped_doc
 from frappe.utils.user import is_website_user
-from erpnext.support.doctype.service_level_agreement.service_level_agreement import get_active_service_level_agreement_for
 from frappe.email.inbox import link_communication_to_document
 
 class Issue(Document):
@@ -25,8 +24,6 @@
 		if not self.raised_by:
 			self.raised_by = frappe.session.user
 
-		self.change_service_level_agreement_and_priority()
-		self.update_status()
 		self.set_lead_contact(self.raised_by)
 
 	def on_update(self):
@@ -54,99 +51,6 @@
 				self.company = frappe.db.get_value("Lead", self.lead, "company") or \
 					frappe.db.get_default("Company")
 
-	def update_status(self):
-		status = frappe.db.get_value("Issue", self.name, "status")
-		if self.status != "Open" and status == "Open" and not self.first_responded_on:
-			self.first_responded_on = frappe.flags.current_time or now_datetime()
-
-		if self.status in ["Closed", "Resolved"] and status not in ["Resolved", "Closed"]:
-			self.resolution_date = frappe.flags.current_time or now_datetime()
-			if frappe.db.get_value("Issue", self.name, "agreement_status") == "Ongoing":
-				set_service_level_agreement_variance(issue=self.name)
-				self.update_agreement_status()
-			set_resolution_time(issue=self)
-			set_user_resolution_time(issue=self)
-
-		if self.status == "Open" and status != "Open":
-			# if no date, it should be set as None and not a blank string "", as per mysql strict config
-			self.resolution_date = None
-			self.reset_issue_metrics()
-			# enable SLA and variance on Reopen
-			self.agreement_status = "Ongoing"
-			set_service_level_agreement_variance(issue=self.name)
-
-		self.handle_hold_time(status)
-
-	def handle_hold_time(self, status):
-		if self.service_level_agreement:
-			# set response and resolution variance as None as the issue is on Hold
-			pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"],
-				filters={"parent": self.service_level_agreement})
-			hold_statuses = [entry.status for entry in pause_sla_on]
-			update_values = {}
-
-			if hold_statuses:
-				if self.status in hold_statuses and status not in hold_statuses:
-					update_values['on_hold_since'] = frappe.flags.current_time or now_datetime()
-					if not self.first_responded_on:
-						update_values['response_by'] = None
-						update_values['response_by_variance'] = 0
-					update_values['resolution_by'] = None
-					update_values['resolution_by_variance'] = 0
-
-				# calculate hold time when status is changed from any hold status to any non-hold status
-				if self.status not in hold_statuses and status in hold_statuses:
-					hold_time = self.total_hold_time if self.total_hold_time else 0
-					now_time = frappe.flags.current_time or now_datetime()
-					last_hold_time = 0
-					if self.on_hold_since:
-						# last_hold_time will be added to the sla variables
-						last_hold_time = time_diff_in_seconds(now_time, self.on_hold_since)
-						update_values['total_hold_time'] = hold_time + last_hold_time
-
-					# re-calculate SLA variables after issue changes from any hold status to any non-hold status
-					# add hold time to SLA variables
-					start_date_time = get_datetime(self.service_level_agreement_creation)
-					priority = get_priority(self)
-					now_time = frappe.flags.current_time or now_datetime()
-
-					if not self.first_responded_on:
-						response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
-						response_by = add_to_date(response_by, seconds=round(last_hold_time))
-						response_by_variance = round(time_diff_in_seconds(response_by, now_time))
-						update_values['response_by'] = response_by
-						update_values['response_by_variance'] = response_by_variance + last_hold_time
-
-					resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
-					resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time))
-					resolution_by_variance = round(time_diff_in_seconds(resolution_by, now_time))
-					update_values['resolution_by'] = resolution_by
-					update_values['resolution_by_variance'] = resolution_by_variance + last_hold_time
-					update_values['on_hold_since'] = None
-
-				self.db_set(update_values)
-
-	def update_agreement_status(self):
-		if self.service_level_agreement and self.agreement_status == "Ongoing":
-			if cint(frappe.db.get_value("Issue", self.name, "response_by_variance")) < 0 or \
-				cint(frappe.db.get_value("Issue", self.name, "resolution_by_variance")) < 0:
-
-				self.agreement_status = "Failed"
-			else:
-				self.agreement_status = "Fulfilled"
-
-	def update_agreement_status_on_custom_status(self):
-		"""
-			Update Agreement Fulfilled status using Custom Scripts for Custom Issue Status
-		"""
-		if not self.first_responded_on: # first_responded_on set when first reply is sent to customer
-			self.response_by_variance = round(time_diff_in_seconds(self.response_by, now_datetime()), 2)
-
-		if not self.resolution_date: # resolution_date set when issue has been closed
-			self.resolution_by_variance = round(time_diff_in_seconds(self.resolution_by, now_datetime()), 2)
-
-		self.agreement_status = "Fulfilled" if self.response_by_variance > 0 and self.resolution_by_variance > 0 else "Failed"
-
 	def create_communication(self):
 		communication = frappe.new_doc("Communication")
 		communication.update({
@@ -213,194 +117,6 @@
 
 		return replicated_issue.name
 
-	def before_insert(self):
-		if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
-			if frappe.flags.in_test:
-				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-			else:
-				self.set_response_and_resolution_time()
-
-	def set_response_and_resolution_time(self, priority=None, service_level_agreement=None):
-		service_level_agreement = get_active_service_level_agreement_for(priority=priority,
-			customer=self.customer, service_level_agreement=service_level_agreement)
-
-		if not service_level_agreement:
-			if frappe.db.get_value("Issue", self.name, "service_level_agreement"):
-				frappe.throw(_("Couldn't Set Service Level Agreement {0}.").format(self.service_level_agreement))
-			return
-
-		if (service_level_agreement.customer and self.customer) and not (service_level_agreement.customer == self.customer):
-			frappe.throw(_("This Service Level Agreement is specific to Customer {0}").format(service_level_agreement.customer))
-
-		self.service_level_agreement = service_level_agreement.name
-		self.priority = service_level_agreement.default_priority if not priority else priority
-
-		priority = get_priority(self)
-
-		if not self.creation:
-			self.creation = now_datetime()
-			self.service_level_agreement_creation = now_datetime()
-
-		start_date_time = get_datetime(self.service_level_agreement_creation)
-		self.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
-		self.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
-
-		self.response_by_variance = round(time_diff_in_seconds(self.response_by, now_datetime()))
-		self.resolution_by_variance = round(time_diff_in_seconds(self.resolution_by, now_datetime()))
-
-	def change_service_level_agreement_and_priority(self):
-		if self.service_level_agreement and frappe.db.exists("Issue", self.name) and \
-			frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
-
-			if not self.priority == frappe.db.get_value("Issue", self.name, "priority"):
-				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-				frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority))
-
-			if not self.service_level_agreement == frappe.db.get_value("Issue", self.name, "service_level_agreement"):
-				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-				frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
-
-	@frappe.whitelist()
-	def reset_service_level_agreement(self, reason, user):
-		if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
-			frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
-
-		frappe.get_doc({
-			"doctype": "Comment",
-			"comment_type": "Info",
-			"reference_doctype": self.doctype,
-			"reference_name": self.name,
-			"comment_email": user,
-			"content": " resetted Service Level Agreement - {0}".format(_(reason)),
-		}).insert(ignore_permissions=True)
-
-		self.service_level_agreement_creation = now_datetime()
-		self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-		self.agreement_status = "Ongoing"
-		self.save()
-
-	def reset_issue_metrics(self):
-		self.db_set("resolution_time", None)
-		self.db_set("user_resolution_time", None)
-
-
-def get_priority(issue):
-	service_level_agreement = frappe.get_doc("Service Level Agreement", issue.service_level_agreement)
-	priority = service_level_agreement.get_service_level_agreement_priority(issue.priority)
-	priority.update({
-		"support_and_resolution": service_level_agreement.support_and_resolution,
-		"holiday_list": service_level_agreement.holiday_list
-	})
-	return priority
-
-
-def get_expected_time_for(parameter, service_level, start_date_time):
-	current_date_time = start_date_time
-	expected_time = current_date_time
-	start_time = None
-	end_time = None
-
-	if parameter == "response":
-		allotted_seconds = service_level.get("response_time")
-	elif parameter == "resolution":
-		allotted_seconds = service_level.get("resolution_time")
-	else:
-		frappe.throw(_("{0} parameter is invalid").format(parameter))
-
-	expected_time_is_set = 0
-
-	support_days = {}
-	for service in service_level.get("support_and_resolution"):
-		support_days[service.workday] = frappe._dict({
-			"start_time": service.start_time,
-			"end_time": service.end_time,
-		})
-
-	holidays = get_holidays(service_level.get("holiday_list"))
-	weekdays = get_weekdays()
-
-	while not expected_time_is_set:
-		current_weekday = weekdays[current_date_time.weekday()]
-
-		if not is_holiday(current_date_time, holidays) and current_weekday in support_days:
-			start_time = current_date_time - datetime(current_date_time.year, current_date_time.month, current_date_time.day) \
-				if getdate(current_date_time) == getdate(start_date_time) and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time \
-				else support_days[current_weekday].start_time
-			end_time = support_days[current_weekday].end_time
-			time_left_today = time_diff_in_seconds(end_time, start_time)
-
-			# no time left for support today
-			if time_left_today <= 0: pass
-			elif allotted_seconds:
-				if time_left_today >= allotted_seconds:
-					expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
-					expected_time = add_to_date(expected_time, seconds=allotted_seconds)
-					expected_time_is_set = 1
-				else:
-					allotted_seconds = allotted_seconds - time_left_today
-
-		if not expected_time_is_set:
-			current_date_time = add_to_date(current_date_time, days=1)
-
-	if end_time and allotted_seconds >= 86400:
-		current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
-	else:
-		current_date_time = expected_time
-
-	return current_date_time
-
-def set_service_level_agreement_variance(issue=None):
-	current_time = frappe.flags.current_time or now_datetime()
-
-	filters = {"status": "Open", "agreement_status": "Ongoing"}
-	if issue:
-		filters = {"name": issue}
-
-	for issue in frappe.get_list("Issue", filters=filters):
-		doc = frappe.get_doc("Issue", issue.name)
-
-		if not doc.first_responded_on: # first_responded_on set when first reply is sent to customer
-			variance = round(time_diff_in_seconds(doc.response_by, current_time), 2)
-			frappe.db.set_value(dt="Issue", dn=doc.name, field="response_by_variance", val=variance, update_modified=False)
-			if variance < 0:
-				frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)
-
-		if not doc.resolution_date: # resolution_date set when issue has been closed
-			variance = round(time_diff_in_seconds(doc.resolution_by, current_time), 2)
-			frappe.db.set_value(dt="Issue", dn=doc.name, field="resolution_by_variance", val=variance, update_modified=False)
-			if variance < 0:
-				frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)
-
-
-def set_resolution_time(issue):
-	# total time taken from issue creation to closing
-	resolution_time = time_diff_in_seconds(issue.resolution_date, issue.creation)
-	issue.db_set("resolution_time", resolution_time)
-
-
-def set_user_resolution_time(issue):
-	# total time taken by a user to close the issue apart from wait_time
-	communications = frappe.get_list("Communication", filters={
-			"reference_doctype": issue.doctype,
-			"reference_name": issue.name
-		},
-		fields=["sent_or_received", "name", "creation"],
-		order_by="creation"
-	)
-
-	pending_time = []
-	for i in range(len(communications)):
-		if communications[i].sent_or_received == "Received" and communications[i-1].sent_or_received == "Sent":
-			wait_time = time_diff_in_seconds(communications[i].creation, communications[i-1].creation)
-			if wait_time > 0:
-				pending_time.append(wait_time)
-
-	total_pending_time = sum(pending_time)
-	resolution_time_in_secs = time_diff_in_seconds(issue.resolution_date, issue.creation)
-	user_resolution_time = resolution_time_in_secs - total_pending_time
-	issue.db_set("user_resolution_time", user_resolution_time)
-
-
 def get_list_context(context=None):
 	return {
 		"title": _("Issues"),
@@ -439,15 +155,13 @@
 
 @frappe.whitelist()
 def set_multiple_status(names, status):
-	names = json.loads(names)
-	for name in names:
-		set_status(name, status)
+
+	for name in json.loads(names):
+		frappe.db.set_value("Issue", name, "status", status)
 
 @frappe.whitelist()
 def set_status(name, status):
-	st = frappe.get_doc("Issue", name)
-	st.status = status
-	st.save()
+	frappe.db.set_value("Issue", name, "status", status)
 
 def auto_close_tickets():
 	"""Auto-close replied support tickets after 7 days"""
@@ -473,14 +187,6 @@
 	"""Called when Contact is deleted"""
 	frappe.db.sql("""UPDATE `tabIssue` set contact='' where contact=%s""", contact.name)
 
-def get_holidays(holiday_list_name):
-	holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
-	holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
-	return holidays
-
-def is_holiday(date, holidays):
-	return getdate(date) in holidays
-
 @frappe.whitelist()
 def make_task(source_name, target_doc=None):
 	return get_mapped_doc("Issue", source_name, {
@@ -506,9 +212,7 @@
 
 	return issue.name
 
-def get_time_in_timedelta(time):
-	"""
-		Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215)
-	"""
-	import datetime
-	return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
\ No newline at end of file
+def get_holidays(holiday_list_name):
+	holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
+	holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
+	return holidays
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 7da5d7f..7b9b144 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -68,7 +68,7 @@
 		self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
 
 		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
-
+		issue.reload()
 		issue.status = 'Closed'
 		issue.save()
 
diff --git a/erpnext/support/doctype/service_day/service_day.json b/erpnext/support/doctype/service_day/service_day.json
index 68614b1..9662130 100644
--- a/erpnext/support/doctype/service_day/service_day.json
+++ b/erpnext/support/doctype/service_day/service_day.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2019-03-04 12:55:36.403035",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -16,7 +17,8 @@
    "fieldtype": "Select",
    "in_list_view": 1,
    "label": "Workday",
-   "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday"
+   "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
+   "reqd": 1
   },
   {
    "fieldname": "section_break_2",
@@ -26,7 +28,8 @@
    "fieldname": "start_time",
    "fieldtype": "Time",
    "in_list_view": 1,
-   "label": "Start Time"
+   "label": "Start Time",
+   "reqd": 1
   },
   {
    "fieldname": "column_break_3",
@@ -36,11 +39,13 @@
    "fieldname": "end_time",
    "fieldtype": "Time",
    "in_list_view": 1,
-   "label": "End Time"
+   "label": "End Time",
+   "reqd": 1
   }
  ],
  "istable": 1,
- "modified": "2019-05-05 19:15:08.999579",
+ "links": [],
+ "modified": "2020-07-06 13:28:47.303873",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Service Day",
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
index 00060b9..308bce4 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
@@ -3,16 +3,87 @@
 
 frappe.ui.form.on('Service Level Agreement', {
 	setup: function(frm) {
-		let allow_statuses = [];
-		const exclude_statuses = ['Open', 'Closed', 'Resolved'];
+		if (cint(frm.doc.apply_sla_for_resolution) === 1) {
+			frm.get_field('priorities').grid.editable_fields = [
+				{fieldname: 'priority', columns: 1},
+				{fieldname: 'default_priority', columns: 1},
+				{fieldname: 'response_time', columns: 2},
+				{fieldname: 'resolution_time', columns: 2}
+			];
+		} else {
+			frm.get_field('priorities').grid.editable_fields = [
+				{fieldname: 'priority', columns: 1},
+				{fieldname: 'default_priority', columns: 1},
+				{fieldname: 'response_time', columns: 3},
+			];
+		}
+	},
 
-		frappe.model.with_doctype('Issue', () => {
-			let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
-			statuses = statuses.split('\n');
-			allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
-			frm.fields_dict.pause_sla_on.grid.update_docfield_property(
-				'status', 'options', [''].concat(allow_statuses)
-			);
+	refresh: function(frm) {
+		frm.trigger('fetch_status_fields');
+		frm.trigger('toggle_resolution_fields');
+	},
+
+	document_type: function(frm) {
+		frm.trigger('fetch_status_fields');
+	},
+
+	fetch_status_fields: function(frm) {
+		let allow_statuses = [];
+		let exclude_statuses = [];
+
+		if (frm.doc.document_type) {
+			frappe.model.with_doctype(frm.doc.document_type, () => {
+				let statuses = frappe.meta.get_docfield(frm.doc.document_type, 'status', frm.doc.name).options;
+				statuses = statuses.split('\n');
+
+				exclude_statuses = ['Open', 'Closed'];
+				allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
+
+				frm.fields_dict.pause_sla_on.grid.update_docfield_property(
+					'status', 'options', [''].concat(allow_statuses)
+				);
+
+				exclude_statuses = ['Open'];
+				allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
+				frm.fields_dict.sla_fulfilled_on.grid.update_docfield_property(
+					'status', 'options', [''].concat(allow_statuses)
+				);
+			});
+		}
+
+		frm.refresh_field('pause_sla_on');
+	},
+
+	apply_sla_for_resolution: function(frm) {
+		frm.trigger('toggle_resolution_fields');
+	},
+
+	toggle_resolution_fields: function(frm) {
+		if (cint(frm.doc.apply_sla_for_resolution) === 1) {
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'hidden', 0);
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'reqd', 1);
+		} else {
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'hidden', 1);
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'reqd', 0);
+		}
+
+		frm.refresh_field('priorities');
+	},
+
+	onload: function(frm) {
+		frm.set_query("document_type", function() {
+			let invalid_doctypes = frappe.model.core_doctypes_list;
+			invalid_doctypes.push(frm.doc.doctype, 'Cost Center', 'Company');
+
+			return {
+				filters: [
+					['DocType', 'issingle', '=', 0],
+					['DocType', 'istable', '=', 0],
+					['DocType', 'name', 'not in', invalid_doctypes],
+					['DocType', 'module', 'not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
+				]
+			};
 		});
 	}
 });
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
index 939c199..61ca3a3 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
@@ -1,18 +1,18 @@
 {
  "actions": [],
- "autoname": "format:SLA-{service_level}-{####}",
+ "autoname": "format:SLA-{document_type}-{service_level}-{####}",
  "creation": "2018-12-26 21:08:15.448812",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "enable",
+  "enabled",
   "section_break_2",
-  "service_level",
-  "default_priority",
+  "document_type",
   "default_service_level_agreement",
+  "default_priority",
   "column_break_2",
-  "employee_group",
+  "service_level",
   "holiday_list",
   "entity_section",
   "entity_type",
@@ -20,13 +20,14 @@
   "entity",
   "agreement_details_section",
   "start_date",
-  "active",
   "column_break_7",
   "end_date",
-  "section_break_18",
-  "pause_sla_on",
   "response_and_resolution_time_section",
+  "apply_sla_for_resolution",
   "priorities",
+  "status_details",
+  "sla_fulfilled_on",
+  "pause_sla_on",
   "support_and_resolution_section_break",
   "support_and_resolution"
  ],
@@ -36,7 +37,7 @@
    "fieldtype": "Data",
    "in_list_view": 1,
    "in_standard_filter": 1,
-   "label": "Service Level",
+   "label": "Service Level Name",
    "reqd": 1
   },
   {
@@ -51,20 +52,12 @@
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "employee_group",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Employee Group",
-   "options": "Employee Group"
-  },
-  {
+   "depends_on": "eval: !doc.default_service_level_agreement",
    "fieldname": "agreement_details_section",
    "fieldtype": "Section Break",
    "label": "Agreement Details"
   },
   {
-   "depends_on": "eval: !doc.default_service_level_agreement",
    "fieldname": "start_date",
    "fieldtype": "Date",
    "label": "Start Date"
@@ -81,21 +74,18 @@
    "label": "End Date"
   },
   {
-   "collapsible": 1,
    "fieldname": "response_and_resolution_time_section",
    "fieldtype": "Section Break",
    "label": "Response and Resolution Time"
   },
   {
-   "collapsible": 1,
    "fieldname": "support_and_resolution_section_break",
    "fieldtype": "Section Break",
-   "label": "Support Hours"
+   "label": "Working Hours"
   },
   {
    "fieldname": "support_and_resolution",
    "fieldtype": "Table",
-   "label": "Support and Resolution",
    "options": "Service Day",
    "reqd": 1
   },
@@ -107,13 +97,6 @@
    "reqd": 1
   },
   {
-   "default": "1",
-   "fieldname": "active",
-   "fieldtype": "Check",
-   "label": "Active",
-   "read_only": 1
-  },
-  {
    "fieldname": "column_break_10",
    "fieldtype": "Column Break"
   },
@@ -139,14 +122,9 @@
    "options": "\nCustomer\nCustomer Group\nTerritory"
   },
   {
-   "default": "1",
-   "fieldname": "enable",
-   "fieldtype": "Check",
-   "label": "Enable"
-  },
-  {
    "fieldname": "section_break_2",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "hide_border": 1
   },
   {
    "default": "0",
@@ -162,19 +140,45 @@
    "read_only": 1
   },
   {
-   "fieldname": "section_break_18",
-   "fieldtype": "Section Break",
-   "hide_border": 1
-  },
-  {
    "fieldname": "pause_sla_on",
    "fieldtype": "Table",
-   "label": "Pause SLA On",
+   "label": "SLA Paused On",
    "options": "Pause SLA On Status"
+  },
+  {
+   "fieldname": "document_type",
+   "fieldtype": "Link",
+   "label": "Document Type",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "enabled",
+   "fieldtype": "Check",
+   "label": "Enabled"
+  },
+  {
+   "fieldname": "status_details",
+   "fieldtype": "Section Break",
+   "label": "Status Details"
+  },
+  {
+   "fieldname": "sla_fulfilled_on",
+   "fieldtype": "Table",
+   "label": "SLA Fulfilled On",
+   "options": "SLA Fulfilled On Status",
+   "reqd": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "apply_sla_for_resolution",
+   "fieldtype": "Check",
+   "label": "Apply SLA for Resolution Time"
   }
  ],
  "links": [],
- "modified": "2020-06-10 12:30:15.050785",
+ "modified": "2021-05-29 13:35:41.956849",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Service Level Agreement",
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 70c4696..60e5fbe 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -6,44 +6,43 @@
 import frappe
 from frappe.model.document import Document
 from frappe import _
-from frappe.utils import getdate, get_weekdays, get_link_to_form
+from frappe.core.utils import get_parent_doc
+from frappe.utils import time_diff_in_seconds, getdate, get_weekdays, add_to_date, get_time, get_datetime, \
+	get_time_zone, to_timedelta, get_datetime_str, get_link_to_form, cint
+from datetime import datetime
+from erpnext.support.doctype.issue.issue import get_holidays
 
 class ServiceLevelAgreement(Document):
-
 	def validate(self):
 		self.validate_doc()
+		self.validate_status_field()
 		self.check_priorities()
 		self.check_support_and_resolution()
 
 	def check_priorities(self):
-		default_priority = []
 		priorities = []
 
 		for priority in self.priorities:
 			# Check if response and resolution time is set for every priority
-			if not priority.response_time or not priority.resolution_time:
-				frappe.throw(_("Set Response Time and Resolution Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+			if not priority.response_time:
+				frappe.throw(_("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+
+			if self.apply_sla_for_resolution:
+				if not priority.resolution_time:
+					frappe.throw(_("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+
+				response = priority.response_time
+				resolution = priority.resolution_time
+				if response > resolution:
+					frappe.throw(_("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
 
 			priorities.append(priority.priority)
 
-			if priority.default_priority:
-				default_priority.append(priority.default_priority)
-
-			response = priority.response_time
-			resolution = priority.resolution_time
-
-			if response > resolution:
-				frappe.throw(_("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
-
 		# Check if repeated priority
 		if not len(set(priorities)) == len(priorities):
 			repeated_priority = get_repeated(priorities)
 			frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
 
-		# Check if repeated default priority
-		if not len(set(default_priority)) == len(default_priority):
-			frappe.throw(_("Select only one Priority as Default."))
-
 		# set default priority from priorities
 		try:
 			self.default_priority = next(d.priority for d in self.priorities if d.default_priority)
@@ -55,17 +54,12 @@
 		support_days = []
 
 		for support_and_resolution in self.support_and_resolution:
-			# Check if start and end time is set for every support day
-			if not (support_and_resolution.start_time or support_and_resolution.end_time):
-				frappe.throw(_("Set Start Time and End Time for  \
-					Support Day {0} at index {1}.".format(support_and_resolution.workday, support_and_resolution.idx)))
-
 			support_days.append(support_and_resolution.workday)
 			support_and_resolution.idx = week.index(support_and_resolution.workday) + 1
 
-			if support_and_resolution.start_time >= support_and_resolution.end_time:
-				frappe.throw(_("Start Time can't be greater than or equal to End Time \
-					for {0}.".format(support_and_resolution.workday)))
+			if to_timedelta(support_and_resolution.start_time) >= to_timedelta(support_and_resolution.end_time):
+				frappe.throw(_("Start Time can't be greater than or equal to End Time for {0}.").format(
+					support_and_resolution.workday))
 
 		# Check for repeated workday
 		if not len(set(support_days)) == len(support_days):
@@ -73,24 +67,34 @@
 			frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
 
 	def validate_doc(self):
-		if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement") and self.enable:
+		if self.enabled and self.document_type == "Issue" \
+			and not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
 			frappe.throw(_("{0} is not enabled in {1}").format(frappe.bold("Track Service Level Agreement"),
 				get_link_to_form("Support Settings", "Support Settings")))
 
-		if self.default_service_level_agreement:
-			if frappe.db.exists("Service Level Agreement", {"default_service_level_agreement": "1", "name": ["!=", self.name]}):
-				frappe.throw(_("A Default Service Level Agreement already exists."))
-		else:
-			if self.start_date and self.end_date:
-				if getdate(self.start_date) >= getdate(self.end_date):
-					frappe.throw(_("Start Date of Agreement can't be greater than or equal to End Date."))
+		if self.default_service_level_agreement and frappe.db.exists("Service Level Agreement", {
+			"document_type": self.document_type,
+			"default_service_level_agreement": "1",
+			"name": ["!=", self.name]
+		}):
+			frappe.throw(_("Default Service Level Agreement for {0} already exists.").format(self.document_type))
 
-				if getdate(self.end_date) < getdate(frappe.utils.getdate()):
-					frappe.throw(_("End Date of Agreement can't be less than today."))
+		if self.start_date and self.end_date:
+			self.validate_from_to_dates(self.start_date, self.end_date)
 
-		if self.entity_type and self.entity:
-			if frappe.db.exists("Service Level Agreement", {"entity_type": self.entity_type, "entity": self.entity, "name": ["!=", self.name]}):
-				frappe.throw(_("Service Level Agreement with Entity Type {0} and Entity {1} already exists.").format(self.entity_type, self.entity))
+		if self.entity_type and self.entity and frappe.db.exists("Service Level Agreement", {
+			"entity_type": self.entity_type,
+			"entity": self.entity,
+			"name": ["!=", self.name]
+		}):
+			frappe.throw(_("Service Level Agreement for {0} {1} already exists.").format(
+				frappe.bold(self.entity_type), frappe.bold(self.entity)))
+
+	def validate_status_field(self):
+		meta = frappe.get_meta(self.document_type)
+		if not meta.get_field("status"):
+			frappe.throw(_("The Document Type {0} must have a Status field to configure Service Level Agreement").format(
+				frappe.bold(self.document_type)))
 
 	def get_service_level_agreement_priority(self, priority):
 		priority = frappe.get_doc("Service Level Priority", {"priority": priority, "parent": self.name})
@@ -101,78 +105,169 @@
 			"resolution_time": priority.resolution_time
 		})
 
+	def before_insert(self):
+		# no need to set up SLA fields for Issue dt as they are standard fields in Issue
+		if self.document_type == "Issue":
+			return
+
+		service_level_agreement_fields = get_service_level_agreement_fields()
+		meta = frappe.get_meta(self.document_type, cached=False)
+
+		if meta.custom:
+			self.create_docfields(meta, service_level_agreement_fields)
+		else:
+			self.create_custom_fields(meta, service_level_agreement_fields)
+
+	def on_trash(self):
+		set_documents_with_active_service_level_agreement()
+
+	def after_insert(self):
+		set_documents_with_active_service_level_agreement()
+
+	def on_update(self):
+		set_documents_with_active_service_level_agreement()
+
+	def create_docfields(self, meta, service_level_agreement_fields):
+		last_index = len(meta.fields)
+
+		for field in service_level_agreement_fields:
+			if not meta.has_field(field.get("fieldname")):
+				last_index += 1
+
+				frappe.get_doc({
+					"doctype": "DocField",
+					"idx": last_index,
+					"parenttype": "DocType",
+					"parentfield": "fields",
+					"parent": self.document_type,
+					"label": field.get("label"),
+					"fieldname": field.get("fieldname"),
+					"fieldtype": field.get("fieldtype"),
+					"collapsible": field.get("collapsible"),
+					"options": field.get("options"),
+					"read_only": field.get("read_only"),
+					"hidden": field.get("hidden"),
+					"description": field.get("description"),
+					"default": field.get("default"),
+				}).insert(ignore_permissions=True)
+			else:
+				existing_field = meta.get_field(field.get("fieldname"))
+				self.reset_field_properties(existing_field, "DocField", field)
+
+		# to update meta and modified timestamp
+		frappe.get_doc('DocType', self.document_type).save(ignore_permissions=True)
+
+	def create_custom_fields(self, meta, service_level_agreement_fields):
+		for field in service_level_agreement_fields:
+			if not meta.has_field(field.get("fieldname")):
+				frappe.get_doc({
+					"doctype": "Custom Field",
+					"dt": self.document_type,
+					"label": field.get("label"),
+					"fieldname": field.get("fieldname"),
+					"fieldtype": field.get("fieldtype"),
+					"insert_after": "append",
+					"collapsible": field.get("collapsible"),
+					"options": field.get("options"),
+					"read_only": field.get("read_only"),
+					"hidden": field.get("hidden"),
+					"description": field.get("description"),
+					"default": field.get("default"),
+				}).insert(ignore_permissions=True)
+			else:
+				existing_field = meta.get_field(field.get("fieldname"))
+				self.reset_field_properties(existing_field, "Custom Field", field)
+
+	def reset_field_properties(self, field, field_dt, sla_field):
+		field = frappe.get_doc(field_dt, {"fieldname": field.fieldname})
+		field.label = sla_field.get("label")
+		field.fieldname = sla_field.get("fieldname")
+		field.fieldtype = sla_field.get("fieldtype")
+		field.collapsible = sla_field.get("collapsible")
+		field.hidden = sla_field.get("hidden")
+		field.options = sla_field.get("options")
+		field.read_only = sla_field.get("read_only")
+		field.hidden = sla_field.get("hidden")
+		field.description = sla_field.get("description")
+		field.default = sla_field.get("default")
+		field.save(ignore_permissions=True)
+
+
 def check_agreement_status():
-	service_level_agreements = frappe.get_list("Service Level Agreement", filters=[
-		{"active": 1},
+	service_level_agreements = frappe.get_all("Service Level Agreement", filters=[
+		{"enabled": 1},
 		{"default_service_level_agreement": 0}
 	], fields=["name"])
 
 	for service_level_agreement in service_level_agreements:
 		doc = frappe.get_doc("Service Level Agreement", service_level_agreement.name)
 		if doc.end_date and getdate(doc.end_date) < getdate(frappe.utils.getdate()):
-			frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "active", 0)
+			frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "enabled", 0)
 
-def get_active_service_level_agreement_for(priority, customer=None, service_level_agreement=None):
-	if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
+
+def get_active_service_level_agreement_for(doctype, priority, customer=None, service_level_agreement=None):
+	if doctype == "Issue" and not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
 		return
 
 	filters = [
-		["Service Level Agreement", "active", "=", 1],
-		["Service Level Agreement", "enable", "=", 1]
+		["Service Level Agreement", "document_type", "=", doctype],
+		["Service Level Agreement", "enabled", "=", 1]
 	]
-
 	if priority:
 		filters.append(["Service Level Priority", "priority", "=", priority])
 
-	or_filters = [
-		["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer)]]
-	]
+	or_filters = []
 	if service_level_agreement:
 		or_filters = [
 			["Service Level Agreement", "name", "=", service_level_agreement],
 		]
 
+	if customer:
+		or_filters.append(
+			["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer)]]
+		)
 	or_filters.append(["Service Level Agreement", "default_service_level_agreement", "=", 1])
 
-	agreement = frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters,
-		fields=["name", "default_priority"])
+	agreement = frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters,
+		fields=["name", "default_priority", "apply_sla_for_resolution"])
 
 	return agreement[0] if agreement else None
 
+
 def get_customer_group(customer):
-	if customer:
-		return frappe.db.get_value("Customer", customer, "customer_group")
+	return frappe.db.get_value("Customer", customer, "customer_group") if customer else None
+
 
 def get_customer_territory(customer):
-	if customer:
-		return frappe.db.get_value("Customer", customer, "territory")
+	return frappe.db.get_value("Customer", customer, "territory") if customer else None
+
 
 @frappe.whitelist()
-def get_service_level_agreement_filters(name, customer=None):
+def get_service_level_agreement_filters(doctype, name, customer=None):
 	if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
 		return
 
 	filters = [
-		["Service Level Agreement", "active", "=", 1],
-		["Service Level Agreement", "enable", "=", 1]
+		["Service Level Agreement", "document_type", "=", doctype],
+		["Service Level Agreement", "enabled", "=", 1]
 	]
 
-	if not customer:
-		or_filters = [
-			["Service Level Agreement", "default_service_level_agreement", "=", 1]
-		]
-	else:
+	or_filters = [
+		["Service Level Agreement", "default_service_level_agreement", "=", 1]
+	]
+
+	if customer:
 		# Include SLA with No Entity and Entity Type
-		or_filters = [
-			["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer), ""]],
-			["Service Level Agreement", "default_service_level_agreement", "=", 1]
-		]
+		or_filters.append(
+			["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer), ""]]
+		)
 
 	return {
-		"priority": [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])],
-		"service_level_agreements": [d.name for d in frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters)]
+		"priority": [priority.priority for priority in frappe.get_all("Service Level Priority", filters={"parent": name}, fields=["priority"])],
+		"service_level_agreements": [d.name for d in frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters)]
 	}
 
+
 def get_repeated(values):
 	unique_list = []
 	diff = []
@@ -183,3 +278,573 @@
 			if value not in diff:
 				diff.append(str(value))
 	return " ".join(diff)
+
+
+def get_documents_with_active_service_level_agreement():
+	if not frappe.cache().hget("service_level_agreement", "active"):
+		set_documents_with_active_service_level_agreement()
+
+	return frappe.cache().hget("service_level_agreement", "active")
+
+
+def set_documents_with_active_service_level_agreement():
+	active = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])]
+	frappe.cache().hset("service_level_agreement", "active", active)
+
+
+def apply(doc, method=None):
+	# Applies SLA to document on validate
+	if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \
+		doc.doctype not in get_documents_with_active_service_level_agreement():
+		return
+
+	service_level_agreement = get_active_service_level_agreement_for(doctype=doc.get("doctype"), priority=doc.get("priority"),
+		customer=doc.get("customer"), service_level_agreement=doc.get("service_level_agreement"))
+
+	if not service_level_agreement:
+		return
+
+	set_sla_properties(doc, service_level_agreement)
+
+
+def set_sla_properties(doc, service_level_agreement):
+	if frappe.db.exists(doc.doctype, doc.name):
+		from_db = frappe.get_doc(doc.doctype, doc.name)
+	else:
+		from_db = frappe._dict({})
+
+	meta = frappe.get_meta(doc.doctype)
+
+	if meta.has_field("customer") and service_level_agreement.customer and doc.get("customer") and \
+		not service_level_agreement.customer == doc.get("customer"):
+		frappe.throw(_("Service Level Agreement {0} is specific to Customer {1}").format(service_level_agreement.name,
+			service_level_agreement.customer))
+
+	doc.service_level_agreement = service_level_agreement.name
+	doc.priority = doc.get("priority") or service_level_agreement.default_priority
+	priority = get_priority(doc)
+
+	if not doc.creation:
+		doc.creation = now_datetime(doc.get("owner"))
+
+		if meta.has_field("service_level_agreement_creation"):
+			doc.service_level_agreement_creation = now_datetime(doc.get("owner"))
+
+	start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation)
+
+	set_response_by_and_variance(doc, meta, start_date_time, priority)
+	if service_level_agreement.apply_sla_for_resolution:
+		set_resolution_by_and_variance(doc, meta, start_date_time, priority)
+
+	update_status(doc, from_db, meta)
+
+
+def update_status(doc, from_db, meta):
+	if meta.has_field("status"):
+		if meta.has_field("first_responded_on") and doc.status != "Open" and \
+			from_db.status == "Open" and not doc.first_responded_on:
+			doc.first_responded_on = frappe.flags.current_time or now_datetime(doc.get("owner"))
+
+		if meta.has_field("service_level_agreement") and doc.service_level_agreement:
+			# mark sla status as fulfilled based on the configuration
+			fulfillment_statuses = [entry.status for entry in frappe.db.get_all("SLA Fulfilled On Status", filters={
+				"parent": doc.service_level_agreement
+			}, fields=["status"])]
+
+			if doc.status in fulfillment_statuses and from_db.status not in fulfillment_statuses:
+				apply_sla_for_resolution = frappe.db.get_value("Service Level Agreement", doc.service_level_agreement,
+					"apply_sla_for_resolution")
+
+				if apply_sla_for_resolution and meta.has_field("resolution_date"):
+					doc.resolution_date = frappe.flags.current_time or now_datetime(doc.get("owner"))
+
+				if meta.has_field("agreement_status") and from_db.agreement_status == "Ongoing":
+					set_service_level_agreement_variance(doc.doctype, doc.name)
+					update_agreement_status(doc, meta)
+
+				if apply_sla_for_resolution:
+					set_resolution_time(doc, meta)
+					set_user_resolution_time(doc, meta)
+
+		if doc.status == "Open" and from_db.status != "Open":
+			# if no date, it should be set as None and not a blank string "", as per mysql strict config
+			# enable SLA and variance on Reopen
+			reset_metrics(doc, meta)
+			set_service_level_agreement_variance(doc.doctype, doc.name)
+
+	handle_hold_time(doc, meta, from_db.status)
+
+
+def get_expected_time_for(parameter, service_level, start_date_time):
+	current_date_time = start_date_time
+	expected_time = current_date_time
+	start_time = end_time = None
+	expected_time_is_set = 0
+
+	allotted_seconds = get_allotted_seconds(parameter, service_level)
+	support_days = get_support_days(service_level)
+	holidays = get_holidays(service_level.get("holiday_list"))
+	weekdays = get_weekdays()
+
+	while not expected_time_is_set:
+		current_weekday = weekdays[current_date_time.weekday()]
+
+		if not is_holiday(current_date_time, holidays) and current_weekday in support_days:
+			if getdate(current_date_time) == getdate(start_date_time) \
+				and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time:
+				start_time = current_date_time - datetime(current_date_time.year, current_date_time.month, current_date_time.day)
+			else:
+				start_time = support_days[current_weekday].start_time
+
+			end_time = support_days[current_weekday].end_time
+			time_left_today = time_diff_in_seconds(end_time, start_time)
+			# no time left for support today
+			if time_left_today <= 0:
+				pass
+
+			elif allotted_seconds:
+				if time_left_today >= allotted_seconds:
+					expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
+					expected_time = add_to_date(expected_time, seconds=allotted_seconds)
+					expected_time_is_set = 1
+				else:
+					allotted_seconds = allotted_seconds - time_left_today
+
+		if not expected_time_is_set:
+			current_date_time = add_to_date(current_date_time, days=1)
+
+	if end_time and allotted_seconds >= 86400:
+		current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
+	else:
+		current_date_time = expected_time
+
+	return current_date_time
+
+
+def get_allotted_seconds(parameter, service_level):
+	allotted_seconds = 0
+	if parameter == "response":
+		allotted_seconds = service_level.get("response_time")
+	elif parameter == "resolution":
+		allotted_seconds = service_level.get("resolution_time")
+	else:
+		frappe.throw(_("{0} parameter is invalid").format(parameter))
+
+	return allotted_seconds
+
+
+def get_support_days(service_level):
+	support_days = {}
+	for service in service_level.get("support_and_resolution"):
+		support_days[service.workday] = frappe._dict({
+			"start_time": service.start_time,
+			"end_time": service.end_time,
+		})
+	return support_days
+
+
+def set_service_level_agreement_variance(doctype, doc=None):
+
+	filters = {"status": "Open", "agreement_status": "Ongoing"}
+
+	if doc:
+		filters = {"name": doc}
+
+	for entry in frappe.get_all(doctype, filters=filters):
+		current_doc = frappe.get_doc(doctype, entry.name)
+		current_time = frappe.flags.current_time or now_datetime(current_doc.get("owner"))
+		apply_sla_for_resolution = frappe.db.get_value("Service Level Agreement", current_doc.service_level_agreement,
+			"apply_sla_for_resolution")
+
+		if not current_doc.first_responded_on: # first_responded_on set when first reply is sent to customer
+			variance = round(time_diff_in_seconds(current_doc.response_by, current_time), 2)
+			frappe.db.set_value(current_doc.doctype, current_doc.name, "response_by_variance", variance, update_modified=False)
+
+			if variance < 0:
+				frappe.db.set_value(current_doc.doctype, current_doc.name, "agreement_status", "Failed", update_modified=False)
+
+		if apply_sla_for_resolution and not current_doc.get("resolution_date"): # resolution_date set when issue has been closed
+			variance = round(time_diff_in_seconds(current_doc.resolution_by, current_time), 2)
+			frappe.db.set_value(current_doc.doctype, current_doc.name, "resolution_by_variance", variance, update_modified=False)
+
+			if variance < 0:
+				frappe.db.set_value(current_doc.doctype, current_doc.name, "agreement_status", "Failed", update_modified=False)
+
+
+def set_user_resolution_time(doc, meta):
+	# total time taken by a user to close the issue apart from wait_time
+	if not meta.has_field("user_resolution_time"):
+		return
+
+	communications = frappe.get_all("Communication", filters={
+			"reference_doctype": doc.doctype,
+			"reference_name": doc.name
+		}, fields=["sent_or_received", "name", "creation"], order_by="creation")
+
+	pending_time = []
+	for i in range(len(communications)):
+		if communications[i].sent_or_received == "Received" and communications[i-1].sent_or_received == "Sent":
+			wait_time = time_diff_in_seconds(communications[i].creation, communications[i-1].creation)
+			if wait_time > 0:
+				pending_time.append(wait_time)
+
+	total_pending_time = sum(pending_time)
+	resolution_time_in_secs = time_diff_in_seconds(doc.resolution_date, doc.creation)
+	doc.user_resolution_time = resolution_time_in_secs - total_pending_time
+
+
+def change_service_level_agreement_and_priority(self):
+	if self.service_level_agreement and frappe.db.exists("Issue", self.name) and \
+		frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
+
+		if not self.priority == frappe.db.get_value("Issue", self.name, "priority"):
+			self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
+			frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority))
+
+		if not self.service_level_agreement == frappe.db.get_value("Issue", self.name, "service_level_agreement"):
+			self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
+			frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
+
+
+def get_priority(doc):
+	service_level_agreement = frappe.get_doc("Service Level Agreement", doc.service_level_agreement)
+	priority = service_level_agreement.get_service_level_agreement_priority(doc.priority)
+	priority.update({
+		"support_and_resolution": service_level_agreement.support_and_resolution,
+		"holiday_list": service_level_agreement.holiday_list
+	})
+	return priority
+
+
+def reset_service_level_agreement(doc, reason, user):
+	if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
+		frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
+
+	frappe.get_doc({
+		"doctype": "Comment",
+		"comment_type": "Info",
+		"reference_doctype": doc.doctype,
+		"reference_name": doc.name,
+		"comment_email": user,
+		"content": " resetted Service Level Agreement - {0}".format(_(reason)),
+	}).insert(ignore_permissions=True)
+
+	doc.service_level_agreement_creation = now_datetime(doc.get("owner"))
+	doc.set_response_and_resolution_time(priority=doc.priority, service_level_agreement=doc.service_level_agreement)
+	doc.agreement_status = "Ongoing"
+	doc.save()
+
+
+def reset_metrics(doc, meta):
+	if meta.has_field("resolution_date"):
+		doc.resolution_date = None
+
+	if not meta.has_field("resolution_time"):
+		doc.resolution_time = None
+
+	if not meta.has_field("user_resolution_time"):
+		doc.user_resolution_time = None
+
+	if meta.has_field("agreement_status"):
+		doc.agreement_status = "Ongoing"
+
+
+def set_resolution_time(doc, meta):
+	# total time taken from issue creation to closing
+	if not meta.has_field("resolution_time"):
+		return
+
+	doc.resolution_time = time_diff_in_seconds(doc.resolution_date, doc.creation)
+
+
+# called via hooks on communication update
+def update_hold_time(doc, status):
+	parent = get_parent_doc(doc)
+	if not parent:
+		return
+
+	if doc.communication_type == "Comment":
+		return
+
+	status_field = parent.meta.get_field("status")
+	if status_field:
+		options = (status_field.options or "").splitlines()
+
+		# if status has a "Replied" option, then handle hold time
+		if ("Replied" in options) and doc.sent_or_received == "Received":
+			meta = frappe.get_meta(parent.doctype)
+			handle_hold_time(parent, meta, 'Replied')
+
+
+def handle_hold_time(doc, meta, status):
+	if meta.has_field("service_level_agreement") and doc.service_level_agreement:
+		# set response and resolution variance as None as the issue is on Hold for status as Replied
+		hold_statuses = [entry.status for entry in frappe.db.get_all("Pause SLA On Status", filters={
+				"parent": doc.service_level_agreement
+			}, fields=["status"])]
+
+		if not hold_statuses:
+			return
+
+		if meta.has_field("status") and doc.status in hold_statuses and status not in hold_statuses:
+			apply_hold_status(doc, meta)
+
+		# calculate hold time when status is changed from any hold status to any non-hold status
+		if meta.has_field("status") and doc.status not in hold_statuses and status in hold_statuses:
+			reset_hold_status_and_update_hold_time(doc, meta)
+
+
+def apply_hold_status(doc, meta):
+	update_values = {'on_hold_since': frappe.flags.current_time or now_datetime(doc.get("owner"))}
+
+	if meta.has_field("first_responded_on") and not doc.first_responded_on:
+		update_values['response_by'] = None
+		update_values['response_by_variance'] = 0
+
+	update_values['resolution_by'] = None
+	update_values['resolution_by_variance'] = 0
+
+	doc.db_set(update_values)
+
+
+def reset_hold_status_and_update_hold_time(doc, meta):
+	hold_time = doc.total_hold_time if meta.has_field("total_hold_time") and doc.total_hold_time else 0
+	now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+	last_hold_time = 0
+	update_values = {}
+
+	if meta.has_field("on_hold_since") and doc.on_hold_since:
+		# last_hold_time will be added to the sla variables
+		last_hold_time = time_diff_in_seconds(now_time, doc.on_hold_since)
+		update_values['total_hold_time'] = hold_time + last_hold_time
+
+	# re-calculate SLA variables after issue changes from any hold status to any non-hold status
+	start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation)
+	priority = get_priority(doc)
+	now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+
+	# add hold time to response by variance
+	if meta.has_field("first_responded_on") and not doc.first_responded_on:
+		response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+		response_by = add_to_date(response_by, seconds=round(last_hold_time))
+		response_by_variance = round(time_diff_in_seconds(response_by, now_time))
+
+		update_values['response_by'] = response_by
+		update_values['response_by_variance'] = response_by_variance + last_hold_time
+
+	# add hold time to resolution by variance
+	if frappe.db.get_value("Service Level Agreement", doc.service_level_agreement, "apply_sla_for_resolution"):
+		resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
+		resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time))
+		resolution_by_variance = round(time_diff_in_seconds(resolution_by, now_time))
+
+		update_values['resolution_by'] = resolution_by
+		update_values['resolution_by_variance'] = resolution_by_variance + last_hold_time
+
+	update_values['on_hold_since'] = None
+
+	doc.db_set(update_values)
+
+
+def get_service_level_agreement_fields():
+	return [
+		{
+			"collapsible": 1,
+			"fieldname": "service_level_section",
+			"fieldtype": "Section Break",
+			"label": "Service Level"
+		},
+		{
+			"fieldname": "service_level_agreement",
+			"fieldtype": "Link",
+			"label": "Service Level Agreement",
+			"options": "Service Level Agreement"
+		},
+		{
+			"fieldname": "priority",
+			"fieldtype": "Link",
+			"label": "Priority",
+			"options": "Issue Priority"
+		},
+		{
+			"fieldname": "response_by",
+			"fieldtype": "Datetime",
+			"label": "Response By",
+			"read_only": 1
+		},
+		{
+			"fieldname": "response_by_variance",
+			"fieldtype": "Duration",
+			"hide_seconds": 1,
+			"label": "Response By Variance",
+			"read_only": 1
+		},
+		{
+			"fieldname": "first_responded_on",
+			"fieldtype": "Datetime",
+			"label": "First Responded On",
+			"read_only": 1
+		},
+		{
+			"fieldname": "on_hold_since",
+			"fieldtype": "Datetime",
+			"hidden": 1,
+			"label": "On Hold Since",
+			"read_only": 1
+		},
+		{
+			"fieldname": "total_hold_time",
+			"fieldtype": "Duration",
+			"label": "Total Hold Time",
+			"read_only": 1
+		},
+		{
+			"fieldname": "cb",
+			"fieldtype": "Column Break",
+			"read_only": 1
+		},
+		{
+			"default": "Ongoing",
+			"fieldname": "agreement_status",
+			"fieldtype": "Select",
+			"label": "Service Level Agreement Status",
+			"options": "Ongoing\nFulfilled\nFailed",
+			"read_only": 1
+		},
+		{
+			"fieldname": "resolution_by",
+			"fieldtype": "Datetime",
+			"label": "Resolution By",
+			"read_only": 1
+		},
+		{
+			"fieldname": "resolution_by_variance",
+			"fieldtype": "Duration",
+			"hide_seconds": 1,
+			"label": "Resolution By Variance",
+			"read_only": 1
+		},
+		{
+			"fieldname": "service_level_agreement_creation",
+			"fieldtype": "Datetime",
+			"hidden": 1,
+			"label": "Service Level Agreement Creation",
+			"read_only": 1
+		},
+		{
+			"depends_on": "eval:!doc.__islocal",
+			"fieldname": "resolution_date",
+			"fieldtype": "Datetime",
+			"label": "Resolution Date",
+			"no_copy": 1,
+			"read_only": 1
+		}
+	]
+
+
+def update_agreement_status_on_custom_status(doc):
+	# Update Agreement Fulfilled status using Custom Scripts for Custom Status
+
+	meta = frappe.get_meta(doc.doctype)
+	now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+	if meta.has_field("first_responded_on") and not doc.first_responded_on:
+		# first_responded_on set when first reply is sent to customer
+		doc.response_by_variance = round(time_diff_in_seconds(doc.response_by, now_time), 2)
+
+	if meta.has_field("resolution_date") and not doc.resolution_date:
+		# resolution_date set when issue has been closed
+		doc.resolution_by_variance = round(time_diff_in_seconds(doc.resolution_by, now_time), 2)
+
+	if meta.has_field("agreement_status"):
+		doc.agreement_status = "Fulfilled" if doc.response_by_variance > 0 and doc.resolution_by_variance > 0 else "Failed"
+
+
+def update_agreement_status(doc, meta):
+	if meta.has_field("service_level_agreement") and meta.has_field("agreement_status") and \
+		doc.service_level_agreement and doc.agreement_status == "Ongoing":
+
+		apply_sla_for_resolution = frappe.db.get_value("Service Level Agreement", doc.service_level_agreement,
+			"apply_sla_for_resolution")
+
+		# if SLA is applied for resolution check for response and resolution, else only response
+		if apply_sla_for_resolution:
+			if meta.has_field("response_by_variance") and meta.has_field("resolution_by_variance"):
+				if cint(frappe.db.get_value(doc.doctype, doc.name, "response_by_variance")) < 0 or \
+					cint(frappe.db.get_value(doc.doctype, doc.name, "resolution_by_variance")) < 0:
+
+					doc.agreement_status = "Failed"
+				else:
+					doc.agreement_status = "Fulfilled"
+		else:
+			if meta.has_field("response_by_variance") and \
+				cint(frappe.db.get_value(doc.doctype, doc.name, "response_by_variance")) < 0:
+				doc.agreement_status = "Failed"
+			else:
+				doc.agreement_status = "Fulfilled"
+
+
+def is_holiday(date, holidays):
+	return getdate(date) in holidays
+
+
+def get_time_in_timedelta(time):
+	"""Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215)."""
+	import datetime
+	return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
+
+
+def set_response_by_and_variance(doc, meta, start_date_time, priority):
+	if meta.has_field("response_by"):
+		doc.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+
+	if meta.has_field("response_by_variance"):
+		now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+		doc.response_by_variance = round(time_diff_in_seconds(doc.response_by, now_time), 2)
+
+def set_resolution_by_and_variance(doc, meta, start_date_time, priority):
+	if meta.has_field("resolution_by"):
+		doc.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
+
+	if meta.has_field("resolution_by_variance"):
+		now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+		doc.resolution_by_variance = round(time_diff_in_seconds(doc.resolution_by, now_time), 2)
+
+
+def now_datetime(user):
+	dt = convert_utc_to_user_timezone(datetime.utcnow(), user)
+	return dt.replace(tzinfo=None)
+
+
+def convert_utc_to_user_timezone(utc_timestamp, user):
+	from pytz import timezone, UnknownTimeZoneError
+
+	user_tz = get_tz(user)
+	utcnow = timezone('UTC').localize(utc_timestamp)
+	try:
+		return utcnow.astimezone(timezone(user_tz))
+	except UnknownTimeZoneError:
+		return utcnow
+
+
+def get_tz(user):
+	return frappe.db.get_value("User", user, "time_zone") or get_time_zone()
+
+
+@frappe.whitelist()
+def get_user_time(user, to_string=False):
+	return get_datetime_str(now_datetime(user)) if to_string else now_datetime(user)
+
+
+@frappe.whitelist()
+def get_sla_doctypes():
+	doctypes = []
+	data = frappe.get_list('Service Level Agreement',
+		{'enabled': 1},
+		['document_type'],
+		distinct=1
+	)
+
+	for entry in data:
+		doctypes.append(entry.document_type)
+
+	return doctypes
diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
index 07ef368..2a8446d 100644
--- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
@@ -5,19 +5,20 @@
 
 import frappe
 import unittest
-from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
+import datetime
+from frappe.utils import flt
 from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
+from erpnext.support.doctype.service_level_agreement.service_level_agreement import get_service_level_agreement_fields
 
 class TestServiceLevelAgreement(unittest.TestCase):
 	def setUp(self):
-		frappe.db.sql("delete from `tabService Level Agreement`")
 		frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
+		frappe.db.sql("delete from `tabLead`")
 
 	def test_service_level_agreement(self):
 		# Default Service Level Agreement
 		create_default_service_level_agreement = create_service_level_agreement(default_service_level_agreement=1,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
-			entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+			holiday_list="__Test Holiday List", entity_type=None, entity=None, response_time=14400, resolution_time=21600)
 
 		get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)
 
@@ -29,8 +30,8 @@
 		# Service Level Agreement for Customer
 		customer = create_customer()
 		create_customer_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
-			entity_type="Customer", entity=customer, response_time=7200, resolution_time=10800)
+			holiday_list="__Test Holiday List", entity_type="Customer", entity=customer,
+			response_time=7200, resolution_time=10800)
 		get_customer_service_level_agreement = get_service_level_agreement(entity_type="Customer", entity=customer)
 
 		self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
@@ -41,8 +42,8 @@
 		# Service Level Agreement for Customer Group
 		customer_group = create_customer_group()
 		create_customer_group_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
-			entity_type="Customer Group", entity=customer_group, response_time=7200, resolution_time=10800)
+			holiday_list="__Test Holiday List", entity_type="Customer Group", entity=customer_group,
+			response_time=7200, resolution_time=10800)
 		get_customer_group_service_level_agreement = get_service_level_agreement(entity_type="Customer Group", entity=customer_group)
 
 		self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
@@ -53,7 +54,7 @@
 		# Service Level Agreement for Territory
 		territory = create_territory()
 		create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+			holiday_list="__Test Holiday List",
 			entity_type="Territory", entity=territory, response_time=7200, resolution_time=10800)
 		get_territory_service_level_agreement = get_service_level_agreement(entity_type="Territory", entity=territory)
 
@@ -62,64 +63,223 @@
 		self.assertEqual(create_territory_service_level_agreement.entity, get_territory_service_level_agreement.entity)
 		self.assertEqual(create_territory_service_level_agreement.default_service_level_agreement, get_territory_service_level_agreement.default_service_level_agreement)
 
+	def test_custom_field_creation_for_sla_on_standard_dt(self):
+		# Default Service Level Agreement
+		doctype = "Lead"
+		lead_sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Converted"}]
+		)
 
-def get_service_level_agreement(default_service_level_agreement=None, entity_type=None, entity=None):
+		# check default SLA for lead
+		default_sla = get_service_level_agreement(default_service_level_agreement=1, doctype=doctype)
+		self.assertEqual(lead_sla.name, default_sla.name)
+
+		# check SLA custom fields created for leads
+		sla_fields = get_service_level_agreement_fields()
+		meta = frappe.get_meta(doctype, cached=False)
+
+		for field in sla_fields:
+			self.assertTrue(meta.has_field(field.get("fieldname")))
+
+	def test_docfield_creation_for_sla_on_custom_dt(self):
+		doctype = create_custom_doctype()
+		sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype.name
+		)
+
+		# check default SLA for custom dt
+		default_sla = get_service_level_agreement(default_service_level_agreement=1, doctype=doctype.name)
+		self.assertEqual(sla.name, default_sla.name)
+
+		# check SLA docfields created
+		sla_fields = get_service_level_agreement_fields()
+		meta = frappe.get_meta(doctype.name, cached=False)
+
+		for field in sla_fields:
+			self.assertTrue(meta.has_field(field.get("fieldname")))
+
+	def test_sla_application(self):
+		# Default Service Level Agreement
+		doctype = "Lead"
+		lead_sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Converted"}]
+		)
+
+		# make lead with default SLA
+		creation = datetime.datetime(2019, 3, 4, 12, 0)
+		lead = make_lead(creation=creation, index=1)
+
+		self.assertEqual(lead.service_level_agreement, lead_sla.name)
+		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+		self.assertEqual(lead.resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
+
+		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
+		lead.reload()
+		lead.status = 'Converted'
+		lead.save()
+
+		self.assertEqual(lead.agreement_status, 'Fulfilled')
+
+	def test_hold_time(self):
+		doctype = "Lead"
+		create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Converted"}],
+			pause_sla_on=[{"status": "Replied"}]
+		)
+
+		creation = datetime.datetime(2020, 3, 4, 4, 0)
+		lead = make_lead(creation, index=2)
+
+		frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15)
+		lead.reload()
+		lead.status = 'Replied'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(lead.on_hold_since, frappe.flags.current_time)
+
+		frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
+		lead.reload()
+		lead.status = 'Converted'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(flt(lead.total_hold_time, 2), 3000)
+		self.assertEqual(lead.resolution_by, datetime.datetime(2020, 3, 4, 16, 50))
+
+	def test_failed_sla_for_response_only(self):
+		doctype = "Lead"
+		create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Replied"}],
+			pause_sla_on=[],
+			apply_sla_for_resolution=0
+		)
+
+		creation = datetime.datetime(2019, 3, 4, 12, 0)
+		lead = make_lead(creation=creation, index=1)
+		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+
+		# failed with response time only
+		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 16, 5)
+		lead.reload()
+		lead.status = 'Replied'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(lead.agreement_status, 'Failed')
+
+	def test_fulfilled_sla_for_response_only(self):
+		doctype = "Lead"
+		lead_sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Replied"}],
+			apply_sla_for_resolution=0
+		)
+
+		# fulfilled with response time only
+		creation = datetime.datetime(2019, 3, 4, 12, 0)
+		lead = make_lead(creation=creation, index=2)
+
+		self.assertEqual(lead.service_level_agreement, lead_sla.name)
+		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+
+		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 30)
+		lead.reload()
+		lead.status = 'Replied'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(lead.agreement_status, 'Fulfilled')
+
+	def tearDown(self):
+		for d in frappe.get_all("Service Level Agreement"):
+			frappe.delete_doc("Service Level Agreement", d.name, force=1)
+
+
+def get_service_level_agreement(default_service_level_agreement=None, entity_type=None, entity=None, doctype="Issue"):
 	if default_service_level_agreement:
-		filters = {"default_service_level_agreement": default_service_level_agreement}
+		filters = {"default_service_level_agreement": default_service_level_agreement, "document_type": doctype}
 	else:
 		filters = {"entity_type": entity_type, "entity": entity}
 
 	service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
 	return service_level_agreement
 
-def create_service_level_agreement(default_service_level_agreement, holiday_list, employee_group,
-	response_time, entity_type, entity, resolution_time):
+def create_service_level_agreement(default_service_level_agreement, holiday_list, response_time, entity_type,
+	entity, resolution_time=0, doctype="Issue", sla_fulfilled_on=[], pause_sla_on=[], apply_sla_for_resolution=1):
 
-	employee_group = make_employee_group()
 	make_holiday_list()
 	make_priorities()
 
-	service_level_agreement = frappe.get_doc({
+	if not sla_fulfilled_on:
+		sla_fulfilled_on = [
+			{"status": "Resolved"},
+			{"status": "Closed"}
+		]
+
+	pause_sla_on = [{"status": "Replied"}] if doctype == "Issue" else pause_sla_on
+
+	service_level_agreement = frappe._dict({
 		"doctype": "Service Level Agreement",
-		"enable": 1,
+		"enabled": 1,
+		"document_type": doctype,
 		"service_level": "__Test Service Level",
 		"default_service_level_agreement": default_service_level_agreement,
 		"default_priority": "Medium",
 		"holiday_list": holiday_list,
-		"employee_group": employee_group,
 		"entity_type": entity_type,
 		"entity": entity,
 		"start_date": frappe.utils.getdate(),
 		"end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
+		"apply_sla_for_resolution": apply_sla_for_resolution,
 		"priorities": [
 			{
 				"priority": "Low",
 				"response_time": response_time,
-				"response_time_period": "Hour",
 				"resolution_time": resolution_time,
-				"resolution_time_period": "Hour",
 			},
 			{
 				"priority": "Medium",
 				"response_time": response_time,
 				"default_priority": 1,
-				"response_time_period": "Hour",
 				"resolution_time": resolution_time,
-				"resolution_time_period": "Hour",
 			},
 			{
 				"priority": "High",
 				"response_time": response_time,
-				"response_time_period": "Hour",
 				"resolution_time": resolution_time,
-				"resolution_time_period": "Hour",
 			}
 		],
-		"pause_sla_on": [
-			{
-				"status": "Replied"
-			}
-		],
+		"sla_fulfilled_on": sla_fulfilled_on,
+		"pause_sla_on": pause_sla_on,
 		"support_and_resolution": [
 			{
 				"workday": "Monday",
@@ -173,10 +333,13 @@
 	service_level_agreement_exists = frappe.db.exists("Service Level Agreement", filters)
 
 	if not service_level_agreement_exists:
-		service_level_agreement.insert(ignore_permissions=True)
-		return service_level_agreement
+		doc = frappe.get_doc(service_level_agreement).insert(ignore_permissions=True)
 	else:
-		return frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
+		doc = frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
+		doc.update(service_level_agreement)
+		doc.save()
+
+	return doc
 
 
 def create_customer():
@@ -219,19 +382,19 @@
 
 def create_service_level_agreements_for_issues():
 	create_service_level_agreement(default_service_level_agreement=1, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+		entity_type=None, entity=None, response_time=14400, resolution_time=21600)
 
 	create_customer()
 	create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
+		entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
 
 	create_customer_group()
 	create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
+		entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
 
 	create_territory()
 	create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
+		entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
 
 def make_holiday_list():
 	holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
@@ -256,3 +419,55 @@
 				},
 			]
 		}).insert()
+
+def create_custom_doctype():
+	if not frappe.db.exists("DocType", "Test SLA on Custom Dt"):
+		doc = frappe.get_doc({
+				"doctype": "DocType",
+				"module": "Support",
+				"custom": 1,
+				"fields": [
+					{
+						"label": "Date",
+						"fieldname": "date",
+						"fieldtype": "Date"
+					},
+					{
+						"label": "Description",
+						"fieldname": "desc",
+						"fieldtype": "Long Text"
+					},
+					{
+						"label": "Email ID",
+						"fieldname": "email_id",
+						"fieldtype": "Link",
+						"options": "Customer"
+					},
+					{
+						"label": "Status",
+						"fieldname": "status",
+						"fieldtype": "Select",
+						"options": "Open\nReplied\nClosed"
+					}
+				],
+				"permissions": [{
+					"role": "System Manager",
+					"read": 1,
+					"write": 1
+				}],
+				"name": "Test SLA on Custom Dt",
+			})
+		doc.insert()
+		return doc
+	else:
+		return frappe.get_doc("DocType", "Test SLA on Custom Dt")
+
+def make_lead(creation=None, index=0):
+	return frappe.get_doc({
+		"doctype": "Lead",
+		"email_id": "test_lead1@example{0}.com".format(index),
+		"lead_name": "_Test Lead {0}".format(index),
+		"status": "Open",
+		"creation": creation,
+		"service_level_agreement_creation": creation
+	}).insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level_priority/service_level_priority.json b/erpnext/support/doctype/service_level_priority/service_level_priority.json
index 65d5169..0367fc6 100644
--- a/erpnext/support/doctype/service_level_priority/service_level_priority.json
+++ b/erpnext/support/doctype/service_level_priority/service_level_priority.json
@@ -15,12 +15,13 @@
  ],
  "fields": [
   {
-   "columns": 2,
+   "columns": 1,
    "fieldname": "priority",
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Priority",
-   "options": "Issue Priority"
+   "options": "Issue Priority",
+   "reqd": 1
   },
   {
    "fieldname": "sb_00",
@@ -32,7 +33,6 @@
    "fieldtype": "Duration",
    "hide_days": 1,
    "hide_seconds": 1,
-   "in_list_view": 1,
    "label": "Resolution Time"
   },
   {
@@ -58,12 +58,13 @@
    "hide_days": 1,
    "hide_seconds": 1,
    "in_list_view": 1,
-   "label": "First Response Time"
+   "label": "First Response Time",
+   "reqd": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-10 12:45:47.545915",
+ "modified": "2021-05-29 19:52:51.733248",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Service Level Priority",
@@ -73,4 +74,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/sla_fulfilled_on_status/__init__.py b/erpnext/support/doctype/sla_fulfilled_on_status/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/support/doctype/sla_fulfilled_on_status/__init__.py
diff --git a/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.json b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.json
new file mode 100644
index 0000000..87124de
--- /dev/null
+++ b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.json
@@ -0,0 +1,31 @@
+{
+ "actions": [],
+ "creation": "2021-05-26 21:11:29.176369",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "status"
+ ],
+ "fields": [
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "reqd": 1
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-26 21:11:29.176369",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "SLA Fulfilled On Status",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.py b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.py
new file mode 100644
index 0000000..b0b5ffc
--- /dev/null
+++ b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class SLAFulfilledOnStatus(Document):
+	pass
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.js b/erpnext/support/doctype/warranty_claim/warranty_claim.js
index 79f4675..358768e 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.js
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.js
@@ -36,8 +36,8 @@
 	}
 });
 
-erpnext.support.WarrantyClaim = frappe.ui.form.Controller.extend({
-	refresh: function() {
+erpnext.support.WarrantyClaim = class WarrantyClaim extends frappe.ui.form.Controller {
+	refresh() {
 		frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
 
 		if(!cur_frm.doc.__islocal &&
@@ -45,17 +45,17 @@
 			cur_frm.add_custom_button(__('Maintenance Visit'),
 				this.make_maintenance_visit);
 		}
-	},
+	}
 
-	make_maintenance_visit: function() {
+	make_maintenance_visit() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
 			frm: cur_frm
 		})
 	}
-});
+};
 
-$.extend(cur_frm.cscript, new erpnext.support.WarrantyClaim({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new erpnext.support.WarrantyClaim({frm: cur_frm}));
 
 cur_frm.fields_dict['serial_no'].get_query = function(doc, cdt, cdn) {
 	var cond = [];
@@ -93,4 +93,4 @@
 			]
 		}
 	}
-};
\ No newline at end of file
+};
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.py b/erpnext/support/doctype/warranty_claim/warranty_claim.py
index a3428a2..a20e7a8 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.py
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.py
@@ -29,7 +29,7 @@
 			where t2.parent = t1.name and t2.prevdoc_docname = %s and	t1.docstatus!=2""",
 			(self.name))
 		if lst:
-			lst1 = ','.join([x[0] for x in lst])
+			lst1 = ','.join(x[0] for x in lst)
 			frappe.throw(_("Cancel Material Visit {0} before cancelling this Warranty Claim").format(lst1))
 		else:
 			frappe.db.set(self, 'status', 'Cancelled')
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
index 576e0b7..18691fe 100644
--- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
@@ -22,10 +22,10 @@
 	get_chart_data: function(_columns, result) {
 		return {
 			data: {
-				labels: result.map(d => d[0]),
+				labels: result.map(d => d.creation_date),
 				datasets: [{
 					name: 'First Response Time',
-					values: result.map(d => d[1])
+					values: result.map(d => d.first_response_time)
 				}]
 			},
 			type: "line",
@@ -35,8 +35,7 @@
 						hide_days: 0,
 						hide_seconds: 0
 					};
-					value = frappe.utils.get_formatted_duration(d, duration_options);
-					return value;
+					return frappe.utils.get_formatted_duration(d, duration_options);
 				}
 			}
 		}
diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html
index 135982d..17f6880 100644
--- a/erpnext/templates/generators/item/item.html
+++ b/erpnext/templates/generators/item/item.html
@@ -28,9 +28,7 @@
 
 {% block base_scripts %}
 <!-- js should be loaded in body! -->
-<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
-<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
-<script type="text/javascript" src="/assets/js/control.min.js"></script>
-<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
-<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
-{% endblock %}
\ No newline at end of file
+{{ include_script("frappe-web.bundle.js") }}
+{{ include_script("controls.bundle.js") }}
+{{ include_script("dialog.bundle.js") }}
+{% endblock %}
diff --git a/erpnext/templates/includes/rfq.js b/erpnext/templates/includes/rfq.js
index b56c416..37beb5a 100644
--- a/erpnext/templates/includes/rfq.js
+++ b/erpnext/templates/includes/rfq.js
@@ -11,23 +11,23 @@
 	doc.buying_price_list = "{{ doc.buying_price_list }}"
 });
 
-rfq = Class.extend({
-	init: function(){
+rfq = class rfq {
+	constructor(){
 		this.onfocus_select_all();
 		this.change_qty();
 		this.change_rate();
 		this.terms();
 		this.submit_rfq();
 		this.navigate_quotations();
-	},
+	}
 
-	onfocus_select_all: function(){
+	onfocus_select_all(){
 		$("input").click(function(){
 			$(this).select();
 		})
-	},
+	}
 
-	change_qty: function(){
+	change_qty(){
 		var me = this;
 		$('.rfq-items').on("change", ".rfq-qty", function(){
 			me.idx = parseFloat($(this).attr('data-idx'));
@@ -36,9 +36,9 @@
 			me.update_qty_rate();
 			$(this).val(format_number(me.qty, doc.number_format, 2));
 		})
-	},
+	}
 
-	change_rate: function(){
+	change_rate(){
 		var me = this;
 		$(".rfq-items").on("change", ".rfq-rate", function(){
 			me.idx = parseFloat($(this).attr('data-idx'));
@@ -47,15 +47,15 @@
 			me.update_qty_rate();
 			$(this).val(format_number(me.rate, doc.number_format, 2));
 		})
-	},
+	}
 
-	terms: function(){
+	terms(){
 		$(".terms").on("change", ".terms-feedback", function(){
 			doc.terms = $(this).val();
 		})
-	},
+	}
 
-	update_qty_rate: function(){
+	update_qty_rate(){
 		var me = this;
 		doc.grand_total = 0.0;
 		$.each(doc.items, function(idx, data){
@@ -69,9 +69,9 @@
 			doc.grand_total += flt(data.amount);
 			$('.tax-grand-total').text(format_number(doc.grand_total, doc.number_format, 2));
 		})
-	},
+	}
 
-	submit_rfq: function(){
+	submit_rfq(){
 		$('.btn-sm').click(function(){
 			frappe.freeze();
 			frappe.call({
@@ -90,12 +90,12 @@
 				}
 			})
 		})
-	},
+	}
 
-	navigate_quotations: function() {
+	navigate_quotations() {
 		$('.quotations').click(function(){
 			name = $(this).attr('idx')
 			window.location.href = "/quotations/" + encodeURIComponent(name);
 		})
 	}
-})
+}
diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html
index 3834131..3cfb8d8 100644
--- a/erpnext/templates/includes/transaction_row.html
+++ b/erpnext/templates/includes/transaction_row.html
@@ -13,9 +13,11 @@
 				{{ doc.items_preview }}
 			</div>
 		</div>
-		<div class="col-sm-3 text-right bold">
-			{{ doc.get_formatted("grand_total") }}
-		</div>
+		{% if doc.get('grand_total') %}
+			<div class="col-sm-3 text-right bold">
+				{{ doc.get_formatted("grand_total") }}
+			</div>
+		{% endif %}
 	</div>
 	<a class="transaction-item-link" href="/{{ pathname }}/{{ doc.name }}">Link</a>
 </div>
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index ea34371..c64c634 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -139,9 +139,7 @@
 
 {% block base_scripts %}
 <!-- js should be loaded in body! -->
-<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
-<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
-<script type="text/javascript" src="/assets/js/control.min.js"></script>
-<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
-<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
+{{ include_script("frappe-web.bundle.js") }}
+{{ include_script("controls.bundle.js") }}
+{{ include_script("dialog.bundle.js") }}
 {% endblock %}
diff --git a/erpnext/templates/pages/partners.py b/erpnext/templates/pages/partners.py
index 6725a3e..a7e60e2 100644
--- a/erpnext/templates/pages/partners.py
+++ b/erpnext/templates/pages/partners.py
@@ -3,7 +3,6 @@
 
 from __future__ import unicode_literals
 import frappe
-import frappe.website.render
 
 page_title = "Partners"
 
diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py
new file mode 100644
index 0000000..8b0ce09
--- /dev/null
+++ b/erpnext/tests/test_subcontracting.py
@@ -0,0 +1,877 @@
+from __future__ import unicode_literals
+import frappe
+import unittest
+import copy
+from frappe.utils import cint
+from collections import defaultdict
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+from erpnext.buying.doctype.purchase_order.purchase_order import (make_rm_stock_entry,
+	make_purchase_receipt, make_purchase_invoice, get_materials_from_supplier)
+
+class TestSubcontracting(unittest.TestCase):
+	def setUp(self):
+		make_subcontract_items()
+		make_raw_materials()
+		make_bom_for_subcontracted_items()
+
+	def test_po_with_bom(self):
+		'''
+			- Set backflush based on BOM
+			- Create subcontracted PO for the item Subcontracted Item SA1 and add same item two times.
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Create purchase receipt against the PO and check serial nos and batch no.
+		'''
+
+		set_backflush_based_on('BOM')
+		item_code = 'Subcontracted Item SA1'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 5, 'rate': 100},
+			{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 6, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 5},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 5},
+			{'item_code': 'Subcontracted SRM Item 1', 'qty': 6},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 6},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 6}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+
+			for field in ['qty', 'serial_no', 'batch_no']:
+				if value.get(field):
+					transfer, consumed = (transferred_detais.get(field), value.get(field))
+					if field == 'serial_no':
+						transfer, consumed = (sorted(transfer), sorted(consumed))
+
+					self.assertEqual(transfer, consumed)
+
+	def test_po_with_material_transfer(self):
+		'''
+			- Set backflush based on Material Transfer
+			- Create subcontracted PO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
+			- Create partial purchase receipt against the PO and check serial nos and batch no.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA1', 'qty': 5, 'rate': 100},
+			{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA5', 'qty': 6, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
+			{'item_code': 'Subcontracted SRM Item 5', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'},
+			{'item_code': 'Subcontracted SRM Item 4', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+
+		make_stock_transfer_entry(po_no = po.name,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.remove(pr1.items[1])
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+
+			for field in ['qty', 'serial_no', 'batch_no']:
+				if value.get(field):
+					self.assertEqual(value.get(field), transferred_detais.get(field))
+
+		pr2 = make_purchase_receipt(po.name)
+		pr2.submit()
+
+		for key, value in get_supplied_items(pr2).items():
+			transferred_detais = itemwise_details.get(key)
+
+			for field in ['qty', 'serial_no', 'batch_no']:
+				if value.get(field):
+					self.assertEqual(value.get(field), transferred_detais.get(field))
+
+	def test_subcontract_with_same_components_different_fg(self):
+		'''
+			- Set backflush based on Material Transfer
+			- Create subcontracted PO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
+			- Transfer the components from Stores to Supplier warehouse with serial nos.
+			- Transfer extra qty of components for the item Subcontracted Item SA2.
+			- Create partial purchase receipt against the PO and check serial nos and batch no.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100},
+			{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA3', 'qty': 6, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA3'}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+
+		make_stock_transfer_entry(po_no = po.name,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 3
+		pr1.remove(pr1.items[1])
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+			self.assertEqual(value.qty, 4)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:4]))
+
+		pr2 = make_purchase_receipt(po.name)
+		pr2.items[0].qty = 2
+		pr2.remove(pr2.items[1])
+		pr2.submit()
+
+		for key, value in get_supplied_items(pr2).items():
+			transferred_detais = itemwise_details.get(key)
+
+			self.assertEqual(value.qty, 2)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[4:6]))
+
+		pr3 = make_purchase_receipt(po.name)
+		pr3.submit()
+		for key, value in get_supplied_items(pr3).items():
+			transferred_detais = itemwise_details.get(key)
+
+			self.assertEqual(value.qty, 6)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[6:12]))
+
+	def test_return_non_consumed_materials(self):
+		'''
+			- Set backflush based on Material Transfer
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the components from Stores to Supplier warehouse with serial nos.
+			- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
+			- Create purchase receipt for full qty against the PO and change the qty of raw material.
+			- After that return the non consumed material back to the store from supplier's warehouse.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100}]
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.save()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].serial_no = '\n'.join(sorted(
+			itemwise_details.get('Subcontracted SRM Item 2').get('serial_no')[0:5]
+		))
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+			self.assertEqual(value.qty, 5)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:5]))
+
+		po.load_from_db()
+		self.assertEqual(po.supplied_items[0].consumed_qty, 5)
+		doc = get_materials_from_supplier(po.name, [d.name for d in po.supplied_items])
+		self.assertEqual(doc.items[0].qty, 1)
+		self.assertEqual(doc.items[0].s_warehouse, '_Test Warehouse 1 - _TC')
+		self.assertEqual(doc.items[0].t_warehouse, '_Test Warehouse - _TC')
+		self.assertEqual(get_serial_nos(doc.items[0].serial_no),
+			itemwise_details.get(doc.items[0].item_code)['serial_no'][5:6])
+
+	def test_item_with_batch_based_on_bom(self):
+		'''
+			- Set backflush based on BOM
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+		'''
+
+		set_backflush_based_on('BOM')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+	def test_item_with_batch_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches with extra 2 qty for the batched item.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+			- In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			qty = 4 if key != 'Subcontracted SRM Item 3' else 6
+			self.assertEqual(value.qty, qty)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+	def test_partial_transfer_serial_no_components_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the partial components from Stores to Supplier warehouse with serial nos.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with serial nos.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA2'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 5
+		pr1.save()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+	def test_incorrect_serial_no_components_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the serialized componenets to the supplier.
+			- Create purchase receipt and change the serial no which is not transferred.
+			- System should throw the error and not allowed to save the purchase receipt.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA2'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 10}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.save()
+		pr1.supplied_items[0].serial_no = 'ABCD'
+		self.assertRaises(frappe.ValidationError, pr1.save)
+		pr1.delete()
+
+	def test_partial_transfer_batch_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA6.
+			- Transfer the partial components from Stores to Supplier warehouse with batch.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with batch.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA6'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 5
+		pr1.save()
+
+		transferred_batch_no = ''
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			transferred_batch_no = details.batch_no
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+
+	def test_item_with_batch_based_on_material_transfer_for_purchase_invoice(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches with extra 2 qty for the batched item.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+			- In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			qty = 4 if key != 'Subcontracted SRM Item 3' else 6
+			self.assertEqual(value.qty, qty)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+	def test_partial_transfer_serial_no_components_based_on_material_transfer_for_purchase_invoice(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the partial components from Stores to Supplier warehouse with serial nos.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with serial nos.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA2'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 5
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+	def test_partial_transfer_batch_based_on_material_transfer_for_purchase_invoice(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA6.
+			- Transfer the partial components from Stores to Supplier warehouse with batch.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with batch.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA6'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 5
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+
+		transferred_batch_no = ''
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			transferred_batch_no = details.batch_no
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+	def test_item_with_batch_based_on_bom_for_purchase_invoice(self):
+		'''
+			- Set backflush based on BOM
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+		'''
+
+		set_backflush_based_on('BOM')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+def add_second_row_in_pr(pr):
+	item_dict = {}
+	for column in ['item_code', 'item_name', 'qty', 'uom', 'warehouse', 'stock_uom',
+		'purchase_order', 'purchase_order_item', 'conversion_factor', 'rate', 'expense_account', 'po_detail']:
+		item_dict[column] = pr.items[0].get(column)
+
+	pr.append('items', item_dict)
+	pr.set_missing_values()
+
+def get_supplied_items(pr_doc):
+	supplied_items = {}
+	for row in pr_doc.get('supplied_items'):
+		if row.rm_item_code not in supplied_items:
+			supplied_items.setdefault(row.rm_item_code,
+				frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
+
+		details = supplied_items[row.rm_item_code]
+		update_item_details(row, details)
+
+	return supplied_items
+
+def make_stock_in_entry(**args):
+	args = frappe._dict(args)
+
+	items = {}
+	for row in args.rm_items:
+		row = frappe._dict(row)
+
+		doc = make_stock_entry(target=row.warehouse or '_Test Warehouse - _TC',
+			item_code=row.item_code, qty=row.qty or 1, basic_rate=row.rate or 100)
+
+		if row.item_code not in items:
+			items.setdefault(row.item_code, frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
+
+		child_row = doc.items[0]
+		details = items[child_row.item_code]
+		update_item_details(child_row, details)
+
+	return items
+
+def update_item_details(child_row, details):
+	details.qty += (child_row.get('qty') if child_row.doctype == 'Stock Entry Detail'
+		else child_row.get('consumed_qty'))
+
+	if child_row.serial_no:
+		details.serial_no.extend(get_serial_nos(child_row.serial_no))
+
+	if child_row.batch_no:
+		details.batch_no[child_row.batch_no] += (child_row.get('qty') or child_row.get('consumed_qty'))
+
+def make_stock_transfer_entry(**args):
+	args = frappe._dict(args)
+
+	items = []
+	for row in args.rm_items:
+		row = frappe._dict(row)
+
+		item = {'item_code': row.main_item_code or args.main_item_code, 'rm_item_code': row.item_code,
+			'qty': row.qty or 1, 'item_name': row.item_code, 'rate': row.rate or 100,
+			'stock_uom': row.stock_uom or 'Nos', 'warehouse': row.warehuose or '_Test Warehouse - _TC'}
+
+		item_details = args.itemwise_details.get(row.item_code)
+
+		if item_details and item_details.serial_no:
+			serial_nos = item_details.serial_no[0:cint(row.qty)]
+			item['serial_no'] = '\n'.join(serial_nos)
+			item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos))
+
+		if item_details and item_details.batch_no:
+			for batch_no, batch_qty in item_details.batch_no.items():
+				if batch_qty >= row.qty:
+					item['batch_no'] = batch_no
+					item_details.batch_no[batch_no] -= row.qty
+					break
+
+		items.append(item)
+
+	ste_dict = make_rm_stock_entry(args.po_no, items)
+	doc = frappe.get_doc(ste_dict)
+	doc.insert()
+	doc.submit()
+
+	return doc
+
+def make_subcontract_items():
+	sub_contracted_items = {'Subcontracted Item SA1': {}, 'Subcontracted Item SA2': {}, 'Subcontracted Item SA3': {},
+		'Subcontracted Item SA4': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'SBAT.####'},
+		'Subcontracted Item SA5': {}, 'Subcontracted Item SA6': {}}
+
+	for item, properties in sub_contracted_items.items():
+		if not frappe.db.exists('Item', item):
+			properties.update({'is_stock_item': 1, 'is_sub_contracted_item': 1})
+			make_item(item, properties)
+
+def make_raw_materials():
+	raw_materials = {'Subcontracted SRM Item 1': {},
+		'Subcontracted SRM Item 2': {'has_serial_no': 1, 'serial_no_series': 'SRI.####'},
+		'Subcontracted SRM Item 3': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'BAT.####'},
+		'Subcontracted SRM Item 4': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'},
+		'Subcontracted SRM Item 5': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'}}
+
+	for item, properties in raw_materials.items():
+		if not frappe.db.exists('Item', item):
+			properties.update({'is_stock_item': 1})
+			make_item(item, properties)
+
+def make_bom_for_subcontracted_items():
+	boms = {
+		'Subcontracted Item SA1': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
+		'Subcontracted Item SA2': ['Subcontracted SRM Item 2'],
+		'Subcontracted Item SA3': ['Subcontracted SRM Item 2'],
+		'Subcontracted Item SA4': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
+		'Subcontracted Item SA5': ['Subcontracted SRM Item 5'],
+		'Subcontracted Item SA6': ['Subcontracted SRM Item 3']
+	}
+
+	for item_code, raw_materials in boms.items():
+		if not frappe.db.exists('BOM', {'item': item_code}):
+			make_bom(item=item_code, raw_materials=raw_materials, rate=100)
+
+def set_backflush_based_on(based_on):
+	frappe.db.set_value('Buying Settings', None,
+		'backflush_raw_materials_of_subcontract_based_on', based_on)
\ No newline at end of file
diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py
index 66d6cd3..70b4176 100644
--- a/erpnext/utilities/product.py
+++ b/erpnext/utilities/product.py
@@ -131,6 +131,6 @@
 	if frappe.db.exists("Product Bundle", item_code):
 		items = frappe.get_doc("Product Bundle", item_code).get_all_children()
 		bundle_warehouse = frappe.db.get_value('Item', item_code, item_warehouse_field)
-		return all([ get_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items ])
+		return all(get_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items)
 	else:
 		return 1
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index f99da58..db99726 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -147,7 +147,7 @@
 		if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
 			fieldname = self.prev_link_mapper[for_doctype]["fieldname"]
 
-			values = filter(None, tuple([item.as_dict()[fieldname] for item in self.items]))
+			values = filter(None, tuple(item.as_dict()[fieldname] for item in self.items))
 
 			if values:
 				ret = {
@@ -180,7 +180,7 @@
 	if isinstance(qty_fields, string_types):
 		qty_fields = [qty_fields]
 
-	distinct_uoms = list(set([d.get(uom_field) for d in doc.get_all_children()]))
+	distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children()))
 	integer_uoms = list(filter(lambda uom: frappe.db.get_value("UOM", uom,
 		"must_be_whole_number", cache=True) or None, distinct_uoms))
 
diff --git a/requirements.txt b/requirements.txt
index 32da48e..f28906a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,11 @@
+future==0.18.2
 # frappe   # https://github.com/frappe/frappe is installed during bench-init
 gocardless-pro~=1.22.0
 googlemaps  # used in ERPNext, but dependency is defined in Frappe
 pandas~=1.1.5
 plaid-python~=7.2.1
 pycountry~=20.7.3
-PyGithub~=1.54.1
+PyGithub~=1.55
 python-stdnum~=1.16
 python-youtube~=0.8.0
 taxjar~=1.9.2
diff --git a/yarn.lock b/yarn.lock
index 0a2ac1a..242695c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1540,16 +1540,11 @@
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
-ini@1.3.7:
+ini@1.3.7, ini@~1.3.0:
   version "1.3.7"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
   integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
 
-ini@~1.3.0:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
-  integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
-
 is-callable@^1.1.5:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
@@ -2000,9 +1995,9 @@
   integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=
 
 lodash@^4.17.15:
-  version "4.17.19"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
-  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
 log-symbols@^4.0.0:
   version "4.1.0"