Merge branch 'master' into purchase_price_list
This commit is contained in:
commit
03fa78597d
@ -82,6 +82,7 @@ erpnext.desktop.show_pending_notifications = function() {
|
|||||||
add_circle('module-icon-calendar', 'todays_events', 'Todays Events');
|
add_circle('module-icon-calendar', 'todays_events', 'Todays Events');
|
||||||
add_circle('module-icon-projects-home', 'open_tasks', 'Open Tasks');
|
add_circle('module-icon-projects-home', 'open_tasks', 'Open Tasks');
|
||||||
add_circle('module-icon-questions', 'unanswered_questions', 'Unanswered Questions');
|
add_circle('module-icon-questions', 'unanswered_questions', 'Unanswered Questions');
|
||||||
|
add_circle('module-icon-selling-home', 'open_leads', 'Open Leads');
|
||||||
|
|
||||||
erpnext.update_messages();
|
erpnext.update_messages();
|
||||||
|
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
erpnext.updates = [
|
erpnext.updates = [
|
||||||
|
["16th January, 2013", [
|
||||||
|
"Job Applicant: Track Job Applicants and extract them from a mailbox like 'jobs@example.com'. See <a href='#Form/Jobs Email Settings'>Jobs Email Settings</a>.",
|
||||||
|
"Extract leads: Extract Leads from a mailbox like 'sales@example.com'. See <a href='#Form/Sales Email Settings'>Sales Email Settings</a>.",
|
||||||
|
]],
|
||||||
["14th January, 2013", [
|
["14th January, 2013", [
|
||||||
"Stock Reconciliation: Ability to update Valuation Rate",
|
"Stock Reconciliation: Ability to update Valuation Rate",
|
||||||
"Time Field: Added Datetime and new Time Picker",
|
"Time Field: Added Datetime and new Time Picker",
|
||||||
|
0
hr/doctype/job_applicant/__init__.py
Normal file
0
hr/doctype/job_applicant/__init__.py
Normal file
60
hr/doctype/job_applicant/get_job_applications.py
Normal file
60
hr/doctype/job_applicant/get_job_applications.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import webnotes
|
||||||
|
from webnotes.utils import cstr, cint
|
||||||
|
from webnotes.utils.email_lib.receive import POP3Mailbox
|
||||||
|
from core.doctype.communication.communication import make
|
||||||
|
|
||||||
|
class JobsMailbox(POP3Mailbox):
|
||||||
|
def setup(self):
|
||||||
|
self.settings = webnotes.doc("Jobs Email Settings", "Jobs Email Settings")
|
||||||
|
|
||||||
|
def check_mails(self):
|
||||||
|
return webnotes.conn.sql("select user from tabSessions where \
|
||||||
|
time_to_sec(timediff(now(), lastupdate)) < 1800")
|
||||||
|
|
||||||
|
def process_message(self, mail):
|
||||||
|
if mail.from_email == self.settings.email_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
name = webnotes.conn.get_value("Job Applicant", {"email_id": mail.from_email},
|
||||||
|
"name")
|
||||||
|
if name:
|
||||||
|
applicant = webnotes.model_wrapper("Job Applicant", name)
|
||||||
|
if applicant.doc.status!="Rejected":
|
||||||
|
applicant.doc.status = "Open"
|
||||||
|
applicant.doc.save()
|
||||||
|
else:
|
||||||
|
name = (mail.from_real_name and (mail.from_real_name + " - ") or "") \
|
||||||
|
+ mail.from_email
|
||||||
|
applicant = webnotes.model_wrapper({
|
||||||
|
"doctype":"Job Applicant",
|
||||||
|
"applicant_name": name,
|
||||||
|
"email_id": mail.from_email,
|
||||||
|
"status": "Open"
|
||||||
|
})
|
||||||
|
applicant.insert()
|
||||||
|
|
||||||
|
mail.save_attachments_in_doc(applicant.doc)
|
||||||
|
|
||||||
|
make(content=mail.content, sender=mail.from_email,
|
||||||
|
doctype="Job Applicant", name=applicant.doc.name, set_lead=False)
|
||||||
|
|
||||||
|
def get_job_applications():
|
||||||
|
if cint(webnotes.conn.get_value('Jobs Email Settings', None, 'extract_emails')):
|
||||||
|
JobsMailbox()
|
22
hr/doctype/job_applicant/job_applicant.js
Normal file
22
hr/doctype/job_applicant/job_applicant.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
cur_frm.cscript = {
|
||||||
|
onload: function(doc, dt, dn) {
|
||||||
|
if(in_list(user_roles,'System Manager')) {
|
||||||
|
cur_frm.page_layout.footer.help_area.innerHTML = '<hr>\
|
||||||
|
<p><a href="#Form/Jobs Email Settings">Jobs Email Settings</a><br>\
|
||||||
|
<span class="help">Automatically extract Job Applicants from a mail box e.g. "jobs@example.com"</span></p>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refresh: function(doc) {
|
||||||
|
cur_frm.cscript.make_listing(doc);
|
||||||
|
},
|
||||||
|
make_listing: function(doc) {
|
||||||
|
cur_frm.communication_view = new wn.views.CommunicationList({
|
||||||
|
list: wn.model.get("Communication", {"job_applicant": doc.name}),
|
||||||
|
parent: cur_frm.fields_dict['thread_html'].wrapper,
|
||||||
|
doc: doc,
|
||||||
|
recipients: doc.email_id
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
23
hr/doctype/job_applicant/job_applicant.py
Normal file
23
hr/doctype/job_applicant/job_applicant.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import webnotes
|
||||||
|
from utilities.transaction_base import TransactionBase
|
||||||
|
|
||||||
|
class DocType(TransactionBase):
|
||||||
|
def __init__(self, d, dl):
|
||||||
|
self.doc, self.doclist = d, dl
|
||||||
|
|
||||||
|
def onload(self):
|
||||||
|
self.add_communication_list()
|
||||||
|
|
||||||
|
def get_sender(self, comm):
|
||||||
|
return webnotes.conn.get_value('Jobs Email Settings',None,'email_id')
|
||||||
|
|
||||||
|
def on_communication_sent(self, comm):
|
||||||
|
webnotes.conn.set(self.doc, 'status', 'Replied')
|
||||||
|
|
||||||
|
def on_trash(self):
|
||||||
|
webnotes.conn.sql("""delete from `tabCommunication`
|
||||||
|
where job_applicant=%s""", self.doc.name)
|
||||||
|
|
100
hr/doctype/job_applicant/job_applicant.txt
Normal file
100
hr/doctype/job_applicant/job_applicant.txt
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"owner": "Administrator",
|
||||||
|
"docstatus": 0,
|
||||||
|
"creation": "2013-01-15 16:32:13",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"modified": "2013-01-15 17:40:29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autoname": "field:applicant_name",
|
||||||
|
"allow_attach": 1,
|
||||||
|
"description": "Applicant for a Job",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"module": "HR",
|
||||||
|
"document_type": "Transaction",
|
||||||
|
"name": "__common__"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "__common__",
|
||||||
|
"parent": "Job Applicant",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parent": "Job Applicant",
|
||||||
|
"read": 1,
|
||||||
|
"cancel": 1,
|
||||||
|
"name": "__common__",
|
||||||
|
"create": 1,
|
||||||
|
"doctype": "DocPerm",
|
||||||
|
"write": 1,
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"role": "HR User",
|
||||||
|
"report": 1,
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "permissions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job Applicant",
|
||||||
|
"doctype": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Applicant Name",
|
||||||
|
"fieldname": "applicant_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Email Id",
|
||||||
|
"fieldname": "email_id",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Status",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": "Open\nReplied\nRejected\nHold"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"width": "50%",
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Job Opening",
|
||||||
|
"fieldname": "job_opening",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Job Opening"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"fieldname": "section_break_5",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Thread HTML",
|
||||||
|
"fieldname": "thread_html",
|
||||||
|
"fieldtype": "HTML"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"print_hide": 1,
|
||||||
|
"no_copy": 1,
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "File List",
|
||||||
|
"fieldname": "file_list",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"hidden": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocPerm"
|
||||||
|
}
|
||||||
|
]
|
41
hr/doctype/job_applicant/job_applicant_list.js
Normal file
41
hr/doctype/job_applicant/job_applicant_list.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// render
|
||||||
|
wn.doclistviews['Job Applicant'] = wn.views.ListView.extend({
|
||||||
|
init: function(d) {
|
||||||
|
this._super(d)
|
||||||
|
this.fields = this.fields.concat([
|
||||||
|
"`tabJob Applicant`.status",
|
||||||
|
'`tabJob Applicant`.modified_by'
|
||||||
|
|
||||||
|
]);
|
||||||
|
this.stats = this.stats.concat(['status']);
|
||||||
|
this.show_hide_check_column();
|
||||||
|
},
|
||||||
|
|
||||||
|
label_style: {
|
||||||
|
"status": {
|
||||||
|
"Open": "danger",
|
||||||
|
"Hold": "info",
|
||||||
|
"Rejected": "plain",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
prepare_data: function(data) {
|
||||||
|
this._super(data);
|
||||||
|
|
||||||
|
data.label_style = this.label_style.status[data.status];
|
||||||
|
if(data.label_style=="danger")
|
||||||
|
data.label_style = "important"
|
||||||
|
|
||||||
|
data.status_html = repl('<span class="label \
|
||||||
|
label-%(label_style)s">%(status)s</span>', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
columns: [
|
||||||
|
{width: '3%', content: 'check'},
|
||||||
|
{width: '5%', content:'avatar_modified'},
|
||||||
|
{width: '30%', content:'name'},
|
||||||
|
{width: '50%', content:'status_html'},
|
||||||
|
{width: '12%', content:'modified', css: {'text-align': 'right', 'color':'#777'}}
|
||||||
|
]
|
||||||
|
|
||||||
|
});
|
0
hr/doctype/job_opening/__init__.py
Normal file
0
hr/doctype/job_opening/__init__.py
Normal file
8
hr/doctype/job_opening/job_opening.py
Normal file
8
hr/doctype/job_opening/job_opening.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import webnotes
|
||||||
|
|
||||||
|
class DocType:
|
||||||
|
def __init__(self, d, dl):
|
||||||
|
self.doc, self.doclist = d, dl
|
67
hr/doctype/job_opening/job_opening.txt
Normal file
67
hr/doctype/job_opening/job_opening.txt
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"owner": "Administrator",
|
||||||
|
"docstatus": 0,
|
||||||
|
"creation": "2013-01-15 16:13:36",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"modified": "2013-01-15 16:43:05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autoname": "field:job_title",
|
||||||
|
"description": "Description of a Job Opening",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"module": "HR",
|
||||||
|
"document_type": "Transaction",
|
||||||
|
"name": "__common__"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "__common__",
|
||||||
|
"parent": "Job Opening",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parent": "Job Opening",
|
||||||
|
"read": 1,
|
||||||
|
"cancel": 1,
|
||||||
|
"name": "__common__",
|
||||||
|
"create": 1,
|
||||||
|
"doctype": "DocPerm",
|
||||||
|
"write": 1,
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"role": "HR User",
|
||||||
|
"report": 1,
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "permissions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job Opening",
|
||||||
|
"doctype": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Job Title",
|
||||||
|
"fieldname": "job_title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Status",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": "Open\nClosed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Job profile, qualifications required etc.",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Description",
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Text Editor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocPerm"
|
||||||
|
}
|
||||||
|
]
|
@ -31,6 +31,11 @@ wn.module_page["HR"] = [
|
|||||||
description: wn._("Performance appraisal."),
|
description: wn._("Performance appraisal."),
|
||||||
doctype:"Appraisal"
|
doctype:"Appraisal"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: wn._("Job Applicant"),
|
||||||
|
description: wn._("Applicant for a Job (extracted from jobs email)."),
|
||||||
|
doctype:"Job Applicant"
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -108,6 +113,11 @@ wn.module_page["HR"] = [
|
|||||||
title: wn._("Employee Setup"),
|
title: wn._("Employee Setup"),
|
||||||
icon: "icon-cog",
|
icon: "icon-cog",
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
label: wn._("Job Opening"),
|
||||||
|
description: wn._("Opening for a Job."),
|
||||||
|
doctype:"Job Opening"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": wn._("Employment Type"),
|
"label": wn._("Employment Type"),
|
||||||
"description": wn._("Type of employment master."),
|
"description": wn._("Type of employment master."),
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import webnotes
|
import webnotes
|
||||||
from webnotes.utils import cstr, flt, nowdate, get_defaults
|
from webnotes.utils import cstr, flt, nowdate
|
||||||
from webnotes.model.doc import addchild, Document
|
from webnotes.model.doc import addchild, Document
|
||||||
from webnotes.model.wrapper import getlist
|
from webnotes.model.wrapper import getlist
|
||||||
from webnotes.model.code import get_obj
|
from webnotes.model.code import get_obj
|
||||||
@ -210,7 +210,7 @@ class DocType:
|
|||||||
"wip_warehouse" : "",
|
"wip_warehouse" : "",
|
||||||
"fg_warehouse" : "",
|
"fg_warehouse" : "",
|
||||||
"status" : "Draft",
|
"status" : "Draft",
|
||||||
"fiscal_year" : get_defaults()["fiscal_year"]
|
"fiscal_year" : webnotes.conn.get_default("fiscal_year")
|
||||||
}
|
}
|
||||||
return bom_dict, item_dict
|
return bom_dict, item_dict
|
||||||
|
|
||||||
@ -239,18 +239,22 @@ class DocType:
|
|||||||
return self.get_csv()
|
return self.get_csv()
|
||||||
|
|
||||||
def get_raw_materials(self, bom_dict):
|
def get_raw_materials(self, bom_dict):
|
||||||
""" Get raw materials considering sub-assembly items """
|
""" Get raw materials considering sub-assembly items
|
||||||
|
{
|
||||||
|
"item_code": [qty_required, description, stock_uom]
|
||||||
|
}
|
||||||
|
"""
|
||||||
for bom in bom_dict:
|
for bom in bom_dict:
|
||||||
if self.doc.use_multi_level_bom:
|
if self.doc.use_multi_level_bom:
|
||||||
# get all raw materials with sub assembly childs
|
# get all raw materials with sub assembly childs
|
||||||
fl_bom_items = sql("""
|
fl_bom_items = sql("""
|
||||||
select
|
select
|
||||||
item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty,
|
item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty,
|
||||||
description, stock_uom
|
description, stock_uom, min_order_qty
|
||||||
from
|
from
|
||||||
(
|
(
|
||||||
select distinct fb.name, fb.description, fb.item_code,
|
select distinct fb.name, fb.description, fb.item_code,
|
||||||
fb.qty_consumed_per_unit, fb.stock_uom
|
fb.qty_consumed_per_unit, fb.stock_uom, it.min_order_qty
|
||||||
from `tabBOM Explosion Item` fb,`tabItem` it
|
from `tabBOM Explosion Item` fb,`tabItem` it
|
||||||
where it.name = fb.item_code
|
where it.name = fb.item_code
|
||||||
and ifnull(it.is_pro_applicable, 'No') = 'No'
|
and ifnull(it.is_pro_applicable, 'No') = 'No'
|
||||||
@ -263,18 +267,21 @@ class DocType:
|
|||||||
# Get all raw materials considering SA items as raw materials,
|
# Get all raw materials considering SA items as raw materials,
|
||||||
# so no childs of SA items
|
# so no childs of SA items
|
||||||
fl_bom_items = sql("""
|
fl_bom_items = sql("""
|
||||||
select item_code, ifnull(sum(qty_consumed_per_unit), 0) * '%s',
|
select bom_item.item_code,
|
||||||
description, stock_uom
|
ifnull(sum(bom_item.qty_consumed_per_unit), 0) * %s,
|
||||||
from `tabBOM Item`
|
bom_item.description, bom_item.stock_uom, item.min_order_qty
|
||||||
where parent = '%s' and docstatus < 2
|
from `tabBOM Item` bom_item, tabItem item
|
||||||
|
where bom_item.parent = %s and bom_item.docstatus < 2
|
||||||
|
and bom_item.item_code = item.name
|
||||||
group by item_code
|
group by item_code
|
||||||
""" % (flt(bom_dict[bom]), bom))
|
""", (flt(bom_dict[bom]), bom))
|
||||||
|
|
||||||
self.make_items_dict(fl_bom_items)
|
self.make_items_dict(fl_bom_items)
|
||||||
|
|
||||||
def make_items_dict(self, item_list):
|
def make_items_dict(self, item_list):
|
||||||
for i in item_list:
|
for i in item_list:
|
||||||
self.item_dict[i[0]] = [(flt(self.item_dict.get(i[0], [0])[0]) + flt(i[1])), i[2], i[3]]
|
self.item_dict[i[0]] = [(flt(self.item_dict.get(i[0], [0])[0]) + flt(i[1])),
|
||||||
|
i[2], i[3], i[4]]
|
||||||
|
|
||||||
|
|
||||||
def get_csv(self):
|
def get_csv(self):
|
||||||
@ -291,4 +298,63 @@ class DocType:
|
|||||||
if item_qty:
|
if item_qty:
|
||||||
item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty])
|
item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty])
|
||||||
|
|
||||||
return item_list
|
return item_list
|
||||||
|
|
||||||
|
def raise_purchase_request(self):
|
||||||
|
def _get_projected_qty(items):
|
||||||
|
item_projected_qty = webnotes.conn.sql("""select item_code, sum(projected_qty)
|
||||||
|
from `tabBin` where item_code in (%s) group by item_code""" %
|
||||||
|
(", ".join(["%s"]*len(items)),), tuple(items))
|
||||||
|
|
||||||
|
return dict(item_projected_qty)
|
||||||
|
|
||||||
|
item_dict = self.get_raw_materials()
|
||||||
|
item_projected_qty = _get_projected_qty(item_dict.keys())
|
||||||
|
|
||||||
|
from accounts.utils import get_fiscal_year
|
||||||
|
fiscal_year = get_fiscal_year(nowdate())
|
||||||
|
|
||||||
|
items_to_be_requested = []
|
||||||
|
for item in item_dict:
|
||||||
|
if flt(item_dict[item][0]) > item_projected_qty[item]:
|
||||||
|
# shortage
|
||||||
|
requested_qty = flt(item_dict[item][0]) - item_projected_qty[item]
|
||||||
|
# comsider minimum order qty
|
||||||
|
requested_qty = requested_qty > flt(item_dict[item][3]) and \
|
||||||
|
requested_qty or flt(item_dict[item][3])
|
||||||
|
items_to_be_requested.append({
|
||||||
|
"item_code": item,
|
||||||
|
"qty": requested_qty,
|
||||||
|
"description": item_dict[item][1],
|
||||||
|
"stock_uom": item_dict[item][2]
|
||||||
|
})
|
||||||
|
webnotes.errprint(items_to_be_requested)
|
||||||
|
self.insert_purchase_request(items_to_be_requested, fiscal_year)
|
||||||
|
|
||||||
|
def insert_purchase_request(self, items, fiscal_year):
|
||||||
|
for item in items:
|
||||||
|
item_wrapper = webnotes.model_wrapper("Item", args.item_code)
|
||||||
|
pr = [
|
||||||
|
{
|
||||||
|
"doctype": "Purchase Request",
|
||||||
|
"naming_series": "IDT",
|
||||||
|
"transaction_date": nowdate(),
|
||||||
|
"status": "Draft",
|
||||||
|
"company": self.doc.company,
|
||||||
|
"fiscal_year": fiscal_year,
|
||||||
|
"requested_by": webnotes.session.user,
|
||||||
|
"remark": "Automatically raised from Production Planning Tool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Purchase Request Item",
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"item_name": item_wrapper.doc.item_name,
|
||||||
|
"description": item.description,
|
||||||
|
"uom": item.stock_uom,
|
||||||
|
"item_group": item_wrapper.doc.item_group,
|
||||||
|
"brand": item_wrapper.doc.brand,
|
||||||
|
"qty": item.qty,
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
@ -2,9 +2,9 @@
|
|||||||
{
|
{
|
||||||
"owner": "jai@webnotestech.com",
|
"owner": "jai@webnotestech.com",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"creation": "2012-12-14 10:15:16",
|
"creation": "2013-01-16 14:48:56",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"modified": "2012-12-14 11:37:40"
|
"modified": "2013-01-16 15:46:26"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
@ -28,8 +28,10 @@
|
|||||||
"parent": "Production Planning Tool",
|
"parent": "Production Planning Tool",
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
|
"submit": 0,
|
||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
"write": 1,
|
"write": 1,
|
||||||
|
"report": 0,
|
||||||
"parenttype": "DocType",
|
"parenttype": "DocType",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"parentfield": "permissions"
|
"parentfield": "permissions"
|
||||||
@ -68,9 +70,9 @@
|
|||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
|
"reqd": 1,
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"reqd": 1,
|
|
||||||
"options": "Company"
|
"options": "Company"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -154,10 +156,19 @@
|
|||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"options": "Simple"
|
"options": "Simple"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.",
|
||||||
|
"default": "1",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Use Multi-Level BOM",
|
||||||
|
"reqd": 0,
|
||||||
|
"fieldname": "use_multi_level_bom",
|
||||||
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"width": "50%",
|
"width": "50%",
|
||||||
"fieldname": "column_break5",
|
"fieldname": "cb5",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -170,18 +181,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"width": "50%",
|
"fieldname": "sb5",
|
||||||
"fieldname": "column_break6",
|
"fieldtype": "Section Break",
|
||||||
"fieldtype": "Column Break"
|
"options": "Simple"
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.",
|
|
||||||
"default": "1",
|
|
||||||
"doctype": "DocField",
|
|
||||||
"label": "Use Multi-Level BOM",
|
|
||||||
"fieldname": "use_multi_level_bom",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"reqd": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Download a report containing all raw materials with their latest inventory status",
|
"description": "Download a report containing all raw materials with their latest inventory status",
|
||||||
@ -191,8 +193,18 @@
|
|||||||
"fieldtype": "Button"
|
"fieldtype": "Button"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "System Manager",
|
"doctype": "DocField",
|
||||||
"doctype": "DocPerm"
|
"width": "50%",
|
||||||
|
"fieldname": "column_break6",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Raise Purchase Request automatically for items which are \"Out of Stock\" considering already requested, already ordered qty and minimum order qty",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Raise Purchase Request",
|
||||||
|
"fieldname": "raise_purchase_request",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"options": "raise_purchase_request"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "Manufacturing User",
|
"role": "Manufacturing User",
|
||||||
|
@ -8,14 +8,15 @@
|
|||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# 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.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import webnotes
|
import webnotes
|
||||||
|
import time, datetime
|
||||||
|
|
||||||
from webnotes.utils import cint, cstr, getdate, now, nowdate
|
from webnotes.utils import cint, cstr, getdate, now, nowdate
|
||||||
from webnotes.model import db_exists
|
from webnotes.model import db_exists
|
||||||
@ -23,68 +24,71 @@ from webnotes.model.wrapper import getlist, copy_doclist
|
|||||||
from webnotes import msgprint
|
from webnotes import msgprint
|
||||||
|
|
||||||
sql = webnotes.conn.sql
|
sql = webnotes.conn.sql
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DocType:
|
class DocType:
|
||||||
def __init__(self,doc,doclist=[]):
|
def __init__(self,doc,doclist=[]):
|
||||||
self.doc = doc
|
self.doc = doc
|
||||||
self.doclist = doclist
|
self.doclist = doclist
|
||||||
|
|
||||||
def get_customer_details(self, project_name):
|
def get_customer_details(self, project_name):
|
||||||
cust = sql("select customer, customer_name from `tabProject` where name = %s", project_name)
|
cust = sql("select customer, customer_name from `tabProject` where name = %s", project_name)
|
||||||
if cust:
|
if cust:
|
||||||
ret = {'customer': cust and cust[0][0] or '', 'customer_name': cust and cust[0][1] or ''}
|
ret = {'customer': cust and cust[0][0] or '', 'customer_name': cust and cust[0][1] or ''}
|
||||||
return (ret)
|
return (ret)
|
||||||
|
|
||||||
def get_task_details(self, task_sub):
|
def get_task_details(self, task_sub):
|
||||||
tsk = sql("select name, project, customer, customer_name from `tabTask` where subject = %s", task_sub)
|
tsk = sql("select name, project, customer, customer_name from `tabTask` where subject = %s", task_sub)
|
||||||
if tsk:
|
if tsk:
|
||||||
ret = {'task_id': tsk and tsk[0][0] or '', 'project_name': tsk and tsk[0][1] or '', 'customer_name': tsk and tsk[0][3] or ''}
|
ret = {'task_id': tsk and tsk[0][0] or '', 'project_name': tsk and tsk[0][1] or '', 'customer_name': tsk and tsk[0][3] or ''}
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def validate(self):
|
def get_time(self, timestr):
|
||||||
if getdate(self.doc.timesheet_date) > getdate(nowdate()):
|
if len(timestr.split(":"))==2:
|
||||||
msgprint("You can not prepare timesheet for future date")
|
format = "%H:%M"
|
||||||
raise Exception
|
else:
|
||||||
|
format = "%H:%M:%S"
|
||||||
chk = sql("select name from `tabTimesheet` where timesheet_date=%s and owner=%s and status!='Cancelled' and name!=%s", (self.doc.timesheet_date, self.doc.owner, self.doc.name))
|
|
||||||
if chk:
|
return time.strptime(timestr, format)
|
||||||
msgprint("You have already created timesheet "+ cstr(chk and chk[0][0] or '')+" for this date.")
|
|
||||||
raise Exception
|
def validate(self):
|
||||||
|
if getdate(self.doc.timesheet_date) > getdate(nowdate()):
|
||||||
|
msgprint("You can not prepare timesheet for future date")
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
chk = sql("select name from `tabTimesheet` where timesheet_date=%s and owner=%s and status!='Cancelled' and name!=%s", (self.doc.timesheet_date, self.doc.owner, self.doc.name))
|
||||||
|
if chk:
|
||||||
|
msgprint("You have already created timesheet "+ cstr(chk and chk[0][0] or '')+" for this date.")
|
||||||
|
raise Exception
|
||||||
|
|
||||||
import time
|
for d in getlist(self.doclist, 'timesheet_details'):
|
||||||
for d in getlist(self.doclist, 'timesheet_details'):
|
if d.act_start_time and d.act_end_time:
|
||||||
if d.act_start_time and d.act_end_time:
|
d1 = self.get_time(d.act_start_time)
|
||||||
d1 = time.strptime(d.act_start_time, "%H:%M")
|
d2 = self.get_time(d.act_end_time)
|
||||||
d2 = time.strptime(d.act_end_time, "%H:%M")
|
|
||||||
|
if d1 > d2:
|
||||||
if d1 > d2:
|
msgprint("Start time can not be greater than end time. Check for Task Id : "+cstr(d.task_id))
|
||||||
msgprint("Start time can not be greater than end time. Check for Task Id : "+cstr(d.task_id))
|
raise Exception
|
||||||
raise Exception
|
elif d1 == d2:
|
||||||
elif d1 == d2:
|
msgprint("Start time and end time can not be same. Check for Task Id : "+cstr(d.task_id))
|
||||||
msgprint("Start time and end time can not be same. Check for Task Id : "+cstr(d.task_id))
|
raise Exception
|
||||||
raise Exception
|
|
||||||
|
def calculate_total_hr(self):
|
||||||
def calculate_total_hr(self):
|
for d in getlist(self.doclist, 'timesheet_details'):
|
||||||
import datetime
|
x1 = d.act_start_time.split(":")
|
||||||
import time
|
x2 = d.act_end_time.split(":")
|
||||||
for d in getlist(self.doclist, 'timesheet_details'):
|
|
||||||
x1 = d.act_start_time.split(":")
|
d1 = datetime.timedelta(minutes=cint(x1[1]), hours=cint(x1[0]))
|
||||||
x2 = d.act_end_time.split(":")
|
d2 = datetime.timedelta(minutes=cint(x2[1]), hours=cint(x2[0]))
|
||||||
|
d3 = (d2 - d1).seconds
|
||||||
d1 = datetime.timedelta(minutes=cint(x1[1]), hours=cint(x1[0]))
|
d.act_total_hrs = time.strftime("%H:%M:%S", time.gmtime(d3))
|
||||||
d2 = datetime.timedelta(minutes=cint(x2[1]), hours=cint(x2[0]))
|
sql("update `tabTimesheet Detail` set act_total_hrs = %s where parent=%s and name=%s", (d.act_total_hrs,self.doc.name,d.name))
|
||||||
d3 = (d2 - d1).seconds
|
|
||||||
d.act_total_hrs = time.strftime("%H:%M", time.gmtime(d3))
|
def on_update(self):
|
||||||
sql("update `tabTimesheet Detail` set act_total_hrs = %s where parent=%s and name=%s", (d.act_total_hrs,self.doc.name,d.name))
|
self.calculate_total_hr()
|
||||||
|
webnotes.conn.set(self.doc, 'status', 'Draft')
|
||||||
def on_update(self):
|
|
||||||
self.calculate_total_hr()
|
def on_submit(self):
|
||||||
webnotes.conn.set(self.doc, 'status', 'Draft')
|
webnotes.conn.set(self.doc, 'status', 'Submitted')
|
||||||
|
|
||||||
def on_submit(self):
|
def on_cancel(self):
|
||||||
webnotes.conn.set(self.doc, 'status', 'Submitted')
|
webnotes.conn.set(self.doc, 'status', 'Cancelled')
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
webnotes.conn.set(self.doc, 'status', 'Cancelled')
|
|
@ -97,120 +97,4 @@ $.extend(wn.modules, {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
wn.provide('erpnext.module_page');
|
wn.provide('erpnext.module_page');
|
||||||
|
|
||||||
erpnext.module_page.setup_page = function(module, wrapper) {
|
|
||||||
erpnext.module_page.hide_links(wrapper);
|
|
||||||
erpnext.module_page.make_list(module, wrapper);
|
|
||||||
$(wrapper).find("a[title]").tooltip({
|
|
||||||
delay: { show: 500, hide: 100 }
|
|
||||||
});
|
|
||||||
wrapper.appframe.add_home_breadcrumb();
|
|
||||||
wrapper.appframe.add_breadcrumb(wn.modules[module].icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide list links where the user does
|
|
||||||
// not have read permissions
|
|
||||||
|
|
||||||
erpnext.module_page.hide_links = function(wrapper) {
|
|
||||||
function replace_link(link) {
|
|
||||||
var txt = $(link).text();
|
|
||||||
$(link).parent().css('color', '#999');
|
|
||||||
$(link).replaceWith('<span title="No read permission">'
|
|
||||||
+txt+'</span>');
|
|
||||||
}
|
|
||||||
|
|
||||||
// lists
|
|
||||||
$(wrapper).find('[href*="List/"]').each(function() {
|
|
||||||
var href = $(this).attr('href');
|
|
||||||
var dt = href.split('/')[1];
|
|
||||||
if(wn.boot.profile.all_read.indexOf(dt)==-1) {
|
|
||||||
replace_link(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reports
|
|
||||||
$(wrapper).find('[data-doctype]').each(function() {
|
|
||||||
var dt = $(this).attr('data-doctype');
|
|
||||||
if(wn.boot.profile.all_read.indexOf(dt)==-1) {
|
|
||||||
replace_link(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// single (forms)
|
|
||||||
$(wrapper).find('[href*="Form/"]').each(function() {
|
|
||||||
var href = $(this).attr('href');
|
|
||||||
var dt = href.split('/')[1];
|
|
||||||
if(wn.boot.profile.all_read.indexOf(dt)==-1) {
|
|
||||||
replace_link(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// pages
|
|
||||||
$(wrapper).find('[data-role]').each(function() {
|
|
||||||
// can define multiple roles
|
|
||||||
var data_roles = $.map($(this).attr("data-role").split(","), function(role) {
|
|
||||||
return role.trim(); });
|
|
||||||
if(!has_common(user_roles, ["System Manager"].concat(data_roles))) {
|
|
||||||
var html = $(this).html();
|
|
||||||
$(this).parent().css('color', '#999');
|
|
||||||
$(this).replaceWith('<span title="Only accessible by Roles: '+
|
|
||||||
$(this).attr("data-role")
|
|
||||||
+' and System Manager">'+html+'</span>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// make list of reports
|
|
||||||
|
|
||||||
erpnext.module_page.make_list = function(module, wrapper) {
|
|
||||||
// make project listing
|
|
||||||
var $w = $(wrapper).find('.reports-list');
|
|
||||||
var $parent1 = $('<div style="width: 45%; float: left; margin-right: 4.5%"></div>').appendTo($w);
|
|
||||||
var $parent2 = $('<div style="width: 45%; float: left;"></div>').appendTo($w);
|
|
||||||
|
|
||||||
wrapper.list1 = new wn.ui.Listing({
|
|
||||||
parent: $parent1,
|
|
||||||
method: 'utilities.get_sc_list',
|
|
||||||
render_row: function(row, data) {
|
|
||||||
if(!data.parent_doc_type) data.parent_doc_type = data.doc_type;
|
|
||||||
$(row).html(repl('<a href="#!Report/%(doc_type)s/%(criteria_name)s" \
|
|
||||||
data-doctype="%(parent_doc_type)s">\
|
|
||||||
%(criteria_name)s</a>', data))
|
|
||||||
},
|
|
||||||
args: { module: module },
|
|
||||||
no_refresh: true,
|
|
||||||
callback: function(r) {
|
|
||||||
erpnext.module_page.hide_links($parent1)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
wrapper.list1.run();
|
|
||||||
|
|
||||||
wrapper.list2 = new wn.ui.Listing({
|
|
||||||
parent: $parent2,
|
|
||||||
method: 'utilities.get_report_list',
|
|
||||||
render_row: function(row, data) {
|
|
||||||
data.report_type = data.is_query_report
|
|
||||||
? "query-report"
|
|
||||||
: repl("Report2/%(ref_doctype)s", data)
|
|
||||||
|
|
||||||
$(row).html(repl('<a href="#!%(report_type)s/%(name)s" \
|
|
||||||
data-doctype="%(ref_doctype)s">\
|
|
||||||
%(name)s</a>', data))
|
|
||||||
},
|
|
||||||
args: { module: module },
|
|
||||||
no_refresh: true,
|
|
||||||
callback: function(r) {
|
|
||||||
erpnext.module_page.hide_links($parent2)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
wrapper.list2.run();
|
|
||||||
|
|
||||||
// show link to all reports
|
|
||||||
$parent1.find('.list-toolbar-wrapper')
|
|
||||||
.prepend("<div class=\"show-all-reports\">\
|
|
||||||
<a href=\"#List/Search Criteria\"> [ List Of All Reports ]</a></div>");
|
|
||||||
$parent2.find('.list-toolbar-wrapper')
|
|
||||||
.prepend("<div class=\"show-all-reports\">\
|
|
||||||
<a href=\"#List/Report\"> [ List Of All Reports (New) ]</a></div>");
|
|
||||||
}
|
|
@ -108,6 +108,7 @@ erpnext.update_messages = function(reset) {
|
|||||||
show_in_circle('todays_events', r.message.todays_events);
|
show_in_circle('todays_events', r.message.todays_events);
|
||||||
show_in_circle('open_tasks', r.message.open_tasks);
|
show_in_circle('open_tasks', r.message.open_tasks);
|
||||||
show_in_circle('unanswered_questions', r.message.unanswered_questions);
|
show_in_circle('unanswered_questions', r.message.unanswered_questions);
|
||||||
|
show_in_circle('open_leads', r.message.open_leads);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
clearInterval(wn.updates.id);
|
clearInterval(wn.updates.id);
|
||||||
|
@ -11,7 +11,7 @@ erpnext.send_message = function(opts) {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "server.py",
|
url: "server.py",
|
||||||
data: {
|
data: {
|
||||||
cmd: "website.send_message",
|
cmd: "website.helpers.contact.send_message",
|
||||||
subject: opts.subject,
|
subject: opts.subject,
|
||||||
sender: opts.sender,
|
sender: opts.sender,
|
||||||
status: opts.status,
|
status: opts.status,
|
||||||
@ -34,6 +34,8 @@ function valid_email(id) {
|
|||||||
if(id.toLowerCase().search("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")==-1)
|
if(id.toLowerCase().search("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")==-1)
|
||||||
return 0; else return 1; }
|
return 0; else return 1; }
|
||||||
|
|
||||||
|
var validate_email = valid_email;
|
||||||
|
|
||||||
function get_url_arg(name) {
|
function get_url_arg(name) {
|
||||||
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
|
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
|
||||||
var regexS = "[\\?&]"+name+"=([^&#]*)";
|
var regexS = "[\\?&]"+name+"=([^&#]*)";
|
||||||
|
57
selling/doctype/lead/get_leads.py
Normal file
57
selling/doctype/lead/get_leads.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import webnotes
|
||||||
|
from webnotes.utils import cstr, cint
|
||||||
|
from webnotes.utils.email_lib.receive import POP3Mailbox
|
||||||
|
from core.doctype.communication.communication import make
|
||||||
|
|
||||||
|
class SalesMailbox(POP3Mailbox):
|
||||||
|
def setup(self):
|
||||||
|
self.settings = webnotes.doc("Sales Email Settings", "Sales Email Settings")
|
||||||
|
|
||||||
|
def check_mails(self):
|
||||||
|
return webnotes.conn.sql("select user from tabSessions where \
|
||||||
|
time_to_sec(timediff(now(), lastupdate)) < 1800")
|
||||||
|
|
||||||
|
def process_message(self, mail):
|
||||||
|
if mail.from_email == self.settings.email_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
name = webnotes.conn.get_value("Lead", {"email_id": mail.from_email}, "name")
|
||||||
|
if name:
|
||||||
|
lead = webnotes.model_wrapper("Lead", name)
|
||||||
|
lead.doc.status = "Open"
|
||||||
|
lead.doc.save()
|
||||||
|
else:
|
||||||
|
lead = webnotes.model_wrapper({
|
||||||
|
"doctype":"Lead",
|
||||||
|
"lead_name": mail.from_real_name or mail.from_email,
|
||||||
|
"email_id": mail.from_email,
|
||||||
|
"status": "Open",
|
||||||
|
"source": "Email"
|
||||||
|
})
|
||||||
|
lead.insert()
|
||||||
|
|
||||||
|
mail.save_attachments_in_doc(lead.doc)
|
||||||
|
|
||||||
|
make(content=mail.content, sender=mail.from_email,
|
||||||
|
doctype="Lead", name=lead.doc.name, lead=lead.doc.name)
|
||||||
|
|
||||||
|
def get_leads():
|
||||||
|
if cint(webnotes.conn.get_value('Sales Email Settings', None, 'extract_emails')):
|
||||||
|
SalesMailbox()
|
@ -48,6 +48,12 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
|||||||
if(cur_frm.fields_dict.contact_by.df.options.match(/^Profile/)) {
|
if(cur_frm.fields_dict.contact_by.df.options.match(/^Profile/)) {
|
||||||
cur_frm.fields_dict.contact_by.get_query = erpnext.utils.profile_query;
|
cur_frm.fields_dict.contact_by.get_query = erpnext.utils.profile_query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(in_list(user_roles,'System Manager')) {
|
||||||
|
cur_frm.page_layout.footer.help_area.innerHTML = '<hr>\
|
||||||
|
<p><a href="#Form/Sales Email Settings">Sales Email Settings</a><br>\
|
||||||
|
<span class="help">Automatically extract Leads from a mail box e.g. "sales@example.com"</span></p>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_frm.cscript.refresh_custom_buttons = function(doc) {
|
cur_frm.cscript.refresh_custom_buttons = function(doc) {
|
||||||
|
@ -77,8 +77,14 @@ class DocType(TransactionBase):
|
|||||||
event_user.person = self.doc.contact_by
|
event_user.person = self.doc.contact_by
|
||||||
event_user.save()
|
event_user.save()
|
||||||
|
|
||||||
|
def on_communication_sent(self, comm):
|
||||||
|
webnotes.conn.set(self.doc, 'status', 'Replied')
|
||||||
|
|
||||||
|
def get_sender(self, comm):
|
||||||
|
return webnotes.conn.get_value('Sales Email Settings',None,'email_id')
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
webnotes.conn.sql("""update tabCommunication set lead='' where lead=%s""",
|
webnotes.conn.sql("""delete from tabCommunication where lead=%s""",
|
||||||
self.doc.name)
|
self.doc.name)
|
||||||
webnotes.conn.sql("""update `tabSupport Ticket` set lead='' where lead=%s""",
|
webnotes.conn.sql("""update `tabSupport Ticket` set lead='' where lead=%s""",
|
||||||
self.doc.name)
|
self.doc.name)
|
@ -2,9 +2,9 @@
|
|||||||
{
|
{
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"creation": "2012-11-02 17:16:46",
|
"creation": "2013-01-10 16:34:18",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"modified": "2012-11-27 18:27:47"
|
"modified": "2013-01-16 10:51:58"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
@ -29,6 +29,7 @@
|
|||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"parenttype": "DocType",
|
"parenttype": "DocType",
|
||||||
|
"report": 1,
|
||||||
"parentfield": "permissions"
|
"parentfield": "permissions"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -39,19 +40,17 @@
|
|||||||
"description": "To manage multiple series please go to Setup > Manage Series",
|
"description": "To manage multiple series please go to Setup > Manage Series",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Naming Series",
|
"label": "Naming Series",
|
||||||
"oldfieldname": "naming_series",
|
"oldfieldname": "naming_series",
|
||||||
"permlevel": 0,
|
"options": "LEAD\nLEAD/10-11/\nLEAD/MUMBAI/",
|
||||||
"fieldname": "naming_series",
|
"fieldname": "naming_series",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"options": "LEAD\nLEAD/10-11/\nLEAD/MUMBAI/"
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Contact Name",
|
"label": "Contact Name",
|
||||||
"oldfieldname": "lead_name",
|
"oldfieldname": "lead_name",
|
||||||
@ -64,7 +63,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Email Id",
|
"label": "Email Id",
|
||||||
"oldfieldname": "email_id",
|
"oldfieldname": "email_id",
|
||||||
@ -84,29 +82,25 @@
|
|||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"default": "Open",
|
"default": "Open",
|
||||||
"trigger": "Client",
|
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"search_index": 1,
|
"search_index": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"options": "\nOpen\nAttempted to Contact\nContact in Future\nContacted\nInterested\nNot interested\nLead Lost\nConverted",
|
"options": "\nOpen\nReplied\nAttempted to Contact\nContact in Future\nContacted\nInterested\nNot interested\nLead Lost\nConverted",
|
||||||
"in_filter": 1
|
"in_filter": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Source of the lead. If via a campaign, select \"Campaign\"",
|
"description": "Source of the lead. If via a campaign, select \"Campaign\"",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Source",
|
"label": "Source",
|
||||||
"oldfieldname": "source",
|
"oldfieldname": "source",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"trigger": "Client",
|
|
||||||
"fieldname": "source",
|
"fieldname": "source",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -121,9 +115,8 @@
|
|||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"colour": "White:FFF",
|
"allow_on_submit": 0,
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Communication HTML",
|
"label": "Communication HTML",
|
||||||
"oldfieldname": "follow_up",
|
"oldfieldname": "follow_up",
|
||||||
@ -141,45 +134,41 @@
|
|||||||
{
|
{
|
||||||
"description": "Name of organization from where lead has come",
|
"description": "Name of organization from where lead has come",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Company Name",
|
"label": "Company Name",
|
||||||
"oldfieldname": "company_name",
|
"oldfieldname": "company_name",
|
||||||
"trigger": "Client",
|
|
||||||
"fieldname": "company_name",
|
"fieldname": "company_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"in_filter": 1,
|
"permlevel": 0,
|
||||||
"permlevel": 0
|
"in_filter": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Source of th",
|
"description": "Source of th",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "From Customer",
|
"label": "From Customer",
|
||||||
"oldfieldname": "customer",
|
"oldfieldname": "customer",
|
||||||
"permlevel": 0,
|
"options": "Customer",
|
||||||
"fieldname": "customer",
|
"fieldname": "customer",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"depends_on": "eval:doc.source == 'Existing Customer'",
|
"depends_on": "eval:doc.source == 'Existing Customer'",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"options": "Customer"
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Enter campaign name if the source of lead is campaign.",
|
"description": "Enter campaign name if the source of lead is campaign.",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Campaign Name",
|
"label": "Campaign Name",
|
||||||
"oldfieldname": "campaign_name",
|
"oldfieldname": "campaign_name",
|
||||||
"permlevel": 0,
|
"options": "Campaign",
|
||||||
"fieldname": "campaign_name",
|
"fieldname": "campaign_name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"depends_on": "eval:doc.source == 'Campaign'",
|
"depends_on": "eval:doc.source == 'Campaign'",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"options": "Campaign"
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
@ -190,7 +179,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Lead Type",
|
"label": "Lead Type",
|
||||||
"oldfieldname": "type",
|
"oldfieldname": "type",
|
||||||
@ -202,7 +190,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Remark",
|
"label": "Remark",
|
||||||
"oldfieldname": "remark",
|
"oldfieldname": "remark",
|
||||||
@ -220,7 +207,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Phone",
|
"label": "Phone",
|
||||||
"oldfieldname": "contact_no",
|
"oldfieldname": "contact_no",
|
||||||
@ -260,7 +246,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"description": "<a href=\"javascript:cur_frm.cscript.TerritoryHelp();\">To manage Territory, click here</a>",
|
"description": "<a href=\"javascript:cur_frm.cscript.TerritoryHelp();\">To manage Territory, click here</a>",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Territory",
|
"label": "Territory",
|
||||||
"oldfieldname": "territory",
|
"oldfieldname": "territory",
|
||||||
@ -313,17 +298,15 @@
|
|||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Country",
|
"label": "Country",
|
||||||
"oldfieldname": "country",
|
"oldfieldname": "country",
|
||||||
"trigger": "Client",
|
"options": "link:Country",
|
||||||
"fieldname": "country",
|
"fieldname": "country",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"options": "link:Country",
|
|
||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "State",
|
"label": "State",
|
||||||
"oldfieldname": "state",
|
"oldfieldname": "state",
|
||||||
@ -344,7 +327,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"oldfieldtype": "Section Break",
|
"oldfieldtype": "Section Break",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "More Info",
|
"label": "More Info",
|
||||||
"fieldname": "more_info",
|
"fieldname": "more_info",
|
||||||
@ -436,7 +418,6 @@
|
|||||||
"description": "Your sales person who will contact the lead in future",
|
"description": "Your sales person who will contact the lead in future",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"colour": "White:FFF",
|
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Next Contact By",
|
"label": "Next Contact By",
|
||||||
@ -453,7 +434,6 @@
|
|||||||
"description": "Your sales person will get a reminder on this date to contact the lead",
|
"description": "Your sales person will get a reminder on this date to contact the lead",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"colour": "White:FFF",
|
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Next Contact Date",
|
"label": "Next Contact Date",
|
||||||
@ -470,7 +450,6 @@
|
|||||||
"description": "Date on which the lead was last contacted",
|
"description": "Date on which the lead was last contacted",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Last Contact Date",
|
"label": "Last Contact Date",
|
||||||
"oldfieldname": "last_contact_date",
|
"oldfieldname": "last_contact_date",
|
||||||
@ -481,7 +460,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"oldfieldname": "company",
|
"oldfieldname": "company",
|
||||||
@ -563,21 +541,5 @@
|
|||||||
"role": "Sales User",
|
"role": "Sales User",
|
||||||
"cancel": 0,
|
"cancel": 0,
|
||||||
"permlevel": 1
|
"permlevel": 1
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"doctype": "DocPerm",
|
|
||||||
"write": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"cancel": 0,
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"doctype": "DocPerm",
|
|
||||||
"write": 1,
|
|
||||||
"role": "Guest",
|
|
||||||
"cancel": 0,
|
|
||||||
"permlevel": 0
|
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -16,7 +16,10 @@ wn.doclistviews['Lead'] = wn.views.ListView.extend({
|
|||||||
if(data.status=='Interested') {
|
if(data.status=='Interested') {
|
||||||
data.label_type = 'success'
|
data.label_type = 'success'
|
||||||
}
|
}
|
||||||
else if(['Open', 'Attempted to Contact', 'Contacted', 'Contact in Future'].indexOf(data.status)!=-1) {
|
if(data.status=="Open") {
|
||||||
|
data.label_type = "important"
|
||||||
|
}
|
||||||
|
else if(['Attempted to Contact', 'Contacted', 'Contact in Future'].indexOf(data.status)!=-1) {
|
||||||
data.label_type = 'info'
|
data.label_type = 'info'
|
||||||
}
|
}
|
||||||
data.status_html = repl('<span class="label label-%(label_type)s">%(status)s</span>', data);
|
data.status_html = repl('<span class="label label-%(label_type)s">%(status)s</span>', data);
|
||||||
|
@ -35,10 +35,7 @@ class DocType(DocTypeNestedSet):
|
|||||||
(self.doc.customer_group_name)):
|
(self.doc.customer_group_name)):
|
||||||
msgprint("""Another %s record is trashed.
|
msgprint("""Another %s record is trashed.
|
||||||
To untrash please go to Setup -> Recycle Bin.""" %
|
To untrash please go to Setup -> Recycle Bin.""" %
|
||||||
(self.doc.customer_group_name), raise_exception = 1)
|
(self.doc.customer_group_name), raise_exception = 1)
|
||||||
|
|
||||||
super(DocType, self).validate()
|
|
||||||
|
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
cust = sql("select name from `tabCustomer` where ifnull(customer_group, '') = %s",
|
cust = sql("select name from `tabCustomer` where ifnull(customer_group, '') = %s",
|
||||||
|
0
setup/doctype/jobs_email_settings/__init__.py
Normal file
0
setup/doctype/jobs_email_settings/__init__.py
Normal file
12
setup/doctype/jobs_email_settings/jobs_email_settings.js
Normal file
12
setup/doctype/jobs_email_settings/jobs_email_settings.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
cur_frm.cscript = {
|
||||||
|
refresh: function(doc) {
|
||||||
|
cur_frm.set_intro("");
|
||||||
|
if(doc.extract_emails) {
|
||||||
|
cur_frm.set_intro(wn._("Active: Will extract emails from ") + doc.email_id);
|
||||||
|
} else {
|
||||||
|
cur_frm.set_intro(wn._("Not Active"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
setup/doctype/jobs_email_settings/jobs_email_settings.py
Normal file
17
setup/doctype/jobs_email_settings/jobs_email_settings.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import webnotes
|
||||||
|
from webnotes import _
|
||||||
|
from webnotes.utils import cint
|
||||||
|
|
||||||
|
class DocType:
|
||||||
|
def __init__(self, d, dl):
|
||||||
|
self.doc, self.doclist = d, dl
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
if cint(self.doc.extract_emails) and not (self.doc.email_id and self.doc.host and \
|
||||||
|
self.doc.username and self.doc.password):
|
||||||
|
|
||||||
|
webnotes.msgprint(_("""Host, Email and Password required if emails are to be pulled"""),
|
||||||
|
raise_exception=True)
|
89
setup/doctype/jobs_email_settings/jobs_email_settings.txt
Normal file
89
setup/doctype/jobs_email_settings/jobs_email_settings.txt
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"owner": "Administrator",
|
||||||
|
"docstatus": 0,
|
||||||
|
"creation": "2013-01-15 16:50:01",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"modified": "2013-01-15 16:57:08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issingle": 1,
|
||||||
|
"description": "Email settings for jobs email id \"jobs@example.com\"",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"module": "Setup",
|
||||||
|
"name": "__common__"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "__common__",
|
||||||
|
"parent": "Jobs Email Settings",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parent": "Jobs Email Settings",
|
||||||
|
"read": 1,
|
||||||
|
"name": "__common__",
|
||||||
|
"create": 1,
|
||||||
|
"doctype": "DocPerm",
|
||||||
|
"write": 1,
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"role": "System Manager",
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "permissions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jobs Email Settings",
|
||||||
|
"doctype": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Settings to extract Job Applicants from a mailbox e.g. \"jobs@example.com\"",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "POP3 Mail Settings",
|
||||||
|
"fieldname": "pop3_mail_settings",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Check to activate",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Extract Emails",
|
||||||
|
"fieldname": "extract_emails",
|
||||||
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Email Id where a job applicant will email e.g. \"jobs@example.com\"",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Email Id",
|
||||||
|
"fieldname": "email_id",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "POP3 server e.g. (pop.gmail.com)",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Host",
|
||||||
|
"fieldname": "host",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Use SSL",
|
||||||
|
"fieldname": "use_ssl",
|
||||||
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Username",
|
||||||
|
"fieldname": "username",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Password",
|
||||||
|
"fieldname": "password",
|
||||||
|
"fieldtype": "Password"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocPerm"
|
||||||
|
}
|
||||||
|
]
|
0
setup/doctype/sales_email_settings/__init__.py
Normal file
0
setup/doctype/sales_email_settings/__init__.py
Normal file
12
setup/doctype/sales_email_settings/sales_email_settings.js
Normal file
12
setup/doctype/sales_email_settings/sales_email_settings.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
cur_frm.cscript = {
|
||||||
|
refresh: function(doc) {
|
||||||
|
cur_frm.set_intro("");
|
||||||
|
if(doc.extract_emails) {
|
||||||
|
cur_frm.set_intro(wn._("Active: Will extract emails from ") + doc.email_id);
|
||||||
|
} else {
|
||||||
|
cur_frm.set_intro(wn._("Not Active"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
setup/doctype/sales_email_settings/sales_email_settings.py
Normal file
17
setup/doctype/sales_email_settings/sales_email_settings.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import webnotes
|
||||||
|
from webnotes import _
|
||||||
|
from webnotes.utils import cint
|
||||||
|
|
||||||
|
class DocType:
|
||||||
|
def __init__(self, d, dl):
|
||||||
|
self.doc, self.doclist = d, dl
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
if cint(self.doc.extract_emails) and not (self.doc.email_id and self.doc.host and \
|
||||||
|
self.doc.username and self.doc.password):
|
||||||
|
|
||||||
|
webnotes.msgprint(_("""Host, Email and Password required if emails are to be pulled"""),
|
||||||
|
raise_exception=True)
|
89
setup/doctype/sales_email_settings/sales_email_settings.txt
Normal file
89
setup/doctype/sales_email_settings/sales_email_settings.txt
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"owner": "Administrator",
|
||||||
|
"docstatus": 0,
|
||||||
|
"creation": "2013-01-16 10:25:26",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"modified": "2013-01-16 10:25:26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issingle": 1,
|
||||||
|
"description": "Email settings to extract Leads from sales email id e.g. \"sales@example.com\"",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"module": "Setup",
|
||||||
|
"name": "__common__"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "__common__",
|
||||||
|
"parent": "Sales Email Settings",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parent": "Sales Email Settings",
|
||||||
|
"read": 1,
|
||||||
|
"name": "__common__",
|
||||||
|
"create": 1,
|
||||||
|
"doctype": "DocPerm",
|
||||||
|
"write": 1,
|
||||||
|
"parenttype": "DocType",
|
||||||
|
"role": "System Manager",
|
||||||
|
"permlevel": 0,
|
||||||
|
"parentfield": "permissions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sales Email Settings",
|
||||||
|
"doctype": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Email settings to extract Leads from sales email id e.g. \"sales@example.com\"",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "POP3 Mail Settings",
|
||||||
|
"fieldname": "pop3_mail_settings",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Check to activate",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Extract Emails",
|
||||||
|
"fieldname": "extract_emails",
|
||||||
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Email Id where a job applicant will email e.g. \"jobs@example.com\"",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Email Id",
|
||||||
|
"fieldname": "email_id",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "POP3 server e.g. (pop.gmail.com)",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Host",
|
||||||
|
"fieldname": "host",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Use SSL",
|
||||||
|
"fieldname": "use_ssl",
|
||||||
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Username",
|
||||||
|
"fieldname": "username",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"label": "Password",
|
||||||
|
"fieldname": "password",
|
||||||
|
"fieldtype": "Password"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocPerm"
|
||||||
|
}
|
||||||
|
]
|
@ -32,7 +32,4 @@ class DocType(DocTypeNestedSet):
|
|||||||
for d in getlist(self.doclist, 'target_details'):
|
for d in getlist(self.doclist, 'target_details'):
|
||||||
if not flt(d.target_qty) and not flt(d.target_amount):
|
if not flt(d.target_qty) and not flt(d.target_amount):
|
||||||
webnotes.msgprint("Either target qty or target amount is mandatory.")
|
webnotes.msgprint("Either target qty or target amount is mandatory.")
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
super(DocType, self).validate()
|
|
||||||
|
|
@ -32,7 +32,4 @@ class DocType(DocTypeNestedSet):
|
|||||||
for d in getlist(self.doclist, 'target_details'):
|
for d in getlist(self.doclist, 'target_details'):
|
||||||
if not flt(d.target_qty) and not flt(d.target_amount):
|
if not flt(d.target_qty) and not flt(d.target_amount):
|
||||||
msgprint("Either target qty or target amount is mandatory.")
|
msgprint("Either target qty or target amount is mandatory.")
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
super(DocType, self).validate()
|
|
||||||
|
|
@ -116,6 +116,18 @@ wn.module_page["Setup"] = [
|
|||||||
label: wn._("Email Settings"),
|
label: wn._("Email Settings"),
|
||||||
"description":wn._("Out going mail server and support ticket mailbox")
|
"description":wn._("Out going mail server and support ticket mailbox")
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"route":"Form/Sales Email Settings",
|
||||||
|
doctype:"Sales Email Settings",
|
||||||
|
label: wn._("Sales Email Settings"),
|
||||||
|
"description":wn._("Extract Leads from sales email id e.g. sales@example.com")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route":"Form/Jobs Email Settings",
|
||||||
|
doctype:"Jobs Email Settings",
|
||||||
|
label: wn._("Jobs Email Settings"),
|
||||||
|
"description":wn._("Extract Job Applicant from jobs email id e.g. jobs@example.com")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"route":"Form/Notification Control/Notification Control",
|
"route":"Form/Notification Control/Notification Control",
|
||||||
doctype:"Notification Control",
|
doctype:"Notification Control",
|
||||||
|
@ -17,6 +17,7 @@ queries = {
|
|||||||
"Purchase Invoice": {"docstatus":0},
|
"Purchase Invoice": {"docstatus":0},
|
||||||
"Leave Application": {"status":"Open"},
|
"Leave Application": {"status":"Open"},
|
||||||
"Expense Claim": {"approval_status":"Draft"},
|
"Expense Claim": {"approval_status":"Draft"},
|
||||||
|
"Job Applicant": {"status":"Open"},
|
||||||
"Purchase Receipt": {"docstatus":0},
|
"Purchase Receipt": {"docstatus":0},
|
||||||
"Delivery Note": {"docstatus":0},
|
"Delivery Note": {"docstatus":0},
|
||||||
"Stock Entry": {"docstatus":0},
|
"Stock Entry": {"docstatus":0},
|
||||||
|
@ -61,7 +61,8 @@ data_map = {
|
|||||||
# Stock
|
# Stock
|
||||||
"Item": {
|
"Item": {
|
||||||
"columns": ["name", "if(item_name=name, '', item_name) as item_name", "description",
|
"columns": ["name", "if(item_name=name, '', item_name) as item_name", "description",
|
||||||
"item_group as parent_item_group", "stock_uom", "brand", "valuation_method"],
|
"item_group as parent_item_group", "stock_uom", "brand", "valuation_method",
|
||||||
|
"re_order_level", "re_order_qty"],
|
||||||
# "conditions": ["docstatus < 2"],
|
# "conditions": ["docstatus < 2"],
|
||||||
"order_by": "name",
|
"order_by": "name",
|
||||||
"links": {
|
"links": {
|
||||||
|
@ -26,10 +26,15 @@ def execute_all():
|
|||||||
* recurring invoice
|
* recurring invoice
|
||||||
"""
|
"""
|
||||||
# pull emails
|
# pull emails
|
||||||
from support.doctype.support_ticket import get_support_mails
|
from support.doctype.support_ticket.get_support_mails import get_support_mails
|
||||||
run_fn(get_support_mails)
|
run_fn(get_support_mails)
|
||||||
|
|
||||||
# bulk email
|
from hr.doctype.job_applicant.get_job_applications import get_job_applications
|
||||||
|
run_fn(get_job_applications)
|
||||||
|
|
||||||
|
from selling.doctype.lead.get_leads import get_leads
|
||||||
|
run_fn(get_leads)
|
||||||
|
|
||||||
from webnotes.utils.email_lib.bulk import flush
|
from webnotes.utils.email_lib.bulk import flush
|
||||||
run_fn(flush)
|
run_fn(flush)
|
||||||
|
|
||||||
|
@ -29,38 +29,39 @@ def get_unread_messages():
|
|||||||
|
|
||||||
def get_open_support_tickets():
|
def get_open_support_tickets():
|
||||||
"""Returns a count of open support tickets"""
|
"""Returns a count of open support tickets"""
|
||||||
from webnotes.utils import cint
|
|
||||||
open_support_tickets = webnotes.conn.sql("""\
|
open_support_tickets = webnotes.conn.sql("""\
|
||||||
SELECT COUNT(*) FROM `tabSupport Ticket`
|
SELECT COUNT(*) FROM `tabSupport Ticket`
|
||||||
WHERE status = 'Open'""")
|
WHERE status = 'Open'""")
|
||||||
return open_support_tickets and cint(open_support_tickets[0][0]) or 0
|
return open_support_tickets[0][0]
|
||||||
|
|
||||||
def get_open_tasks():
|
def get_open_tasks():
|
||||||
"""Returns a count of open tasks"""
|
"""Returns a count of open tasks"""
|
||||||
from webnotes.utils import cint
|
|
||||||
return webnotes.conn.sql("""\
|
return webnotes.conn.sql("""\
|
||||||
SELECT COUNT(*) FROM `tabTask`
|
SELECT COUNT(*) FROM `tabTask`
|
||||||
WHERE status = 'Open'""")[0][0]
|
WHERE status = 'Open'""")[0][0]
|
||||||
|
|
||||||
def get_things_todo():
|
def get_things_todo():
|
||||||
"""Returns a count of incomplete todos"""
|
"""Returns a count of incomplete todos"""
|
||||||
from webnotes.utils import cint
|
|
||||||
incomplete_todos = webnotes.conn.sql("""\
|
incomplete_todos = webnotes.conn.sql("""\
|
||||||
SELECT COUNT(*) FROM `tabToDo`
|
SELECT COUNT(*) FROM `tabToDo`
|
||||||
WHERE IFNULL(checked, 0) = 0
|
WHERE IFNULL(checked, 0) = 0
|
||||||
AND (owner = %s or assigned_by=%s)""", (webnotes.session.user, webnotes.session.user))
|
AND (owner = %s or assigned_by=%s)""", (webnotes.session.user, webnotes.session.user))
|
||||||
return incomplete_todos and cint(incomplete_todos[0][0]) or 0
|
return incomplete_todos[0][0]
|
||||||
|
|
||||||
def get_todays_events():
|
def get_todays_events():
|
||||||
"""Returns a count of todays events in calendar"""
|
"""Returns a count of todays events in calendar"""
|
||||||
from webnotes.utils import nowdate, cint
|
from webnotes.utils import nowdate
|
||||||
todays_events = webnotes.conn.sql("""\
|
todays_events = webnotes.conn.sql("""\
|
||||||
SELECT COUNT(*) FROM `tabEvent`
|
SELECT COUNT(*) FROM `tabEvent`
|
||||||
WHERE owner = %s
|
WHERE owner = %s
|
||||||
AND event_type != 'Cancel'
|
AND event_type != 'Cancel'
|
||||||
AND event_date = %s""", (
|
AND event_date = %s""", (
|
||||||
webnotes.session.get('user'), nowdate()))
|
webnotes.session.user, nowdate()))
|
||||||
return todays_events and cint(todays_events[0][0]) or 0
|
return todays_events[0][0]
|
||||||
|
|
||||||
|
def get_open_leads():
|
||||||
|
return webnotes.conn.sql("""select count(*) from tabLead
|
||||||
|
where status='Open'""")[0][0]
|
||||||
|
|
||||||
def get_unanswered_questions():
|
def get_unanswered_questions():
|
||||||
return len(filter(lambda d: d[0]==0,
|
return len(filter(lambda d: d[0]==0,
|
||||||
@ -75,5 +76,6 @@ def get_global_status_messages(arg=None):
|
|||||||
'things_todo': get_things_todo(),
|
'things_todo': get_things_todo(),
|
||||||
'todays_events': get_todays_events(),
|
'todays_events': get_todays_events(),
|
||||||
'open_tasks': get_open_tasks(),
|
'open_tasks': get_open_tasks(),
|
||||||
'unanswered_questions': get_unanswered_questions()
|
'unanswered_questions': get_unanswered_questions(),
|
||||||
|
'open_leads': get_open_leads()
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
{
|
{
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"creation": "2012-12-17 14:56:32",
|
"creation": "2012-12-28 11:01:35",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"modified": "2012-12-27 10:36:56"
|
"modified": "2013-01-16 11:14:57"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_attach": 1,
|
"allow_attach": 1,
|
||||||
"search_fields": "item_name,description,item_group,customer_code",
|
"search_fields": "item_name,description,item_group,customer_code",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"autoname": "field:item_code",
|
||||||
"document_type": "Master",
|
"document_type": "Master",
|
||||||
"description": "A Product or a Service that is bought, sold or kept in stock.",
|
"description": "A Product or a Service that is bought, sold or kept in stock.",
|
||||||
"autoname": "field:item_code",
|
|
||||||
"name": "__common__",
|
"name": "__common__",
|
||||||
"default_print_format": "Standard",
|
"default_print_format": "Standard",
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"doctype": "DocType",
|
|
||||||
"max_attachments": 1
|
"max_attachments": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -30,7 +30,9 @@
|
|||||||
"name": "__common__",
|
"name": "__common__",
|
||||||
"parent": "Item",
|
"parent": "Item",
|
||||||
"read": 1,
|
"read": 1,
|
||||||
|
"submit": 0,
|
||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
|
"report": 1,
|
||||||
"parenttype": "DocType",
|
"parenttype": "DocType",
|
||||||
"parentfield": "permissions"
|
"parentfield": "permissions"
|
||||||
},
|
},
|
||||||
@ -860,46 +862,6 @@
|
|||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"create": 0,
|
|
||||||
"doctype": "DocPerm",
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"role": "Material Manager",
|
|
||||||
"permlevel": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"create": 0,
|
|
||||||
"doctype": "DocPerm",
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"role": "Material Manager",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"create": 0,
|
|
||||||
"doctype": "DocPerm",
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"role": "Material User",
|
|
||||||
"permlevel": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"create": 0,
|
|
||||||
"doctype": "DocPerm",
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"role": "Material User",
|
|
||||||
"permlevel": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
@ -909,23 +871,48 @@
|
|||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"amend": 0,
|
||||||
|
"create": 0,
|
||||||
|
"doctype": "DocPerm",
|
||||||
|
"write": 0,
|
||||||
|
"role": "Material Manager",
|
||||||
|
"cancel": 0,
|
||||||
|
"permlevel": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amend": 0,
|
||||||
|
"create": 0,
|
||||||
|
"doctype": "DocPerm",
|
||||||
|
"write": 0,
|
||||||
|
"role": "Material User",
|
||||||
|
"cancel": 0,
|
||||||
|
"permlevel": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amend": 0,
|
||||||
"create": 0,
|
"create": 0,
|
||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
"write": 0,
|
"write": 0,
|
||||||
"role": "Material Master Manager",
|
"role": "Material Master Manager",
|
||||||
|
"cancel": 0,
|
||||||
"permlevel": 1
|
"permlevel": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"create": 1,
|
"amend": 0,
|
||||||
|
"create": 0,
|
||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
"write": 1,
|
"write": 0,
|
||||||
"role": "System Manager",
|
"role": "Material Manager",
|
||||||
"cancel": 1,
|
"cancel": 0,
|
||||||
"permlevel": 0
|
"permlevel": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"amend": 0,
|
||||||
|
"create": 0,
|
||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
"role": "System Manager",
|
"write": 0,
|
||||||
|
"role": "Material User",
|
||||||
|
"cancel": 0,
|
||||||
"permlevel": 1
|
"permlevel": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -226,7 +226,8 @@ cur_frm.cscript.s_warehouse = function(doc, cdt, cdn) {
|
|||||||
'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse),
|
'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse),
|
||||||
'transfer_qty' : d.transfer_qty,
|
'transfer_qty' : d.transfer_qty,
|
||||||
'serial_no' : d.serial_no,
|
'serial_no' : d.serial_no,
|
||||||
'bom_no' : d.bom_no
|
'bom_no' : d.bom_no,
|
||||||
|
'qty' : d.s_warehouse ? -1* d.qty : d.qty
|
||||||
}
|
}
|
||||||
get_server_fields('get_warehouse_details', JSON.stringify(args),
|
get_server_fields('get_warehouse_details', JSON.stringify(args),
|
||||||
'mtn_details', doc, cdt, cdn, 1);
|
'mtn_details', doc, cdt, cdn, 1);
|
||||||
|
@ -25,6 +25,8 @@ from webnotes.model.code import get_obj
|
|||||||
from webnotes import msgprint, _
|
from webnotes import msgprint, _
|
||||||
from stock.utils import get_incoming_rate
|
from stock.utils import get_incoming_rate
|
||||||
from stock.stock_ledger import get_previous_sle
|
from stock.stock_ledger import get_previous_sle
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
sql = webnotes.conn.sql
|
sql = webnotes.conn.sql
|
||||||
|
|
||||||
@ -157,23 +159,46 @@ class DocType(TransactionBase):
|
|||||||
def get_stock_and_rate(self):
|
def get_stock_and_rate(self):
|
||||||
"""get stock and incoming rate on posting date"""
|
"""get stock and incoming rate on posting date"""
|
||||||
for d in getlist(self.doclist, 'mtn_details'):
|
for d in getlist(self.doclist, 'mtn_details'):
|
||||||
args = {
|
args = webnotes._dict({
|
||||||
"item_code": d.item_code,
|
"item_code": d.item_code,
|
||||||
"warehouse": d.s_warehouse or d.t_warehouse,
|
"warehouse": d.s_warehouse or d.t_warehouse,
|
||||||
"posting_date": self.doc.posting_date,
|
"posting_date": self.doc.posting_date,
|
||||||
"posting_time": self.doc.posting_time,
|
"posting_time": self.doc.posting_time,
|
||||||
"qty": d.transfer_qty,
|
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
|
||||||
"serial_no": d.serial_no,
|
"serial_no": d.serial_no,
|
||||||
"bom_no": d.bom_no
|
"bom_no": d.bom_no,
|
||||||
}
|
})
|
||||||
# get actual stock at source warehouse
|
# get actual stock at source warehouse
|
||||||
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
|
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
|
||||||
|
|
||||||
# get incoming rate
|
# get incoming rate
|
||||||
if not flt(d.incoming_rate):
|
if not flt(d.incoming_rate) or self.doc.purpose == "Sales Return":
|
||||||
d.incoming_rate = get_incoming_rate(args)
|
d.incoming_rate = self.get_incoming_rate(args)
|
||||||
|
|
||||||
d.amount = flt(d.qty) * flt(d.incoming_rate)
|
d.amount = flt(d.qty) * flt(d.incoming_rate)
|
||||||
|
|
||||||
|
def get_incoming_rate(self, args):
|
||||||
|
if self.doc.purpose == "Sales Return" and \
|
||||||
|
(self.doc.delivery_note_no or self.doc.sales_invoice_no):
|
||||||
|
sle = webnotes.conn.sql("""select name, posting_date, posting_time,
|
||||||
|
actual_qty, stock_value from `tabStock Ledger Entry`
|
||||||
|
where voucher_type = %s and voucher_no = %s and
|
||||||
|
item_code = %s and ifnull(is_cancelled, 'No') = 'No' limit 1""",
|
||||||
|
((self.doc.delivery_note_no and "Delivery Note" or "Sales Invoice"),
|
||||||
|
self.doc.delivery_note_no or self.doc.sales_invoice_no, args.item_code), as_dict=1)
|
||||||
|
if sle:
|
||||||
|
args.update({
|
||||||
|
"posting_date": sle[0].posting_date,
|
||||||
|
"posting_time": sle[0].posting_time,
|
||||||
|
"sle": sle[0].name
|
||||||
|
})
|
||||||
|
previous_sle = get_previous_sle(args)
|
||||||
|
incoming_rate = (flt(sle[0].stock_value) - flt(previous_sle.get("stock_value"))) / \
|
||||||
|
flt(sle[0].actual_qty)
|
||||||
|
else:
|
||||||
|
incoming_rate = get_incoming_rate(args)
|
||||||
|
|
||||||
|
return incoming_rate
|
||||||
|
|
||||||
def validate_incoming_rate(self):
|
def validate_incoming_rate(self):
|
||||||
for d in getlist(self.doclist, 'mtn_details'):
|
for d in getlist(self.doclist, 'mtn_details'):
|
||||||
@ -264,8 +289,7 @@ class DocType(TransactionBase):
|
|||||||
pro_obj.doc.save()
|
pro_obj.doc.save()
|
||||||
|
|
||||||
def get_item_details(self, arg):
|
def get_item_details(self, arg):
|
||||||
import json
|
arg = json.loads(arg)
|
||||||
arg, actual_qty, in_rate = json.loads(arg), 0, 0
|
|
||||||
|
|
||||||
item = sql("""select stock_uom, description, item_name from `tabItem`
|
item = sql("""select stock_uom, description, item_name from `tabItem`
|
||||||
where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'
|
where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'
|
||||||
@ -305,16 +329,16 @@ class DocType(TransactionBase):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_warehouse_details(self, args):
|
def get_warehouse_details(self, args):
|
||||||
import json
|
args = json.loads(args)
|
||||||
args, actual_qty, in_rate = json.loads(args), 0, 0
|
|
||||||
args.update({
|
args.update({
|
||||||
"posting_date": self.doc.posting_date,
|
"posting_date": self.doc.posting_date,
|
||||||
"posting_time": self.doc.posting_time
|
"posting_time": self.doc.posting_time,
|
||||||
})
|
})
|
||||||
|
args = webnotes._dict(args)
|
||||||
|
|
||||||
ret = {
|
ret = {
|
||||||
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
|
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
|
||||||
"incoming_rate" : get_incoming_rate(args)
|
"incoming_rate" : self.get_incoming_rate(args)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -55,7 +55,14 @@ class DocType(DocListController):
|
|||||||
|
|
||||||
self.validation_messages = []
|
self.validation_messages = []
|
||||||
item_warehouse_combinations = []
|
item_warehouse_combinations = []
|
||||||
for row_num, row in enumerate(data[data.index(self.head_row)+1:]):
|
|
||||||
|
# validate no of rows
|
||||||
|
rows = data[data.index(self.head_row)+1:]
|
||||||
|
if len(rows) > 100:
|
||||||
|
msgprint(_("""Sorry! We can only allow upto 100 rows for Stock Reconciliation."""),
|
||||||
|
raise_exception=True)
|
||||||
|
|
||||||
|
for row_num, row in enumerate(rows):
|
||||||
# find duplicates
|
# find duplicates
|
||||||
if [row[0], row[1]] in item_warehouse_combinations:
|
if [row[0], row[1]] in item_warehouse_combinations:
|
||||||
self.validation_messages.append(_get_msg(row_num, "Duplicate entry"))
|
self.validation_messages.append(_get_msg(row_num, "Duplicate entry"))
|
||||||
@ -249,8 +256,6 @@ class DocType(DocListController):
|
|||||||
""" Delete Stock Ledger Entries related to this Stock Reconciliation
|
""" Delete Stock Ledger Entries related to this Stock Reconciliation
|
||||||
and repost future Stock Ledger Entries"""
|
and repost future Stock Ledger Entries"""
|
||||||
|
|
||||||
from stock.stock_ledger import update_entries_after
|
|
||||||
|
|
||||||
existing_entries = webnotes.conn.sql("""select item_code, warehouse
|
existing_entries = webnotes.conn.sql("""select item_code, warehouse
|
||||||
from `tabStock Ledger Entry` where voucher_type='Stock Reconciliation'
|
from `tabStock Ledger Entry` where voucher_type='Stock Reconciliation'
|
||||||
and voucher_no=%s""", self.doc.name, as_dict=1)
|
and voucher_no=%s""", self.doc.name, as_dict=1)
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
{
|
{
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"creation": "2013-01-14 15:14:40",
|
"creation": "2013-01-15 12:28:57",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"modified": "2013-01-15 12:25:13"
|
"modified": "2013-01-16 13:59:28"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_attach": 0,
|
"allow_attach": 0,
|
||||||
@ -13,6 +13,7 @@
|
|||||||
"search_fields": "posting_date",
|
"search_fields": "posting_date",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
|
"read_only_onload": 0,
|
||||||
"autoname": "SR/.######",
|
"autoname": "SR/.######",
|
||||||
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
|
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
|
||||||
"allow_email": 1,
|
"allow_email": 1,
|
||||||
@ -33,7 +34,7 @@
|
|||||||
"read": 1,
|
"read": 1,
|
||||||
"cancel": 1,
|
"cancel": 1,
|
||||||
"name": "__common__",
|
"name": "__common__",
|
||||||
"amend": 0,
|
"amend": 1,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"doctype": "DocPerm",
|
"doctype": "DocPerm",
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
|
@ -54,7 +54,11 @@ erpnext.StockAgeing = erpnext.StockGridReport.extend({
|
|||||||
{id: "earliest", name: "Earliest", field: "earliest",
|
{id: "earliest", name: "Earliest", field: "earliest",
|
||||||
formatter: this.currency_formatter},
|
formatter: this.currency_formatter},
|
||||||
{id: "latest", name: "Latest", field: "latest",
|
{id: "latest", name: "Latest", field: "latest",
|
||||||
formatter: this.currency_formatter}
|
formatter: this.currency_formatter},
|
||||||
|
{id: "item_name", name: "Item Name", field: "item_name",
|
||||||
|
width: 100, formatter: this.text_formatter},
|
||||||
|
{id: "description", name: "Description", field: "description",
|
||||||
|
width: 200, formatter: this.text_formatter},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
filters: [
|
filters: [
|
||||||
|
@ -83,6 +83,10 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
|
|||||||
field: "reserved_qty", width: 80, formatter: this.currency_formatter},
|
field: "reserved_qty", width: 80, formatter: this.currency_formatter},
|
||||||
{id: "projected_qty", name: "Projected Qty",
|
{id: "projected_qty", name: "Projected Qty",
|
||||||
field: "projected_qty", width: 80, formatter: this.currency_formatter},
|
field: "projected_qty", width: 80, formatter: this.currency_formatter},
|
||||||
|
{id: "re_order_level", name: "Re-Order Level",
|
||||||
|
field: "re_order_level", width: 80, formatter: this.currency_formatter},
|
||||||
|
{id: "re_order_qty", name: "Re-Order Qty",
|
||||||
|
field: "re_order_qty", width: 80, formatter: this.currency_formatter},
|
||||||
{id: "uom", name: "UOM", field: "uom", width: 60},
|
{id: "uom", name: "UOM", field: "uom", width: 60},
|
||||||
{id: "brand", name: "Brand", field: "brand", width: 100,
|
{id: "brand", name: "Brand", field: "brand", width: 100,
|
||||||
link_formatter: {filter_input: "brand"}},
|
link_formatter: {filter_input: "brand"}},
|
||||||
@ -171,7 +175,7 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// sort by item, warehouse
|
// sort by item, warehouse
|
||||||
this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) {
|
this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) {
|
||||||
return me.item_warehouse_map[key];
|
return me.item_warehouse_map[key];
|
||||||
@ -202,6 +206,10 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
|
|||||||
id: key,
|
id: key,
|
||||||
}
|
}
|
||||||
this.reset_item_values(row);
|
this.reset_item_values(row);
|
||||||
|
|
||||||
|
row["re_order_level"] = item.re_order_level
|
||||||
|
row["re_order_qty"] = item.re_order_qty
|
||||||
|
|
||||||
this.item_warehouse_map[key] = row;
|
this.item_warehouse_map[key] = row;
|
||||||
}
|
}
|
||||||
return this.item_warehouse_map[key];
|
return this.item_warehouse_map[key];
|
||||||
|
@ -78,12 +78,11 @@ def get_incoming_rate(args):
|
|||||||
valuation_method = get_valuation_method(args.get("item_code"))
|
valuation_method = get_valuation_method(args.get("item_code"))
|
||||||
previous_sle = get_previous_sle(args)
|
previous_sle = get_previous_sle(args)
|
||||||
if valuation_method == 'FIFO':
|
if valuation_method == 'FIFO':
|
||||||
# get rate based on the last item value?
|
if not previous_sle:
|
||||||
if args.get("qty"):
|
return 0.0
|
||||||
if not previous_sle:
|
previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]'))
|
||||||
return 0.0
|
in_rate = previous_stock_queue and \
|
||||||
stock_queue = json.loads(previous_sle.get('stock_queue', '[]'))
|
get_fifo_rate(previous_stock_queue, args.get("qty") or 0) or 0
|
||||||
in_rate = stock_queue and get_fifo_rate(stock_queue) or 0
|
|
||||||
elif valuation_method == 'Moving Average':
|
elif valuation_method == 'Moving Average':
|
||||||
in_rate = previous_sle.get('valuation_rate') or 0
|
in_rate = previous_sle.get('valuation_rate') or 0
|
||||||
return in_rate
|
return in_rate
|
||||||
@ -104,13 +103,29 @@ def get_valuation_method(item_code):
|
|||||||
val_method = get_defaults().get('valuation_method', 'FIFO')
|
val_method = get_defaults().get('valuation_method', 'FIFO')
|
||||||
return val_method
|
return val_method
|
||||||
|
|
||||||
def get_fifo_rate(stock_queue):
|
def get_fifo_rate(previous_stock_queue, qty):
|
||||||
"""get FIFO (average) Rate from Stack"""
|
"""get FIFO (average) Rate from Queue"""
|
||||||
if not stock_queue:
|
if qty >= 0:
|
||||||
return 0.0
|
total = sum(f[0] for f in previous_stock_queue)
|
||||||
|
return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0
|
||||||
total = sum(f[0] for f in stock_queue)
|
else:
|
||||||
return total and sum(f[0] * f[1] for f in stock_queue) / flt(total) or 0.0
|
outgoing_cost = 0
|
||||||
|
qty_to_pop = abs(qty)
|
||||||
|
while qty_to_pop:
|
||||||
|
batch = previous_stock_queue[0]
|
||||||
|
if 0 < batch[0] <= qty_to_pop:
|
||||||
|
# if batch qty > 0
|
||||||
|
# not enough or exactly same qty in current batch, clear batch
|
||||||
|
outgoing_cost += flt(batch[0]) * flt(batch[1])
|
||||||
|
qty_to_pop -= batch[0]
|
||||||
|
previous_stock_queue.pop(0)
|
||||||
|
else:
|
||||||
|
# all from current batch
|
||||||
|
outgoing_cost += flt(qty_to_pop) * flt(batch[1])
|
||||||
|
batch[0] -= qty_to_pop
|
||||||
|
qty_to_pop = 0
|
||||||
|
|
||||||
|
return outgoing_cost / abs(qty)
|
||||||
|
|
||||||
def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
|
def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
|
||||||
"""split serial nos, validate and return list of valid serial nos"""
|
"""split serial nos, validate and return list of valid serial nos"""
|
||||||
|
@ -13,15 +13,12 @@ wn.doclistviews['Maintenance Visit'] = wn.views.ListView.extend({
|
|||||||
|
|
||||||
]);
|
]);
|
||||||
this.stats = this.stats.concat(['completion_status', 'company']);
|
this.stats = this.stats.concat(['completion_status', 'company']);
|
||||||
//this.show_hide_check_column();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
prepare_data: function(data) {
|
prepare_data: function(data) {
|
||||||
this._super(data);
|
this._super(data);
|
||||||
data.mntc_date = wn.datetime.str_to_user(data.mntc_date);
|
data.mntc_date = wn.datetime.str_to_user(data.mntc_date);
|
||||||
data.mntc_time = wn.datetime.time_to_ampm(data.mntc_time);
|
data.date_time = "on " + data.mntc_date + " at " + data.mntc_time;
|
||||||
data.date_time = "on " + data.mntc_date + " at " +
|
|
||||||
data.mntc_time[0] + ":" + data.mntc_time[1] + " " + data.mntc_time[2];
|
|
||||||
data.customer_name = data.customer_name + " " + data.date_time;
|
data.customer_name = data.customer_name + " " + data.date_time;
|
||||||
data.completion_status = data.completion_status +
|
data.completion_status = data.completion_status +
|
||||||
(data.maintenance_type ? " [" + data.maintenance_type + "]": "");
|
(data.maintenance_type ? " [" + data.maintenance_type + "]": "");
|
||||||
|
@ -87,7 +87,7 @@ class DocType():
|
|||||||
|
|
||||||
args = self.dt_map[doctype]
|
args = self.dt_map[doctype]
|
||||||
|
|
||||||
sender = self.doc.send_from or webnotes.utils.get_email_id(self.doc.owner)
|
sender = self.doc.send_from or webnotes.utils.get_formatted_email(self.doc.owner)
|
||||||
recipients = self.doc.test_email_id.split(",")
|
recipients = self.doc.test_email_id.split(",")
|
||||||
from webnotes.utils.email_lib.bulk import send
|
from webnotes.utils.email_lib.bulk import send
|
||||||
send(recipients = recipients, sender = sender,
|
send(recipients = recipients, sender = sender,
|
||||||
@ -109,7 +109,7 @@ class DocType():
|
|||||||
recipients = self.get_recipients(query_key)
|
recipients = self.get_recipients(query_key)
|
||||||
else:
|
else:
|
||||||
recipients = query_key
|
recipients = query_key
|
||||||
sender = self.doc.send_from or webnotes.utils.get_email_id(self.doc.owner)
|
sender = self.doc.send_from or webnotes.utils.get_formatted_email(self.doc.owner)
|
||||||
args = self.dt_map[doctype]
|
args = self.dt_map[doctype]
|
||||||
self.send_count[doctype] = self.send_count.setdefault(doctype, 0) + \
|
self.send_count[doctype] = self.send_count.setdefault(doctype, 0) + \
|
||||||
len(recipients)
|
len(recipients)
|
||||||
|
@ -1,183 +0,0 @@
|
|||||||
# 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import webnotes
|
|
||||||
from webnotes.utils import cstr, cint
|
|
||||||
|
|
||||||
from webnotes.utils.email_lib.receive import POP3Mailbox
|
|
||||||
|
|
||||||
class SupportMailbox(POP3Mailbox):
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
settings_doc must contain
|
|
||||||
use_ssl, host, username, password
|
|
||||||
"""
|
|
||||||
from webnotes.model.doc import Document
|
|
||||||
|
|
||||||
# extract email settings
|
|
||||||
self.email_settings = Document('Email Settings','Email Settings')
|
|
||||||
if not self.email_settings.fields.get('sync_support_mails'): return
|
|
||||||
|
|
||||||
s = Document('Support Email Settings')
|
|
||||||
s.use_ssl = self.email_settings.support_use_ssl
|
|
||||||
s.host = self.email_settings.support_host
|
|
||||||
s.username = self.email_settings.support_username
|
|
||||||
s.password = self.email_settings.support_password
|
|
||||||
|
|
||||||
POP3Mailbox.__init__(self, s)
|
|
||||||
|
|
||||||
def check_mails(self):
|
|
||||||
"""
|
|
||||||
returns true if there are active sessions
|
|
||||||
"""
|
|
||||||
self.auto_close_tickets()
|
|
||||||
return webnotes.conn.sql("select user from tabSessions where time_to_sec(timediff(now(), lastupdate)) < 1800")
|
|
||||||
|
|
||||||
def process_message(self, mail):
|
|
||||||
"""
|
|
||||||
Updates message from support email as either new or reply
|
|
||||||
"""
|
|
||||||
from home import update_feed
|
|
||||||
|
|
||||||
content, content_type = '[Blank Email]', 'text/plain'
|
|
||||||
if mail.text_content:
|
|
||||||
content, content_type = mail.text_content, 'text/plain'
|
|
||||||
else:
|
|
||||||
content, content_type = mail.html_content, 'text/html'
|
|
||||||
|
|
||||||
thread_list = mail.get_thread_id()
|
|
||||||
|
|
||||||
email_id = mail.mail['From']
|
|
||||||
if "<" in mail.mail['From']:
|
|
||||||
import re
|
|
||||||
re_result = re.findall('(?<=\<)(\S+)(?=\>)', mail.mail['From'])
|
|
||||||
if re_result and re_result[0]:
|
|
||||||
email_id = re_result[0]
|
|
||||||
|
|
||||||
from webnotes.utils import decode_email_header
|
|
||||||
|
|
||||||
full_email_id = decode_email_header(mail.mail['From'])
|
|
||||||
|
|
||||||
for thread_id in thread_list:
|
|
||||||
exists = webnotes.conn.sql("""\
|
|
||||||
SELECT name
|
|
||||||
FROM `tabSupport Ticket`
|
|
||||||
WHERE name=%s AND raised_by REGEXP %s
|
|
||||||
""" , (thread_id, '(' + email_id + ')'))
|
|
||||||
if exists and exists[0] and exists[0][0]:
|
|
||||||
st = webnotes.get_obj('Support Ticket', thread_id)
|
|
||||||
|
|
||||||
from core.doctype.communication.communication import make
|
|
||||||
|
|
||||||
make(content=content, sender=full_email_id, doctype="Support Ticket",
|
|
||||||
name=thread_id, lead = st.doc.lead, contact=st.doc.contact)
|
|
||||||
|
|
||||||
st.doc.status = 'Open'
|
|
||||||
st.doc.save()
|
|
||||||
|
|
||||||
update_feed(st, 'on_update')
|
|
||||||
# extract attachments
|
|
||||||
self.save_attachments(st.doc, mail.attachments)
|
|
||||||
return
|
|
||||||
|
|
||||||
from webnotes.model.doctype import get_property
|
|
||||||
opts = get_property('Support Ticket', 'options', 'naming_series')
|
|
||||||
# new ticket
|
|
||||||
from webnotes.model.doc import Document
|
|
||||||
d = Document('Support Ticket')
|
|
||||||
d.description = content
|
|
||||||
|
|
||||||
d.subject = decode_email_header(mail.mail['Subject'])
|
|
||||||
|
|
||||||
d.raised_by = full_email_id
|
|
||||||
d.content_type = content_type
|
|
||||||
d.status = 'Open'
|
|
||||||
d.naming_series = opts and opts.split("\n")[0] or 'SUP'
|
|
||||||
try:
|
|
||||||
d.save(1)
|
|
||||||
try:
|
|
||||||
# extract attachments
|
|
||||||
self.save_attachments(d, mail.attachments)
|
|
||||||
except Exception, e:
|
|
||||||
self.description += "\n\n[Did not pull attachment]"
|
|
||||||
except:
|
|
||||||
d.description = 'Unable to extract message'
|
|
||||||
d.save(1)
|
|
||||||
else:
|
|
||||||
# send auto reply
|
|
||||||
if cint(self.email_settings.send_autoreply):
|
|
||||||
if "mailer-daemon" not in d.raised_by.lower():
|
|
||||||
self.send_auto_reply(d)
|
|
||||||
|
|
||||||
|
|
||||||
def save_attachments(self, doc, attachment_list=[]):
|
|
||||||
"""
|
|
||||||
Saves attachments from email
|
|
||||||
|
|
||||||
attachment_list is a list of dict containing:
|
|
||||||
'filename', 'content', 'content-type'
|
|
||||||
"""
|
|
||||||
from webnotes.utils.file_manager import save_file, add_file_list
|
|
||||||
for attachment in attachment_list:
|
|
||||||
fid = save_file(attachment['filename'], attachment['content'], 'Support')
|
|
||||||
status = add_file_list('Support Ticket', doc.name, fid, fid)
|
|
||||||
if not status:
|
|
||||||
doc.description = doc.description \
|
|
||||||
+ "\nCould not attach: " + cstr(attachment['filename'])
|
|
||||||
doc.save()
|
|
||||||
|
|
||||||
def send_auto_reply(self, d):
|
|
||||||
"""
|
|
||||||
Send auto reply to emails
|
|
||||||
"""
|
|
||||||
from webnotes.utils import cstr
|
|
||||||
|
|
||||||
signature = self.email_settings.fields.get('support_signature') or ''
|
|
||||||
|
|
||||||
response = self.email_settings.fields.get('support_autoreply') or ("""
|
|
||||||
A new Ticket has been raised for your query. If you have any additional information, please
|
|
||||||
reply back to this mail.
|
|
||||||
|
|
||||||
We will get back to you as soon as possible
|
|
||||||
----------------------
|
|
||||||
Original Query:
|
|
||||||
|
|
||||||
""" + d.description + "\n----------------------\n" + cstr(signature))
|
|
||||||
|
|
||||||
from webnotes.utils.email_lib import sendmail
|
|
||||||
|
|
||||||
sendmail(\
|
|
||||||
recipients = [cstr(d.raised_by)], \
|
|
||||||
sender = cstr(self.email_settings.fields.get('support_email')), \
|
|
||||||
subject = '['+cstr(d.name)+'] ' + cstr(d.subject), \
|
|
||||||
msg = cstr(response))
|
|
||||||
|
|
||||||
def auto_close_tickets(self):
|
|
||||||
"""
|
|
||||||
Auto Closes Waiting for Customer Support Ticket after 15 days
|
|
||||||
"""
|
|
||||||
webnotes.conn.sql("update `tabSupport Ticket` set status = 'Closed' where status = 'Waiting for Customer' and date_sub(curdate(),interval 15 Day) > modified")
|
|
||||||
|
|
||||||
|
|
||||||
def get_support_mails():
|
|
||||||
"""
|
|
||||||
Gets new emails from support inbox and updates / creates Support Ticket records
|
|
||||||
"""
|
|
||||||
import webnotes
|
|
||||||
from webnotes.utils import cint
|
|
||||||
if cint(webnotes.conn.get_value('Email Settings', None, 'sync_support_mails')):
|
|
||||||
SupportMailbox().get_messages()
|
|
96
support/doctype/support_ticket/get_support_mails.py
Normal file
96
support/doctype/support_ticket/get_support_mails.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import webnotes
|
||||||
|
from webnotes.utils import cstr, cint
|
||||||
|
from webnotes.utils.email_lib import sendmail
|
||||||
|
from webnotes.utils.email_lib.receive import POP3Mailbox
|
||||||
|
from core.doctype.communication.communication import make
|
||||||
|
|
||||||
|
class SupportMailbox(POP3Mailbox):
|
||||||
|
def setup(self):
|
||||||
|
self.email_settings = webnotes.doc("Email Settings", "Email Settings")
|
||||||
|
self.settings = webnotes._dict({
|
||||||
|
"use_ssl": self.email_settings.support_use_ssl,
|
||||||
|
"host": self.email_settings.support_host,
|
||||||
|
"username": self.email_settings.support_username,
|
||||||
|
"password": self.email_settings.support_password
|
||||||
|
})
|
||||||
|
|
||||||
|
def check_mails(self):
|
||||||
|
self.auto_close_tickets()
|
||||||
|
return webnotes.conn.sql("select user from tabSessions where \
|
||||||
|
time_to_sec(timediff(now(), lastupdate)) < 1800")
|
||||||
|
|
||||||
|
def process_message(self, mail):
|
||||||
|
if mail.from_email == self.email_settings.fields.get('support_email'):
|
||||||
|
return
|
||||||
|
thread_id = mail.get_thread_id()
|
||||||
|
ticket = None
|
||||||
|
|
||||||
|
if thread_id and webnotes.conn.exists("Support Ticket", thread_id):
|
||||||
|
ticket = webnotes.model_wrapper("Support Ticket", thread_id)
|
||||||
|
ticket.doc.status = 'Open'
|
||||||
|
ticket.doc.save()
|
||||||
|
|
||||||
|
else:
|
||||||
|
ticket = webnotes.model_wrapper([{
|
||||||
|
"doctype":"Support Ticket",
|
||||||
|
"description": mail.content,
|
||||||
|
"subject": mail.mail["Subject"],
|
||||||
|
"raised_by": mail.from_email,
|
||||||
|
"content_type": mail.content_type,
|
||||||
|
"status": "Open"
|
||||||
|
}])
|
||||||
|
ticket.insert()
|
||||||
|
|
||||||
|
if cint(self.email_settings.send_autoreply):
|
||||||
|
if "mailer-daemon" not in mail.from_email.lower():
|
||||||
|
self.send_auto_reply(ticket.doc)
|
||||||
|
|
||||||
|
mail.save_attachments_in_doc(ticket.doc)
|
||||||
|
|
||||||
|
make(content=mail.content, sender=mail.from_email,
|
||||||
|
doctype="Support Ticket", name=ticket.doc.name,
|
||||||
|
lead = ticket.doc.lead, contact=ticket.doc.contact)
|
||||||
|
|
||||||
|
def send_auto_reply(self, d):
|
||||||
|
signature = self.email_settings.fields.get('support_signature') or ''
|
||||||
|
response = self.email_settings.fields.get('support_autoreply') or ("""
|
||||||
|
A new Ticket has been raised for your query. If you have any additional information, please
|
||||||
|
reply back to this mail.
|
||||||
|
|
||||||
|
We will get back to you as soon as possible
|
||||||
|
----------------------
|
||||||
|
Original Query:
|
||||||
|
|
||||||
|
""" + d.description + "\n----------------------\n" + cstr(signature))
|
||||||
|
|
||||||
|
sendmail(\
|
||||||
|
recipients = [cstr(d.raised_by)], \
|
||||||
|
sender = cstr(self.email_settings.fields.get('support_email')), \
|
||||||
|
subject = '['+cstr(d.name)+'] ' + cstr(d.subject), \
|
||||||
|
msg = cstr(response))
|
||||||
|
|
||||||
|
def auto_close_tickets(self):
|
||||||
|
webnotes.conn.sql("""update `tabSupport Ticket` set status = 'Closed'
|
||||||
|
where status = 'Waiting for Customer'
|
||||||
|
and date_sub(curdate(),interval 15 Day) > modified""")
|
||||||
|
|
||||||
|
def get_support_mails():
|
||||||
|
if cint(webnotes.conn.get_value('Email Settings', None, 'sync_support_mails')):
|
||||||
|
SupportMailbox()
|
@ -49,11 +49,17 @@ $.extend(cur_frm.cscript, {
|
|||||||
var wrapper = cur_frm.fields_dict['thread_html'].wrapper;
|
var wrapper = cur_frm.fields_dict['thread_html'].wrapper;
|
||||||
|
|
||||||
var comm_list = wn.model.get("Communication", {"support_ticket": doc.name})
|
var comm_list = wn.model.get("Communication", {"support_ticket": doc.name})
|
||||||
comm_list.push({
|
|
||||||
"sender": doc.raised_by,
|
var sortfn = function (a, b) { return (b.creation > a.creation) ? 1 : -1; }
|
||||||
"creation": doc.creation,
|
comm_list = comm_list.sort(sortfn);
|
||||||
"modified": doc.creation,
|
|
||||||
"content": doc.description});
|
if(!comm_list.length || (comm_list[comm_list.length - 1].sender != doc.raised_by)) {
|
||||||
|
comm_list.push({
|
||||||
|
"sender": doc.raised_by,
|
||||||
|
"creation": doc.creation,
|
||||||
|
"modified": doc.creation,
|
||||||
|
"content": doc.description});
|
||||||
|
}
|
||||||
|
|
||||||
cur_frm.communication_view = new wn.views.CommunicationList({
|
cur_frm.communication_view = new wn.views.CommunicationList({
|
||||||
list: comm_list,
|
list: comm_list,
|
||||||
@ -63,16 +69,7 @@ $.extend(cur_frm.cscript, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function(doc, dt, dn) {
|
|
||||||
$c_obj(make_doclist(doc.doctype, doc.name), 'send_response', '', function(r,rt) {
|
|
||||||
locals[dt][dn].new_response = '';
|
|
||||||
if(!(r.exc || r.server_messages)) {
|
|
||||||
cur_frm.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
customer: function(doc, dt, dn) {
|
customer: function(doc, dt, dn) {
|
||||||
var callback = function(r,rt) {
|
var callback = function(r,rt) {
|
||||||
var doc = locals[cur_frm.doctype][cur_frm.docname];
|
var doc = locals[cur_frm.doctype][cur_frm.docname];
|
||||||
|
@ -94,13 +94,11 @@ Calendar.prototype.show_event = function(ev, cal_ev) {
|
|||||||
d.onshow = function() {
|
d.onshow = function() {
|
||||||
// heading
|
// heading
|
||||||
var c = me.selected_date;
|
var c = me.selected_date;
|
||||||
var tmp = time_to_ampm(this.ev.event_hour);
|
|
||||||
tmp = tmp[0]+':'+tmp[1]+' '+tmp[2];
|
|
||||||
|
|
||||||
this.widgets['Heading'].innerHTML =
|
this.widgets['Heading'].innerHTML =
|
||||||
'<div style="text-align: center; padding:4px; font-size: 14px">'
|
'<div style="text-align: center; padding:4px; font-size: 14px">'
|
||||||
+ erpnext.calendar.weekdays[c.getDay()] + ', ' + c.getDate() + ' ' + month_list_full[c.getMonth()] + ' ' + c.getFullYear()
|
+ erpnext.calendar.weekdays[c.getDay()] + ', ' + c.getDate() + ' ' + month_list_full[c.getMonth()] + ' ' + c.getFullYear()
|
||||||
+ ' - <b>'+tmp+'</b></div>';
|
+ ' - <b>'+this.ev.event_hour+'</b></div>';
|
||||||
|
|
||||||
// set
|
// set
|
||||||
this.widgets['Description'].value = cstr(this.ev.description);
|
this.widgets['Description'].value = cstr(this.ev.description);
|
||||||
@ -175,7 +173,7 @@ Calendar.prototype.add_event = function() {
|
|||||||
ev = locals['Event'][ev];
|
ev = locals['Event'][ev];
|
||||||
|
|
||||||
ev.event_date = dateutil.obj_to_str(this.selected_date);
|
ev.event_date = dateutil.obj_to_str(this.selected_date);
|
||||||
ev.event_hour = this.selected_hour+':00';
|
ev.event_hour = this.selected_hour+':00:00';
|
||||||
ev.event_type = 'Private';
|
ev.event_type = 'Private';
|
||||||
|
|
||||||
this.show_event(ev);
|
this.show_event(ev);
|
||||||
@ -447,8 +445,7 @@ Calendar.DayView.prototype.create_table = function() {
|
|||||||
for(var j=0;j<2;j++) {
|
for(var j=0;j<2;j++) {
|
||||||
var cell = r.insertCell(j);
|
var cell = r.insertCell(j);
|
||||||
if(j==0) {
|
if(j==0) {
|
||||||
var tmp = time_to_ampm((i)+':00');
|
cell.innerHTML = i+':00:00';
|
||||||
cell.innerHTML = tmp[0]+':'+tmp[1]+' '+tmp[2];
|
|
||||||
$w(cell, '10%');
|
$w(cell, '10%');
|
||||||
} else {
|
} else {
|
||||||
cell.viewunit = new Calendar.DayViewUnit(cell);
|
cell.viewunit = new Calendar.DayViewUnit(cell);
|
||||||
@ -510,9 +507,7 @@ Calendar.WeekView.prototype.create_table = function() {
|
|||||||
for(var j=0;j<8;j++) {
|
for(var j=0;j<8;j++) {
|
||||||
var cell = r.insertCell(j);
|
var cell = r.insertCell(j);
|
||||||
if(j==0) {
|
if(j==0) {
|
||||||
var tmp = time_to_ampm(i+':00');
|
cell.innerHTML = i+':00:00';
|
||||||
cell.innerHTML = tmp[0]+':'+tmp[1]+' '+tmp[2];
|
|
||||||
|
|
||||||
$w(cell, '10%');
|
$w(cell, '10%');
|
||||||
} else {
|
} else {
|
||||||
cell.viewunit = new Calendar.WeekViewUnit(cell);
|
cell.viewunit = new Calendar.WeekViewUnit(cell);
|
||||||
|
@ -6,34 +6,6 @@ install_docs = [
|
|||||||
|
|
||||||
import webnotes
|
import webnotes
|
||||||
|
|
||||||
max_tickets_per_hour = 200
|
|
||||||
|
|
||||||
@webnotes.whitelist(allow_guest=True)
|
|
||||||
def send_message():
|
|
||||||
from webnotes.model.doc import Document
|
|
||||||
|
|
||||||
d = Document('Support Ticket')
|
|
||||||
d.subject = webnotes.form_dict.get('subject', 'Website Query')
|
|
||||||
d.description = webnotes.form_dict.get('message')
|
|
||||||
d.raised_by = webnotes.form_dict.get('sender')
|
|
||||||
d.status = webnotes.form_dict.get("status") or "Open"
|
|
||||||
|
|
||||||
if not d.description:
|
|
||||||
webnotes.response["message"] = 'Please write something'
|
|
||||||
return
|
|
||||||
|
|
||||||
if not d.raised_by:
|
|
||||||
webnotes.response["message"] = 'Email Id Required'
|
|
||||||
return
|
|
||||||
|
|
||||||
# guest method, cap max writes per hour
|
|
||||||
if webnotes.conn.sql("""select count(*) from `tabSupport Ticket`
|
|
||||||
where TIMEDIFF(NOW(), modified) < '01:00:00'""")[0][0] > max_tickets_per_hour:
|
|
||||||
webnotes.response["message"] = "Sorry: we believe we have received an unreasonably high number of requests of this kind. Please try later"
|
|
||||||
return
|
|
||||||
|
|
||||||
d.save()
|
|
||||||
webnotes.response["message"] = 'Thank You'
|
|
||||||
|
|
||||||
def get_site_address():
|
def get_site_address():
|
||||||
from webnotes.utils import get_request_site_address
|
from webnotes.utils import get_request_site_address
|
||||||
|
63
website/helpers/contact.py
Normal file
63
website/helpers/contact.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import webnotes
|
||||||
|
from core.doctype.communication.communication import make
|
||||||
|
|
||||||
|
max_communications_per_hour = 300
|
||||||
|
|
||||||
|
@webnotes.whitelist(allow_guest=True)
|
||||||
|
def send_message(subject="Website Query", message="", sender="", status="Open"):
|
||||||
|
if not message:
|
||||||
|
webnotes.response["message"] = 'Please write something'
|
||||||
|
return
|
||||||
|
|
||||||
|
if not sender:
|
||||||
|
webnotes.response["message"] = 'Email Id Required'
|
||||||
|
return
|
||||||
|
|
||||||
|
# make lead / communication
|
||||||
|
|
||||||
|
name = webnotes.conn.get_value("Lead", {"email_id": sender}, "name")
|
||||||
|
if name:
|
||||||
|
lead = webnotes.model_wrapper("Lead", name)
|
||||||
|
lead.doc.status = "Open"
|
||||||
|
lead.ignore_permissions = True
|
||||||
|
lead.save()
|
||||||
|
else:
|
||||||
|
lead = webnotes.model_wrapper({
|
||||||
|
"doctype":"Lead",
|
||||||
|
"lead_name": sender,
|
||||||
|
"email_id": sender,
|
||||||
|
"status": "Open",
|
||||||
|
"source": "Website"
|
||||||
|
})
|
||||||
|
lead.ignore_permissions = True
|
||||||
|
lead.insert()
|
||||||
|
|
||||||
|
make(content=message, sender=sender,
|
||||||
|
doctype="Lead", name=lead.doc.name, lead=lead.doc.name)
|
||||||
|
|
||||||
|
|
||||||
|
# guest method, cap max writes per hour
|
||||||
|
if webnotes.conn.sql("""select count(*) from `tabCommunication`
|
||||||
|
where TIMEDIFF(NOW(), modified) < '01:00:00'""")[0][0] > max_communications_per_hour:
|
||||||
|
webnotes.response["message"] = "Sorry: we believe we have received an unreasonably high number of requests of this kind. Please try later"
|
||||||
|
return
|
||||||
|
|
||||||
|
webnotes.response["message"] = 'Thank You'
|
Loading…
x
Reference in New Issue
Block a user