From fd1536b50bbdfc7e42dae251b67e51d3efabcce5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 12:29:42 +0530 Subject: [PATCH 01/31] cleaned up support email --- support/doctype/newsletter/newsletter.py | 4 +- support/doctype/support_ticket/__init__.py | 87 ++++------------------ 2 files changed, 16 insertions(+), 75 deletions(-) diff --git a/support/doctype/newsletter/newsletter.py b/support/doctype/newsletter/newsletter.py index c4b622a1a3..48ed21a327 100644 --- a/support/doctype/newsletter/newsletter.py +++ b/support/doctype/newsletter/newsletter.py @@ -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) diff --git a/support/doctype/support_ticket/__init__.py b/support/doctype/support_ticket/__init__.py index da1755feb9..113671462b 100644 --- a/support/doctype/support_ticket/__init__.py +++ b/support/doctype/support_ticket/__init__.py @@ -22,17 +22,7 @@ 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 = webnotes.doc('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 @@ -41,37 +31,15 @@ class SupportMailbox(POP3Mailbox): 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") + 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 @@ -83,7 +51,8 @@ class SupportMailbox(POP3Mailbox): from core.doctype.communication.communication import make - make(content=content, sender=full_email_id, doctype="Support Ticket", + make(content=mail.content, sender=mail.from_email, + doctype="Support Ticket", name=thread_id, lead = st.doc.lead, contact=st.doc.contact) st.doc.status = 'Open' @@ -91,7 +60,7 @@ class SupportMailbox(POP3Mailbox): update_feed(st, 'on_update') # extract attachments - self.save_attachments(st.doc, mail.attachments) + mail.save_attachments_in_doc(st.doc) return from webnotes.model.doctype import get_property @@ -99,21 +68,17 @@ class SupportMailbox(POP3Mailbox): # new ticket from webnotes.model.doc import Document d = Document('Support Ticket') - d.description = content + d.description = mail.content - d.subject = decode_email_header(mail.mail['Subject']) + d.subject = mail.mail['Subject'] - d.raised_by = full_email_id - d.content_type = content_type + d.raised_by = mail.from_email + d.content_type = mail.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]" + mail.save_attachments_in_doc(d) except: d.description = 'Unable to extract message' d.save(1) @@ -122,28 +87,8 @@ class SupportMailbox(POP3Mailbox): 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 '' @@ -167,16 +112,12 @@ Original Query: 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") + 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')): From 971ccf9c0f5e3b233fec0484195bfe977c529478 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 12:46:17 +0530 Subject: [PATCH 02/31] support fixes --- support/doctype/support_ticket/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/support/doctype/support_ticket/__init__.py b/support/doctype/support_ticket/__init__.py index 113671462b..81a5050725 100644 --- a/support/doctype/support_ticket/__init__.py +++ b/support/doctype/support_ticket/__init__.py @@ -22,6 +22,7 @@ from webnotes.utils.email_lib.receive import POP3Mailbox class SupportMailbox(POP3Mailbox): def __init__(self): + self.email_settings = webnotes.doc("Email Settings") s = webnotes.doc('Support Email Settings') s.use_ssl = self.email_settings.support_use_ssl s.host = self.email_settings.support_host From 8e10cea6d6acc040f272d28f2ac52909d559181c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 12:51:01 +0530 Subject: [PATCH 03/31] support fixes --- support/doctype/support_ticket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/doctype/support_ticket/__init__.py b/support/doctype/support_ticket/__init__.py index 81a5050725..85ee89a7b8 100644 --- a/support/doctype/support_ticket/__init__.py +++ b/support/doctype/support_ticket/__init__.py @@ -22,7 +22,7 @@ from webnotes.utils.email_lib.receive import POP3Mailbox class SupportMailbox(POP3Mailbox): def __init__(self): - self.email_settings = webnotes.doc("Email Settings") + self.email_settings = webnotes.doc("Email Settings", "Email Settings") s = webnotes.doc('Support Email Settings') s.use_ssl = self.email_settings.support_use_ssl s.host = self.email_settings.support_host From 3e4f3405646a0ad81a57c36021efec88b07153f3 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 12:54:31 +0530 Subject: [PATCH 04/31] support fixes --- support/doctype/support_ticket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/doctype/support_ticket/__init__.py b/support/doctype/support_ticket/__init__.py index 85ee89a7b8..316eff8086 100644 --- a/support/doctype/support_ticket/__init__.py +++ b/support/doctype/support_ticket/__init__.py @@ -46,7 +46,7 @@ class SupportMailbox(POP3Mailbox): SELECT name FROM `tabSupport Ticket` WHERE name=%s AND raised_by REGEXP %s - """ , (thread_id, '(' + email_id + ')')) + """ , (thread_id, '(' + mail.from_email + ')')) if exists and exists[0] and exists[0][0]: st = webnotes.get_obj('Support Ticket', thread_id) From 133d752ca4a32652aa665e9b3799c61e4f336488 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 14:17:31 +0530 Subject: [PATCH 05/31] cleanup get_support_ticket --- startup/schedule_handlers.py | 2 +- support/doctype/support_ticket/__init__.py | 125 ------------------ .../support_ticket/get_support_mails.py | 94 +++++++++++++ .../doctype/support_ticket/support_ticket.js | 16 ++- 4 files changed, 106 insertions(+), 131 deletions(-) create mode 100644 support/doctype/support_ticket/get_support_mails.py diff --git a/startup/schedule_handlers.py b/startup/schedule_handlers.py index 668c11d92c..54b9892b13 100644 --- a/startup/schedule_handlers.py +++ b/startup/schedule_handlers.py @@ -26,7 +26,7 @@ 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 diff --git a/support/doctype/support_ticket/__init__.py b/support/doctype/support_ticket/__init__.py index 316eff8086..e69de29bb2 100644 --- a/support/doctype/support_ticket/__init__.py +++ b/support/doctype/support_ticket/__init__.py @@ -1,125 +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 . - -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): - self.email_settings = webnotes.doc("Email Settings", "Email Settings") - s = webnotes.doc('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): - 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): - from home import update_feed - - thread_list = mail.get_thread_id() - - 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, '(' + mail.from_email + ')')) - 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=mail.content, sender=mail.from_email, - 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 - mail.save_attachments_in_doc(st.doc) - 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 = mail.content - - d.subject = mail.mail['Subject'] - - d.raised_by = mail.from_email - d.content_type = mail.content_type - d.status = 'Open' - d.naming_series = opts and opts.split("\n")[0] or 'SUP' - try: - d.save(1) - mail.save_attachments_in_doc(d) - 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 send_auto_reply(self, d): - 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): - 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(): - import webnotes - from webnotes.utils import cint - if cint(webnotes.conn.get_value('Email Settings', None, 'sync_support_mails')): - SupportMailbox().get_messages() \ No newline at end of file diff --git a/support/doctype/support_ticket/get_support_mails.py b/support/doctype/support_ticket/get_support_mails.py new file mode 100644 index 0000000000..204ca3431f --- /dev/null +++ b/support/doctype/support_ticket/get_support_mails.py @@ -0,0 +1,94 @@ +# 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 . + +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): + 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=thread_id, lead = st.doc.lead, contact=st.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() \ No newline at end of file diff --git a/support/doctype/support_ticket/support_ticket.js b/support/doctype/support_ticket/support_ticket.js index 159dddd62a..28b08f8ccb 100644 --- a/support/doctype/support_ticket/support_ticket.js +++ b/support/doctype/support_ticket/support_ticket.js @@ -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}) - comm_list.push({ - "sender": doc.raised_by, - "creation": doc.creation, - "modified": doc.creation, - "content": doc.description}); + + var sortfn = function (a, b) { return (b.creation > a.creation) ? 1 : -1; } + comm_list = comm_list.sort(sortfn); + + if(!comm_list.length || (comm_list[0].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, From c6bdbe62036e8a3cd987978bed5454c2af0d5eb0 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 14:26:45 +0530 Subject: [PATCH 06/31] cleanup get_support_ticket --- support/doctype/support_ticket/get_support_mails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/doctype/support_ticket/get_support_mails.py b/support/doctype/support_ticket/get_support_mails.py index 204ca3431f..bb0c008444 100644 --- a/support/doctype/support_ticket/get_support_mails.py +++ b/support/doctype/support_ticket/get_support_mails.py @@ -28,7 +28,7 @@ class SupportMailbox(POP3Mailbox): "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 + "password": self.email_settings.support_password }) def check_mails(self): From 8a8c5ad74cf8a0973a3821afe70973e2a0d5b9c6 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 14:30:08 +0530 Subject: [PATCH 07/31] cleanup get_support_ticket --- support/doctype/support_ticket/get_support_mails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/doctype/support_ticket/get_support_mails.py b/support/doctype/support_ticket/get_support_mails.py index bb0c008444..12c2fbc770 100644 --- a/support/doctype/support_ticket/get_support_mails.py +++ b/support/doctype/support_ticket/get_support_mails.py @@ -64,7 +64,7 @@ class SupportMailbox(POP3Mailbox): make(content=mail.content, sender=mail.from_email, doctype="Support Ticket", - name=thread_id, lead = st.doc.lead, contact=st.doc.contact) + name=thread_id, lead = ticket.doc.lead, contact=ticket.doc.contact) def send_auto_reply(self, d): signature = self.email_settings.fields.get('support_signature') or '' From 64eff9124e4cb488453a13649c631a8a9b403e79 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 14:38:29 +0530 Subject: [PATCH 08/31] cleanup get_support_ticket --- support/doctype/support_ticket/get_support_mails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/support/doctype/support_ticket/get_support_mails.py b/support/doctype/support_ticket/get_support_mails.py index 12c2fbc770..e5e99f5942 100644 --- a/support/doctype/support_ticket/get_support_mails.py +++ b/support/doctype/support_ticket/get_support_mails.py @@ -63,8 +63,8 @@ class SupportMailbox(POP3Mailbox): mail.save_attachments_in_doc(ticket.doc) make(content=mail.content, sender=mail.from_email, - doctype="Support Ticket", - name=thread_id, lead = ticket.doc.lead, contact=ticket.doc.contact) + 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 '' From 0e7751b9feb6f1109a336ba21654cbe0ce0a34d9 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 16:38:16 +0530 Subject: [PATCH 09/31] added job_applicant, job_opening doctypes --- hr/doctype/job_applicant/__init__.py | 0 hr/doctype/job_applicant/job_applicant.py | 8 ++ hr/doctype/job_applicant/job_applicant.txt | 84 +++++++++++++++++++ hr/doctype/job_opening/__init__.py | 0 hr/doctype/job_opening/job_opening.py | 8 ++ hr/doctype/job_opening/job_opening.txt | 66 +++++++++++++++ .../support_ticket/get_support_mails.py | 2 + 7 files changed, 168 insertions(+) create mode 100644 hr/doctype/job_applicant/__init__.py create mode 100644 hr/doctype/job_applicant/job_applicant.py create mode 100644 hr/doctype/job_applicant/job_applicant.txt create mode 100644 hr/doctype/job_opening/__init__.py create mode 100644 hr/doctype/job_opening/job_opening.py create mode 100644 hr/doctype/job_opening/job_opening.txt diff --git a/hr/doctype/job_applicant/__init__.py b/hr/doctype/job_applicant/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hr/doctype/job_applicant/job_applicant.py b/hr/doctype/job_applicant/job_applicant.py new file mode 100644 index 0000000000..928aa9ff9f --- /dev/null +++ b/hr/doctype/job_applicant/job_applicant.py @@ -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 \ No newline at end of file diff --git a/hr/doctype/job_applicant/job_applicant.txt b/hr/doctype/job_applicant/job_applicant.txt new file mode 100644 index 0000000000..3031211f3b --- /dev/null +++ b/hr/doctype/job_applicant/job_applicant.txt @@ -0,0 +1,84 @@ +[ + { + "owner": "Administrator", + "docstatus": 0, + "creation": "2013-01-15 16:32:13", + "modified_by": "Administrator", + "modified": "2013-01-15 16:32:13" + }, + { + "autoname": "field:applicant_name", + "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": "Status", + "fieldname": "status", + "fieldtype": "Select", + "options": "Open\nReject\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" + }, + { + "doctype": "DocPerm" + } +] \ No newline at end of file diff --git a/hr/doctype/job_opening/__init__.py b/hr/doctype/job_opening/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hr/doctype/job_opening/job_opening.py b/hr/doctype/job_opening/job_opening.py new file mode 100644 index 0000000000..928aa9ff9f --- /dev/null +++ b/hr/doctype/job_opening/job_opening.py @@ -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 \ No newline at end of file diff --git a/hr/doctype/job_opening/job_opening.txt b/hr/doctype/job_opening/job_opening.txt new file mode 100644 index 0000000000..bd994c6ad9 --- /dev/null +++ b/hr/doctype/job_opening/job_opening.txt @@ -0,0 +1,66 @@ +[ + { + "owner": "Administrator", + "docstatus": 0, + "creation": "2013-01-15 16:13:36", + "modified_by": "Administrator", + "modified": "2013-01-15 16:13:36" + }, + { + "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" + } +] \ No newline at end of file diff --git a/support/doctype/support_ticket/get_support_mails.py b/support/doctype/support_ticket/get_support_mails.py index e5e99f5942..c0b85e49b8 100644 --- a/support/doctype/support_ticket/get_support_mails.py +++ b/support/doctype/support_ticket/get_support_mails.py @@ -37,6 +37,8 @@ class SupportMailbox(POP3Mailbox): 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 From 3169ef06919b3f923aaf16041dc1e077ac8bcd81 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 17:23:23 +0530 Subject: [PATCH 10/31] added job application --- .../job_applicant/get_job_applications.py | 55 ++++++++++++ hr/doctype/job_applicant/job_applicant.js | 22 +++++ hr/doctype/job_applicant/job_applicant.txt | 8 +- hr/doctype/job_opening/job_opening.txt | 3 +- setup/doctype/jobs_email_settings/__init__.py | 0 .../jobs_email_settings.js | 12 +++ .../jobs_email_settings.py | 17 ++++ .../jobs_email_settings.txt | 89 +++++++++++++++++++ startup/schedule_handlers.py | 3 + .../doctype/support_ticket/support_ticket.js | 11 +-- 10 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 hr/doctype/job_applicant/get_job_applications.py create mode 100644 hr/doctype/job_applicant/job_applicant.js create mode 100644 setup/doctype/jobs_email_settings/__init__.py create mode 100644 setup/doctype/jobs_email_settings/jobs_email_settings.js create mode 100644 setup/doctype/jobs_email_settings/jobs_email_settings.py create mode 100644 setup/doctype/jobs_email_settings/jobs_email_settings.txt diff --git a/hr/doctype/job_applicant/get_job_applications.py b/hr/doctype/job_applicant/get_job_applications.py new file mode 100644 index 0000000000..c5066dcc4e --- /dev/null +++ b/hr/doctype/job_applicant/get_job_applications.py @@ -0,0 +1,55 @@ +# 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 . + +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 get_existing_application(self, email_id): + name = webnotes.conn.sql("""select name from `tabJob Applicant` where + email_id = %s""", email_id) + return name and name[0][0] or None + + def process_message(self, mail): + name = self.get_existing_application(mail.from_email) + if name: + applicant = webnotes.model_wrapper("Job Applicant", name) + else: + applicant = webnotes.model_wrapper({ + "doctype":"Job Applicant", + "applicant_name": mail.from_real_name or mail.from_email, + "email_id": mail.from_email + }) + 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() \ No newline at end of file diff --git a/hr/doctype/job_applicant/job_applicant.js b/hr/doctype/job_applicant/job_applicant.js new file mode 100644 index 0000000000..2b8e064e8d --- /dev/null +++ b/hr/doctype/job_applicant/job_applicant.js @@ -0,0 +1,22 @@ +// 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")); + } + cur_frm.cscript.make_listing(doc); + }, + make_listing: function(doc) { + var wrapper = cur_frm.fields_dict['thread_html'].wrapper; + cur_frm.communication_view = new wn.views.CommunicationList({ + list: comm_list, + parent: wn.model.get("Communication", {"job_applicant": doc.name}), + doc: doc, + recipients: doc.email_id + }) + }, +} \ No newline at end of file diff --git a/hr/doctype/job_applicant/job_applicant.txt b/hr/doctype/job_applicant/job_applicant.txt index 3031211f3b..390b659a99 100644 --- a/hr/doctype/job_applicant/job_applicant.txt +++ b/hr/doctype/job_applicant/job_applicant.txt @@ -4,7 +4,7 @@ "docstatus": 0, "creation": "2013-01-15 16:32:13", "modified_by": "Administrator", - "modified": "2013-01-15 16:32:13" + "modified": "2013-01-15 17:08:46" }, { "autoname": "field:applicant_name", @@ -47,6 +47,12 @@ "fieldtype": "Data", "reqd": 1 }, + { + "doctype": "DocField", + "label": "Email Id", + "fieldname": "email_id", + "fieldtype": "Data" + }, { "doctype": "DocField", "label": "Status", diff --git a/hr/doctype/job_opening/job_opening.txt b/hr/doctype/job_opening/job_opening.txt index bd994c6ad9..5e26f0d1c1 100644 --- a/hr/doctype/job_opening/job_opening.txt +++ b/hr/doctype/job_opening/job_opening.txt @@ -4,9 +4,10 @@ "docstatus": 0, "creation": "2013-01-15 16:13:36", "modified_by": "Administrator", - "modified": "2013-01-15 16:13:36" + "modified": "2013-01-15 16:43:05" }, { + "autoname": "field:job_title", "description": "Description of a Job Opening", "doctype": "DocType", "module": "HR", diff --git a/setup/doctype/jobs_email_settings/__init__.py b/setup/doctype/jobs_email_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/setup/doctype/jobs_email_settings/jobs_email_settings.js b/setup/doctype/jobs_email_settings/jobs_email_settings.js new file mode 100644 index 0000000000..0a75b89360 --- /dev/null +++ b/setup/doctype/jobs_email_settings/jobs_email_settings.js @@ -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")); + } + } +} \ No newline at end of file diff --git a/setup/doctype/jobs_email_settings/jobs_email_settings.py b/setup/doctype/jobs_email_settings/jobs_email_settings.py new file mode 100644 index 0000000000..b09cefd5e2 --- /dev/null +++ b/setup/doctype/jobs_email_settings/jobs_email_settings.py @@ -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) \ No newline at end of file diff --git a/setup/doctype/jobs_email_settings/jobs_email_settings.txt b/setup/doctype/jobs_email_settings/jobs_email_settings.txt new file mode 100644 index 0000000000..788f51b7e9 --- /dev/null +++ b/setup/doctype/jobs_email_settings/jobs_email_settings.txt @@ -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" + } +] \ No newline at end of file diff --git a/startup/schedule_handlers.py b/startup/schedule_handlers.py index 54b9892b13..ab53b211ed 100644 --- a/startup/schedule_handlers.py +++ b/startup/schedule_handlers.py @@ -28,6 +28,9 @@ def execute_all(): # pull emails from support.doctype.support_ticket.get_support_mails import get_support_mails run_fn(get_support_mails) + + from hr.doctype.job_applicant.get_job_applications import get_job_applications + run_fn(get_job_applications) # bulk email from webnotes.utils.email_lib.bulk import flush diff --git a/support/doctype/support_ticket/support_ticket.js b/support/doctype/support_ticket/support_ticket.js index 28b08f8ccb..bbaf95b523 100644 --- a/support/doctype/support_ticket/support_ticket.js +++ b/support/doctype/support_ticket/support_ticket.js @@ -69,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) { var callback = function(r,rt) { var doc = locals[cur_frm.doctype][cur_frm.docname]; From 84e360562d90d9f75547d72ba7d065c440f79529 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 17:30:56 +0530 Subject: [PATCH 11/31] added job application --- hr/doctype/job_applicant/job_applicant.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/hr/doctype/job_applicant/job_applicant.txt b/hr/doctype/job_applicant/job_applicant.txt index 390b659a99..a0c72245df 100644 --- a/hr/doctype/job_applicant/job_applicant.txt +++ b/hr/doctype/job_applicant/job_applicant.txt @@ -4,10 +4,11 @@ "docstatus": 0, "creation": "2013-01-15 16:32:13", "modified_by": "Administrator", - "modified": "2013-01-15 17:08:46" + "modified": "2013-01-15 17:30:46" }, { "autoname": "field:applicant_name", + "allow_attach": 1, "description": "Applicant for a Job", "doctype": "DocType", "module": "HR", @@ -84,6 +85,15 @@ "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" } From 73f84176f0a682a7e379d0ba2a3f8dbedc34b6cd Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 17:36:36 +0530 Subject: [PATCH 12/31] added job application --- hr/doctype/job_applicant/job_applicant.js | 11 ++--------- hr/doctype/job_applicant/job_applicant.py | 8 ++++++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/hr/doctype/job_applicant/job_applicant.js b/hr/doctype/job_applicant/job_applicant.js index 2b8e064e8d..a63f8335e3 100644 --- a/hr/doctype/job_applicant/job_applicant.js +++ b/hr/doctype/job_applicant/job_applicant.js @@ -2,19 +2,12 @@ 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")); - } cur_frm.cscript.make_listing(doc); }, make_listing: function(doc) { - var wrapper = cur_frm.fields_dict['thread_html'].wrapper; cur_frm.communication_view = new wn.views.CommunicationList({ - list: comm_list, - parent: wn.model.get("Communication", {"job_applicant": doc.name}), + list: wn.model.get("Communication", {"job_applicant": doc.name}), + parent: cur_frm.fields_dict['thread_html'].wrapper, doc: doc, recipients: doc.email_id }) diff --git a/hr/doctype/job_applicant/job_applicant.py b/hr/doctype/job_applicant/job_applicant.py index 928aa9ff9f..e1e1a24626 100644 --- a/hr/doctype/job_applicant/job_applicant.py +++ b/hr/doctype/job_applicant/job_applicant.py @@ -2,7 +2,11 @@ from __future__ import unicode_literals import webnotes +from utilities.transaction_base import TransactionBase -class DocType: +class DocType(TransactionBase): def __init__(self, d, dl): - self.doc, self.doclist = d, dl \ No newline at end of file + self.doc, self.doclist = d, dl + + def onload(self): + self.add_communication_list() \ No newline at end of file From 3074c89ffd47fa59073327eb4d282bcba1d9283e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 17:36:45 +0530 Subject: [PATCH 13/31] added job application --- .../job_applicant/job_applicant_list.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 hr/doctype/job_applicant/job_applicant_list.js diff --git a/hr/doctype/job_applicant/job_applicant_list.js b/hr/doctype/job_applicant/job_applicant_list.js new file mode 100644 index 0000000000..d471519795 --- /dev/null +++ b/hr/doctype/job_applicant/job_applicant_list.js @@ -0,0 +1,41 @@ +// render +wn.doclistviews['Job Applicant'] = wn.views.ListView.extend({ + init: function(d) { + this._super(d) + this.fields = this.fields.concat([ + "`tabSupport Ticket`.status", + '`tabSupport Ticket`.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('%(status)s', data); + }, + + columns: [ + {width: '3%', content: 'check'}, + {width: '5%', content:'avatar_modified'}, + {width: '50%', content:'name'}, + {width: '30%', content:'status_html'}, + {width: '12%', content:'modified', css: {'text-align': 'right', 'color':'#777'}} + ] + +}); From b6f6d68de0834168889830ac84bb1b788d71174e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 17:42:41 +0530 Subject: [PATCH 14/31] added job application --- hr/doctype/job_applicant/get_job_applications.py | 6 +++++- hr/doctype/job_applicant/job_applicant.py | 6 +++++- hr/doctype/job_applicant/job_applicant.txt | 4 ++-- hr/doctype/job_applicant/job_applicant_list.js | 8 ++++---- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/hr/doctype/job_applicant/get_job_applications.py b/hr/doctype/job_applicant/get_job_applications.py index c5066dcc4e..2884de8ec7 100644 --- a/hr/doctype/job_applicant/get_job_applications.py +++ b/hr/doctype/job_applicant/get_job_applications.py @@ -37,11 +37,15 @@ class JobsMailbox(POP3Mailbox): name = self.get_existing_application(mail.from_email) if name: applicant = webnotes.model_wrapper("Job Applicant", name) + if applicant.doc.status!="Rejected": + applicant.doc.status = "Open" + applicant.doc.save() else: applicant = webnotes.model_wrapper({ "doctype":"Job Applicant", "applicant_name": mail.from_real_name or mail.from_email, - "email_id": mail.from_email + "email_id": mail.from_email, + "status": "Open" }) applicant.insert() diff --git a/hr/doctype/job_applicant/job_applicant.py b/hr/doctype/job_applicant/job_applicant.py index e1e1a24626..46fb3d7fe2 100644 --- a/hr/doctype/job_applicant/job_applicant.py +++ b/hr/doctype/job_applicant/job_applicant.py @@ -9,4 +9,8 @@ class DocType(TransactionBase): self.doc, self.doclist = d, dl def onload(self): - self.add_communication_list() \ No newline at end of file + self.add_communication_list() + + def on_communication_sent(self, comm): + webnotes.conn.set(self.doc, 'status', 'Replied') + \ No newline at end of file diff --git a/hr/doctype/job_applicant/job_applicant.txt b/hr/doctype/job_applicant/job_applicant.txt index a0c72245df..0078c95593 100644 --- a/hr/doctype/job_applicant/job_applicant.txt +++ b/hr/doctype/job_applicant/job_applicant.txt @@ -4,7 +4,7 @@ "docstatus": 0, "creation": "2013-01-15 16:32:13", "modified_by": "Administrator", - "modified": "2013-01-15 17:30:46" + "modified": "2013-01-15 17:40:29" }, { "autoname": "field:applicant_name", @@ -59,7 +59,7 @@ "label": "Status", "fieldname": "status", "fieldtype": "Select", - "options": "Open\nReject\nHold" + "options": "Open\nReplied\nRejected\nHold" }, { "doctype": "DocField", diff --git a/hr/doctype/job_applicant/job_applicant_list.js b/hr/doctype/job_applicant/job_applicant_list.js index d471519795..3d149efef1 100644 --- a/hr/doctype/job_applicant/job_applicant_list.js +++ b/hr/doctype/job_applicant/job_applicant_list.js @@ -3,8 +3,8 @@ wn.doclistviews['Job Applicant'] = wn.views.ListView.extend({ init: function(d) { this._super(d) this.fields = this.fields.concat([ - "`tabSupport Ticket`.status", - '`tabSupport Ticket`.modified_by' + "`tabJob Applicant`.status", + '`tabJob Applicant`.modified_by' ]); this.stats = this.stats.concat(['status']); @@ -33,8 +33,8 @@ wn.doclistviews['Job Applicant'] = wn.views.ListView.extend({ columns: [ {width: '3%', content: 'check'}, {width: '5%', content:'avatar_modified'}, - {width: '50%', content:'name'}, - {width: '30%', content:'status_html'}, + {width: '30%', content:'name'}, + {width: '50%', content:'status_html'}, {width: '12%', content:'modified', css: {'text-align': 'right', 'color':'#777'}} ] From a9d4fb9a732bb33b72c24c7e80249b6923192d8d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 17:48:19 +0530 Subject: [PATCH 15/31] added job application --- hr/doctype/job_applicant/get_job_applications.py | 3 +++ hr/doctype/job_applicant/job_applicant.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/hr/doctype/job_applicant/get_job_applications.py b/hr/doctype/job_applicant/get_job_applications.py index 2884de8ec7..e5805ac6b3 100644 --- a/hr/doctype/job_applicant/get_job_applications.py +++ b/hr/doctype/job_applicant/get_job_applications.py @@ -34,6 +34,9 @@ class JobsMailbox(POP3Mailbox): return name and name[0][0] or None def process_message(self, mail): + if mail.from_email == self.settings.email_id: + return + name = self.get_existing_application(mail.from_email) if name: applicant = webnotes.model_wrapper("Job Applicant", name) diff --git a/hr/doctype/job_applicant/job_applicant.py b/hr/doctype/job_applicant/job_applicant.py index 46fb3d7fe2..b4db3c07f7 100644 --- a/hr/doctype/job_applicant/job_applicant.py +++ b/hr/doctype/job_applicant/job_applicant.py @@ -13,4 +13,8 @@ class DocType(TransactionBase): 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) \ No newline at end of file From 7d9f02cc996bb38c4822a8ff58fd9f49e68f8b7d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 17:54:25 +0530 Subject: [PATCH 16/31] added job application --- hr/doctype/job_applicant/get_job_applications.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hr/doctype/job_applicant/get_job_applications.py b/hr/doctype/job_applicant/get_job_applications.py index e5805ac6b3..a11a6191d0 100644 --- a/hr/doctype/job_applicant/get_job_applications.py +++ b/hr/doctype/job_applicant/get_job_applications.py @@ -44,9 +44,11 @@ class JobsMailbox(POP3Mailbox): 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": mail.from_real_name or mail.from_email, + "applicant_name": name, "email_id": mail.from_email, "status": "Open" }) From 390f4e9475ced626068ff60a9b3c7855e58aeb80 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 15 Jan 2013 18:37:22 +0530 Subject: [PATCH 17/31] added job application --- hr/doctype/job_applicant/get_job_applications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hr/doctype/job_applicant/get_job_applications.py b/hr/doctype/job_applicant/get_job_applications.py index a11a6191d0..7509380729 100644 --- a/hr/doctype/job_applicant/get_job_applications.py +++ b/hr/doctype/job_applicant/get_job_applications.py @@ -44,7 +44,7 @@ class JobsMailbox(POP3Mailbox): applicant.doc.status = "Open" applicant.doc.save() else: - name = (mail.from_real_name and (mail.from_real_name + "-" or "")) \ + name = (mail.from_real_name and (mail.from_real_name + " - ") or "") \ + mail.from_email applicant = webnotes.model_wrapper({ "doctype":"Job Applicant", From 8ec06aed5c3313955a898f3ef7cd2afcf7d16ab6 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 15 Jan 2013 18:54:16 +0530 Subject: [PATCH 18/31] only allow upto 100 rows in stock reco --- .../stock_reconciliation/stock_reconciliation.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.py b/stock/doctype/stock_reconciliation/stock_reconciliation.py index 4ab0acc541..9017843c66 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -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) From c0f2e6dd23ea62c25638f5ddc37f5cb17ee99478 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 16 Jan 2013 07:47:33 +0530 Subject: [PATCH 19/31] timesheet fix --- projects/doctype/timesheet/timesheet.py | 132 ++++++++++++------------ 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/projects/doctype/timesheet/timesheet.py b/projects/doctype/timesheet/timesheet.py index c2b296f371..4f4d824347 100644 --- a/projects/doctype/timesheet/timesheet.py +++ b/projects/doctype/timesheet/timesheet.py @@ -8,14 +8,15 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see . from __future__ import unicode_literals import webnotes +import time, datetime from webnotes.utils import cint, cstr, getdate, now, nowdate from webnotes.model import db_exists @@ -23,68 +24,71 @@ from webnotes.model.wrapper import getlist, copy_doclist from webnotes import msgprint sql = webnotes.conn.sql - - class DocType: - def __init__(self,doc,doclist=[]): - self.doc = doc - self.doclist = doclist - - def get_customer_details(self, project_name): - cust = sql("select customer, customer_name from `tabProject` where name = %s", project_name) - if cust: - ret = {'customer': cust and cust[0][0] or '', 'customer_name': cust and cust[0][1] or ''} - return (ret) - - def get_task_details(self, task_sub): - tsk = sql("select name, project, customer, customer_name from `tabTask` where subject = %s", task_sub) - 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 ''} - return ret - - 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 + def __init__(self,doc,doclist=[]): + self.doc = doc + self.doclist = doclist + + def get_customer_details(self, project_name): + cust = sql("select customer, customer_name from `tabProject` where name = %s", project_name) + if cust: + ret = {'customer': cust and cust[0][0] or '', 'customer_name': cust and cust[0][1] or ''} + return (ret) + + def get_task_details(self, task_sub): + tsk = sql("select name, project, customer, customer_name from `tabTask` where subject = %s", task_sub) + 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 ''} + 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") + 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'): - 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") - - if d1 > d2: - msgprint("Start time can not be greater than end time. Check for Task Id : "+cstr(d.task_id)) - raise Exception - elif d1 == d2: - msgprint("Start time and end time can not be same. Check for Task Id : "+cstr(d.task_id)) - 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(":") - - 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)) - 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): - self.calculate_total_hr() - webnotes.conn.set(self.doc, 'status', 'Draft') - - def on_submit(self): - webnotes.conn.set(self.doc, 'status', 'Submitted') - - def on_cancel(self): - webnotes.conn.set(self.doc, 'status', 'Cancelled') \ No newline at end of file + for d in getlist(self.doclist, 'timesheet_details'): + if d.act_start_time and d.act_end_time: + 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)) + raise Exception + elif d1 == d2: + msgprint("Start time and end time can not be same. Check for Task Id : "+cstr(d.task_id)) + raise Exception + + def calculate_total_hr(self): + for d in getlist(self.doclist, 'timesheet_details'): + x1 = d.act_start_time.split(":") + x2 = d.act_end_time.split(":") + + 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:%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): + self.calculate_total_hr() + webnotes.conn.set(self.doc, 'status', 'Draft') + + def on_submit(self): + webnotes.conn.set(self.doc, 'status', 'Submitted') + + def on_cancel(self): + webnotes.conn.set(self.doc, 'status', 'Cancelled') \ No newline at end of file From c57c0d4e1df3cd1e6bc47373d56e966b48cda067 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 10:31:55 +0530 Subject: [PATCH 20/31] item name and description added in stock ageing report --- stock/page/stock_ageing/stock_ageing.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stock/page/stock_ageing/stock_ageing.js b/stock/page/stock_ageing/stock_ageing.js index ea495ce6cd..780fb86f3e 100644 --- a/stock/page/stock_ageing/stock_ageing.js +++ b/stock/page/stock_ageing/stock_ageing.js @@ -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: [ From 44ffd4360ceca3bcc4326b4c6aeaed6914134193 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 11:16:21 +0530 Subject: [PATCH 21/31] reorder level and qty added in stock level report --- startup/report_data_map.py | 3 +- stock/doctype/item/item.txt | 87 ++++++++++++--------------- stock/page/stock_level/stock_level.js | 10 ++- 3 files changed, 48 insertions(+), 52 deletions(-) diff --git a/startup/report_data_map.py b/startup/report_data_map.py index 228a8ae2a1..06bbf44e12 100644 --- a/startup/report_data_map.py +++ b/startup/report_data_map.py @@ -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": { diff --git a/stock/doctype/item/item.txt b/stock/doctype/item/item.txt index d1fc4fcb93..1f623bbd39 100644 --- a/stock/doctype/item/item.txt +++ b/stock/doctype/item/item.txt @@ -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 } ] \ No newline at end of file diff --git a/stock/page/stock_level/stock_level.js b/stock/page/stock_level/stock_level.js index 5b8c2e827b..9c755ef806 100644 --- a/stock/page/stock_level/stock_level.js +++ b/stock/page/stock_level/stock_level.js @@ -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"}}, @@ -171,7 +175,7 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({ }); } ); - + // sort by item, warehouse this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) { return me.item_warehouse_map[key]; @@ -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]; From 2e5db35856888a50fbdb077cc94ecac390dd4e32 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 16 Jan 2013 11:34:26 +0530 Subject: [PATCH 22/31] added automatic lead creation for sales email id --- home/page/desktop/desktop.js | 1 + .../job_applicant/get_job_applications.py | 8 +- public/js/startup.js | 1 + public/js/website_utils.js | 4 +- selling/doctype/lead/get_leads.py | 57 ++++++++++++ selling/doctype/lead/lead.py | 5 +- selling/doctype/lead/lead.txt | 66 +++----------- selling/doctype/lead/lead_list.js | 5 +- .../doctype/sales_email_settings/__init__.py | 0 .../sales_applicant_list.js | 12 +++ .../sales_email_settings.py | 17 ++++ .../sales_email_settings.txt | 89 +++++++++++++++++++ startup/open_count.py | 1 + startup/schedule_handlers.py | 6 +- startup/startup.py | 20 +++-- .../doctype/support_ticket/support_ticket.js | 2 +- website/__init__.py | 28 ------ website/helpers/contact.py | 63 +++++++++++++ 18 files changed, 284 insertions(+), 101 deletions(-) create mode 100644 selling/doctype/lead/get_leads.py create mode 100644 setup/doctype/sales_email_settings/__init__.py create mode 100644 setup/doctype/sales_email_settings/sales_applicant_list.js create mode 100644 setup/doctype/sales_email_settings/sales_email_settings.py create mode 100644 setup/doctype/sales_email_settings/sales_email_settings.txt create mode 100644 website/helpers/contact.py diff --git a/home/page/desktop/desktop.js b/home/page/desktop/desktop.js index 77e0a3df73..37011bbca2 100644 --- a/home/page/desktop/desktop.js +++ b/home/page/desktop/desktop.js @@ -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(); diff --git a/hr/doctype/job_applicant/get_job_applications.py b/hr/doctype/job_applicant/get_job_applications.py index 7509380729..f2b776c011 100644 --- a/hr/doctype/job_applicant/get_job_applications.py +++ b/hr/doctype/job_applicant/get_job_applications.py @@ -28,16 +28,12 @@ class JobsMailbox(POP3Mailbox): return webnotes.conn.sql("select user from tabSessions where \ time_to_sec(timediff(now(), lastupdate)) < 1800") - def get_existing_application(self, email_id): - name = webnotes.conn.sql("""select name from `tabJob Applicant` where - email_id = %s""", email_id) - return name and name[0][0] or None - def process_message(self, mail): if mail.from_email == self.settings.email_id: return - name = self.get_existing_application(mail.from_email) + 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": diff --git a/public/js/startup.js b/public/js/startup.js index 817175bf47..e2413f124d 100644 --- a/public/js/startup.js +++ b/public/js/startup.js @@ -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); diff --git a/public/js/website_utils.js b/public/js/website_utils.js index ff480ed5ee..6f90434b7b 100644 --- a/public/js/website_utils.js +++ b/public/js/website_utils.js @@ -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+"=([^&#]*)"; diff --git a/selling/doctype/lead/get_leads.py b/selling/doctype/lead/get_leads.py new file mode 100644 index 0000000000..61274574b7 --- /dev/null +++ b/selling/doctype/lead/get_leads.py @@ -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 . + +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() \ No newline at end of file diff --git a/selling/doctype/lead/lead.py b/selling/doctype/lead/lead.py index 63f9bd0258..13d1714830 100644 --- a/selling/doctype/lead/lead.py +++ b/selling/doctype/lead/lead.py @@ -77,8 +77,11 @@ 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 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) \ No newline at end of file diff --git a/selling/doctype/lead/lead.txt b/selling/doctype/lead/lead.txt index 6ffe3c4274..a4f0206070 100644 --- a/selling/doctype/lead/lead.txt +++ b/selling/doctype/lead/lead.txt @@ -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": "To manage Territory, click here", "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 } ] \ No newline at end of file diff --git a/selling/doctype/lead/lead_list.js b/selling/doctype/lead/lead_list.js index cfbfc36d67..3b2f53a57f 100644 --- a/selling/doctype/lead/lead_list.js +++ b/selling/doctype/lead/lead_list.js @@ -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('%(status)s', data); diff --git a/setup/doctype/sales_email_settings/__init__.py b/setup/doctype/sales_email_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/setup/doctype/sales_email_settings/sales_applicant_list.js b/setup/doctype/sales_email_settings/sales_applicant_list.js new file mode 100644 index 0000000000..0a75b89360 --- /dev/null +++ b/setup/doctype/sales_email_settings/sales_applicant_list.js @@ -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")); + } + } +} \ No newline at end of file diff --git a/setup/doctype/sales_email_settings/sales_email_settings.py b/setup/doctype/sales_email_settings/sales_email_settings.py new file mode 100644 index 0000000000..b09cefd5e2 --- /dev/null +++ b/setup/doctype/sales_email_settings/sales_email_settings.py @@ -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) \ No newline at end of file diff --git a/setup/doctype/sales_email_settings/sales_email_settings.txt b/setup/doctype/sales_email_settings/sales_email_settings.txt new file mode 100644 index 0000000000..d8042e970f --- /dev/null +++ b/setup/doctype/sales_email_settings/sales_email_settings.txt @@ -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" + } +] \ No newline at end of file diff --git a/startup/open_count.py b/startup/open_count.py index e0767c318d..a5fcc77559 100644 --- a/startup/open_count.py +++ b/startup/open_count.py @@ -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}, diff --git a/startup/schedule_handlers.py b/startup/schedule_handlers.py index ab53b211ed..b1ce1836c8 100644 --- a/startup/schedule_handlers.py +++ b/startup/schedule_handlers.py @@ -31,8 +31,10 @@ def execute_all(): from hr.doctype.job_applicant.get_job_applications import get_job_applications run_fn(get_job_applications) - - # bulk email + + from selling.doctype.lead.get_leads import get_leads + run_fn(get_job_applications) + from webnotes.utils.email_lib.bulk import flush run_fn(flush) diff --git a/startup/startup.py b/startup/startup.py index 15ea491b13..c74c596235 100644 --- a/startup/startup.py +++ b/startup/startup.py @@ -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() } diff --git a/support/doctype/support_ticket/support_ticket.js b/support/doctype/support_ticket/support_ticket.js index bbaf95b523..9abdff074a 100644 --- a/support/doctype/support_ticket/support_ticket.js +++ b/support/doctype/support_ticket/support_ticket.js @@ -53,7 +53,7 @@ $.extend(cur_frm.cscript, { var sortfn = function (a, b) { return (b.creation > a.creation) ? 1 : -1; } comm_list = comm_list.sort(sortfn); - if(!comm_list.length || (comm_list[0].sender != doc.raised_by)) { + if(!comm_list.length || (comm_list[comm_list.length - 1].sender != doc.raised_by)) { comm_list.push({ "sender": doc.raised_by, "creation": doc.creation, diff --git a/website/__init__.py b/website/__init__.py index 909a936768..aace68bb64 100644 --- a/website/__init__.py +++ b/website/__init__.py @@ -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 diff --git a/website/helpers/contact.py b/website/helpers/contact.py new file mode 100644 index 0000000000..df4510fa39 --- /dev/null +++ b/website/helpers/contact.py @@ -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 . + +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' \ No newline at end of file From 83f51819870238a9a5c63d7d43e682517075b731 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 16 Jan 2013 11:35:09 +0530 Subject: [PATCH 23/31] added automatic lead creation for sales email id --- .../{sales_applicant_list.js => sales_email_settings.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename setup/doctype/sales_email_settings/{sales_applicant_list.js => sales_email_settings.js} (100%) diff --git a/setup/doctype/sales_email_settings/sales_applicant_list.js b/setup/doctype/sales_email_settings/sales_email_settings.js similarity index 100% rename from setup/doctype/sales_email_settings/sales_applicant_list.js rename to setup/doctype/sales_email_settings/sales_email_settings.js From 8d3fe553e578ca4dff1600e90d8570deaffeb461 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 16 Jan 2013 12:19:28 +0530 Subject: [PATCH 24/31] added automatic lead creation for sales email id --- hr/doctype/job_applicant/job_applicant.py | 3 + public/js/modules.js | 118 +--------------------- selling/doctype/lead/lead.py | 3 + 3 files changed, 7 insertions(+), 117 deletions(-) diff --git a/hr/doctype/job_applicant/job_applicant.py b/hr/doctype/job_applicant/job_applicant.py index b4db3c07f7..da722fbff4 100644 --- a/hr/doctype/job_applicant/job_applicant.py +++ b/hr/doctype/job_applicant/job_applicant.py @@ -10,6 +10,9 @@ class DocType(TransactionBase): 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') diff --git a/public/js/modules.js b/public/js/modules.js index 5c572dda8e..5fcd64b5a3 100644 --- a/public/js/modules.js +++ b/public/js/modules.js @@ -97,120 +97,4 @@ $.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('' - +txt+''); - } - - // 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(''+html+''); - } - }); -} - -// make list of reports - -erpnext.module_page.make_list = function(module, wrapper) { - // make project listing - var $w = $(wrapper).find('.reports-list'); - var $parent1 = $('
').appendTo($w); - var $parent2 = $('
').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('\ - %(criteria_name)s', 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('\ - %(name)s', 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(""); - $parent2.find('.list-toolbar-wrapper') - .prepend(""); -} \ No newline at end of file +wn.provide('erpnext.module_page'); \ No newline at end of file diff --git a/selling/doctype/lead/lead.py b/selling/doctype/lead/lead.py index 13d1714830..dd24ff696c 100644 --- a/selling/doctype/lead/lead.py +++ b/selling/doctype/lead/lead.py @@ -80,6 +80,9 @@ class DocType(TransactionBase): 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("""delete from tabCommunication where lead=%s""", self.doc.name) From 1a0b27ee7bf61114beaa8498dbc8bcbe4983f4e4 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 16 Jan 2013 12:36:07 +0530 Subject: [PATCH 25/31] added automatic lead creation for sales email id --- startup/schedule_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startup/schedule_handlers.py b/startup/schedule_handlers.py index b1ce1836c8..c710c54086 100644 --- a/startup/schedule_handlers.py +++ b/startup/schedule_handlers.py @@ -33,7 +33,7 @@ def execute_all(): run_fn(get_job_applications) from selling.doctype.lead.get_leads import get_leads - run_fn(get_job_applications) + run_fn(get_leads) from webnotes.utils.email_lib.bulk import flush run_fn(flush) From 8f3916dd54822a6e6f358ed49ad9d60797432f4f Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 16 Jan 2013 12:50:55 +0530 Subject: [PATCH 26/31] add job applicant, sales and jobs email settings to module help --- hr/doctype/job_applicant/job_applicant.js | 7 +++++++ hr/page/hr_home/hr_home.js | 10 ++++++++++ selling/doctype/lead/lead.js | 6 ++++++ setup/page/setup/setup.js | 12 ++++++++++++ 4 files changed, 35 insertions(+) diff --git a/hr/doctype/job_applicant/job_applicant.js b/hr/doctype/job_applicant/job_applicant.js index a63f8335e3..c30125059e 100644 --- a/hr/doctype/job_applicant/job_applicant.js +++ b/hr/doctype/job_applicant/job_applicant.js @@ -1,6 +1,13 @@ // 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 = '
\ +

Jobs Email Settings
\ + Automatically extract Job Applicants from a mail box e.g. "jobs@example.com"

'; + } + }, refresh: function(doc) { cur_frm.cscript.make_listing(doc); }, diff --git a/hr/page/hr_home/hr_home.js b/hr/page/hr_home/hr_home.js index c2c5cd9f88..e26cbd3ecc 100644 --- a/hr/page/hr_home/hr_home.js +++ b/hr/page/hr_home/hr_home.js @@ -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."), diff --git a/selling/doctype/lead/lead.js b/selling/doctype/lead/lead.js index 9dd64de25d..d8d322d324 100644 --- a/selling/doctype/lead/lead.js +++ b/selling/doctype/lead/lead.js @@ -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 = '
\ +

Sales Email Settings
\ + Automatically extract Leads from a mail box e.g. "sales@example.com"

'; + } } cur_frm.cscript.refresh_custom_buttons = function(doc) { diff --git a/setup/page/setup/setup.js b/setup/page/setup/setup.js index f9c8796fe3..aa75893221 100644 --- a/setup/page/setup/setup.js +++ b/setup/page/setup/setup.js @@ -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", From 23ac4369335513076275618261f4df74c0bed63d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 16 Jan 2013 12:54:24 +0530 Subject: [PATCH 27/31] add job applicant, sales and jobs email settings to module help --- home/page/latest_updates/latest_updates.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/home/page/latest_updates/latest_updates.js b/home/page/latest_updates/latest_updates.js index c576e8866a..e5b0547d18 100644 --- a/home/page/latest_updates/latest_updates.js +++ b/home/page/latest_updates/latest_updates.js @@ -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 Jobs Email Settings.", + "Extract leads: Extract Leads from a mailbox like 'sales@example.com'. See Sales Email Settings.", + ]], ["14th January, 2013", [ "Stock Reconciliation: Ability to update Valuation Rate", "Time Field: Added Datetime and new Time Picker", From 30ab3b40a327043557ca966410b691878862c46a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 13:03:33 +0530 Subject: [PATCH 28/31] fixes in tree type documents --- setup/doctype/customer_group/customer_group.py | 5 +---- setup/doctype/sales_person/sales_person.py | 5 +---- setup/doctype/territory/territory.py | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/setup/doctype/customer_group/customer_group.py b/setup/doctype/customer_group/customer_group.py index cf126b8c07..f13690d93d 100644 --- a/setup/doctype/customer_group/customer_group.py +++ b/setup/doctype/customer_group/customer_group.py @@ -35,10 +35,7 @@ class DocType(DocTypeNestedSet): (self.doc.customer_group_name)): msgprint("""Another %s record is trashed. To untrash please go to Setup -> Recycle Bin.""" % - (self.doc.customer_group_name), raise_exception = 1) - - super(DocType, self).validate() - + (self.doc.customer_group_name), raise_exception = 1) def on_trash(self): cust = sql("select name from `tabCustomer` where ifnull(customer_group, '') = %s", diff --git a/setup/doctype/sales_person/sales_person.py b/setup/doctype/sales_person/sales_person.py index 943e724c9b..65e7ac14ab 100644 --- a/setup/doctype/sales_person/sales_person.py +++ b/setup/doctype/sales_person/sales_person.py @@ -32,7 +32,4 @@ class DocType(DocTypeNestedSet): for d in getlist(self.doclist, 'target_details'): 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() - \ No newline at end of file + raise Exception \ No newline at end of file diff --git a/setup/doctype/territory/territory.py b/setup/doctype/territory/territory.py index 4b6468e663..6d2da6a336 100644 --- a/setup/doctype/territory/territory.py +++ b/setup/doctype/territory/territory.py @@ -32,7 +32,4 @@ class DocType(DocTypeNestedSet): for d in getlist(self.doclist, 'target_details'): 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() - \ No newline at end of file + raise Exception \ No newline at end of file From 831207ff54319e5d31e85658788f6139a7bd3eeb Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 14:15:48 +0530 Subject: [PATCH 29/31] incoming rate for sales return as per delivery note outgoing rate --- stock/doctype/stock_entry/stock_entry.js | 3 +- stock/doctype/stock_entry/stock_entry.py | 48 ++++++++++++++----- .../stock_reconciliation.txt | 7 +-- stock/utils.py | 41 +++++++++++----- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js index bb55622453..6613ec43b3 100644 --- a/stock/doctype/stock_entry/stock_entry.js +++ b/stock/doctype/stock_entry/stock_entry.js @@ -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); diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index eaf796655d..12e69e6e94 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -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,23 +159,46 @@ 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'): @@ -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 diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.txt b/stock/doctype/stock_reconciliation/stock_reconciliation.txt index 58384cc42f..145d6fa9ad 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.txt +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.txt @@ -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, diff --git a/stock/utils.py b/stock/utils.py index a65406beb0..e05e07aa38 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -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 + if not previous_sle: + return 0.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 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 - - 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 +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 + + 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""" From d11e9d66195eeb6e9f91ac58b1438437d5cf9df4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 16:51:58 +0530 Subject: [PATCH 30/31] removed time_to_ampm and time_to_hhmm function --- .../production_planning_tool.py | 90 ++++++++++++++++--- .../production_planning_tool.txt | 48 ++++++---- .../maintenance_visit_list.js | 5 +- utilities/page/calendar/calendar.js | 13 +-- 4 files changed, 113 insertions(+), 43 deletions(-) diff --git a/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/manufacturing/doctype/production_planning_tool/production_planning_tool.py index adc80918e1..fa08789b91 100644 --- a/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -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): @@ -291,4 +298,63 @@ class DocType: if item_qty: item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty]) - return item_list \ No newline at end of file + 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, + + + } + ] \ No newline at end of file diff --git a/manufacturing/doctype/production_planning_tool/production_planning_tool.txt b/manufacturing/doctype/production_planning_tool/production_planning_tool.txt index dd7acfdb14..7eb0a2da11 100644 --- a/manufacturing/doctype/production_planning_tool/production_planning_tool.txt +++ b/manufacturing/doctype/production_planning_tool/production_planning_tool.txt @@ -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", diff --git a/support/doctype/maintenance_visit/maintenance_visit_list.js b/support/doctype/maintenance_visit/maintenance_visit_list.js index 654f455fd0..6dc5daccb3 100644 --- a/support/doctype/maintenance_visit/maintenance_visit_list.js +++ b/support/doctype/maintenance_visit/maintenance_visit_list.js @@ -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 + "]": ""); diff --git a/utilities/page/calendar/calendar.js b/utilities/page/calendar/calendar.js index d62dc68a86..5e59f1f4c8 100644 --- a/utilities/page/calendar/calendar.js +++ b/utilities/page/calendar/calendar.js @@ -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 = '
' + erpnext.calendar.weekdays[c.getDay()] + ', ' + c.getDate() + ' ' + month_list_full[c.getMonth()] + ' ' + c.getFullYear() - + ' - '+tmp+'
'; + + ' - '+this.ev.event_hour+''; // 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); From 6b1f21d1bb4053362aaa3a31915ececb0409589e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 17:17:17 +0530 Subject: [PATCH 31/31] fixes in get_incoming_rate --- stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock/utils.py b/stock/utils.py index e05e07aa38..e7702a89fd 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -82,7 +82,7 @@ def get_incoming_rate(args): return 0.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 + 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