Merge branch 'master' of github.com:webnotes/erpnext
diff --git a/patches/december_2012/clear_web_cache.py b/patches/december_2012/clear_web_cache.py
deleted file mode 100644
index b92f4bd..0000000
--- a/patches/december_2012/clear_web_cache.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import webnotes
-def execute():
-	webnotes.reload_doc("website", "doctype", "web_page")
-	webnotes.reload_doc("website", "doctype", "blog")
-	webnotes.reload_doc("stock", "doctype", "item")
-	webnotes.reload_doc("setup", "doctype", "item_group")
-	
-	# build wn-web.js and wn-web.css
-	from website.helpers.make_web_include_files import make
-	make()
-
-	import website.utils
-	website.utils.clear_cache()
\ No newline at end of file
diff --git a/patches/patch_list.py b/patches/patch_list.py
index 5a504f8..3f51209 100644
--- a/patches/patch_list.py
+++ b/patches/patch_list.py
@@ -132,7 +132,6 @@
 	"patches.december_2012.fix_default_print_format", 
 	"patches.december_2012.file_list_rename", 
 	"patches.december_2012.replace_createlocal", 
-	"patches.december_2012.clear_web_cache", 
 	"patches.december_2012.remove_quotation_next_contact", 
 	"patches.december_2012.stock_entry_cleanup", 
 	"patches.december_2012.production_order_naming_series", 
diff --git a/setup/page/setup/setup.js b/setup/page/setup/setup.js
index 394a5f6..a9df459 100644
--- a/setup/page/setup/setup.js
+++ b/setup/page/setup/setup.js
@@ -45,7 +45,7 @@
 			},
 			{
 				"doctype":"Workflow",
-				label:wn._("Workfow"),
+				label:wn._("Workflow"),
 				"description":wn._("Set workflow rules.")
 			},
 			{
diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py
index 74b71d9..cd20266 100644
--- a/stock/doctype/stock_entry/stock_entry.py
+++ b/stock/doctype/stock_entry/stock_entry.py
@@ -39,7 +39,6 @@
 		
 	def validate(self):
 		self.validate_purpose()
-
 		self.validate_serial_nos()
 		pro_obj = self.doc.production_order and \
 			get_obj('Production Order', self.doc.production_order) or None
@@ -51,22 +50,20 @@
 		self.validate_incoming_rate()
 		self.validate_bom()
 		self.validate_finished_goods()
-
 		self.validate_return_reference_doc()
-		
 		self.validate_with_material_request()
 		
 	def on_submit(self):
 		self.update_serial_no(1)
 		self.update_stock_ledger(0)
-		# update Production Order
 		self.update_production_order(1)
+		self.make_gl_entries()
 
 	def on_cancel(self):
 		self.update_serial_no(0)
 		self.update_stock_ledger(1)
-		# update Production Order
 		self.update_production_order(0)
+		self.make_gl_entries()
 		
 	def validate_purpose(self):
 		valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", 
@@ -167,6 +164,47 @@
 		elif self.doc.purpose != "Material Transfer":
 			self.doc.production_order = None
 			
+	def make_gl_entries(self):
+		if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
+			return
+		
+		abbr = webnotes.conn.get_value("Company", self.doc.company, "abbr")
+		stock_in_hand_account = self.get_stock_in_hand_account()
+		total_valuation_amount = self.get_total_valuation_amount()
+
+		if total_valuation_amount:
+			gl_entries = [
+				# debit stock in hand account
+				self.get_gl_dict({
+					"account": stock_in_hand_account,
+					"against": "Stock Adjustment - %s" % abbr,
+					"debit": total_valuation_amount,
+					"remarks": self.doc.remarks or "Accounting Entry for Stock",
+				}, self.doc.docstatus == 2),
+				
+				# debit stock received but not billed account
+				self.get_gl_dict({
+					"account": "Stock Adjustment - %s" % abbr,
+					"against": stock_in_hand_account,
+					"credit": total_valuation_amount,
+					"cost_center": "Auto Inventory Accounting - %s" % abbr, 
+					"remarks": self.doc.remarks or "Accounting Entry for Stock",
+				}, self.doc.docstatus == 2),
+			]
+			from accounts.general_ledger import make_gl_entries
+			make_gl_entries(gl_entries, cancel=self.doc.docstatus == 2)
+			
+	def get_total_valuation_amount(self):
+		total_valuation_amount = 0
+		for item in self.doclist.get({"parentfield": "mtn_details"}):
+			if item.t_warehouse and not item.s_warehouse:
+				total_valuation_amount += flt(item.incoming_rate) * flt(item.transfer_qty)
+				
+			if item.s_warehouse and not item.t_warehouse:
+				total_valuation_amount -= flt(item.incoming_rate) * flt(item.transfer_qty)
+						
+		return total_valuation_amount
+			
 	def get_stock_and_rate(self):
 		"""get stock and incoming rate on posting date"""
 		for d in getlist(self.doclist, 'mtn_details'):
diff --git a/stock/doctype/stock_entry/test_stock_entry.py b/stock/doctype/stock_entry/test_stock_entry.py
index 21d15f7..a4103c3 100644
--- a/stock/doctype/stock_entry/test_stock_entry.py
+++ b/stock/doctype/stock_entry/test_stock_entry.py
@@ -43,6 +43,20 @@
 			])
 		)
 		
