Merge branch 'master' into purchase_price_list

This commit is contained in:
Anand Doshi 2013-01-17 10:58:41 +05:30
commit 03fa78597d
55 changed files with 1185 additions and 616 deletions

View File

@ -82,6 +82,7 @@ erpnext.desktop.show_pending_notifications = function() {
add_circle('module-icon-calendar', 'todays_events', 'Todays Events');
add_circle('module-icon-projects-home', 'open_tasks', 'Open Tasks');
add_circle('module-icon-questions', 'unanswered_questions', 'Unanswered Questions');
add_circle('module-icon-selling-home', 'open_leads', 'Open Leads');
erpnext.update_messages();

View File

@ -1,4 +1,8 @@
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", [
"Stock Reconciliation: Ability to update Valuation Rate",
"Time Field: Added Datetime and new Time Picker",

View File

View 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()

View 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
})
},
}

View 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)

View 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"
}
]

View 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'}}
]
});

View File

View 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

View 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"
}
]

View File

@ -31,6 +31,11 @@ wn.module_page["HR"] = [
description: wn._("Performance 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"),
icon: "icon-cog",
items: [
{
label: wn._("Job Opening"),
description: wn._("Opening for a Job."),
doctype:"Job Opening"
},
{
"label": wn._("Employment Type"),
"description": wn._("Type of employment master."),

View File

@ -16,7 +16,7 @@
from __future__ import unicode_literals
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.wrapper import getlist
from webnotes.model.code import get_obj
@ -210,7 +210,7 @@ class DocType:
"wip_warehouse" : "",
"fg_warehouse" : "",
"status" : "Draft",
"fiscal_year" : get_defaults()["fiscal_year"]
"fiscal_year" : webnotes.conn.get_default("fiscal_year")
}
return bom_dict, item_dict
@ -239,18 +239,22 @@ class DocType:
return self.get_csv()
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:
if self.doc.use_multi_level_bom:
# get all raw materials with sub assembly childs
fl_bom_items = sql("""
select
item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty,
description, stock_uom
description, stock_uom, min_order_qty
from
(
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
where it.name = fb.item_code
and ifnull(it.is_pro_applicable, 'No') = 'No'
@ -263,18 +267,21 @@ class DocType:
# Get all raw materials considering SA items as raw materials,
# so no childs of SA items
fl_bom_items = sql("""
select item_code, ifnull(sum(qty_consumed_per_unit), 0) * '%s',
description, stock_uom
from `tabBOM Item`
where parent = '%s' and docstatus < 2
select bom_item.item_code,
ifnull(sum(bom_item.qty_consumed_per_unit), 0) * %s,
bom_item.description, bom_item.stock_uom, item.min_order_qty
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
""" % (flt(bom_dict[bom]), bom))
""", (flt(bom_dict[bom]), bom))
self.make_items_dict(fl_bom_items)
def make_items_dict(self, 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):
@ -292,3 +299,62 @@ class DocType:
item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty])
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,
}
]

View File

