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 c4b622a1a3e..48ed21a327b 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 da1755feb9b..113671462b5 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 113671462b5..81a50507254 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 81a50507254..85ee89a7b83 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 85ee89a7b83..316eff80862 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 668c11d92c5..54b9892b138 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 316eff80862..e69de29bb2d 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 00000000000..204ca3431fd --- /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 159dddd62a0..28b08f8ccb5 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 204ca3431fd..bb0c008444b 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 bb0c008444b..12c2fbc7702 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 12c2fbc7702..e5e99f5942e 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 00000000000..e69de29bb2d diff --git a/hr/doctype/job_applicant/job_applicant.py b/hr/doctype/job_applicant/job_applicant.py new file mode 100644 index 00000000000..928aa9ff9f2 --- /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 00000000000..3031211f3b4 --- /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 00000000000..e69de29bb2d diff --git a/hr/doctype/job_opening/job_opening.py b/hr/doctype/job_opening/job_opening.py new file mode 100644 index 00000000000..928aa9ff9f2 --- /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 00000000000..bd994c6ad97 --- /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 e5e99f5942e..c0b85e49b8f 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 00000000000..c5066dcc4e0 --- /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 00000000000..2b8e064e8d9 --- /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 3031211f3b4..390b659a99c 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 bd994c6ad97..5e26f0d1c17 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 00000000000..e69de29bb2d 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 00000000000..0a75b89360b --- /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 00000000000..b09cefd5e2a --- /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 00000000000..788f51b7e94 --- /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 54b9892b138..ab53b211edb 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 28b08f8ccb5..bbaf95b523b 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 390b659a99c..a0c72245dfb 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 2b8e064e8d9..a63f8335e30 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 928aa9ff9f2..e1e1a246262 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 00000000000..d4715197956 --- /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 c5066dcc4e0..2884de8ec78 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 e1e1a246262..46fb3d7fe2a 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 a0c72245dfb..0078c95593d 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 d4715197956..3d149efef11 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 2884de8ec78..e5805ac6b3a 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 46fb3d7fe2a..b4db3c07f71 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 e5805ac6b3a..a11a6191d0c 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 a11a6191d0c..7509380729d 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 4ab0acc541b..9017843c662 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 c2b296f371b..4f4d8243476 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 ea495ce6cd0..780fb86f3ed 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 228a8ae2a19..06bbf44e12b 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 d1fc4fcb93a..1f623bbd39d 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 5b8c2e827b2..9c755ef806c 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 77e0a3df73d..37011bbca2b 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 7509380729d..f2b776c011b 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 817175bf474..e2413f124d4 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 ff480ed5ee2..6f90434b7b2 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 00000000000..61274574b72 --- /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 63f9bd0258a..13d1714830b 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 6ffe3c42744..a4f0206070c 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 cfbfc36d679..3b2f53a57ff 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 00000000000..e69de29bb2d 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 00000000000..0a75b89360b --- /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 00000000000..b09cefd5e2a --- /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 00000000000..d8042e970fe --- /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 e0767c318d0..a5fcc77559f 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 ab53b211edb..b1ce1836c84 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 15ea491b135..c74c5962354 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 bbaf95b523b..9abdff074aa 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 909a9367689..aace68bb648 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 00000000000..df4510fa391 --- /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 b4db3c07f71..da722fbff4b 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 5c572dda8ed..5fcd64b5a3e 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 13d1714830b..dd24ff696ca 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 b1ce1836c84..c710c54086c 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 a63f8335e30..c30125059ee 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 c2c5cd9f883..e26cbd3ecc0 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 9dd64de25db..d8d322d3249 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 f9c8796fe3d..aa75893221a 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 c576e8866af..e5b0547d18a 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 cf126b8c07e..f13690d93d8 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 943e724c9b9..65e7ac14abc 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 4b6468e6633..6d2da6a3366 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 bb556224537..6613ec43b38 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 eaf796655df..12e69e6e94b 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 58384cc42ff..145d6fa9adc 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 a65406beb05..e05e07aa381 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 adc80918e15..fa08789b91c 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 dd7acfdb14e..7eb0a2da112 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 654f455fd0d..6dc5daccb34 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 d62dc68a866..5e59f1f4c8e 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 e05e07aa381..e7702a89fd5 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