+		mr.cancel()
+		self.check_stock_ledger_entries("Stock Entry", mr.doc.name, 
+			sorted([["_Test Item", "_Test Warehouse", 50.0], 
+				["_Test Item", "_Test Warehouse", -50.0]]))
+			
+		self.check_gl_entries("Stock Entry", mr.doc.name, 
+			sorted([
+				[stock_in_hand_account, 5000.0, 0.0], 
+				["Stock Adjustment - _TC", 0.0, 5000.0],
+				[stock_in_hand_account, 0.0, 5000.0], 
+				["Stock Adjustment - _TC", 5000.0, 0.0]
+			])
+		)
+		
 		webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
 
 	def test_material_issue_gl_entry(self):
@@ -53,25 +67,78 @@
 		mr.insert()
 		mr.submit()
 		
+		mi = webnotes.bean(copy=test_records[1])
+		mi.insert()
+		mi.submit()
+		
 		stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company", 
 			"stock_in_hand_account")
 		
-		self.check_stock_ledger_entries("Stock Entry", mr.doc.name, 
-			[["_Test Item", "_Test Warehouse", 50.0]])
+		self.check_stock_ledger_entries("Stock Entry", mi.doc.name, 
+			[["_Test Item", "_Test Warehouse", -40.0]])
 			
-		self.check_gl_entries("Stock Entry", mr.doc.name, 
+		self.check_gl_entries("Stock Entry", mi.doc.name, 
 			sorted([
-				[stock_in_hand_account, 5000.0, 0.0], 
-				["Stock Adjustment - _TC", 0.0, 5000.0]
+				[stock_in_hand_account, 0.0, 4000.0], 
+				["Stock Adjustment - _TC", 4000.0, 0.0]
 			])
 		)
 		
+		mi.cancel()
+		
+		self.check_stock_ledger_entries("Stock Entry", mi.doc.name, 
+			sorted([["_Test Item", "_Test Warehouse", -40.0], 
+				["_Test Item", "_Test Warehouse", 40.0]]))
+			
+		self.check_gl_entries("Stock Entry", mi.doc.name, 
+			sorted([
+				[stock_in_hand_account, 0.0, 4000.0], 
+				["Stock Adjustment - _TC", 4000.0, 0.0],
+				[stock_in_hand_account, 4000.0, 0.0], 
+				["Stock Adjustment - _TC", 0.0, 4000.0],
+			])
+		)
+		
+		webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
+		
+	def test_material_transfer_gl_entry(self):
+		webnotes.conn.sql("delete from `tabStock Ledger Entry`")
+		webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
+
+		mr = webnotes.bean(copy=test_records[0])
+		mr.insert()
+		mr.submit()
+
+		mtn = webnotes.bean(copy=test_records[2])
+		mtn.insert()
+		mtn.submit()
+
+		self.check_stock_ledger_entries("Stock Entry", mtn.doc.name, 
+			[["_Test Item", "_Test Warehouse", -45.0], ["_Test Item", "_Test Warehouse 1", 45.0]])
+
+		# no gl entry
+		gl_entries = webnotes.conn.sql("""select * from `tabGL Entry` 
+			where voucher_type = 'Stock Entry' and voucher_no=%s""", mtn.doc.name)
+		self.assertFalse(gl_entries)
+		
+		mtn.cancel()
+		self.check_stock_ledger_entries("Stock Entry", mtn.doc.name, 
+			sorted([["_Test Item", "_Test Warehouse", 45.0], 
+				["_Test Item", "_Test Warehouse 1", -45.0],
+				["_Test Item", "_Test Warehouse", -45.0], 
+				["_Test Item", "_Test Warehouse 1", 45.0]]))
+
+		# no gl entry
+		gl_entries = webnotes.conn.sql("""select * from `tabGL Entry` 
+			where voucher_type = 'Stock Entry' and voucher_no=%s""", mtn.doc.name)
+		self.assertFalse(gl_entries)
+		
 		webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
 	
 	def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):
 		# check stock ledger entries
