refactor: move `js` validations to `py`
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.js b/erpnext/stock/doctype/packing_slip/packing_slip.js
index 85a611f..ae3d9ba 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.js
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.js
@@ -28,63 +28,5 @@
 
 	refresh: (frm) => {
 		frm.toggle_display("misc_details", frm.doc.amended_from);
-	},
-
-	validate: (frm) => {
-		frm.trigger("validate_case_nos");
-		frm.trigger("validate_calculate_item_details");
-	},
-
-	// To Case No. cannot be less than From Case No.
-	validate_case_nos: (frm) => {
-		doc = locals[frm.doc.doctype][frm.doc.name];
-
-		if(cint(doc.from_case_no) == 0) {
-			frappe.msgprint(__("The 'From Package No.' field must neither be empty nor it's value less than 1."));
-			frappe.validated = false;
-		} else if(!cint(doc.to_case_no)) {
-			doc.to_case_no = doc.from_case_no;
-			refresh_field('to_case_no');
-		} else if(cint(doc.to_case_no) < cint(doc.from_case_no)) {
-			frappe.msgprint(__("'To Case No.' cannot be less than 'From Case No.'"));
-			frappe.validated = false;
-		}
-	},
-
-	validate_calculate_item_details: (frm) => {
-		frm.trigger("validate_items_qty");
-		frm.trigger("calc_net_total_pkg");
-	},
-
-	validate_items_qty: (frm) => {
-		frm.doc.items.forEach(item => {
-			if (item.qty <= 0) {
-				frappe.msgprint(__("Invalid quantity specified for item {0}. Quantity should be greater than 0.", [item.item_code]));
-				frappe.validated = false;
-			}
-		});
-	},
-
-	calc_net_total_pkg: (frm) => {
-		var net_weight_pkg = 0;
-		var items = frm.doc.items || [];
-		frm.doc.net_weight_uom = (items && items.length) ? items[0].weight_uom : '';
-		frm.doc.gross_weight_uom = frm.doc.net_weight_uom;
-
-		items.forEach(item => {
-			if(item.weight_uom != frm.doc.net_weight_uom) {
-				frappe.msgprint(__("Different UOM for items will lead to incorrect (Total) Net Weight value. Make sure that Net Weight of each item is in the same UOM."));
-				frappe.validated = false;
-			}
-			net_weight_pkg += flt(item.net_weight) * flt(item.qty);
-		});
-
-		frm.doc.net_weight_pkg = roundNumber(net_weight_pkg, 2);
-
-		if(!flt(frm.doc.gross_weight_pkg)) {
-			frm.doc.gross_weight_pkg = frm.doc.net_weight_pkg;
-		}
-
-		refresh_many(['net_weight_pkg', 'net_weight_uom', 'gross_weight_uom', 'gross_weight_pkg']);
 	}
 });
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index c5b928b..6ea5938 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -4,7 +4,7 @@
 
 import frappe
 from frappe import _
-from frappe.utils import cint
+from frappe.utils import cint, flt
 
 from erpnext.controllers.status_updater import StatusUpdater
 
@@ -44,6 +44,7 @@
 		validate_uom_is_integer(self, "weight_uom", "net_weight")
 
 		self.set_missing_values()
+		self.calculate_net_total_pkg()
 
 	def on_submit(self):
 		self.update_prevdoc_status()
@@ -62,9 +63,13 @@
 	def validate_case_nos(self):
 		"""Validate if case nos overlap. If they do, recommend next case no."""
 
-		if not self.to_case_no:
+		if cint(self.from_case_no) <= 0:
+			frappe.throw(
+				_("The 'From Package No.' field must neither be empty nor it's value less than 1.")
+			)
+		elif not self.to_case_no:
 			self.to_case_no = self.from_case_no
-		elif cint(self.from_case_no) > cint(self.to_case_no):
+		elif cint(self.to_case_no) < cint(self.from_case_no):
 			frappe.throw(_("'To Package No.' cannot be less than 'From Package No.'"))
 		else:
 			ps = frappe.qb.DocType("Packing Slip")
@@ -93,9 +98,14 @@
 
 	def validate_items(self):
 		for item in self.items:
+			if item.qty <= 0:
+				frappe.throw(_("Row {0}: Qty must be greater than 0.").format(item.idx))
+
 			if not item.dn_detail and not item.pi_detail:
 				frappe.throw(
-					_("Row {0}: Either Delivery Note Item or Packed Item reference is mandatory").format(item.idx)
+					_("Row {0}: Either Delivery Note Item or Packed Item reference is mandatory.").format(
+						item.idx
+					)
 				)
 
 			remaining_qty = frappe.db.get_value(
@@ -150,6 +160,26 @@
 			+ 1
 		)
 
+	def calculate_net_total_pkg(self):
+		self.net_weight_uom = self.items[0].weight_uom if self.items else None
+		self.gross_weight_uom = self.net_weight_uom
+
+		net_weight_pkg = 0
+		for item in self.items:
+			if item.weight_uom != self.net_weight_uom:
+				frappe.throw(
+					_(
+						"Different UOM for items will lead to incorrect (Total) Net Weight value. Make sure that Net Weight of each item is in the same UOM."
+					)
+				)
+
+			net_weight_pkg += flt(item.net_weight) * flt(item.qty)
+
+		self.net_weight_pkg = round(net_weight_pkg, 2)
+
+		if not flt(self.gross_weight_pkg):
+			self.gross_weight_pkg = self.net_weight_pkg
+
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs