diff --git a/accounts/page/accounts_home/accounts_home.html b/accounts/page/accounts_home/accounts_home.html
index 498f811732..498b214587 100644
--- a/accounts/page/accounts_home/accounts_home.html
+++ b/accounts/page/accounts_home/accounts_home.html
@@ -18,7 +18,11 @@
Structure cost centers
-
+
+
+
+ Reports
+
General Ledger
@@ -29,16 +33,16 @@
data-role="Analytics, Accounts Manager, Accounts User">Trial Balance
Tree view of all Account balances
-
+
+
Visual representation of financial trends
-
+
- Reports
diff --git a/buying/doctype/purchase_order/purchase_order.py b/buying/doctype/purchase_order/purchase_order.py
index 85a11ed813..f589b67503 100644
--- a/buying/doctype/purchase_order/purchase_order.py
+++ b/buying/doctype/purchase_order/purchase_order.py
@@ -128,9 +128,14 @@ class DocType(TransactionBase):
# Validate
def validate(self):
self.validate_fiscal_year()
- # Step 1:=> set status as "Draft"
- webnotes.conn.set(self.doc, 'status', 'Draft')
-
+
+ if not self.doc.status:
+ self.doc.status = "Draft"
+
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped",
+ "Cancelled"])
+
# Step 2:=> get Purchase Common Obj
pc_obj = get_obj(dt='Purchase Common')
diff --git a/buying/doctype/purchase_request/purchase_request.py b/buying/doctype/purchase_request/purchase_request.py
index 3f7f932c25..0a7ae19812 100644
--- a/buying/doctype/purchase_request/purchase_request.py
+++ b/buying/doctype/purchase_request/purchase_request.py
@@ -141,8 +141,12 @@ class DocType:
self.validate_schedule_date()
self.validate_fiscal_year()
- # set status as "Draft"
- webnotes.conn.set(self.doc, 'status', 'Draft')
+ if not self.doc.status:
+ self.doc.status = "Draft"
+
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped",
+ "Cancelled"])
# Get Purchase Common Obj
pc_obj = get_obj(dt='Purchase Common')
diff --git a/buying/doctype/supplier_quotation/supplier_quotation.py b/buying/doctype/supplier_quotation/supplier_quotation.py
index cac4bab5f3..9e62e1303b 100644
--- a/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -30,10 +30,16 @@ class DocType(TransactionBase):
self.doc.name = make_autoname(self.doc.naming_series + ".#####")
def validate(self):
+ if not self.doc.status:
+ self.doc.status = "Draft"
+
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped",
+ "Cancelled"])
+
self.validate_fiscal_year()
self.validate_common()
self.set_in_words()
- self.doc.status = "Draft"
def on_submit(self):
purchase_controller = webnotes.get_obj("Purchase Common")
diff --git a/buying/page/buying_home/buying_home.html b/buying/page/buying_home/buying_home.html
index 68544fb3d3..785abd5ac8 100644
--- a/buying/page/buying_home/buying_home.html
+++ b/buying/page/buying_home/buying_home.html
@@ -23,15 +23,20 @@
Address Master
-
+
+
+
+ Reports
+
Purchase trends based on Purchase Invoice
-
+
+
+
- Reports
diff --git a/home/page/latest_updates/latest_updates.js b/home/page/latest_updates/latest_updates.js
index b9296f2424..304947e06f 100644
--- a/home/page/latest_updates/latest_updates.js
+++ b/home/page/latest_updates/latest_updates.js
@@ -1,7 +1,9 @@
erpnext.updates = [
["28th December 2012", [
"Workflow: Added System for Multi-level approval before Submission. \
-
See video at
https://www.youtube.com/watch?v=zuGv59_wJKw.",
+
See video at
https://www.youtube.com/watch?v=zuGv59_wJKw.",
+ "Stock Level Report: New report to see available and estimated qty of stock",
]],
["27th December 2012", [
"Website: Added auto-generated Contact Us and About Us Pages",
diff --git a/hr/doctype/appraisal/appraisal.py b/hr/doctype/appraisal/appraisal.py
index b4b0e4e8c5..82fb77b2f6 100644
--- a/hr/doctype/appraisal/appraisal.py
+++ b/hr/doctype/appraisal/appraisal.py
@@ -57,6 +57,7 @@ class DocType:
def validate(self):
if not self.doc.status:
self.doc.status = "Draft"
+
self.validate_dates()
self.validate_existing_appraisal()
self.calculate_total()
diff --git a/hr/doctype/attendance/attendance.py b/hr/doctype/attendance/attendance.py
index d3daeb1dfc..d1ebc97333 100644
--- a/hr/doctype/attendance/attendance.py
+++ b/hr/doctype/attendance/attendance.py
@@ -8,11 +8,11 @@
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
.
+# along with this program. If not, see
.
from __future__ import unicode_literals
import webnotes
@@ -28,79 +28,81 @@ sql = webnotes.conn.sql
class DocType:
- def __init__(self, doc, doclist=[]):
- self.doc = doc
- self.doclist = doclist
-
- #autoname function
- def autoname(self):
- self.doc.name = make_autoname(self.doc.naming_series+'.#####')
-
- #get employee name based on employee id selected
- def get_emp_name(self):
- emp_nm = sql("select employee_name from `tabEmployee` where name=%s", self.doc.employee)
+ def __init__(self, doc, doclist=[]):
+ self.doc = doc
+ self.doclist = doclist
+
+ #autoname function
+ def autoname(self):
+ self.doc.name = make_autoname(self.doc.naming_series+'.#####')
+
+ #get employee name based on employee id selected
+ def get_emp_name(self):
+ emp_nm = sql("select employee_name from `tabEmployee` where name=%s", self.doc.employee)
- #this is done because sometimes user entered wrong employee name while uploading employee attendance
- webnotes.conn.set(self.doc, 'employee_name', emp_nm and emp_nm[0][0] or '')
+ #this is done because sometimes user entered wrong employee name while uploading employee attendance
+ webnotes.conn.set(self.doc, 'employee_name', emp_nm and emp_nm[0][0] or '')
- ret = { 'employee_name' : emp_nm and emp_nm[0][0] or ''}
- return ret
-
- #validation for duplicate record
- def validate_duplicate_record(self):
- res = sql("select name from `tabAttendance` where employee = '%s' and att_date = '%s' and not name = '%s' and docstatus = 1"%(self.doc.employee,self.doc.att_date, self.doc.name))
- if res:
- msgprint("Employee's attendance already marked.")
- raise Exception
-
-
- #check for already record present in leave transaction for same date
- def check_leave_record(self):
- if self.doc.status == 'Present':
- chk = sql("select name from `tabLeave Application` where employee=%s and (from_date <= %s and to_date >= %s) and docstatus!=2", (self.doc.employee, self.doc.att_date, self.doc.att_date))
- if chk:
- msgprint("Leave Application created for employee "+self.doc.employee+" whom you are trying to mark as 'Present' ")
- raise Exception
-
-
- def validate_fiscal_year(self):
- fy=sql("select year_start_date from `tabFiscal Year` where name='%s'"% self.doc.fiscal_year)
- ysd=fy and fy[0][0] or ""
- yed=add_days(str(ysd),365)
- if str(self.doc.att_date) < str(ysd) or str(self.doc.att_date) > str(yed):
- msgprint("'%s' Not Within The Fiscal Year selected"%(self.doc.att_date))
- raise Exception
-
- def validate_att_date(self):
- import datetime
- if getdate(self.doc.att_date)>getdate(datetime.datetime.now().date().strftime('%Y-%m-%d')):
- msgprint("Attendance can not be marked for future dates")
- raise Exception
+ ret = { 'employee_name' : emp_nm and emp_nm[0][0] or ''}
+ return ret
+
+ #validation for duplicate record
+ def validate_duplicate_record(self):
+ res = sql("select name from `tabAttendance` where employee = '%s' and att_date = '%s' and not name = '%s' and docstatus = 1"%(self.doc.employee,self.doc.att_date, self.doc.name))
+ if res:
+ msgprint("Employee's attendance already marked.")
+ raise Exception
+
+
+ #check for already record present in leave transaction for same date
+ def check_leave_record(self):
+ if self.doc.status == 'Present':
+ chk = sql("select name from `tabLeave Application` where employee=%s and (from_date <= %s and to_date >= %s) and docstatus!=2", (self.doc.employee, self.doc.att_date, self.doc.att_date))
+ if chk:
+ msgprint("Leave Application created for employee "+self.doc.employee+" whom you are trying to mark as 'Present' ")
+ raise Exception
+
+
+ def validate_fiscal_year(self):
+ fy=sql("select year_start_date from `tabFiscal Year` where name='%s'" % \
+ self.doc.fiscal_year)
+ ysd=fy and fy[0][0] or ""
+ yed=add_days(str(ysd),365)
+ if str(self.doc.att_date) < str(ysd) or str(self.doc.att_date) > str(yed):
+ msgprint("'%s' Not Within The Fiscal Year selected"%(self.doc.att_date))
+ raise Exception
+
+ def validate_att_date(self):
+ import datetime
+ if getdate(self.doc.att_date)>getdate(datetime.datetime.now().date().strftime('%Y-%m-%d')):
+ msgprint("Attendance can not be marked for future dates")
+ raise Exception
- # Validate employee
- #-------------------
- def validate_employee(self):
- emp = sql("select name, status from `tabEmployee` where name = '%s'" % self.doc.employee)
- if not emp:
- msgprint("Employee: %s does not exists in the system" % self.doc.employee, raise_exception=1)
- elif emp[0][1] != 'Active':
- msgprint("Employee: %s is not Active" % self.doc.employee, raise_exception=1)
-
- # validate...
- def validate(self):
- self.validate_fiscal_year()
- self.validate_att_date()
- self.validate_duplicate_record()
- #self.validate_status()
- self.check_leave_record()
-
- def on_update(self):
- #self.validate()
-
- #this is done because sometimes user entered wrong employee name while uploading employee attendance
- x=self.get_emp_name()
+ # Validate employee
+ #-------------------
+ def validate_employee(self):
+ emp = sql("select name, status from `tabEmployee` where name = '%s'" % self.doc.employee)
+ if not emp:
+ msgprint("Employee: %s does not exists in the system" % self.doc.employee, raise_exception=1)
+ elif emp[0][1] != 'Active':
+ msgprint("Employee: %s is not Active" % self.doc.employee, raise_exception=1)
+
+ def validate(self):
+ import utilities
+ utilities.validate_status(self.doc.status, ["Present", "Absent", "Half Day"])
- def on_submit(self):
- #this is done because while uploading attendance chnage docstatus to 1 i.e. submit
- webnotes.conn.set(self.doc,'docstatus',1)
- pass
+ self.validate_fiscal_year()
+ self.validate_att_date()
+ self.validate_duplicate_record()
+ self.check_leave_record()
+
+ def on_update(self):
+ #self.validate()
+
+ #this is done because sometimes user entered wrong employee name while uploading employee attendance
+ x=self.get_emp_name()
+
+ def on_submit(self):
+ #this is done because while uploading attendance chnage docstatus to 1 i.e. submit
+ webnotes.conn.set(self.doc,'docstatus',1)
+ pass
diff --git a/hr/doctype/employee/employee.py b/hr/doctype/employee/employee.py
index c0f7be3659..ab3f842d22 100644
--- a/hr/doctype/employee/employee.py
+++ b/hr/doctype/employee/employee.py
@@ -19,7 +19,7 @@ import webnotes
from webnotes.utils import getdate, validate_email_add
from webnotes.model.doc import make_autoname
-from webnotes import msgprint
+from webnotes import msgprint, _
sql = webnotes.conn.sql
@@ -39,6 +39,16 @@ class DocType:
self.doc.name = make_autoname(self.doc.employee_number)
self.doc.employee = self.doc.name
+
+ def validate(self):
+ import utilities
+ utilities.validate_status(self.doc.status, ["Active", "Left"])
+
+ self.doc.employee = self.doc.name
+ self.validate_date()
+ self.validate_email()
+ self.validate_name()
+ self.validate_status()
def get_retirement_date(self):
import datetime
@@ -52,13 +62,6 @@ class DocType:
ret_sal_struct=sql("select name from `tabSalary Structure` where employee='%s' and is_active = 'Yes' and docstatus!= 2"%nm)
return ret_sal_struct and ret_sal_struct[0][0] or ''
- def validate(self):
- self.doc.employee = self.doc.name
- self.validate_date()
- self.validate_email()
- self.validate_name()
- self.validate_status()
-
def on_update(self):
self.update_user_default()
diff --git a/hr/doctype/expense_claim/expense_claim.py b/hr/doctype/expense_claim/expense_claim.py
index b3a132ed45..2ba53aa5ed 100644
--- a/hr/doctype/expense_claim/expense_claim.py
+++ b/hr/doctype/expense_claim/expense_claim.py
@@ -33,9 +33,8 @@ class DocType:
# if self.doc.exp_approver == self.doc.owner:
# webnotes.msgprint("""Self Approval is not allowed.""", raise_exception=1)
- if self.doc.status not in ("Draft", "Approved", "Rejected"):
- webnotes.msgprint("Status must be one of 'Draft', 'Approved' or 'Rejected'",
- raise_exception=True)
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Approved", "Rejected"])
self.validate_fiscal_year()
self.validate_exp_details()
diff --git a/hr/doctype/leave_application/leave_application.py b/hr/doctype/leave_application/leave_application.py
index e61f7b5137..ebefc0eb15 100755
--- a/hr/doctype/leave_application/leave_application.py
+++ b/hr/doctype/leave_application/leave_application.py
@@ -33,9 +33,8 @@ class DocType:
def validate(self):
# if self.doc.leave_approver == self.doc.owner:
# webnotes.msgprint("""Self Approval is not allowed.""", raise_exception=1)
- if self.doc.status not in ("Open", "Approved", "Rejected"):
- webnotes.msgprint("Status must be one of 'Open', 'Approved' or 'Rejected'",
- raise_exception=True)
+ import utilities
+ utilities.validate_status(self.doc.status, ["Open", "Approved", "Rejected"])
self.validate_to_date()
self.validate_balance_leaves()
diff --git a/hr/doctype/salary_structure/salary_structure.py b/hr/doctype/salary_structure/salary_structure.py
index f310b7b4c8..9c5ad246ef 100644
--- a/hr/doctype/salary_structure/salary_structure.py
+++ b/hr/doctype/salary_structure/salary_structure.py
@@ -8,11 +8,11 @@
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
.
+# along with this program. If not, see
.
from __future__ import unicode_literals
import webnotes
@@ -28,87 +28,86 @@ sql = webnotes.conn.sql
class DocType:
- #init function
- def __init__(self,doc,doclist=[]):
- self.doc = doc
- self.doclist = doclist
+ #init function
+ def __init__(self,doc,doclist=[]):
+ self.doc = doc
+ self.doclist = doclist
- #autoname function
- #---------------------------------------------------------
- def autoname(self):
- self.doc.name = make_autoname(self.doc.employee + '/.SST' + '/.#####')
-
- #get employee details
- #---------------------------------------------------------
- def get_employee_details(self):
- ret = {}
- det = sql("select employee_name, branch, designation, department, grade from `tabEmployee` where name = '%s'" %self.doc.employee)
- if det:
- ret = {
- 'employee_name' : cstr(det[0][0]),
- 'branch' : cstr(det[0][1]),
- 'designation' : cstr(det[0][2]),
- 'department' : cstr(det[0][3]),
- 'grade' : cstr(det[0][4]),
- 'backup_employee': cstr(self.doc.employee)
- }
- return ret
-
+ #autoname function
+ #---------------------------------------------------------
+ def autoname(self):
+ self.doc.name = make_autoname(self.doc.employee + '/.SST' + '/.#####')
+
+ #get employee details
+ #---------------------------------------------------------
+ def get_employee_details(self):
+ ret = {}
+ det = sql("select employee_name, branch, designation, department, grade from `tabEmployee` where name = '%s'" %self.doc.employee)
+ if det:
+ ret = {
+ 'employee_name': cstr(det[0][0]),
+ 'branch': cstr(det[0][1]),
+ 'designation': cstr(det[0][2]),
+ 'department': cstr(det[0][3]),
+ 'grade': cstr(det[0][4]),
+ 'backup_employee': cstr(self.doc.employee)
+ }
+ return ret
+
- # Set Salary structure field values
- #---------------------------------------------------------
- def get_ss_values(self,employee):
- basic_info = sql("select bank_name, bank_ac_no, esic_card_no, pf_number from `tabEmployee` where name ='%s'" % employee)
- ret = {'bank_name' : basic_info and basic_info[0][0] or '',
- 'bank_ac_no' : basic_info and basic_info[0][1] or '',
- 'esic_no' : basic_info and basic_info[0][2] or '',
- 'pf_no' : basic_info and basic_info[0][3] or ''}
- return ret
-
- # Make earning and deduction table
- #---------------------------------------------------------
- def make_table(self, doct_name, tab_fname, tab_name):
- list1 = sql("select name from `tab%s` where docstatus != 2" % doct_name)
- for li in list1:
- child = addchild(self.doc, tab_fname, tab_name, self.doclist)
- if(tab_fname == 'earning_details'):
- child.e_type = cstr(li[0])
- child.modified_value = 0
- elif(tab_fname == 'deduction_details'):
- child.d_type = cstr(li[0])
- child.d_modified_amt = 0
-
- # add earning & deduction types to table
- #---------------------------------------------------------
- def make_earn_ded_table(self):
- #Earning List
- self.make_table('Earning Type','earning_details','Salary Structure Earning')
-
- #Deduction List
- self.make_table('Deduction Type','deduction_details', 'Salary Structure Deduction')
-
+ # Set Salary structure field values
+ #---------------------------------------------------------
+ def get_ss_values(self,employee):
+ basic_info = sql("select bank_name, bank_ac_no, esic_card_no, pf_number from `tabEmployee` where name ='%s'" % employee)
+ ret = {'bank_name': basic_info and basic_info[0][0] or '',
+ 'bank_ac_no': basic_info and basic_info[0][1] or '',
+ 'esic_no': basic_info and basic_info[0][2] or '',
+ 'pf_no': basic_info and basic_info[0][3] or ''}
+ return ret
+
+ # Make earning and deduction table
+ #---------------------------------------------------------
+ def make_table(self, doct_name, tab_fname, tab_name):
+ list1 = sql("select name from `tab%s` where docstatus != 2" % doct_name)
+ for li in list1:
+ child = addchild(self.doc, tab_fname, tab_name, self.doclist)
+ if(tab_fname == 'earning_details'):
+ child.e_type = cstr(li[0])
+ child.modified_value = 0
+ elif(tab_fname == 'deduction_details'):
+ child.d_type = cstr(li[0])
+ child.d_modified_amt = 0
+
+ # add earning & deduction types to table
+ #---------------------------------------------------------
+ def make_earn_ded_table(self):
+ #Earning List
+ self.make_table('Earning Type','earning_details','Salary Structure Earning')
+
+ #Deduction List
+ self.make_table('Deduction Type','deduction_details',
+ 'Salary Structure Deduction')
+
- # Check if another active ss exists
- #---------------------------------------------------------
- def check_existing(self):
- ret = sql("select name from `tabSalary Structure` where is_active = 'Yes' and employee = '%s' and name!='%s'" %(self.doc.employee,self.doc.name))
- if ret and self.doc.is_active=='Yes':
- msgprint("Another Salary Structure '%s' is active for employee '%s'. Please make its status 'Inactive' to proceed."%(cstr(ret), self.doc.employee))
- raise Exception
+ # Check if another active ss exists
+ #---------------------------------------------------------
+ def check_existing(self):
+ ret = sql("select name from `tabSalary Structure` where is_active = 'Yes' and employee = '%s' and name!='%s'" %(self.doc.employee,self.doc.name))
+ if ret and self.doc.is_active=='Yes':
+ msgprint("Another Salary Structure '%s' is active for employee '%s'. Please make its status 'Inactive' to proceed."%(cstr(ret), self.doc.employee))
+ raise Exception
- # Validate net pay
- #---------------------------------------------------------
- def validate_net_pay(self):
- if flt(self.doc.net_pay) < 0:
- msgprint("Net pay can not be negative")
- raise Exception
- elif flt(self.doc.net_pay) > flt(self.doc.ctc):
- msgprint("Net pay can not be greater than CTC")
- raise Exception
+ # Validate net pay
+ #---------------------------------------------------------
+ def validate_net_pay(self):
+ if flt(self.doc.net_pay) < 0:
+ msgprint("Net pay can not be negative")
+ raise Exception
+ elif flt(self.doc.net_pay) > flt(self.doc.ctc):
+ msgprint("Net pay can not be greater than CTC")
+ raise Exception
- # Validate
- #---------------------------------------------------------
- def validate(self):
- self.check_existing()
- self.validate_net_pay()
+ def validate(self):
+ self.check_existing()
+ self.validate_net_pay()
diff --git a/hr/page/hr_home/hr_home.html b/hr/page/hr_home/hr_home.html
index b7c91bb58a..9d6875a994 100644
--- a/hr/page/hr_home/hr_home.html
+++ b/hr/page/hr_home/hr_home.html
@@ -23,7 +23,7 @@
- Reports
+ Reports
diff --git a/manufacturing/doctype/production_order/production_order.py b/manufacturing/doctype/production_order/production_order.py
index 3ba368efca..9a09494b92 100644
--- a/manufacturing/doctype/production_order/production_order.py
+++ b/manufacturing/doctype/production_order/production_order.py
@@ -34,6 +34,10 @@ class DocType:
self.doclist = doclist
def validate(self):
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped",
+ "In Process", "Completed", "Cancelled"])
+
if self.doc.production_item :
item_detail = sql("select name from `tabItem` where name = '%s' and docstatus != 2"
% self.doc.production_item, as_dict = 1)
diff --git a/manufacturing/page/manufacturing_home/manufacturing_home.html b/manufacturing/page/manufacturing_home/manufacturing_home.html
index 201eaeb618..737a7c1b11 100644
--- a/manufacturing/page/manufacturing_home/manufacturing_home.html
+++ b/manufacturing/page/manufacturing_home/manufacturing_home.html
@@ -17,7 +17,7 @@
- Reports
+ Reports
diff --git a/projects/page/projects_home/projects_home.html b/projects/page/projects_home/projects_home.html
index 5cd48a0aaf..cb4f1f8aba 100644
--- a/projects/page/projects_home/projects_home.html
+++ b/projects/page/projects_home/projects_home.html
@@ -17,7 +17,7 @@
- Reports
+ Reports
diff --git a/public/js/stock_analytics.js b/public/js/stock_analytics.js
index 35b3f563e1..53641ce996 100644
--- a/public/js/stock_analytics.js
+++ b/public/js/stock_analytics.js
@@ -70,7 +70,7 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({
{fieldtype:"Select", label: "Brand", link:"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",
default_value: "Select Warehouse..."},
{fieldtype:"Date", label: "From Date"},
diff --git a/selling/doctype/installation_note/installation_note.py b/selling/doctype/installation_note/installation_note.py
index 1b8590b1f5..5a997fdc04 100644
--- a/selling/doctype/installation_note/installation_note.py
+++ b/selling/doctype/installation_note/installation_note.py
@@ -8,11 +8,11 @@
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
.
+# along with this program. If not, see
.
from __future__ import unicode_literals
import webnotes
@@ -30,178 +30,176 @@ sql = webnotes.conn.sql
from utilities.transaction_base import TransactionBase
class DocType(TransactionBase):
- def __init__(self, doc, doclist=[]):
- self.doc = doc
- self.doclist = doclist
- self.tname = 'Installation Note Item'
- self.fname = 'installed_item_details'
+ def __init__(self, doc, doclist=[]):
+ self.doc = doc
+ self.doclist = doclist
+ self.tname = 'Installation Note Item'
+ self.fname = 'installed_item_details'
- # Autoname
- # ---------
- def autoname(self):
- self.doc.name = make_autoname(self.doc.naming_series+'.#####')
+ def autoname(self):
+ self.doc.name = make_autoname(self.doc.naming_series+'.#####')
+
+ def validate(self):
+ self.validate_fiscal_year()
+ self.validate_installation_date()
+ self.check_item_table()
+ sales_com_obj = get_obj(dt = 'Sales Common')
+ sales_com_obj.check_active_sales_items(self)
+ sales_com_obj.get_prevdoc_date(self)
+ self.validate_mandatory()
+ self.validate_reference_value()
-
- #fetch delivery note details
- #====================================
- def pull_delivery_note_details(self):
- self.validate_prev_docname()
- self.doclist = get_obj('DocType Mapper', 'Delivery Note-Installation Note').dt_map('Delivery Note', 'Installation Note', self.doc.delivery_note_no, self.doc, self.doclist, "[['Delivery Note', 'Installation Note'],['Delivery Note Item', 'Installation Note Item']]")
-
- # Validates that Delivery Note is not pulled twice
- #============================================
- def validate_prev_docname(self):
- for d in getlist(self.doclist, 'installed_item_details'):
- if self.doc.delivery_note_no == d.prevdoc_docname:
- msgprint(cstr(self.doc.delivery_note_no) + " delivery note details have already been pulled. ")
- raise Exception, "Validation Error. "
-
- #Fiscal Year Validation
- #================================
- def validate_fiscal_year(self):
- get_obj('Sales Common').validate_fiscal_year(self.doc.fiscal_year,self.doc.inst_date,'Installation Date')
-
- # Validate Mandatory
- #===============================
- def validate_mandatory(self):
- # Amendment Date
- if self.doc.amended_from and not self.doc.amendment_date:
- msgprint("Please Enter Amendment Date")
- raise Exception, "Validation Error. "
-
- # Validate values with reference document
- #----------------------------------------
- def validate_reference_value(self):
- get_obj('DocType Mapper', 'Delivery Note-Installation Note', with_children = 1).validate_reference_value(self, self.doc.name)
-
- #check if serial no added
- #-----------------------------
- def is_serial_no_added(self,item_code,serial_no):
- ar_required = sql("select has_serial_no from tabItem where name = '%s'" % item_code)
- ar_required = ar_required and ar_required[0][0] or ''
- if ar_required == 'Yes' and not serial_no:
- msgprint("Serial No is mandatory for item: "+ item_code)
- raise Exception
- elif ar_required != 'Yes' and cstr(serial_no).strip():
- msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :"+item_code)
- raise Exception
-
- #check if serial no exist in system
- #-------------------------------------
- def is_serial_no_exist(self, item_code, serial_no):
- for x in serial_no:
- chk = sql("select name from `tabSerial No` where name =%s", x)
- if not chk:
- msgprint("Serial No "+x+" does not exist in the system")
- raise Exception
-
- #check if serial no already installed
- #------------------------------------------
- def is_serial_no_installed(self,cur_s_no,item_code):
- for x in cur_s_no:
- status = sql("select status from `tabSerial No` where name = %s", x)
- status = status and status[0][0] or ''
-
- if status == 'Installed':
- msgprint("Item "+item_code+" with serial no. "+x+" already installed")
- raise Exception, "Validation Error."
-
- #get list of serial no from previous_doc
- #----------------------------------------------
- def get_prevdoc_serial_no(self, prevdoc_detail_docname, prevdoc_docname):
- from stock.doctype.stock_ledger.stock_ledger import get_sr_no_list
- res = sql("select serial_no from `tabDelivery Note Item` where name = '%s' and parent ='%s'" % (prevdoc_detail_docname, prevdoc_docname))
- return get_sr_no_list(res[0][0])
-
- #check if all serial nos from current record exist in resp delivery note
- #---------------------------------------------------------------------------------
- def is_serial_no_match(self, cur_s_no, prevdoc_s_no, prevdoc_docname):
- for x in cur_s_no:
- if not(x in prevdoc_s_no):
- msgprint("Serial No. "+x+" not present in the Delivery Note "+prevdoc_docname, raise_exception = 1)
- raise Exception, "Validation Error."
-
- #validate serial number
- #----------------------------------------
- def validate_serial_no(self):
- cur_s_no, prevdoc_s_no, sr_list = [], [], []
- from stock.doctype.stock_ledger.stock_ledger import get_sr_no_list
-
- for d in getlist(self.doclist, 'installed_item_details'):
- self.is_serial_no_added(d.item_code, d.serial_no)
-
- if d.serial_no:
+ #fetch delivery note details
+ #====================================
+ def pull_delivery_note_details(self):
+ self.validate_prev_docname()
+ self.doclist = get_obj('DocType Mapper', 'Delivery Note-Installation Note').dt_map('Delivery Note', 'Installation Note', self.doc.delivery_note_no, self.doc, self.doclist, "[['Delivery Note', 'Installation Note'],['Delivery Note Item', 'Installation Note Item']]")
+
+ # Validates that Delivery Note is not pulled twice
+ #============================================
+ def validate_prev_docname(self):
+ for d in getlist(self.doclist, 'installed_item_details'):
+ if self.doc.delivery_note_no == d.prevdoc_docname:
+ msgprint(cstr(self.doc.delivery_note_no) + " delivery note details have already been pulled. ")
+ raise Exception, "Validation Error. "
+
+ #Fiscal Year Validation
+ #================================
+ def validate_fiscal_year(self):
+ get_obj('Sales Common').validate_fiscal_year(self.doc.fiscal_year,self.doc.inst_date,'Installation Date')
+
+ # Validate Mandatory
+ #===============================
+ def validate_mandatory(self):
+ # Amendment Date
+ if self.doc.amended_from and not self.doc.amendment_date:
+ msgprint("Please Enter Amendment Date")
+ raise Exception, "Validation Error. "
+
+ # Validate values with reference document
+ #----------------------------------------
+ def validate_reference_value(self):
+ get_obj('DocType Mapper', 'Delivery Note-Installation Note', with_children = 1).validate_reference_value(self, self.doc.name)
+
+ #check if serial no added
+ #-----------------------------
+ def is_serial_no_added(self,item_code,serial_no):
+ ar_required = sql("select has_serial_no from tabItem where name = '%s'" % item_code)
+ ar_required = ar_required and ar_required[0][0] or ''
+ if ar_required == 'Yes' and not serial_no:
+ msgprint("Serial No is mandatory for item: "+ item_code)
+ raise Exception
+ elif ar_required != 'Yes' and cstr(serial_no).strip():
+ msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :"+item_code)
+ raise Exception
+
+ #check if serial no exist in system
+ #-------------------------------------
+ def is_serial_no_exist(self, item_code, serial_no):
+ for x in serial_no:
+ chk = sql("select name from `tabSerial No` where name =%s", x)
+ if not chk:
+ msgprint("Serial No "+x+" does not exist in the system")
+ raise Exception
+
+ #check if serial no already installed
+ #------------------------------------------
+ def is_serial_no_installed(self,cur_s_no,item_code):
+ for x in cur_s_no:
+ status = sql("select status from `tabSerial No` where name = %s", x)
+ status = status and status[0][0] or ''
+
+ if status == 'Installed':
+ msgprint("Item "+item_code+" with serial no. "+x+" already installed")
+ raise Exception, "Validation Error."
+
+ #get list of serial no from previous_doc
+ #----------------------------------------------
+ def get_prevdoc_serial_no(self, prevdoc_detail_docname, prevdoc_docname):
+ from stock.doctype.stock_ledger.stock_ledger import get_sr_no_list
+
+ res = sql("select serial_no from `tabDelivery Note Item` where name = '%s' and parent ='%s'" % (prevdoc_detail_docname, prevdoc_docname))
+ return get_sr_no_list(res[0][0])
+
+ #check if all serial nos from current record exist in resp delivery note
+ #---------------------------------------------------------------------------------
+ def is_serial_no_match(self, cur_s_no, prevdoc_s_no, prevdoc_docname):
+ for x in cur_s_no:
+ if not(x in prevdoc_s_no):
+ msgprint("Serial No. "+x+" not present in the Delivery Note "+prevdoc_docname, raise_exception = 1)
+ raise Exception, "Validation Error."
+
+ #validate serial number
+ #----------------------------------------
+ def validate_serial_no(self):
+ cur_s_no, prevdoc_s_no, sr_list = [], [], []
+ from stock.doctype.stock_ledger.stock_ledger import get_sr_no_list
+
+ for d in getlist(self.doclist, 'installed_item_details'):
+ self.is_serial_no_added(d.item_code, d.serial_no)
+
+ if d.serial_no:
- sr_list = get_sr_no_list(d.serial_no, d.qty, d.item_code)
- self.is_serial_no_exist(d.item_code, sr_list)
-
- prevdoc_s_no = self.get_prevdoc_serial_no(d.prevdoc_detail_docname, d.prevdoc_docname)
- if prevdoc_s_no:
- self.is_serial_no_match(sr_list, prevdoc_s_no, d.prevdoc_docname)
-
- self.is_serial_no_installed(sr_list, d.item_code)
- return sr_list
-
- #validate installation date
- #-------------------------------
- def validate_installation_date(self):
- for d in getlist(self.doclist, 'installed_item_details'):
- if d.prevdoc_docname:
- d_date = sql("select posting_date from `tabDelivery Note` where name=%s", d.prevdoc_docname)
- d_date = d_date and d_date[0][0] or ''
-
- if d_date > getdate(self.doc.inst_date):
- msgprint("Installation Date can not be before Delivery Date "+cstr(d_date)+" for item "+d.item_code)
- raise Exception
-
- def validate(self):
- self.validate_fiscal_year()
- self.validate_installation_date()
- self.check_item_table()
- sales_com_obj = get_obj(dt = 'Sales Common')
- sales_com_obj.check_active_sales_items(self)
- sales_com_obj.get_prevdoc_date(self)
- self.validate_mandatory()
- self.validate_reference_value()
-
- def check_item_table(self):
- if not(getlist(self.doclist, 'installed_item_details')):
- msgprint("Please fetch items from Delivery Note selected")
- raise Exception
-
- def on_update(self):
- webnotes.conn.set(self.doc, 'status', 'Draft')
-
- def on_submit(self):
- valid_lst = []
- valid_lst = self.validate_serial_no()
-
- get_obj("Sales Common").update_prevdoc_detail(1,self)
-
- for x in valid_lst:
- wp = sql("select warranty_period from `tabSerial No` where name = '%s'"% x)
- wp = wp and wp[0][0] or 0
- if wp:
- sql("update `tabSerial No` set maintenance_status = 'Under Warranty' where name = '%s'" % x)
-
- sql("update `tabSerial No` set status = 'Installed' where name = '%s'" % x)
-
- webnotes.conn.set(self.doc, 'status', 'Submitted')
+ sr_list = get_sr_no_list(d.serial_no, d.qty, d.item_code)
+ self.is_serial_no_exist(d.item_code, sr_list)
+
+ prevdoc_s_no = self.get_prevdoc_serial_no(d.prevdoc_detail_docname, d.prevdoc_docname)
+ if prevdoc_s_no:
+ self.is_serial_no_match(sr_list, prevdoc_s_no, d.prevdoc_docname)
+
+ self.is_serial_no_installed(sr_list, d.item_code)
+ return sr_list
+
+ #validate installation date
+ #-------------------------------
+ def validate_installation_date(self):
+ for d in getlist(self.doclist, 'installed_item_details'):
+ if d.prevdoc_docname:
+ d_date = sql("select posting_date from `tabDelivery Note` where name=%s", d.prevdoc_docname)
+ d_date = d_date and d_date[0][0] or ''
+
+ if d_date > getdate(self.doc.inst_date):
+ msgprint("Installation Date can not be before Delivery Date "+cstr(d_date)+" for item "+d.item_code)
+ raise Exception
+
+ def check_item_table(self):
+ if not(getlist(self.doclist, 'installed_item_details')):
+ msgprint("Please fetch items from Delivery Note selected")
+ raise Exception
+
+ def on_update(self):
+ webnotes.conn.set(self.doc, 'status', 'Draft')
+
+ def on_submit(self):
+ valid_lst = []
+ valid_lst = self.validate_serial_no()
+
+ get_obj("Sales Common").update_prevdoc_detail(1,self)
+
+ for x in valid_lst:
+ wp = sql("select warranty_period from `tabSerial No` where name = '%s'"% x)
+ wp = wp and wp[0][0] or 0
+ if wp:
+ sql("update `tabSerial No` set maintenance_status = 'Under Warranty' where name = '%s'" % x)
+
+ sql("update `tabSerial No` set status = 'Installed' where name = '%s'" % x)
+
+ webnotes.conn.set(self.doc, 'status', 'Submitted')
-
- def on_cancel(self):
- cur_s_no = []
- sales_com_obj = get_obj(dt = 'Sales Common')
- sales_com_obj.update_prevdoc_detail(0,self)
-
- for d in getlist(self.doclist, 'installed_item_details'):
- if d.serial_no:
- #get current list of serial no
- cur_serial_no = d.serial_no.replace(' ', '')
- cur_s_no = cur_serial_no.split(',')
-
- for x in cur_s_no:
- sql("update `tabSerial No` set status = 'Delivered' where name = '%s'" % x)
-
- webnotes.conn.set(self.doc, 'status', 'Cancelled')
+
+ def on_cancel(self):
+ cur_s_no = []
+ sales_com_obj = get_obj(dt = 'Sales Common')
+ sales_com_obj.update_prevdoc_detail(0,self)
+
+ for d in getlist(self.doclist, 'installed_item_details'):
+ if d.serial_no:
+ #get current list of serial no
+ cur_serial_no = d.serial_no.replace(' ', '')
+ cur_s_no = cur_serial_no.split(',')
+
+ for x in cur_s_no:
+ sql("update `tabSerial No` set status = 'Delivered' where name = '%s'" % x)
+
+ webnotes.conn.set(self.doc, 'status', 'Cancelled')
diff --git a/selling/doctype/quotation/quotation.py b/selling/doctype/quotation/quotation.py
index 3b452f18b7..ed09ab03f8 100644
--- a/selling/doctype/quotation/quotation.py
+++ b/selling/doctype/quotation/quotation.py
@@ -179,6 +179,10 @@ class DocType(TransactionBase):
# Validate
# --------
def validate(self):
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Submitted",
+ "Order Confirmed", "Order Lost", "Cancelled"])
+
self.validate_fiscal_year()
self.validate_mandatory()
self.set_last_contact_date()
diff --git a/selling/doctype/sales_order/sales_order.py b/selling/doctype/sales_order/sales_order.py
index e0ac2ffb57..3e7b03dd83 100644
--- a/selling/doctype/sales_order/sales_order.py
+++ b/selling/doctype/sales_order/sales_order.py
@@ -226,8 +226,13 @@ class DocType(TransactionBase):
self.doc.in_words = sales_com_obj.get_total_in_words(dcc, self.doc.rounded_total)
self.doc.in_words_export = sales_com_obj.get_total_in_words(self.doc.currency, self.doc.rounded_total_export)
- # set SO status
- self.doc.status='Draft'
+ if not self.doc.status:
+ self.doc.status = "Draft"
+
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped",
+ "Cancelled"])
+
if not self.doc.billing_status: self.doc.billing_status = 'Not Billed'
if not self.doc.delivery_status: self.doc.delivery_status = 'Not Delivered'
diff --git a/selling/page/selling_home/selling_home.html b/selling/page/selling_home/selling_home.html
index 3009cebe3b..e483b508b4 100644
--- a/selling/page/selling_home/selling_home.html
+++ b/selling/page/selling_home/selling_home.html
@@ -29,15 +29,20 @@
Address Master
-
+
+
+
+ Reports
+
Sales trends based on Sales Invoice
-
+
+
+
- Reports
diff --git a/startup/report_data_map.py b/startup/report_data_map.py
index b07ded52f0..6a7ba42b32 100644
--- a/startup/report_data_map.py
+++ b/startup/report_data_map.py
@@ -93,13 +93,57 @@ data_map = {
"item_code": ["Item", "name"],
"warehouse": ["Warehouse", "name"]
},
- "force_index": "posting_sort_index"
+ "force_index": "posting_sort_index"
},
"Stock Entry": {
"columns": ["name", "purpose"],
"conditions": ["docstatus=1"],
"order_by": "posting_date, posting_time, name",
},
+ "Production Order": {
+ "columns": ["production_item as item_code",
+ "(ifnull(qty, 0) - ifnull(produced_qty, 0)) as qty",
+ "fg_warehouse as warehouse"],
+ "conditions": ["docstatus=1", "status != 'Stopped'", "ifnull(fg_warehouse, '')!=''",
+ "ifnull(qty, 0) > ifnull(produced_qty, 0)"],
+ "links": {
+ "item_code": ["Item", "name"],
+ "warehouse": ["Warehouse", "name"]
+ },
+ },
+ "Purchase Request Item": {
+ "columns": ["item_code", "warehouse",
+ "(ifnull(qty, 0) - ifnull(ordered_qty, 0)) as qty"],
+ "from": "`tabPurchase Request Item` item, `tabPurchase Request` main",
+ "conditions": ["item.parent = main.name", "main.docstatus=1", "main.status != 'Stopped'",
+ "ifnull(warehouse, '')!=''", "ifnull(qty, 0) > ifnull(ordered_qty, 0)"],
+ "links": {
+ "item_code": ["Item", "name"],
+ "warehouse": ["Warehouse", "name"]
+ },
+ },
+ "Purchase Order Item": {
+ "columns": ["item_code", "warehouse",
+ "(ifnull(qty, 0) - ifnull(received_qty, 0)) as qty"],
+ "from": "`tabPurchase Order Item` item, `tabPurchase Order` main",
+ "conditions": ["item.parent = main.name", "main.docstatus=1", "main.status != 'Stopped'",
+ "ifnull(warehouse, '')!=''", "ifnull(qty, 0) > ifnull(received_qty, 0)"],
+ "links": {
+ "item_code": ["Item", "name"],
+ "warehouse": ["Warehouse", "name"]
+ },
+ },
+ "Sales Order Item": {
+ "columns": ["item_code", "(ifnull(qty, 0) - ifnull(delivered_qty, 0)) as qty",
+ "reserved_warehouse as warehouse"],
+ "from": "`tabSales Order Item` item, `tabSales Order` main",
+ "conditions": ["item.parent = main.name", "main.docstatus=1", "main.status != 'Stopped'",
+ "ifnull(reserved_warehouse, '')!=''", "ifnull(qty, 0) > ifnull(delivered_qty, 0)"],
+ "links": {
+ "item_code": ["Item", "name"],
+ "warehouse": ["Warehouse", "name"]
+ },
+ },
# Sales
"Customer": {
diff --git a/stock/doctype/delivery_note/delivery_note.py b/stock/doctype/delivery_note/delivery_note.py
index af4d91e9aa..411ce5c4c0 100644
--- a/stock/doctype/delivery_note/delivery_note.py
+++ b/stock/doctype/delivery_note/delivery_note.py
@@ -131,6 +131,9 @@ class DocType(TransactionBase):
def validate(self):
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "submitted", "Cancelled"])
+
self.so_required()
self.validate_fiscal_year()
self.validate_proj_cust()
diff --git a/stock/doctype/purchase_receipt/purchase_receipt.py b/stock/doctype/purchase_receipt/purchase_receipt.py
index 5e60753a18..8c2ad016e0 100644
--- a/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -148,7 +148,13 @@ class DocType(TransactionBase):
def validate(self):
self.po_required()
self.validate_fiscal_year()
- webnotes.conn.set(self.doc, 'status', 'Draft') # set status as "Draft"
+
+ if not self.doc.status:
+ self.doc.status = "Draft"
+
+ import utilities
+ utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Cancelled"])
+
self.validate_accepted_rejected_qty()
self.validate_inspection() # Validate Inspection
get_obj('Stock Ledger').validate_serial_no(self, 'purchase_receipt_details')
diff --git a/stock/doctype/serial_no/serial_no.py b/stock/doctype/serial_no/serial_no.py
index b6f60f57f3..190b92be3f 100644
--- a/stock/doctype/serial_no/serial_no.py
+++ b/stock/doctype/serial_no/serial_no.py
@@ -65,6 +65,10 @@ class DocType(TransactionBase):
# validate
# ---------
def validate(self):
+ # import utilities
+ # utilities.validate_status(self.doc.status, ["In Store", "Delivered",
+ # "Not in Use", "Purchase Returned"])
+
self.validate_warranty_status()
self.validate_amc_status()
self.validate_warehouse()
diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py
index 2e26a1a86a..18440329ae 100644
--- a/stock/doctype/stock_entry/stock_entry.py
+++ b/stock/doctype/stock_entry/stock_entry.py
@@ -17,7 +17,7 @@
from __future__ import unicode_literals
import webnotes
-from webnotes.utils import cstr, cint, flt, getdate, now
+from webnotes.utils import cstr, cint, flt, getdate, now, comma_or
from webnotes.model import db_exists, delete_doc
from webnotes.model.doc import Document, addchild
from webnotes.model.wrapper import getlist, copy_doclist
@@ -35,6 +35,8 @@ class DocType(TransactionBase):
self.fname = 'mtn_details'
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
@@ -57,6 +59,13 @@ class DocType(TransactionBase):
self.update_stock_ledger(1)
# update Production Order
self.update_production_order(0)
+
+ def validate_purpose(self):
+ valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
+ "Manufacture/Repack", "Subcontract", "Sales Return", "Purchase Return"]
+ if self.doc.purpose not in valid_purposes:
+ msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
+ raise_exception=True)
def validate_serial_nos(self):
sl_obj = get_obj("Stock Ledger")
diff --git a/stock/page/stock_ageing/stock_ageing.js b/stock/page/stock_ageing/stock_ageing.js
index 7809ab9cd9..ea495ce6cd 100644
--- a/stock/page/stock_ageing/stock_ageing.js
+++ b/stock/page/stock_ageing/stock_ageing.js
@@ -63,7 +63,7 @@ erpnext.StockAgeing = erpnext.StockGridReport.extend({
{fieldtype:"Select", label: "Brand", link:"Brand",
default_value: "Select Brand...", filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val;
- }},
+ }, link_formatter: {filter_input: "brand"}},
{fieldtype:"Select", label: "Plot By",
options: ["Average Age", "Earliest", "Latest"]},
{fieldtype:"Date", label: "To Date"},
diff --git a/stock/page/stock_balance/stock_balance.js b/stock/page/stock_balance/stock_balance.js
index 3033470a06..485909c2b9 100644
--- a/stock/page/stock_balance/stock_balance.js
+++ b/stock/page/stock_balance/stock_balance.js
@@ -63,7 +63,7 @@ erpnext.StockBalance = erpnext.StockAnalytics.extend({
{fieldtype:"Select", label: "Brand", link:"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",
default_value: "Select Warehouse..."},
{fieldtype:"Date", label: "From Date"},
diff --git a/stock/page/stock_home/stock_home.html b/stock/page/stock_home/stock_home.html
index 7e43e9976a..d4dfd4cea5 100644
--- a/stock/page/stock_home/stock_home.html
+++ b/stock/page/stock_home/stock_home.html
@@ -23,7 +23,11 @@
Warehouse is where items are stored
-
+
+
+
+ Reports
+
@@ -34,6 +38,12 @@
Inflow, outflow and balance of stock
+
+
Available and estimated qty of stock, as of now
+
+
@@ -44,9 +54,8 @@
Analysis of slow moving stock
-
+
- Reports
diff --git a/stock/page/stock_ledger/stock_ledger.js b/stock/page/stock_ledger/stock_ledger.js
index e0cfd02af5..97d64175ba 100644
--- a/stock/page/stock_ledger/stock_ledger.js
+++ b/stock/page/stock_ledger/stock_ledger.js
@@ -86,7 +86,7 @@ erpnext.StockLedger = erpnext.StockGridReport.extend({
{fieldtype:"Select", label: "Brand", link:"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:"Data", label: "Voucher No",
filter: function(val, item, opts) {
if(!val) return true;
diff --git a/stock/page/stock_level/__init__.py b/stock/page/stock_level/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/stock/page/stock_level/stock_level.js b/stock/page/stock_level/stock_level.js
new file mode 100644
index 0000000000..87fce6a9bc
--- /dev/null
+++ b/stock/page/stock_level/stock_level.js
@@ -0,0 +1,225 @@
+// ERPNext - web based ERP (http://erpnext.com)
+// Copyright (C) 2012 Web Notes Technologies Pvt Ltd
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see
.
+
+wn.pages['stock-level'].onload = function(wrapper) {
+ wn.ui.make_app_page({
+ parent: wrapper,
+ title: 'Stock Level',
+ single_column: true
+ });
+
+ new erpnext.StockLevel(wrapper);
+
+ wrapper.appframe.add_home_breadcrumb()
+ wrapper.appframe.add_module_breadcrumb("Stock")
+ wrapper.appframe.add_breadcrumb("icon-bar-chart");
+}
+
+wn.require("app/js/stock_grid_report.js");
+
+erpnext.StockLevel = erpnext.StockGridReport.extend({
+ init: function(wrapper) {
+ var me = this;
+
+ this._super({
+ title: "Stock Level",
+ page: wrapper,
+ parent: $(wrapper).find('.layout-main'),
+ appframe: wrapper.appframe,
+ doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Production Order",
+ "Purchase Request Item", "Purchase Order Item", "Sales Order Item", "Brand"],
+ });
+
+ this.wrapper.bind("make", function() {
+ wn.utils.set_footnote(me, me.wrapper.get(0),
+ "
\
+ - \
+ Projected Qty = Actual Qty + Planned Qty + Requested Qty \
+ + Ordered Qty - Reserved Qty
\
+ \
+ - Actual Qty: Quantity available in the warehouse.
\
+ - Planned Qty: Quantity, for which, Production Order has been raised, \
+ but is pending to be manufactured.
\
+ - Requested Qty: Quantity requested for purchase, but not ordered.
\
+ - Ordered Qty: Quantity ordered for purchase, but not received.
\
+ - Reserved Qty: Quantity ordered for sale, but not delivered.
\
+
\
+
");
+ });
+ },
+
+ setup_columns: function() {
+ this.columns = [
+ {id: "item_code", name: "Item Code", field: "item_code", width: 160,
+ link_formatter: {
+ filter_input: "item_code",
+ open_btn: true,
+ doctype: '"Item"',
+ }},
+ {id: "warehouse", name: "Warehouse", field: "warehouse", width: 100,
+ link_formatter: {filter_input: "warehouse"}},
+ {id: "actual_qty", name: "Actual Qty",
+ field: "actual_qty", width: 80, formatter: this.currency_formatter},
+ {id: "planned_qty", name: "Planned Qty",
+ field: "planned_qty", width: 80, formatter: this.currency_formatter},
+ {id: "requested_qty", name: "Requested Qty",
+ field: "requested_qty", width: 80, formatter: this.currency_formatter},
+ {id: "ordered_qty", name: "Ordered Qty",
+ field: "ordered_qty", width: 80, formatter: this.currency_formatter},
+ {id: "reserved_qty", name: "Reserved Qty",
+ field: "reserved_qty", width: 80, formatter: this.currency_formatter},
+ {id: "projected_qty", name: "Projected Qty",
+ field: "projected_qty", width: 80, formatter: this.currency_formatter},
+ {id: "uom", name: "UOM", field: "uom", width: 60},
+ {id: "item_name", name: "Item Name", field: "item_name", width: 100,
+ formatter: this.text_formatter},
+ {id: "brand", name: "Brand", field: "brand", width: 100,
+ link_formatter: {filter_input: "brand"}},
+ ];
+ },
+
+ filters: [
+ {fieldtype:"Select", label: "Item Code", link:"Item", default_value: "Select Item...",
+ filter: function(val, item, opts) {
+ return item.item_code == val || val == opts.default_value;
+ }},
+
+ {fieldtype:"Select", label: "Warehouse", link:"Warehouse",
+ default_value: "Select Warehouse...", filter: function(val, item, opts) {
+ return item.warehouse == val || val == opts.default_value;
+ }},
+
+ {fieldtype:"Select", label: "Brand", link:"Brand",
+ default_value: "Select Brand...", filter: function(val, item, opts) {
+ return val == opts.default_value || item.brand == val;
+ }},
+ {fieldtype:"Button", label: "Refresh", icon:"icon-refresh icon-white", cssClass:"btn-info"},
+ {fieldtype:"Button", label: "Reset Filters"}
+ ],
+
+ setup_filters: function() {
+ var me = this;
+ this._super();
+
+ this.wrapper.bind("apply_filters_from_route", function() { me.toggle_enable_brand(); });
+ this.filter_inputs.item_code.change(function() { me.toggle_enable_brand(); });
+
+ this.trigger_refresh_on_change(["item_code", "warehouse", "brand"]);
+ },
+
+ toggle_enable_brand: function() {
+ if(this.filter_inputs.item_code.val() ==
+ this.filter_inputs.item_code.get(0).opts.default_value) {
+ this.filter_inputs.brand.removeAttr("disabled");
+ } else {
+ this.filter_inputs.brand
+ .val(this.filter_inputs.brand.get(0).opts.default_value)
+ .attr("disabled", "disabled");
+ }
+ },
+
+ init_filter_values: function() {
+ this._super();
+ this.filter_inputs.warehouse.get(0).selectedIndex = 0;
+ },
+
+ prepare_data: function() {
+ var me = this;
+
+ if(!this._data) {
+ this._data = [];
+ this.item_warehouse_map = [];
+ this.item_by_name = this.make_name_map(wn.report_dump.data["Item"]);
+ var sorted_item_list = Object.keys(this.item_by_name).sort();
+ $.each(sorted_item_list, function(i, item_code) {
+ var item = me.item_by_name[item_code];
+ $.each(wn.report_dump.data["Warehouse"], function(i, warehouse) {
+ // a list of item warehouse combination objects
+ var row = {
+ item_code: item_code,
+ warehouse: warehouse.name,
+ brand: item.brand,
+ item_name: item.item_name || item.name,
+ uom: item.stock_uom,
+ id: item_code + ":" + warehouse.name,
+ }
+ me.reset_item_values(row);
+ me._data.push(row);
+ me.item_warehouse_map[row.id] = row;
+ });
+ });
+ this.calculate_quantities();
+
+ // filter out rows with zero values
+ this._data = $.map(this._data, function(d) {
+ return me.apply_zero_filter(null, d, null, me) ? d : null;
+ });
+ }
+
+ this.data = [].concat(this._data);
+ this.data = $.map(this.data, function(d) {
+ return me.apply_filters(d) ? d : null;
+ });
+
+ this.calculate_total();
+ },
+
+ calculate_quantities: function() {
+ var me = this;
+ $.each([
+ ["Stock Ledger Entry", "actual_qty"],
+ ["Production Order", "planned_qty"],
+ ["Purchase Request Item", "requested_qty"],
+ ["Purchase Order Item", "ordered_qty"],
+ ["Sales Order Item", "reserved_qty"]],
+ function(i, v) {
+ $.each(wn.report_dump.data[v[0]], function(i, item) {
+ var row = me.item_warehouse_map[item.item_code + ":" + item.warehouse];
+ row[v[1]] += flt(item.qty);
+ });
+ }
+ );
+
+ $.each(this._data, function(i, row) {
+ row.projected_qty = row.actual_qty + row.planned_qty + row.requested_qty
+ + row.ordered_qty - row.reserved_qty;
+ });
+ },
+
+ calculate_total: function() {
+ var me = this;
+ // show total if a specific item is selected and warehouse is not filtered
+ if(this.is_default("warehouse") && !this.is_default("item_code")) {
+ var total = {
+ id: "_total",
+ item_code: "Total",
+ _style: "font-weight: bold",
+ _show: true
+ };
+ this.reset_item_values(total);
+
+ $.each(this.data, function(i, row) {
+ $.each(me.columns, function(i, col) {
+ if (col.formatter==me.currency_formatter) {
+ total[col.id] += row[col.id];
+ }
+ });
+ });
+
+ this.data = this.data.concat([total]);
+ }
+ }
+})
diff --git a/stock/page/stock_level/stock_level.txt b/stock/page/stock_level/stock_level.txt
new file mode 100644
index 0000000000..6bf5c41cec
--- /dev/null
+++ b/stock/page/stock_level/stock_level.txt
@@ -0,0 +1,21 @@
+[
+ {
+ "owner": "Administrator",
+ "docstatus": 0,
+ "creation": "2012-12-28 11:02:23",
+ "modified_by": "Administrator",
+ "modified": "2012-12-28 11:02:23"
+ },
+ {
+ "name": "__common__",
+ "title": "Stock Level",
+ "doctype": "Page",
+ "module": "Stock",
+ "standard": "Yes",
+ "page_name": "stock-level"
+ },
+ {
+ "name": "stock-level",
+ "doctype": "Page"
+ }
+]
\ No newline at end of file
diff --git a/support/doctype/customer_issue/customer_issue.py b/support/doctype/customer_issue/customer_issue.py
index 44ae891c84..0a08d82f38 100644
--- a/support/doctype/customer_issue/customer_issue.py
+++ b/support/doctype/customer_issue/customer_issue.py
@@ -41,9 +41,8 @@ class DocType(TransactionBase):
def validate(self):
if session['user'] != 'Guest' and not self.doc.customer:
- msgprint("Please select Customer from whom issue is raised")
- raise Exception
-
+ msgprint("Please select Customer from whom issue is raised",
+ raise_exception=True)
def on_cancel(self):
lst = sql("select t1.name from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t2.prevdoc_docname = '%s' and t1.docstatus!=2"%(self.doc.name))
diff --git a/support/page/support_home/support_home.html b/support/page/support_home/support_home.html
index 8975bae66f..ed01fa094e 100644
--- a/support/page/support_home/support_home.html
+++ b/support/page/support_home/support_home.html
@@ -26,7 +26,7 @@
- Reports
+ Reports
diff --git a/utilities/__init__.py b/utilities/__init__.py
index 486568d638..7c44ec31dd 100644
--- a/utilities/__init__.py
+++ b/utilities/__init__.py
@@ -16,7 +16,8 @@
from __future__ import unicode_literals
import webnotes
-from webnotes.utils import cint
+from webnotes import _, msgprint
+from webnotes.utils import cint, comma_or
@webnotes.whitelist()
def get_sc_list(arg=None):
@@ -52,4 +53,8 @@ def get_report_list():
and ifnull(tabReport.disabled,0) != 1
order by tabReport.name
limit %s, %s""" % \
- ("%s", cint(limit_start), cint(limit_page_length)), (module,), as_dict=True)
\ No newline at end of file
+ ("%s", cint(limit_start), cint(limit_page_length)), (module,), as_dict=True)
+
+def validate_status(status, options):
+ if status not in options:
+ msgprint(_("Status must be one of ") + comma_or(options), raise_exception=True)
diff --git a/website/helpers/product.py b/website/helpers/product.py
index 1e8257a7a7..623ceeee34 100644
--- a/website/helpers/product.py
+++ b/website/helpers/product.py
@@ -102,4 +102,5 @@ def get_parent_item_groups(item_group_name):
def invalidate_cache_for(item_group):
for i in get_parent_item_groups(item_group):
- delete_page_cache(i.page_name)
\ No newline at end of file
+ if i.page_name:
+ delete_page_cache(i.page_name)
\ No newline at end of file
diff --git a/website/utils.py b/website/utils.py
index ec33103e29..674d752114 100644
--- a/website/utils.py
+++ b/website/utils.py
@@ -263,7 +263,8 @@ def clear_cache(page_name=None):
webnotes.cache().delete_keys("page:")
def delete_page_cache(page_name):
- webnotes.cache().delete_value("page:" + page_name)
+ if page_name:
+ webnotes.cache().delete_value("page:" + page_name)
def url_for_website(url):
if url and not url.lower().startswith("http"):