-		sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` 
-			where voucher_type = %s and voucher_no = %s order by item_code, warehouse""", 
+		sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` where voucher_type = %s 
+			and voucher_no = %s order by item_code, warehouse, actual_qty""", 
 			(voucher_type, voucher_no), as_dict=1)
 		self.assertTrue(sle)
 		
@@ -87,7 +154,6 @@
 			from `tabGL Entry` where voucher_type=%s and voucher_no=%s 
 			order by account asc, debit asc""", (voucher_type, voucher_no), as_dict=1)
 		self.assertTrue(gl_entries)
-		
 		for i, gle in enumerate(gl_entries):
 			self.assertEquals(expected_gl_entries[i][0], gle.account)
 			self.assertEquals(expected_gl_entries[i][1], gle.debit)
@@ -100,7 +166,8 @@
 			"doctype": "Stock Entry", 
 			"posting_date": "2013-01-25", 
 			"posting_time": "17:14:24", 
-			"purpose": "Material Receipt"
+			"purpose": "Material Receipt",
+			"fiscal_year": "_Test Fiscal Year 2013", 
 		}, 
 		{
 			"conversion_factor": 1.0, 
@@ -121,7 +188,8 @@
 			"doctype": "Stock Entry", 
 			"posting_date": "2013-01-25", 
 			"posting_time": "17:15", 
-			"purpose": "Material Issue"
+			"purpose": "Material Issue",
+			"fiscal_year": "_Test Fiscal Year 2013", 
 		}, 
 		{
 			"conversion_factor": 1.0, 
@@ -142,12 +210,13 @@
 			"doctype": "Stock Entry", 
 			"posting_date": "2013-01-25", 
 			"posting_time": "17:14:24", 
-			"purpose": "Material Transfer"
+			"purpose": "Material Transfer",
+			"fiscal_year": "_Test Fiscal Year 2013", 
 		}, 
 		{
 			"conversion_factor": 1.0, 
 			"doctype": "Stock Entry Detail", 
-			"item_code": "_Test Item Home Desktop 100", 
+			"item_code": "_Test Item", 
 			"parentfield": "mtn_details", 
 			"incoming_rate": 100,
 			"qty": 45.0, 
@@ -156,19 +225,6 @@
 			"uom": "_Test UOM",
 			"s_warehouse": "_Test Warehouse",
 			"t_warehouse": "_Test Warehouse 1",
-		}, 
-		{
-			"conversion_factor": 1.0, 
-			"doctype": "Stock Entry Detail", 
-			"item_code": "_Test Item Home Desktop 100", 
-			"parentfield": "mtn_details", 
-			"qty": 45.0, 
-			"incoming_rate": 100,
-			"stock_uom": "_Test UOM", 
-			"transfer_qty": 45.0, 
-			"uom": "_Test UOM",
-			"s_warehouse": "_Test Warehouse",
-			"t_warehouse": "_Test Warehouse 1",
 		}
 	]
 ]
\ No newline at end of file
diff --git a/website/doctype/about_us_settings/about_us_settings.txt b/website/doctype/about_us_settings/about_us_settings.txt
index 6a27f73..b846d2b 100644
--- a/website/doctype/about_us_settings/about_us_settings.txt
+++ b/website/doctype/about_us_settings/about_us_settings.txt
@@ -2,7 +2,7 @@
  {
   "creation": "2013-03-07 15:53:15", 
   "docstatus": 0, 
-  "modified": "2013-03-12 13:51:29", 
+  "modified": "2013-03-12 14:48:34", 
   "modified_by": "Administrator", 
   "owner": "Administrator"
  }, 