@ -2,9 +2,9 @@
{
"owner": "jai@webnotestech.com",
"docstatus": 0,
"creation": "2012-12-14 10:15:16",
"creation": "2013-01-16 14:48:56",
"modified_by": "Administrator",
"modified": "2012-12-14 11:37:40"
"modified": "2013-01-16 15:46:26"
},
{
"read_only": 1,
@ -28,8 +28,10 @@
"parent": "Production Planning Tool",
"read": 1,
"create": 1,
"submit": 0,
"doctype": "DocPerm",
"write": 1,
"report": 0,
"parenttype": "DocType",
"permlevel": 0,
"parentfield": "permissions"
@ -68,9 +70,9 @@
{
"doctype": "DocField",
"label": "Company",
"reqd": 1,
"fieldname": "company",
"fieldtype": "Link",
"reqd": 1,
"options": "Company"
},
{
@ -154,10 +156,19 @@
"fieldtype": "Section 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",
"reqd": 0,
"fieldname": "use_multi_level_bom",
"fieldtype": "Check"
},
{
"doctype": "DocField",
"width": "50%",
"fieldname": "column_break5",
"fieldname": "cb5",
"fieldtype": "Column Break"
},
{
@ -170,18 +181,9 @@
},
{
"doctype": "DocField",
"width": "50%",
"fieldname": "column_break6",
"fieldtype": "Column Break"
},
{
"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
"fieldname": "sb5",
"fieldtype": "Section Break",
"options": "Simple"
},
{
"description": "Download a report containing all raw materials with their latest inventory status",
@ -191,8 +193,18 @@
"fieldtype": "Button"
},
{
"role": "System Manager",
"doctype": "DocPerm"
"doctype": "DocField",
"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",

View File

@ -16,6 +16,7 @@
from __future__ import unicode_literals
import webnotes
import time, datetime
from webnotes.utils import cint, cstr, getdate, now, nowdate
from webnotes.model import db_exists
@ -24,8 +25,6 @@ from webnotes import msgprint
sql = webnotes.conn.sql
class DocType:
def __init__(self,doc,doclist=[]):
self.doc = doc
@ -43,6 +42,14 @@ class DocType:
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
def get_time(self, timestr):
if len(timestr.split(":"))==2:
format = "%H:%M"
else:
format = "%H:%M:%S"
return time.strptime(timestr, format)
def validate(self):
if getdate(self.doc.timesheet_date) > getdate(nowdate()):
msgprint("You can not prepare timesheet for future date")
@ -53,11 +60,10 @@ class DocType:
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'):
if d.act_start_time and d.act_end_time:
d1 = time.strptime(d.act_start_time, "%H:%M")
d2 = time.strptime(d.act_end_time, "%H:%M")
d1 = self.get_time(d.act_start_time)
d2 = self.get_time(d.act_end_time)
if d1 > d2:
msgprint("Start time can not be greater than end time. Check for Task Id : "+cstr(d.task_id))
@ -67,8 +73,6 @@ class DocType:
raise Exception
def calculate_total_hr(self):
import datetime
import time
for d in getlist(self.doclist, 'timesheet_details'):
x1 = d.act_start_time.split(":")
x2 = d.act_end_time.split(":")
@ -76,7 +80,7 @@ class DocType:
d1 = datetime.timedelta(minutes=cint(x1[1]), hours=cint(x1[0]))
d2 = datetime.timedelta(minutes=cint(x2[1]), hours=cint(x2[0]))
d3 = (d2 - d1).seconds
d.act_total_hrs = time.strftime("%H:%M", time.gmtime(d3))
d.act_total_hrs = time.strftime("%H:%M:%S", time.gmtime(d3))
sql("update `tabTimesheet Detail` set act_total_hrs = %s where parent=%s and name=%s", (d.act_total_hrs,self.doc.name,d.name))
def on_update(self):

View File

@ -98,119 +98,3 @@ $.extend(wn.modules, {
});
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>");
}

View File

@ -108,6 +108,7 @@ erpnext.update_messages = function(reset) {
show_in_circle('todays_events', r.message.todays_events);
show_in_circle('open_tasks', r.message.open_tasks);
show_in_circle('unanswered_questions', r.message.unanswered_questions);
show_in_circle('open_leads', r.message.open_leads);
} else {
clearInterval(wn.updates.id);

View File

@ -11,7 +11,7 @@ erpnext.send_message = function(opts) {
method: "POST",
url: "server.py",
data: {
cmd: "website.send_message",
cmd: "website.helpers.contact.send_message",
subject: opts.subject,
sender: opts.sender,
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)
return 0; else return 1; }
var validate_email = valid_email;
function get_url_arg(name) {
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";

View 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()

View File

@ -48,6 +48,12 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) {
if(cur_frm.fields_dict.contact_by.df.options.match(/^Profile/)) {
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) {

View File

@ -77,8 +77,14 @@ class DocType(TransactionBase):
event_user.person = self.doc.contact_by
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):
webnotes.conn.sql("""update tabCommunication set lead='' where lead=%s""",
webnotes.conn.sql("""delete from tabCommunication where lead=%s""",
self.doc.name)
webnotes.conn.sql("""update `tabSupport Ticket` set lead='' where lead=%s""",
self.doc.name)

View File

@ -2,9 +2,9 @@
{
"owner": "Administrator",
"docstatus": 0,
"creation": "2012-11-02 17:16:46",
"creation": "2013-01-10 16:34:18",
"modified_by": "Administrator",
"modified": "2012-11-27 18:27:47"
"modified": "2013-01-16 10:51:58"
},
{
"autoname": "naming_series:",
@ -29,6 +29,7 @@
"doctype": "DocPerm",
"read": 1,
"parenttype": "DocType",
"report": 1,
"parentfield": "permissions"
},
{
@ -39,19 +40,17 @@
"description": "To manage multiple series please go to Setup > Manage Series",
"no_copy": 1,
"oldfieldtype": "Select",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Naming Series",
"oldfieldname": "naming_series",
"permlevel": 0,
"options": "LEAD\nLEAD/10-11/\nLEAD/MUMBAI/",
"fieldname": "naming_series",
"fieldtype": "Select",
"reqd": 0,
"options": "LEAD\nLEAD/10-11/\nLEAD/MUMBAI/"
"permlevel": 0
},
{
"oldfieldtype": "Data",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Contact Name",
"oldfieldname": "lead_name",
@ -64,7 +63,6 @@
},
{
"oldfieldtype": "Data",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Email Id",
"oldfieldname": "email_id",
@ -84,29 +82,25 @@
"permlevel": 0,
"no_copy": 1,
"oldfieldtype": "Select",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Status",
"oldfieldname": "status",
"default": "Open",
"trigger": "Client",
"fieldname": "status",
"fieldtype": "Select",
"search_index": 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
},
{
"description": "Source of the lead. If via a campaign, select \"Campaign\"",
"no_copy": 1,
"oldfieldtype": "Select",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Source",
"oldfieldname": "source",
"permlevel": 0,
"trigger": "Client",
"fieldname": "source",
"fieldtype": "Select",
"search_index": 0,
@ -121,9 +115,8 @@
"permlevel": 0
},
{
"allow_on_submit": 0,
"oldfieldtype": "Table",
"colour": "White:FFF",
"allow_on_submit": 0,
"doctype": "DocField",
"label": "Communication HTML",
"oldfieldname": "follow_up",
@ -141,45 +134,41 @@
{
"description": "Name of organization from where lead has come",
"oldfieldtype": "Data",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Company Name",
"oldfieldname": "company_name",
"trigger": "Client",
"fieldname": "company_name",
"fieldtype": "Data",
"search_index": 0,
"reqd": 0,
"in_filter": 1,
"permlevel": 0
"permlevel": 0,
"in_filter": 1
},
{
"description": "Source of th",
"oldfieldtype": "Link",
"colour": "White:FFF",
"doctype": "DocField",
"label": "From Customer",
"oldfieldname": "customer",
"permlevel": 0,
"options": "Customer",
"fieldname": "customer",
"fieldtype": "Link",
"depends_on": "eval:doc.source == 'Existing Customer'",
"hidden": 0,
"options": "Customer"
"permlevel": 0
},
{
"description": "Enter campaign name if the source of lead is campaign.",
"oldfieldtype": "Link",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Campaign Name",
"oldfieldname": "campaign_name",
"permlevel": 0,
"options": "Campaign",
"fieldname": "campaign_name",
"fieldtype": "Link",
"depends_on": "eval:doc.source == 'Campaign'",
"hidden": 0,
"options": "Campaign"
"permlevel": 0
},
{
"doctype": "DocField",
@ -190,7 +179,6 @@
},
{
"oldfieldtype": "Select",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Lead Type",
"oldfieldname": "type",
@ -202,7 +190,6 @@
},
{
"oldfieldtype": "Text",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Remark",
"oldfieldname": "remark",
@ -220,7 +207,6 @@
},
{
"oldfieldtype": "Data",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Phone",
"oldfieldname": "contact_no",
@ -260,7 +246,6 @@
"print_hide": 1,
"description": "<a href=\"javascript:cur_frm.cscript.TerritoryHelp();\">To manage Territory, click here</a>",
"oldfieldtype": "Link",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Territory",
"oldfieldname": "territory",
@ -313,17 +298,15 @@
"doctype": "DocField",
"label": "Country",
"oldfieldname": "country",
"trigger": "Client",
"options": "link:Country",
"fieldname": "country",
"fieldtype": "Select",
"reqd": 0,
"options": "link:Country",
"permlevel": 0
},
{
"print_hide": 1,
"oldfieldtype": "Select",
"colour": "White:FFF",
"doctype": "DocField",
"label": "State",
"oldfieldname": "state",
@ -344,7 +327,6 @@
},
{
"oldfieldtype": "Section Break",
"colour": "White:FFF",
"doctype": "DocField",
"label": "More Info",
"fieldname": "more_info",
@ -436,7 +418,6 @@
"description": "Your sales person who will contact the lead in future",
"permlevel": 0,
"oldfieldtype": "Link",
"colour": "White:FFF",
"allow_on_submit": 0,
"doctype": "DocField",
"label": "Next Contact By",
@ -453,7 +434,6 @@
"description": "Your sales person will get a reminder on this date to contact the lead",
"no_copy": 1,
"oldfieldtype": "Date",
"colour": "White:FFF",
"allow_on_submit": 0,
"doctype": "DocField",
"label": "Next Contact Date",
@ -470,7 +450,6 @@
"description": "Date on which the lead was last contacted",
"no_copy": 1,
"oldfieldtype": "Date",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Last Contact Date",
"oldfieldname": "last_contact_date",
@ -481,7 +460,6 @@
},
{
"oldfieldtype": "Link",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Company",
"oldfieldname": "company",
@ -563,21 +541,5 @@
"role": "Sales User",
"cancel": 0,
"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
}
]

View File

@ -16,7 +16,10 @@ wn.doclistviews['Lead'] = wn.views.ListView.extend({
if(data.status=='Interested') {
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.status_html = repl('<span class="label label-%(label_type)s">%(status)s</span>', data);

View File

@ -37,9 +37,6 @@ class DocType(DocTypeNestedSet):
To untrash please go to Setup -> Recycle Bin.""" %
(self.doc.customer_group_name), raise_exception = 1)
super(DocType, self).validate()
def on_trash(self):
cust = sql("select name from `tabCustomer` where ifnull(customer_group, '') = %s",
self.doc.name)

View 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"));
}
}
}

View 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)

View 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"
}
]

View 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"));
}
}
}

View 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)

View 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"
}
]

View File

@ -33,6 +33,3 @@ class DocType(DocTypeNestedSet):
if not flt(d.target_qty) and not flt(d.target_amount):
webnotes.msgprint("Either target qty or target amount is mandatory.")
raise Exception
super(DocType, self).validate()

View File

@ -33,6 +33,3 @@ class DocType(DocTypeNestedSet):
if not flt(d.target_qty) and not flt(d.target_amount):
msgprint("Either target qty or target amount is mandatory.")
raise Exception
super(DocType, self).validate()

View File

@ -116,6 +116,18 @@ wn.module_page["Setup"] = [
label: wn._("Email Settings"),
"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",
doctype:"Notification Control",

View File

@ -17,6 +17,7 @@ queries = {
"Purchase Invoice": {"docstatus":0},
"Leave Application": {"status":"Open"},
"Expense Claim": {"approval_status":"Draft"},
"Job Applicant": {"status":"Open"},
"Purchase Receipt": {"docstatus":0},
"Delivery Note": {"docstatus":0},
"Stock Entry": {"docstatus":0},

View File

@ -61,7 +61,8 @@ data_map = {
# Stock
"Item": {
"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"],
"order_by": "name",
"links": {

View File

@ -26,10 +26,15 @@ def execute_all():
* recurring invoice
"""
# 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)
# 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
run_fn(flush)

View File

@ -29,38 +29,39 @@ def get_unread_messages():
def get_open_support_tickets():
"""Returns a count of open support tickets"""
from webnotes.utils import cint
open_support_tickets = webnotes.conn.sql("""\
SELECT COUNT(*) FROM `tabSupport Ticket`
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():
"""Returns a count of open tasks"""
from webnotes.utils import cint
return webnotes.conn.sql("""\
SELECT COUNT(*) FROM `tabTask`
WHERE status = 'Open'""")[0][0]
def get_things_todo():
"""Returns a count of incomplete todos"""
from webnotes.utils import cint
incomplete_todos = webnotes.conn.sql("""\
SELECT COUNT(*) FROM `tabToDo`
WHERE IFNULL(checked, 0) = 0
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():
"""Returns a count of todays events in calendar"""
from webnotes.utils import nowdate, cint
from webnotes.utils import nowdate
todays_events = webnotes.conn.sql("""\
SELECT COUNT(*) FROM `tabEvent`
WHERE owner = %s
AND event_type != 'Cancel'
AND event_date = %s""", (
webnotes.session.get('user'), nowdate()))
return todays_events and cint(todays_events[0][0]) or 0
webnotes.session.user, nowdate()))
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():
return len(filter(lambda d: d[0]==0,
@ -75,5 +76,6 @@ def get_global_status_messages(arg=None):
'things_todo': get_things_todo(),
'todays_events': get_todays_events(),
'open_tasks': get_open_tasks(),
'unanswered_questions': get_unanswered_questions()
'unanswered_questions': get_unanswered_questions(),
'open_leads': get_open_leads()
}

View File

@ -2,21 +2,21 @@
{
"owner": "Administrator",
"docstatus": 0,
"creation": "2012-12-17 14:56:32",
"creation": "2012-12-28 11:01:35",
"modified_by": "Administrator",
"modified": "2012-12-27 10:36:56"
"modified": "2013-01-16 11:14:57"
},
{
"allow_attach": 1,
"search_fields": "item_name,description,item_group,customer_code",
"module": "Stock",
"doctype": "DocType",
"autoname": "field:item_code",
"document_type": "Master",
"description": "A Product or a Service that is bought, sold or kept in stock.",
"autoname": "field:item_code",
"name": "__common__",
"default_print_format": "Standard",
"allow_rename": 1,
"doctype": "DocType",
"max_attachments": 1
},
{
@ -30,7 +30,9 @@
"name": "__common__",
"parent": "Item",
"read": 1,
"submit": 0,
"doctype": "DocPerm",
"report": 1,
"parenttype": "DocType",
"parentfield": "permissions"
},
@ -860,46 +862,6 @@
"fieldtype": "Text Editor",
"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,
"doctype": "DocPerm",
@ -909,23 +871,48 @@
"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,
"doctype": "DocPerm",
"write": 0,
"role": "Material Master Manager",
"cancel": 0,
"permlevel": 1
},
{
"create": 1,
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"write": 1,
"role": "System Manager",
"cancel": 1,
"permlevel": 0
"write": 0,
"role": "Material Manager",
"cancel": 0,
"permlevel": 1
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"role": "System Manager",
"write": 0,
"role": "Material User",
"cancel": 0,
"permlevel": 1
}
]

View File

@ -226,7 +226,8 @@ cur_frm.cscript.s_warehouse = function(doc, cdt, cdn) {
'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse),
'transfer_qty' : d.transfer_qty,
'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),
'mtn_details', doc, cdt, cdn, 1);

View File

@ -25,6 +25,8 @@ from webnotes.model.code import get_obj
from webnotes import msgprint, _
from stock.utils import get_incoming_rate
from stock.stock_ledger import get_previous_sle
import json
sql = webnotes.conn.sql
@ -157,24 +159,47 @@ class DocType(TransactionBase):
def get_stock_and_rate(self):
"""get stock and incoming rate on posting date"""
for d in getlist(self.doclist, 'mtn_details'):
args = {
args = webnotes._dict({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.doc.posting_date,
"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,
"bom_no": d.bom_no
}
"bom_no": d.bom_no,
})
# get actual stock at source warehouse
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
# get incoming rate
if not flt(d.incoming_rate):
d.incoming_rate = get_incoming_rate(args)
if not flt(d.incoming_rate) or self.doc.purpose == "Sales Return":
d.incoming_rate = self.get_incoming_rate(args)
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):
for d in getlist(self.doclist, 'mtn_details'):
if not flt(d.incoming_rate) and d.t_warehouse:
@ -264,8 +289,7 @@ class DocType(TransactionBase):
pro_obj.doc.save()
def get_item_details(self, arg):
import json
arg, actual_qty, in_rate = json.loads(arg), 0, 0
arg = json.loads(arg)
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'
@ -305,16 +329,16 @@ class DocType(TransactionBase):
return ret
def get_warehouse_details(self, args):
import json
args, actual_qty, in_rate = json.loads(args), 0, 0
args = json.loads(args)
args.update({
"posting_date": self.doc.posting_date,
"posting_time": self.doc.posting_time
"posting_time": self.doc.posting_time,
})
args = webnotes._dict(args)
ret = {
"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

View File

@ -55,7 +55,14 @@ class DocType(DocListController):
self.validation_messages = []
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
if [row[0], row[1]] in item_warehouse_combinations:
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
and repost future Stock Ledger Entries"""
from stock.stock_ledger import update_entries_after
existing_entries = webnotes.conn.sql("""select item_code, warehouse
from `tabStock Ledger Entry` where voucher_type='Stock Reconciliation'
and voucher_no=%s""", self.doc.name, as_dict=1)

View File

@ -2,9 +2,9 @@
{
"owner": "Administrator",
"docstatus": 0,
"creation": "2013-01-14 15:14:40",
"creation": "2013-01-15 12:28:57",
"modified_by": "Administrator",
"modified": "2013-01-15 12:25:13"
"modified": "2013-01-16 13:59:28"
},
{
"allow_attach": 0,
@ -13,6 +13,7 @@
"search_fields": "posting_date",
"module": "Stock",
"doctype": "DocType",
"read_only_onload": 0,
"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.",
"allow_email": 1,
@ -33,7 +34,7 @@
"read": 1,
"cancel": 1,
"name": "__common__",
"amend": 0,
"amend": 1,
"create": 1,
"doctype": "DocPerm",
"submit": 1,

View File

@ -54,7 +54,11 @@ erpnext.StockAgeing = erpnext.StockGridReport.extend({
{id: "earliest", name: "Earliest", field: "earliest",
formatter: this.currency_formatter},
{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: [

View File

@ -83,6 +83,10 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
field: "reserved_qty", width: 80, formatter: this.currency_formatter},
{id: "projected_qty", name: "Projected Qty",
field: "projected_qty", width: 80, formatter: this.currency_formatter},
{id: "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: "brand", name: "Brand", field: "brand", width: 100,
link_formatter: {filter_input: "brand"}},
@ -202,6 +206,10 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
id: key,
}
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;
}
return this.item_warehouse_map[key];

View File

@ -78,12 +78,11 @@ def get_incoming_rate(args):
valuation_method = get_valuation_method(args.get("item_code"))
previous_sle = get_previous_sle(args)
if valuation_method == 'FIFO':
# get rate based on the last item value?
if args.get("qty"):
if not previous_sle:
return 0.0
stock_queue = json.loads(previous_sle.get('stock_queue', '[]'))
in_rate = stock_queue and get_fifo_rate(stock_queue) or 0
previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]'))
in_rate = previous_stock_queue and \
get_fifo_rate(previous_stock_queue, args.get("qty") or 0) or 0
elif valuation_method == 'Moving Average':
in_rate = previous_sle.get('valuation_rate') or 0
return in_rate
@ -104,13 +103,29 @@ def get_valuation_method(item_code):
val_method = get_defaults().get('valuation_method', 'FIFO')
return val_method
def get_fifo_rate(stock_queue):
"""get FIFO (average) Rate from Stack"""
if not stock_queue:
return 0.0
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
if qty >= 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
else:
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
total = sum(f[0] for f in stock_queue)
return total and sum(f[0] * f[1] for f in stock_queue) / flt(total) or 0.0
return outgoing_cost / abs(qty)
def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
"""split serial nos, validate and return list of valid serial nos"""

View File

@ -13,15 +13,12 @@ wn.doclistviews['Maintenance Visit'] = wn.views.ListView.extend({
]);
this.stats = this.stats.concat(['completion_status', 'company']);
//this.show_hide_check_column();
},
prepare_data: function(data) {
this._super(data);
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[0] + ":" + data.mntc_time[1] + " " + data.mntc_time[2];
data.date_time = "on " + data.mntc_date + " at " + data.mntc_time;
data.customer_name = data.customer_name + " " + data.date_time;
data.completion_status = data.completion_status +
(data.maintenance_type ? " [" + data.maintenance_type + "]": "");

View File

@ -87,7 +87,7 @@ class 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(",")
from webnotes.utils.email_lib.bulk import send
send(recipients = recipients, sender = sender,
@ -109,7 +109,7 @@ class DocType():
recipients = self.get_recipients(query_key)
else:
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]
self.send_count[doctype] = self.send_count.setdefault(doctype, 0) + \
len(recipients)

View File

@ -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()

View 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()

View File

@ -49,11 +49,17 @@ $.extend(cur_frm.cscript, {
var wrapper = cur_frm.fields_dict['thread_html'].wrapper;
var comm_list = wn.model.get("Communication", {"support_ticket": doc.name})
var sortfn = function (a, b) { return (b.creation > a.creation) ? 1 : -1; }
comm_list = comm_list.sort(sortfn);
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({
list: comm_list,
@ -64,15 +70,6 @@ $.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) {
var callback = function(r,rt) {
var doc = locals[cur_frm.doctype][cur_frm.docname];

View File

@ -94,13 +94,11 @@ Calendar.prototype.show_event = function(ev, cal_ev) {
d.onshow = function() {
// heading
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 =
'<div style="text-align: center; padding:4px; font-size: 14px">'
+ 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
this.widgets['Description'].value = cstr(this.ev.description);
@ -175,7 +173,7 @@ Calendar.prototype.add_event = function() {
ev = locals['Event'][ev];
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';
this.show_event(ev);
@ -447,8 +445,7 @@ Calendar.DayView.prototype.create_table = function() {
for(var j=0;j<2;j++) {
var cell = r.insertCell(j);
if(j==0) {
var tmp = time_to_ampm((i)+':00');
cell.innerHTML = tmp[0]+':'+tmp[1]+' '+tmp[2];
cell.innerHTML = i+':00:00';
$w(cell, '10%');
} else {
cell.viewunit = new Calendar.DayViewUnit(cell);
@ -510,9 +507,7 @@ Calendar.WeekView.prototype.create_table = function() {
for(var j=0;j<8;j++) {
var cell = r.insertCell(j);
if(j==0) {
var tmp = time_to_ampm(i+':00');
cell.innerHTML = tmp[0]+':'+tmp[1]+' '+tmp[2];
cell.innerHTML = i+':00:00';
$w(cell, '10%');
} else {
cell.viewunit = new Calendar.WeekViewUnit(cell);

View File

@ -6,34 +6,6 @@ install_docs = [
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():
from webnotes.utils import get_request_site_address

View 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'