@@ -46,7 +46,7 @@
   "fieldname": "help", 
   "fieldtype": "HTML", 
   "label": "Help", 
-  "options": "<div class=\"alert\">Link for About Us Page is \"about.html\"</div>"
+  "options": "<div class=\"alert\">Link for About Us Page is \"/about\"</div>"
  }, 
  {
   "description": "Introduce your company to the website visitor.", 
diff --git a/website/doctype/contact_us_settings/contact_us_settings.txt b/website/doctype/contact_us_settings/contact_us_settings.txt
index 1bc7b05..ef2da02 100644
--- a/website/doctype/contact_us_settings/contact_us_settings.txt
+++ b/website/doctype/contact_us_settings/contact_us_settings.txt
@@ -1,8 +1,8 @@
 [
  {
-  "creation": "2012-12-27 19:04:50", 
+  "creation": "2013-02-21 20:12:42", 
   "docstatus": 0, 
-  "modified": "2013-02-21 16:49:33", 
+  "modified": "2013-03-12 14:49:01", 
   "modified_by": "Administrator", 
   "owner": "Administrator"
  }, 
@@ -44,7 +44,7 @@
   "fieldname": "help", 
   "fieldtype": "HTML", 
   "label": "Help", 
-  "options": "<div class=\"alert\">Link for Contact Page is \"contact.html\"</div>"
+  "options": "<div class=\"alert\">Link for Contact Page is \"/contact\"</div>"
  }, 
  {
   "description": "Address to be displayed on the Contact Page", 
diff --git a/website/doctype/style_settings/custom_template.css b/website/doctype/style_settings/custom_template.css
index 24353f9..208c7dd 100644
--- a/website/doctype/style_settings/custom_template.css
+++ b/website/doctype/style_settings/custom_template.css
@@ -29,7 +29,7 @@
 {% endif %}
 
 div.outer {
-	background-color: #{{ doc.page_background or "fff" }};
+	background-color: #{{ doc.page_background or "fffffff" }};
 }
 
 {% if doc.google_web_font_for_heading or doc.heading_font %}h1, h2, h3, h4, h5 {
@@ -54,14 +54,14 @@
 }
 {% else %}
 div.web-footer {
-	border-top: 1px solid #eee;
+	border-top: 1px solid #{{ get_hex_shade(doc.page_background or "ffffff", 15) }};
 	padding-top: 10px;
 }
 {% endif %}
 
 /* Bootstrap Navbar */
 .navbar-inverse .navbar-inner {
-    background-color: #{{ doc.top_bar_background or "444"}};
+    background-color: #{{ doc.top_bar_background or "444444"}};
     background-repeat: repeat-x;
 	border-color: transparent;
 	background-image: none;
@@ -71,13 +71,13 @@
 .navbar-inverse .brand:hover,
 .navbar-inverse .brand:focus,
 .navbar-inverse .nav > li > a {
-  color: #{{ doc.top_bar_foreground or "fff"}};
+  color: #{{ doc.top_bar_foreground or "fffffff"}};
   text-shadow: none;
 }
 
 .navbar-inverse .nav > li > a:hover,
 .navbar-inverse .nav > li > a:focus {
-  color: #{{ doc.top_bar_background or "000"}};
+  color: #{{ doc.top_bar_background or "0000000"}};
 }
 
 .navbar-inverse .navbar-text {
@@ -86,14 +86,14 @@
 
 .navbar-inverse .nav > li > a:focus,
 .navbar-inverse .nav > li > a:hover {
-  color: #{{ doc.top_bar_foreground or "fff"}};
+  color: #{{ doc.top_bar_foreground or "fffffff"}};
   background-color: transparent;
 }
 
 .navbar-inverse .nav .active > a,
 .navbar-inverse .nav .active > a:hover,
 .navbar-inverse .nav .active > a:focus {
-  color: #{{ doc.top_bar_foreground or "fff"}};
+  color: #{{ doc.top_bar_foreground or "fffffff"}};
   background-color: transparent;
 }
 
@@ -103,7 +103,7 @@
 
 .navbar-inverse .navbar-link:hover,
 .navbar-inverse .navbar-link:focus {
-  color: #{{ doc.top_bar_foreground or "fff"}};
+  color: #{{ doc.top_bar_foreground or "fffffff"}};
 }
 
 .navbar-fixed-top .navbar-inner,
@@ -126,47 +126,90 @@
 
 .navbar-inverse .nav li.dropdown > .dropdown-toggle .caret, 
 .navbar-inverse .nav li.dropdown > .dropdown-toggle:hover .caret {
-	border-top-color: #{{ doc.top_bar_foreground or "fff"}};
-	border-bottom-color: #{{ doc.top_bar_foreground or "fff"}};
+	border-top-color: #{{ doc.top_bar_foreground or "fffffff"}};
+	border-bottom-color: #{{ doc.top_bar_foreground or "fffffff"}};
 }
 
 .navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
 .navbar-inverse .nav li.dropdown.open > .dropdown-toggle:hover .caret {
-	border-top-color: #{{ doc.top_bar_background or "000"}};
-	border-bottom-color: #{{ doc.top_bar_background or "000"}};
+	border-top-color: #{{ doc.top_bar_background or "0000000"}};
+	border-bottom-color: #{{ doc.top_bar_background or "0000000"}};
 }
 
 .navbar-inverse .nav li.dropdown.open > .dropdown-toggle {
-  color: #{{ doc.top_bar_background or "000"}};
-  background-color: #{{ doc.top_bar_foreground or "fff"}};
+  color: #{{ doc.top_bar_background or "0000000"}};
+  background-color: #{{ doc.top_bar_foreground or "fffffff"}};
 }
 
 @media (max-width: 800px) {
   .navbar-inverse .nav-collapse .nav > li > a,
   .navbar-inverse .nav-collapse .dropdown-menu a {
-    background-color: #{{ doc.top_bar_background or "000"}};
-    color: #{{ doc.top_bar_foreground or "fff"}};
+    background-color: #{{ doc.top_bar_background or "0000000"}};
+    color: #{{ doc.top_bar_foreground or "fffffff"}};
   }
   .navbar-inverse .nav-collapse .nav > li > a:hover, 
   .navbar-inverse .nav-collapse .dropdown-menu a:hover {
-    background-color: #{{ doc.top_bar_foreground or "fff"}};
-    color: #{{ doc.top_bar_background or "000"}};
+    background-color: #{{ doc.top_bar_foreground or "fffffff"}};
+    color: #{{ doc.top_bar_background or "0000000"}};
   }
 
   .navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
-    border-top-color: #{{ doc.top_bar_foreground or "fff" }};
-    border-bottom-color: #{{ doc.top_bar_foreground or "fff" }};	
+    border-top-color: #{{ doc.top_bar_foreground or "fffffff" }};
+    border-bottom-color: #{{ doc.top_bar_foreground or "fffffff" }};	
   }
 
   .navbar-inverse .nav li.dropdown > .dropdown-toggle:hover .caret {
-    border-top-color: #{{ doc.top_bar_background or "000" }};
-    border-bottom-color: #{{ doc.top_bar_background or "000" }};
+    border-top-color: #{{ doc.top_bar_background or "0000000" }};
+    border-bottom-color: #{{ doc.top_bar_background or "0000000" }};
   }
 
   .navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
   .navbar-inverse .nav li.dropdown.open > .dropdown-toggle:hover .caret {
-    border-top-color: #{{ doc.top_bar_background or "000" }};
-    border-bottom-color: #{{ doc.top_bar_background or "000" }};	
+    border-top-color: #{{ doc.top_bar_background or "0000000" }};
+    border-bottom-color: #{{ doc.top_bar_background or "0000000" }};	
   }
 
 }
+
+.breadcrumb {
+	background-color: #{{ get_hex_shade(doc.page_background or "ffffff", 5) }};
+}
+
+.table-striped tbody > tr:nth-child(odd) > td,
+.table-striped tbody > tr:nth-child(odd) > th {
+	background-color: #{{ get_hex_shade(doc.page_background or "ffffff", 5) }};
+}
+
+.table-hover tbody tr:hover td,
+.table-hover tbody tr:hover th {
+	background-color: #{{ get_hex_shade(doc.page_background or "ffffff", 10) }};
+}
+
+.table-bordered {
+	border: 1px solid #{{ get_hex_shade(doc.page_background or "ffffff", 15) }};
+}
+
+.table th,
+.table td {
+  border-top: 1px solid #{{ get_hex_shade(doc.page_background or "ffffff", 15) }};
+}
+
+.table-bordered th,
+.table-bordered td {
+	border-left: 1px solid #{{ get_hex_shade(doc.page_background or "ffffff", 15) }};
+}
+
+
+
+.hero-unit {
+	background-color: #{{ get_hex_shade(doc.page_background or "ffffff", 15) }};
+}
+
+pre, code {
+	background-color: #{{ get_hex_shade(doc.page_background or "ffffff", 5) }};
+}
+
+hr {
+	border-top: 1px solid #{{ get_hex_shade(doc.page_background or "ffffff", 15) }};
+	border-bottom: 1px solid #{{ get_hex_shade(doc.page_background or "ffffff", 5) }};	
+}
diff --git a/website/doctype/style_settings/style_settings.py b/website/doctype/style_settings/style_settings.py
index 91ca0a5..1cc3467 100644
--- a/website/doctype/style_settings/style_settings.py
+++ b/website/doctype/style_settings/style_settings.py
@@ -27,6 +27,7 @@
 	def validate(self):
 		"""make custom css"""
 		from jinja2 import Template
+		from website.utils import get_hex_shade
 		import os
 		
 		self.validate_colors()
@@ -38,7 +39,7 @@
 		
 		self.prepare()
 		
-		self.doc.custom_css = temp.render(doc = self.doc)
+		self.doc.custom_css = temp.render(doc = self.doc, get_hex_shade=get_hex_shade)
 		if self.doc.add_css:
 			self.doc.custom_css += '\n\n/* User CSS */\n\n' + self.doc.add_css
 		
diff --git a/website/templates/pages/about.html b/website/templates/pages/about.html
index 28fb1f6..380c543 100644
--- a/website/templates/pages/about.html
+++ b/website/templates/pages/about.html
@@ -9,7 +9,7 @@
 	<h3>{{ obj.doc.company_history_heading or "Company History" }}</h3>
 	{% for d in obj.doclist.get({"doctype":"Company History"}) %}
 	<div class="row">
-		<span class="span2" style="text-align: right"><h4 style="margin:0px;">{{ d.year }}</h4></span>
+		<span class="span2"><h4 style="margin:0px;">{{ d.year }}</h4></span>
 		<span class="span10"><p>{{ d.highlight }}</p></span>
 	</div>
 	{% endfor %}
@@ -20,11 +20,11 @@
 	<div class="row" itemscope itemtype="http://schema.org/Person">
 		<span class="span2">
 			<div class="avatar avatar-large">
-				<img class="avatar" src="{{ d.image }}" style="" itemprop="image">
+				<img class="avatar" src="{{ d.image_link }}" style="" itemprop="image">
 			</div>
 		</span>
 		<span class="span10"><h4 itemprop="name">{{ d.full_name }}</h4>
-			<div itemprop="description">{{ d.bio }}</div>
+			<p itemprop="description">{{ d.bio }}</p>
 		</span>
 	</div>
 	{% endfor %}
diff --git a/website/utils.py b/website/utils.py
index 0f62d26..b38d2b3 100644
--- a/website/utils.py
+++ b/website/utils.py
@@ -302,4 +302,27 @@
 	if url and not url.lower().startswith("http"):
 		return "files/" + url
 	else:
-		return url
\ No newline at end of file
+		return url
+		
+def get_hex_shade(color, percent):
+	# switch dark and light shades
+	if int(color, 16) > int("808080", 16):
+		percent = -percent
+		
+	# stronger diff for darker shades
+	if int(color, 16) < int("333333", 16):
+		percent = percent * 2
+	
+	def p(c):
+		v = int(c, 16) + int(int('ff', 16) * (float(percent)/100))
+		if v < 0: 
+			v=0
+		if v > 255: 
+			v=255
+		h = hex(v)[2:]
+		if len(h) < 2:
+			h = "0" + h
+		return h
+		
+	r, g, b = color[0:2], color[2:4], color[4:6]
+	return p(r) + p(g) + p(b)
\ No newline at end of file