Merge branch 'wsgi' of https://github.com/webnotes/erpnext into i18n

Conflicts:
	accounts/doctype/purchase_taxes_and_charges_master/purchase_taxes_and_charges_master.js
	accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js
	public/js/complete_setup.js
	selling/doctype/opportunity/opportunity.js
	selling/doctype/quotation/quotation.js
This commit is contained in:
Bárbara Perretti
2013-10-16 15:09:18 -03:00
250 changed files with 5061 additions and 2506 deletions

View File

@@ -34,7 +34,6 @@ cur_frm.cscript.refresh = function(doc,dt,dn) {
cur_frm.cscript.make_contact(doc,dt,dn);
cur_frm.communication_view = new wn.views.CommunicationList({
list: wn.model.get("Communication", {"customer": doc.name}),
parent: cur_frm.fields_dict.communication_html.wrapper,
doc: doc,
});
@@ -45,7 +44,7 @@ cur_frm.cscript.setup_dashboard = function(doc) {
cur_frm.dashboard.reset(doc);
if(doc.__islocal)
return;
cur_frm.dashboard.set_headline('<span class="text-muted">Loading...</span>')
cur_frm.dashboard.set_headline('<span class="text-muted">'+ wn._('Loading...')+ '</span>')
cur_frm.dashboard.add_doctype_badge("Opportunity", "customer");
cur_frm.dashboard.add_doctype_badge("Quotation", "customer");
@@ -119,4 +118,4 @@ cur_frm.fields_dict.lead_name.get_query = function(doc,cdt,cdn) {
return{
query:"controllers.queries.lead_query"
}
}
}

View File

@@ -9,7 +9,6 @@ from webnotes.model.doc import Document, make_autoname
from webnotes import msgprint, _
import webnotes.defaults
sql = webnotes.conn.sql
from utilities.transaction_base import TransactionBase
@@ -31,7 +30,7 @@ class DocType(TransactionBase):
return webnotes.conn.get_value('Company', self.doc.company, 'abbr')
def get_receivables_group(self):
g = sql("select receivables_group from tabCompany where name=%s", self.doc.company)
g = webnotes.conn.sql("select receivables_group from tabCompany where name=%s", self.doc.company)
g = g and g[0][0] or ''
if not g:
msgprint("Update Company master, assign a default group for Receivables")
@@ -47,7 +46,7 @@ class DocType(TransactionBase):
def update_lead_status(self):
if self.doc.lead_name:
sql("update `tabLead` set status='Converted' where name = %s", self.doc.lead_name)
webnotes.conn.sql("update `tabLead` set status='Converted' where name = %s", self.doc.lead_name)
def create_account_head(self):
if self.doc.company :
@@ -132,7 +131,7 @@ class DocType(TransactionBase):
def delete_customer_account(self):
"""delete customer's ledger if exist and check balance before deletion"""
acc = sql("select name from `tabAccount` where master_type = 'Customer' \
acc = webnotes.conn.sql("select name from `tabAccount` where master_type = 'Customer' \
and master_name = %s and docstatus < 2", self.doc.name)
if acc:
from webnotes.model import delete_doc
@@ -143,7 +142,7 @@ class DocType(TransactionBase):
self.delete_customer_contact()
self.delete_customer_account()
if self.doc.lead_name:
sql("update `tabLead` set status='Interested' where name=%s",self.doc.lead_name)
webnotes.conn.sql("update `tabLead` set status='Interested' where name=%s",self.doc.lead_name)
def on_rename(self, new, old, merge=False):
#update customer_name if not naming series

View File

@@ -17,6 +17,8 @@ class TestCustomer(unittest.TestCase):
(("_Test Customer 1 Renamed",),))
self.assertEqual(webnotes.conn.exists("Customer", "_Test Customer 1"), ())
webnotes.rename_doc("Customer", "_Test Customer 1 Renamed", "_Test Customer 1")
def test_merge(self):
from webnotes.test_runner import make_test_records
make_test_records("Sales Invoice")
@@ -57,6 +59,9 @@ class TestCustomer(unittest.TestCase):
# check that old name doesn't exist
self.assertEqual(webnotes.conn.exists("Customer", "_Test Customer"), ())
self.assertEqual(webnotes.conn.exists("Account", "_Test Customer - _TC"), ())
# create back _Test Customer
webnotes.bean(copy=test_records[0]).insert()
test_ignore = ["Price List"]

View File

@@ -38,7 +38,6 @@ class DocType(TransactionBase):
self.check_item_table()
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_active_sales_items(self)
sales_com_obj.get_prevdoc_date(self)
def validate_fiscal_year(self):
from accounts.utils import validate_fiscal_year

View File

@@ -2,7 +2,7 @@
{
"creation": "2013-02-22 01:27:51",
"docstatus": 0,
"modified": "2013-07-10 14:54:09",
"modified": "2013-10-10 17:02:31",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -48,18 +48,6 @@
"read_only": 1,
"width": "300px"
},
{
"doctype": "DocField",
"fieldname": "prevdoc_date",
"fieldtype": "Date",
"hidden": 0,
"in_list_view": 1,
"label": "Delivery Date",
"oldfieldname": "prevdoc_date",
"oldfieldtype": "Date",
"print_hide": 0,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "serial_no",

View File

@@ -29,7 +29,7 @@ def add_sales_communication(subject, content, sender, real_name, mail=None,
parent_name = contact_name or lead_name
message = make(content=content, sender=sender, subject=subject,
doctype = parent_doctype, name = parent_name, date=date)
doctype = parent_doctype, name = parent_name, date=date, sent_or_received="Received")
if mail:
# save attachments to parent if from mail

View File

@@ -33,7 +33,7 @@ erpnext.LeadController = wn.ui.form.Controller.extend({
var doc = this.frm.doc;
erpnext.hide_naming_series();
this.frm.clear_custom_buttons();
this.frm.__is_customer = this.frm.__is_customer || this.frm.doc.__is_customer;
if(!this.frm.doc.__islocal && !this.frm.__is_customer) {
this.frm.add_custom_button(wn._("Create Customer"), this.create_customer);

View File

@@ -7,7 +7,6 @@ from webnotes import _
from webnotes.utils import cstr, validate_email_add, cint, extract_email_id
from webnotes import session, msgprint
sql = webnotes.conn.sql
from controllers.selling_controller import SellingController
@@ -27,24 +26,9 @@ class DocType(SellingController):
customer = webnotes.conn.get_value("Customer", {"lead_name": self.doc.name})
if customer:
self.doc.fields["__is_customer"] = customer
def on_communication(self, comm):
if comm.sender == self.get_sender(comm) or \
webnotes.conn.get_value("Profile", extract_email_id(comm.sender), "user_type")=="System User":
status = "Replied"
else:
status = "Open"
webnotes.conn.set(self.doc, 'status', status)
def check_status(self):
chk = sql("select status from `tabLead` where name=%s", self.doc.name)
chk = chk and chk[0][0] or ''
return cstr(chk)
def validate(self):
if self.doc.status == 'Lead Lost' and not self.doc.order_lost_reason:
webnotes.throw("Please Enter Lost Reason under More Info section")
self.set_status()
if self.doc.source == 'Campaign' and not self.doc.campaign_name and session['user'] != 'Guest':
webnotes.throw("Please specify campaign name")
@@ -76,14 +60,18 @@ class DocType(SellingController):
webnotes.msgprint(_("""Email Id must be unique, already exists for: """) + \
", ".join(items), raise_exception=True)
def get_sender(self, comm):
return webnotes.conn.get_value('Sales Email Settings',None,'email_id')
def on_trash(self):
webnotes.conn.sql("""update `tabSupport Ticket` set lead='' where lead=%s""",
self.doc.name)
self.delete_events()
def has_customer(self):
return webnotes.conn.get_value("Customer", {"lead_name": self.doc.name})
def has_opportunity(self):
return webnotes.conn.get_value("Opportunity", {"lead": self.doc.name, "docstatus": 1,
"status": ["!=", "Lost"]})
@webnotes.whitelist()
def make_customer(source_name, target_doclist=None):
@@ -133,4 +121,4 @@ def make_opportunity(source_name, target_doclist=None):
}
}}, target_doclist)
return [d.fields for d in doclist]
return [d if isinstance(d, dict) else d.fields for d in doclist]

View File

@@ -2,11 +2,12 @@
{
"creation": "2013-04-10 11:45:37",
"docstatus": 0,
"modified": "2013-09-26 16:30:36",
"modified": "2013-10-09 15:27:54",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_import": 1,
"autoname": "naming_series:",
"doctype": "DocType",
"document_type": "Master",
@@ -101,7 +102,7 @@
"fieldtype": "Column Break"
},
{
"default": "Open",
"default": "Lead",
"doctype": "DocField",
"fieldname": "status",
"fieldtype": "Select",
@@ -111,7 +112,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "\nOpen\nReplied\nAttempted to Contact\nContact in Future\nContacted\nOpportunity Made\nInterested\nNot interested\nLead Lost\nConverted\nPassive",
"options": "Lead\nOpen\nReplied\nOpportunity\nInterested\nConverted\nDo Not Contact",
"reqd": 1,
"search_index": 1
},
@@ -174,19 +175,6 @@
"options": "Profile",
"search_index": 1
},
{
"depends_on": "eval:!doc.__islocal",
"description": "Date on which the lead was last contacted",
"doctype": "DocField",
"fieldname": "last_contact_date",
"fieldtype": "Date",
"label": "Last Contact Date",
"no_copy": 1,
"oldfieldname": "last_contact_date",
"oldfieldtype": "Date",
"print_hide": 1,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "col_break123",
@@ -236,8 +224,7 @@
"fieldtype": "HTML",
"label": "Communication HTML",
"oldfieldname": "follow_up",
"oldfieldtype": "Table",
"print_hide": 1
"oldfieldtype": "Table"
},
{
"doctype": "DocField",
@@ -390,18 +377,6 @@
"oldfieldtype": "Column Break",
"width": "50%"
},
{
"allow_on_submit": 0,
"depends_on": "eval:doc.status == 'Lead Lost'",
"doctype": "DocField",
"fieldname": "order_lost_reason",
"fieldtype": "Link",
"hidden": 0,
"label": "Lost Reason",
"oldfieldname": "order_lost_reason",
"oldfieldtype": "Link",
"options": "Quotation Lost Reason"
},
{
"doctype": "DocField",
"fieldname": "company",
@@ -430,8 +405,7 @@
"fieldtype": "Table",
"hidden": 1,
"label": "Communications",
"options": "Communication",
"print_hide": 1
"options": "Communication"
},
{
"cancel": 1,

View File

@@ -101,21 +101,21 @@ $.extend(cur_frm.cscript, new erpnext.selling.Opportunity({frm: cur_frm}));
cur_frm.cscript.refresh = function(doc, cdt, cdn){
erpnext.hide_naming_series();
cur_frm.dashboard.reset(doc);
if(!doc.__islocal) {
if(doc.status=="Converted" || doc.status=="Order Confirmed") {
cur_frm.dashboard.set_headline_alert(wn._(doc.status), "alert-success", "icon-ok-sign");
} else if(doc.status=="Opportunity Lost") {
cur_frm.dashboard.set_headline_alert(wn._(doc.status), "alert-danger", "icon-exclamation-sign");
}
}
cur_frm.clear_custom_buttons();
<<<<<<< HEAD
if(doc.docstatus === 1 && doc.status!=="Opportunity Lost") {
cur_frm.add_custom_button( wn._('Create Quotation'), cur_frm.cscript.create_quotation);
cur_frm.add_custom_button(wn._('Opportunity Lost'), cur_frm.cscript['Declare Opportunity Lost']);
cur_frm.add_custom_button(wn._('Send SMS'), cur_frm.cscript.send_sms);
=======
if(doc.docstatus === 1 && doc.status!=="Lost") {
cur_frm.add_custom_button('Create Quotation', cur_frm.cscript.create_quotation);
if(doc.status!=="Quotation") {
cur_frm.add_custom_button('Opportunity Lost', cur_frm.cscript['Declare Opportunity Lost']);
}
cur_frm.add_custom_button('Send SMS', cur_frm.cscript.send_sms);
>>>>>>> f146e8b7f52a3e46e335c0fefd92c347717b370b
}
cur_frm.toggle_display("contact_info", doc.customer || doc.lead);

View File

@@ -4,11 +4,10 @@
from __future__ import unicode_literals
import webnotes
from webnotes.utils import cstr, getdate, cint
from webnotes.utils import cstr, cint
from webnotes.model.bean import getlist
from webnotes import msgprint
from webnotes import msgprint, _
sql = webnotes.conn.sql
from utilities.transaction_base import TransactionBase
@@ -18,7 +17,7 @@ class DocType(TransactionBase):
self.doclist = doclist
self.fname = 'enq_details'
self.tname = 'Opportunity Item'
self._prev = webnotes._dict({
"contact_date": webnotes.conn.get_value("Opportunity", self.doc.name, "contact_date") if \
(not cint(self.doc.fields.get("__islocal"))) else None,
@@ -27,7 +26,7 @@ class DocType(TransactionBase):
})
def get_item_details(self, item_code):
item = sql("""select item_name, stock_uom, description_html, description, item_group, brand
item = webnotes.conn.sql("""select item_name, stock_uom, description_html, description, item_group, brand
from `tabItem` where name = %s""", item_code, as_dict=1)
ret = {
'item_name': item and item[0]['item_name'] or '',
@@ -39,7 +38,7 @@ class DocType(TransactionBase):
return ret
def get_cust_address(self,name):
details = sql("select customer_name, address, territory, customer_group from `tabCustomer` where name = '%s' and docstatus != 2" %(name), as_dict = 1)
details = webnotes.conn.sql("select customer_name, address, territory, customer_group from `tabCustomer` where name = '%s' and docstatus != 2" %(name), as_dict = 1)
if details:
ret = {
'customer_name': details and details[0]['customer_name'] or '',
@@ -49,7 +48,7 @@ class DocType(TransactionBase):
}
# ********** get primary contact details (this is done separately coz. , in case there is no primary contact thn it would not be able to fetch customer details in case of join query)
contact_det = sql("select contact_name, contact_no, email_id from `tabContact` where customer = '%s' and is_customer = 1 and is_primary_contact = 'Yes' and docstatus != 2" %(name), as_dict = 1)
contact_det = webnotes.conn.sql("select contact_name, contact_no, email_id from `tabContact` where customer = '%s' and is_customer = 1 and is_primary_contact = 'Yes' and docstatus != 2" %(name), as_dict = 1)
ret['contact_person'] = contact_det and contact_det[0]['contact_name'] or ''
ret['contact_no'] = contact_det and contact_det[0]['contact_no'] or ''
@@ -62,18 +61,15 @@ class DocType(TransactionBase):
def get_contact_details(self, arg):
arg = eval(arg)
contact = sql("select contact_no, email_id from `tabContact` where contact_name = '%s' and customer_name = '%s'" %(arg['contact_person'],arg['customer']), as_dict = 1)
contact = webnotes.conn.sql("select contact_no, email_id from `tabContact` where contact_name = '%s' and customer_name = '%s'" %(arg['contact_person'],arg['customer']), as_dict = 1)
ret = {
'contact_no' : contact and contact[0]['contact_no'] or '',
'email_id' : contact and contact[0]['email_id'] or ''
}
return ret
def on_update(self):
# Add to calendar
if self.doc.contact_date and self.doc.contact_date_ref != self.doc.contact_date:
webnotes.conn.set(self.doc, 'contact_date_ref',self.doc.contact_date)
self.add_calendar_event()
def add_calendar_event(self, opts=None, force=False):
@@ -101,14 +97,6 @@ class DocType(TransactionBase):
super(DocType, self).add_calendar_event(opts, force)
def set_last_contact_date(self):
if self.doc.contact_date_ref and self.doc.contact_date_ref != self.doc.contact_date:
if getdate(self.doc.contact_date_ref) < getdate(self.doc.contact_date):
self.doc.last_contact_date=self.doc.contact_date_ref
else:
msgprint("Contact Date Cannot be before Last Contact Date")
raise Exception
def validate_item_details(self):
if not getlist(self.doclist, 'enquiry_details'):
msgprint("Please select items for which enquiry needs to be made")
@@ -121,49 +109,36 @@ class DocType(TransactionBase):
msgprint("Customer is mandatory if 'Opportunity From' is selected as Customer", raise_exception=1)
def validate(self):
self.set_last_contact_date()
self.set_status()
self.validate_item_details()
self.validate_uom_is_integer("uom", "qty")
self.validate_lead_cust()
from accounts.utils import validate_fiscal_year
validate_fiscal_year(self.doc.transaction_date, self.doc.fiscal_year, "Opportunity Date")
self.doc.status = "Draft"
def on_submit(self):
webnotes.conn.set(self.doc, 'status', 'Submitted')
if self.doc.lead and webnotes.conn.get_value("Lead", self.doc.lead, "status")!="Converted":
webnotes.conn.set_value("Lead", self.doc.lead, "status", "Opportunity Made")
if self.doc.lead:
webnotes.bean("Lead", self.doc.lead).get_controller().set_status(update=True)
def on_cancel(self):
chk = sql("select t1.name from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.docstatus=1 and (t1.status!='Order Lost' and t1.status!='Cancelled') and t2.prevdoc_docname = %s",self.doc.name)
if chk:
msgprint("Quotation No. "+cstr(chk[0][0])+" is submitted against this Opportunity. Thus can not be cancelled.")
raise Exception
else:
webnotes.conn.set(self.doc, 'status', 'Cancelled')
if self.doc.lead and webnotes.conn.get_value("Lead", self.doc.lead,
"status")!="Converted":
if webnotes.conn.get_value("Communication", {"parent": self.doc.lead}):
status = "Contacted"
else:
status = "Open"
webnotes.conn.set_value("Lead", self.doc.lead, "status", status)
if self.has_quotation():
webnotes.throw(_("Cannot Cancel Opportunity as Quotation Exists"))
self.set_status(update=True)
def declare_enquiry_lost(self,arg):
chk = sql("select t1.name from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.docstatus=1 and (t1.status!='Order Lost' and t1.status!='Cancelled') and t2.prevdoc_docname = %s",self.doc.name)
if chk:
msgprint("Quotation No. "+cstr(chk[0][0])+" is submitted against this Opportunity. Thus 'Opportunity Lost' can not be declared against it.")
raise Exception
else:
webnotes.conn.set(self.doc, 'status', 'Opportunity Lost')
if not self.has_quotation():
webnotes.conn.set(self.doc, 'status', 'Lost')
webnotes.conn.set(self.doc, 'order_lost_reason', arg)
return 'true'
else:
webnotes.throw(_("Cannot declare as lost, because Quotation has been made."))
def on_trash(self):
self.delete_events()
def has_quotation(self):
return webnotes.conn.get_value("Quotation Item", {"prevdoc_docname": self.doc.name, "docstatus": 1})
@webnotes.whitelist()
def make_quotation(source_name, target_doclist=None):
from webnotes.model.mapper import get_mapped_doclist

View File

@@ -2,11 +2,12 @@
{
"creation": "2013-03-07 18:50:30",
"docstatus": 0,
"modified": "2013-09-25 19:32:29",
"modified": "2013-10-09 15:26:29",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_import": 1,
"autoname": "naming_series:",
"description": "Potential Sales Deal",
"doctype": "DocType",
@@ -125,7 +126,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "\nDraft\nSubmitted\nQuotation Sent\nOrder Confirmed\nOpportunity Lost\nCancelled",
"options": "Draft\nSubmitted\nQuotation\nLost\nCancelled\nReplied\nOpen",
"read_only": 1,
"reqd": 1
},
@@ -168,7 +169,6 @@
"label": "Communication History",
"oldfieldtype": "Section Break",
"options": "icon-comments",
"print_hide": 1,
"read_only": 0
},
{
@@ -179,7 +179,6 @@
"label": "Communication HTML",
"oldfieldname": "follow_up",
"oldfieldtype": "Table",
"print_hide": 1,
"read_only": 0
},
{
@@ -351,18 +350,6 @@
"options": "Campaign",
"read_only": 0
},
{
"depends_on": "eval:!doc.__islocal",
"doctype": "DocField",
"fieldname": "order_lost_reason",
"fieldtype": "Small Text",
"label": "Quotation Lost Reason",
"no_copy": 1,
"oldfieldname": "order_lost_reason",
"oldfieldtype": "Small Text",
"read_only": 1,
"report_hide": 0
},
{
"doctype": "DocField",
"fieldname": "company",
@@ -377,6 +364,15 @@
"reqd": 1,
"search_index": 1
},
{
"depends_on": "eval:!doc.__islocal",
"doctype": "DocField",
"fieldname": "order_lost_reason",
"fieldtype": "Text",
"label": "Lost Reason",
"no_copy": 1,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "column_break2",
@@ -408,20 +404,6 @@
"oldfieldtype": "Date",
"read_only": 0
},
{
"allow_on_submit": 0,
"depends_on": "eval:!doc.__islocal",
"description": "Date on which the lead was last contacted",
"doctype": "DocField",
"fieldname": "last_contact_date",
"fieldtype": "Date",
"label": "Last Contact Date",
"no_copy": 1,
"oldfieldname": "last_contact_date",
"oldfieldtype": "Date",
"print_hide": 1,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "to_discuss",
@@ -450,8 +432,7 @@
"fieldtype": "Table",
"hidden": 1,
"label": "Communications",
"options": "Communication",
"print_hide": 1
"options": "Communication"
},
{
"doctype": "DocPerm",

View File

@@ -11,6 +11,7 @@ cur_frm.cscript.sales_team_fname = "sales_team";
wn.require('app/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js');
wn.require('app/utilities/doctype/sms_control/sms_control.js');
wn.require('app/selling/doctype/sales_common/sales_common.js');
wn.require('app/accounts/doctype/sales_invoice/pos.js');
erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
onload: function(doc, dt, dn) {
@@ -23,20 +24,18 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
},
refresh: function(doc, dt, dn) {
this._super(doc, dt, dn);
cur_frm.dashboard.reset(doc);
if(!doc.__islocal) {
if(doc.status=="Converted" || doc.status=="Order Confirmed") {
cur_frm.dashboard.set_headline_alert(wn._(doc.status), "alert-success", "icon-ok-sign");
} else if(doc.status==="Order Lost") {
cur_frm.dashboard.set_headline_alert(wn._(doc.status), "alert-danger", "icon-exclamation-sign");
}
}
<<<<<<< HEAD
if(doc.docstatus == 1 && doc.status!=='Order Lost') {
cur_frm.add_custom_button(wn._('Make Sales Order'), cur_frm.cscript['Make Sales Order']);
if(doc.status!=="Order Confirmed") {
cur_frm.add_custom_button(wn._('Set as Lost'), cur_frm.cscript['Declare Order Lost']);
=======
if(doc.docstatus == 1 && doc.status!=='Lost') {
cur_frm.add_custom_button('Make Sales Order', cur_frm.cscript['Make Sales Order']);
if(doc.status!=="Ordered") {
cur_frm.add_custom_button('Set as Lost', cur_frm.cscript['Declare Order Lost']);
>>>>>>> f146e8b7f52a3e46e335c0fefd92c347717b370b
}
cur_frm.add_custom_button(wn._('Send SMS'), cur_frm.cscript.send_sms);
}
@@ -82,12 +81,12 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
},
validate_company_and_party: function(party_field) {
if(this.frm.doc.quotation_to == "Lead") {
return true;
} else if(!this.frm.doc.quotation_to) {
if(!this.frm.doc.quotation_to) {
msgprint(wn._("Please select a value for" + " " + wn.meta.get_label(this.frm.doc.doctype,
"quotation_to", this.frm.doc.name)));
return false;
} else if (this.frm.doc.quotation_to == "Lead") {
return true;
} else {
return this._super(party_field);
}

View File

@@ -4,12 +4,11 @@
from __future__ import unicode_literals
import webnotes
from webnotes.utils import cstr, getdate
from webnotes.utils import cstr
from webnotes.model.bean import getlist
from webnotes.model.code import get_obj
from webnotes import _, msgprint
sql = webnotes.conn.sql
from controllers.selling_controller import SellingController
@@ -48,17 +47,16 @@ class DocType(SellingController):
if not doc.fields.get(r):
doc.fields[r] = res[r]
def has_sales_order(self):
return webnotes.conn.get_value("Sales Order Item", {"prevdoc_docname": self.doc.name, "docstatus": 1})
# Re-calculates Basic Rate & amount based on Price List Selected
# --------------------------------------------------------------
def get_adj_percent(self, arg=''):
get_obj('Sales Common').get_adj_percent(self)
# Get Tax rate if account type is TAX
# -----------------------------------
def get_rate(self,arg):
return get_obj('Sales Common').get_rate(arg)
# Does not allow same item code to be entered twice
# -------------------------------------------------
def validate_for_items(self):
@@ -78,7 +76,7 @@ class DocType(SellingController):
if self.doc.order_type in ['Maintenance', 'Service']:
for d in getlist(self.doclist, 'quotation_details'):
is_service_item = sql("select is_service_item from `tabItem` where name=%s", d.item_code)
is_service_item = webnotes.conn.sql("select is_service_item from `tabItem` where name=%s", d.item_code)
is_service_item = is_service_item and is_service_item[0][0] or 'No'
if is_service_item == 'No':
@@ -86,37 +84,16 @@ class DocType(SellingController):
raise Exception
else:
for d in getlist(self.doclist, 'quotation_details'):
is_sales_item = sql("select is_sales_item from `tabItem` where name=%s", d.item_code)
is_sales_item = webnotes.conn.sql("select is_sales_item from `tabItem` where name=%s", d.item_code)
is_sales_item = is_sales_item and is_sales_item[0][0] or 'No'
if is_sales_item == 'No':
msgprint("You can not select non sales item "+d.item_code+" in Sales Quotation")
raise Exception
#--------------Validation For Last Contact Date-----------------
# ====================================================================================================================
def set_last_contact_date(self):
#if not self.doc.contact_date_ref:
#self.doc.contact_date_ref=self.doc.contact_date
#self.doc.last_contact_date=self.doc.contact_date_ref
if self.doc.contact_date_ref and self.doc.contact_date_ref != self.doc.contact_date:
if getdate(self.doc.contact_date_ref) < getdate(self.doc.contact_date):
self.doc.last_contact_date=self.doc.contact_date_ref
else:
msgprint("Contact Date Cannot be before Last Contact Date")
raise Exception
def validate(self):
super(DocType, self).validate()
import utilities
if not self.doc.status:
self.doc.status = "Draft"
else:
utilities.validate_status(self.doc.status, ["Draft", "Submitted",
"Order Confirmed", "Order Lost", "Cancelled"])
self.set_last_contact_date()
self.set_status()
self.validate_order_type()
self.validate_for_items()
@@ -126,42 +103,22 @@ class DocType(SellingController):
sales_com_obj.check_active_sales_items(self)
sales_com_obj.validate_max_discount(self,'quotation_details')
sales_com_obj.check_conversion_rate(self)
def on_update(self):
# Set Quotation Status
webnotes.conn.set(self.doc, 'status', 'Draft')
#update enquiry
#------------------
def update_enquiry(self, flag):
prevdoc=''
for d in getlist(self.doclist, 'quotation_details'):
if d.prevdoc_docname:
prevdoc = d.prevdoc_docname
if prevdoc:
if flag == 'submit': #on submit
sql("update `tabOpportunity` set status = 'Quotation Sent' where name = %s", prevdoc)
elif flag == 'cancel': #on cancel
sql("update `tabOpportunity` set status = 'Open' where name = %s", prevdoc)
elif flag == 'order lost': #order lost
sql("update `tabOpportunity` set status = 'Opportunity Lost' where name=%s", prevdoc)
elif flag == 'order confirm': #order confirm
sql("update `tabOpportunity` set status='Order Confirmed' where name=%s", prevdoc)
def update_opportunity(self):
for opportunity in self.doclist.get_distinct_values("prevdoc_docname"):
webnotes.bean("Opportunity", opportunity).get_controller().set_status(update=True)
# declare as order lost
#-------------------------
def declare_order_lost(self, arg):
chk = sql("select t1.name from `tabSales Order` t1, `tabSales Order Item` t2 where t2.parent = t1.name and t1.docstatus=1 and t2.prevdoc_docname = %s",self.doc.name)
if chk:
msgprint("Sales Order No. "+cstr(chk[0][0])+" is submitted against this Quotation. Thus 'Order Lost' can not be declared against it.")
raise Exception
else:
webnotes.conn.set(self.doc, 'status', 'Order Lost')
if not self.has_sales_order():
webnotes.conn.set(self.doc, 'status', 'Lost')
webnotes.conn.set(self.doc, 'order_lost_reason', arg)
self.update_enquiry('order lost')
return 'true'
self.update_opportunity()
else:
webnotes.throw(_("Cannot set as Lost as Sales Order is made."))
#check if value entered in item table
#--------------------------------------
@@ -177,21 +134,17 @@ class DocType(SellingController):
# Check for Approving Authority
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)
# Set Quotation Status
webnotes.conn.set(self.doc, 'status', 'Submitted')
#update enquiry status
self.update_enquiry('submit')
self.update_opportunity()
# ON CANCEL
# ==========================================================================
def on_cancel(self):
#update enquiry status
self.update_enquiry('cancel')
webnotes.conn.set(self.doc,'status','Cancelled')
self.set_status()
self.update_opportunity()
# Print other charges
# ===========================================================================
@@ -203,6 +156,7 @@ class DocType(SellingController):
lst1.append(d.total)
print_lst.append(lst1)
return print_lst
@webnotes.whitelist()
def make_sales_order(source_name, target_doclist=None):

View File

@@ -2,13 +2,14 @@
{
"creation": "2013-05-24 19:29:08",
"docstatus": 0,
"modified": "2013-09-10 10:46:33",
"modified": "2013-10-11 13:21:07",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_attach": 1,
"allow_email": 0,
"allow_import": 1,
"autoname": "naming_series:",
"doctype": "DocType",
"document_type": "Transaction",
@@ -257,7 +258,6 @@
"width": "100px"
},
{
"default": "1.00",
"description": "Rate at which customer's currency is converted to company's base currency",
"doctype": "DocField",
"fieldname": "conversion_rate",
@@ -733,7 +733,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "\nDraft\nSubmitted\nOrder Confirmed\nOrder Lost\nCancelled",
"options": "Draft\nSubmitted\nOrdered\nLost\nCancelled",
"print_hide": 1,
"read_only": 1,
"reqd": 1,
@@ -841,8 +841,7 @@
"fieldtype": "Table",
"hidden": 1,
"label": "Communications",
"options": "Communication",
"print_hide": 1
"options": "Communication"
},
{
"amend": 1,

View File

@@ -11,17 +11,19 @@ class TestQuotation(unittest.TestCase):
def test_make_sales_order(self):
from selling.doctype.quotation.quotation import make_sales_order
self.assertRaises(webnotes.ValidationError, make_sales_order, "_T-Quotation-00001")
quotation = webnotes.bean(copy=test_records[0])
quotation.insert()
self.assertRaises(webnotes.ValidationError, make_sales_order, quotation.doc.name)
quotation = webnotes.bean("Quotation","_T-Quotation-00001")
quotation.submit()
sales_order = make_sales_order("_T-Quotation-00001")
sales_order = make_sales_order(quotation.doc.name)
self.assertEquals(sales_order[0]["doctype"], "Sales Order")
self.assertEquals(len(sales_order), 2)
self.assertEquals(sales_order[1]["doctype"], "Sales Order Item")
self.assertEquals(sales_order[1]["prevdoc_docname"], "_T-Quotation-00001")
self.assertEquals(sales_order[1]["prevdoc_docname"], quotation.doc.name)
self.assertEquals(sales_order[0]["customer"], "_Test Customer")
sales_order[0]["delivery_date"] = "2014-01-01"

View File

@@ -10,6 +10,7 @@
wn.provide("erpnext.selling");
wn.require("app/js/transaction.js");
wn.require("app/js/controllers/accounts.js");
erpnext.selling.SellingController = erpnext.TransactionController.extend({
onload: function() {
@@ -162,8 +163,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
var item = wn.model.get_doc(cdt, cdn);
if(item.item_code || item.barcode) {
if(!this.validate_company_and_party("customer")) {
item.item_code = null;
refresh_field("item_code", item.name, item.parentfield);
cur_frm.fields_dict[me.frm.cscript.fname].grid.grid_rows[item.idx - 1].remove();
} else {
return this.frm.call({
method: "selling.utils.get_item_details",

View File

@@ -86,26 +86,6 @@ class DocType(TransactionBase):
if (obj.doc.price_list_currency == default_currency and flt(obj.doc.plc_conversion_rate) != 1.00) or not obj.doc.plc_conversion_rate or (obj.doc.price_list_currency != default_currency and flt(obj.doc.plc_conversion_rate) == 1.00):
msgprint("Please Enter Appropriate Conversion Rate for Price List Currency to Base Currency (%s --> %s)" % (obj.doc.price_list_currency, default_currency), raise_exception = 1)
# Get Tax rate if account type is TAX
# =========================================================================
def get_rate(self, arg):
arg = eval(arg)
rate = webnotes.conn.sql("select account_type, tax_rate from `tabAccount` where name = '%s' and docstatus != 2" %(arg['account_head']), as_dict=1)
ret = {'rate' : 0}
if arg['charge_type'] == 'Actual' and rate[0]['account_type'] == 'Tax':
msgprint("You cannot select ACCOUNT HEAD of type TAX as your CHARGE TYPE is 'ACTUAL'")
ret = {
'account_head' : ''
}
elif rate[0]['account_type'] in ['Tax', 'Chargeable'] and not arg['charge_type'] == 'Actual':
ret = {
'rate' : rate and flt(rate[0]['tax_rate']) or 0
}
return ret
def get_item_list(self, obj, is_stopped=0):
"""get item list"""
@@ -123,12 +103,12 @@ class DocType(TransactionBase):
if flt(d.qty) > flt(d.delivered_qty):
reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty)
if obj.doc.doctype == "Delivery Note" and d.prevdoc_doctype == 'Sales Order':
if obj.doc.doctype == "Delivery Note" and d.against_sales_order:
# if SO qty is 10 and there is tolerance of 20%, then it will allow DN of 12.
# But in this case reserved qty should only be reduced by 10 and not 12
already_delivered_qty = self.get_already_delivered_qty(obj.doc.name,
d.prevdoc_docname, d.prevdoc_detail_docname)
d.against_sales_order, d.prevdoc_detail_docname)
so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.prevdoc_detail_docname)
if already_delivered_qty + d.qty > so_qty:
@@ -168,7 +148,7 @@ class DocType(TransactionBase):
def get_already_delivered_qty(self, dn, so, so_detail):
qty = webnotes.conn.sql("""select sum(qty) from `tabDelivery Note Item`
where prevdoc_detail_docname = %s and docstatus = 1
and prevdoc_doctype = 'Sales Order' and prevdoc_docname = %s
and against_sales_order = %s
and parent != %s""", (so_detail, so, dn))
return qty and flt(qty[0][0]) or 0.0
@@ -218,7 +198,6 @@ class DocType(TransactionBase):
pi.qty = flt(qty)
pi.actual_qty = bin and flt(bin['actual_qty']) or 0
pi.projected_qty = bin and flt(bin['projected_qty']) or 0
pi.prevdoc_doctype = line.prevdoc_doctype
if not pi.warehouse:
pi.warehouse = warehouse
if not pi.batch_no:
@@ -283,8 +262,8 @@ class DocType(TransactionBase):
def check_stop_sales_order(self,obj):
for d in getlist(obj.doclist,obj.fname):
ref_doc_name = ''
if d.fields.has_key('prevdoc_docname') and d.prevdoc_docname and d.prevdoc_doctype == 'Sales Order':
ref_doc_name = d.prevdoc_docname
if d.fields.has_key('against_sales_order') and d.against_sales_order:
ref_doc_name = d.against_sales_order
elif d.fields.has_key('sales_order') and d.sales_order and not d.delivery_note:
ref_doc_name = d.sales_order
if ref_doc_name:
@@ -319,17 +298,6 @@ class DocType(TransactionBase):
exact_outstanding = flt(tot_outstanding) + flt(grand_total)
get_obj('Account',acc_head[0][0]).check_credit_limit(acc_head[0][0], obj.doc.company, exact_outstanding)
def get_prevdoc_date(self, obj):
for d in getlist(obj.doclist, obj.fname):
if d.prevdoc_doctype and d.prevdoc_docname:
if d.prevdoc_doctype in ["Sales Invoice", "Delivery Note"]:
date_field = "posting_date"
else:
date_field = "transaction_date"
d.prevdoc_date = webnotes.conn.get_value(d.prevdoc_doctype,
d.prevdoc_docname, date_field)
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
from controllers.queries import get_match_cond

View File

@@ -12,6 +12,7 @@ cur_frm.cscript.sales_team_fname = "sales_team";
wn.require('app/selling/doctype/sales_common/sales_common.js');
wn.require('app/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js');
wn.require('app/utilities/doctype/sms_control/sms_control.js');
wn.require('app/accounts/doctype/sales_invoice/pos.js');
erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend({
refresh: function(doc, dt, dn) {
@@ -65,7 +66,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
source_doctype: "Quotation",
get_query_filters: {
docstatus: 1,
status: ["!=", "Order Lost"],
status: ["!=", "Lost"],
order_type: cur_frm.doc.order_type,
customer: cur_frm.doc.customer || undefined,
company: cur_frm.doc.company
@@ -188,4 +189,4 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
if(cint(wn.boot.notification_settings.sales_order)) {
cur_frm.email_doc(wn.boot.notification_settings.sales_order_message);
}
};
};

View File

@@ -11,7 +11,6 @@ from webnotes.model.code import get_obj
from webnotes import msgprint
from webnotes.model.mapper import get_mapped_doclist
sql = webnotes.conn.sql
from controllers.selling_controller import SellingController
@@ -39,9 +38,6 @@ class DocType(SellingController):
def get_available_qty(self,args):
return get_obj('Sales Common').get_available_qty(eval(args))
def get_rate(self,arg):
return get_obj('Sales Common').get_rate(arg)
def validate_mandatory(self):
# validate transaction date v/s delivery date
if self.doc.delivery_date:
@@ -88,14 +84,14 @@ class DocType(SellingController):
# used for production plan
d.transaction_date = self.doc.transaction_date
tot_avail_qty = sql("select projected_qty from `tabBin` \
tot_avail_qty = webnotes.conn.sql("select projected_qty from `tabBin` \
where item_code = '%s' and warehouse = '%s'" % (d.item_code,d.reserved_warehouse))
d.projected_qty = tot_avail_qty and flt(tot_avail_qty[0][0]) or 0
def validate_sales_mntc_quotation(self):
for d in getlist(self.doclist, 'sales_order_details'):
if d.prevdoc_docname:
res = sql("select name from `tabQuotation` where name=%s and order_type = %s", (d.prevdoc_docname, self.doc.order_type))
res = webnotes.conn.sql("select name from `tabQuotation` where name=%s and order_type = %s", (d.prevdoc_docname, self.doc.order_type))
if not res:
msgprint("""Order Type (%s) should be same in Quotation: %s \
and current Sales Order""" % (self.doc.order_type, d.prevdoc_docname))
@@ -112,7 +108,7 @@ class DocType(SellingController):
def validate_proj_cust(self):
if self.doc.project_name and self.doc.customer_name:
res = sql("select name from `tabProject` where name = '%s' and (customer = '%s' or ifnull(customer,'')='')"%(self.doc.project_name, self.doc.customer))
res = webnotes.conn.sql("select name from `tabProject` where name = '%s' and (customer = '%s' or ifnull(customer,'')='')"%(self.doc.project_name, self.doc.customer))
if not res:
msgprint("Customer - %s does not belong to project - %s. \n\nIf you want to use project for multiple customers then please make customer details blank in project - %s."%(self.doc.customer,self.doc.project_name,self.doc.project_name))
raise Exception
@@ -127,7 +123,7 @@ class DocType(SellingController):
self.validate_po()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_for_items()
self.validate_warehouse_user()
self.validate_warehouse()
sales_com_obj = get_obj(dt = 'Sales Common')
sales_com_obj.check_active_sales_items(self)
sales_com_obj.check_conversion_rate(self)
@@ -148,14 +144,15 @@ class DocType(SellingController):
if not self.doc.delivery_status: self.doc.delivery_status = 'Not Delivered'
def validate_warehouse_user(self):
from stock.utils import validate_warehouse_user
def validate_warehouse(self):
from stock.utils import validate_warehouse_user, validate_warehouse_company
warehouses = list(set([d.reserved_warehouse for d in
self.doclist.get({"doctype": self.tname}) if d.reserved_warehouse]))
for w in warehouses:
validate_warehouse_user(w)
validate_warehouse_company(w, self.doc.company)
def validate_with_previous_doc(self):
super(DocType, self).validate_with_previous_doc(self.tname, {
@@ -166,36 +163,20 @@ class DocType(SellingController):
})
def check_prev_docstatus(self):
for d in getlist(self.doclist, 'sales_order_details'):
cancel_quo = sql("select name from `tabQuotation` where docstatus = 2 and name = '%s'" % d.prevdoc_docname)
if cancel_quo:
msgprint("Quotation :" + cstr(cancel_quo[0][0]) + " is already cancelled !")
raise Exception , "Validation Error. "
def update_enquiry_status(self, prevdoc, flag):
enq = sql("select t2.prevdoc_docname from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.name=%s", prevdoc)
enq = webnotes.conn.sql("select t2.prevdoc_docname from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.name=%s", prevdoc)
if enq:
sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
webnotes.conn.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
def update_prevdoc_status(self, flag):
for d in getlist(self.doclist, 'sales_order_details'):
if d.prevdoc_docname:
if flag=='submit':
sql("update `tabQuotation` set status = 'Order Confirmed' where name=%s",d.prevdoc_docname)
#update enquiry
self.update_enquiry_status(d.prevdoc_docname, 'Order Confirmed')
elif flag == 'cancel':
chk = sql("select t1.name from `tabSales Order` t1, `tabSales Order Item` t2 where t2.parent = t1.name and t2.prevdoc_docname=%s and t1.name!=%s and t1.docstatus=1", (d.prevdoc_docname,self.doc.name))
if not chk:
sql("update `tabQuotation` set status = 'Submitted' where name=%s",d.prevdoc_docname)
#update enquiry
self.update_enquiry_status(d.prevdoc_docname, 'Quotation Sent')
def update_prevdoc_status(self, flag):
for quotation in self.doclist.get_distinct_values("prevdoc_docname"):
bean = webnotes.bean("Quotation", quotation)
if bean.doc.docstatus==2:
webnotes.throw(d.prevdoc_docname + ": " + webnotes._("Quotation is cancelled."))
bean.get_controller().set_status(update=True)
def on_submit(self):
self.check_prev_docstatus()
self.update_stock_ledger(update_stock = 1)
get_obj('Sales Common').check_credit(self,self.doc.grand_total)
@@ -219,35 +200,35 @@ class DocType(SellingController):
def check_nextdoc_docstatus(self):
# Checks Delivery Note
submit_dn = sql("select t1.name from `tabDelivery Note` t1,`tabDelivery Note Item` t2 where t1.name = t2.parent and t2.prevdoc_docname = '%s' and t1.docstatus = 1" % (self.doc.name))
submit_dn = webnotes.conn.sql("select t1.name from `tabDelivery Note` t1,`tabDelivery Note Item` t2 where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 1", self.doc.name)
if submit_dn:
msgprint("Delivery Note : " + cstr(submit_dn[0][0]) + " has been submitted against " + cstr(self.doc.doctype) + ". Please cancel Delivery Note : " + cstr(submit_dn[0][0]) + " first and then cancel "+ cstr(self.doc.doctype), raise_exception = 1)
# Checks Sales Invoice
submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.sales_order = '%s' and t1.docstatus = 1" % (self.doc.name))
submit_rv = webnotes.conn.sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.sales_order = '%s' and t1.docstatus = 1" % (self.doc.name))
if submit_rv:
msgprint("Sales Invoice : " + cstr(submit_rv[0][0]) + " has already been submitted against " +cstr(self.doc.doctype)+ ". Please cancel Sales Invoice : "+ cstr(submit_rv[0][0]) + " first and then cancel "+ cstr(self.doc.doctype), raise_exception = 1)
#check maintenance schedule
submit_ms = sql("select t1.name from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1",self.doc.name)
submit_ms = webnotes.conn.sql("select t1.name from `tabMaintenance Schedule` t1, `tabMaintenance Schedule Item` t2 where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1",self.doc.name)
if submit_ms:
msgprint("Maintenance Schedule : " + cstr(submit_ms[0][0]) + " has already been submitted against " +cstr(self.doc.doctype)+ ". Please cancel Maintenance Schedule : "+ cstr(submit_ms[0][0]) + " first and then cancel "+ cstr(self.doc.doctype), raise_exception = 1)
# check maintenance visit
submit_mv = sql("select t1.name from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1",self.doc.name)
submit_mv = webnotes.conn.sql("select t1.name from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1",self.doc.name)
if submit_mv:
msgprint("Maintenance Visit : " + cstr(submit_mv[0][0]) + " has already been submitted against " +cstr(self.doc.doctype)+ ". Please cancel Maintenance Visit : " + cstr(submit_mv[0][0]) + " first and then cancel "+ cstr(self.doc.doctype), raise_exception = 1)
# check production order
pro_order = sql("""select name from `tabProduction Order` where sales_order = %s and docstatus = 1""", self.doc.name)
pro_order = webnotes.conn.sql("""select name from `tabProduction Order` where sales_order = %s and docstatus = 1""", self.doc.name)
if pro_order:
msgprint("""Production Order: %s exists against this sales order.
Please cancel production order first and then cancel this sales order""" %
pro_order[0][0], raise_exception=1)
def check_modified_date(self):
mod_db = sql("select modified from `tabSales Order` where name = '%s'" % self.doc.name)
date_diff = sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.doc.modified)))
mod_db = webnotes.conn.sql("select modified from `tabSales Order` where name = '%s'" % self.doc.name)
date_diff = webnotes.conn.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.doc.modified)))
if date_diff and date_diff[0][0]:
msgprint("%s: %s has been modified after you have opened. Please Refresh"
% (self.doc.doctype, self.doc.name), raise_exception=1)
@@ -342,8 +323,7 @@ def make_delivery_note(source_name, target_doclist=None):
"field_map": {
"export_rate": "export_rate",
"name": "prevdoc_detail_docname",
"parent": "prevdoc_docname",
"parenttype": "prevdoc_doctype",
"parent": "against_sales_order",
"reserved_warehouse": "warehouse"
},
"postprocess": update_item,

View File

@@ -2,12 +2,13 @@
{
"creation": "2013-06-18 12:39:59",
"docstatus": 0,
"modified": "2013-08-09 14:46:17",
"modified": "2013-10-11 13:18:47",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"allow_attach": 1,
"allow_import": 1,
"autoname": "naming_series:",
"doctype": "DocType",
"document_type": "Transaction",
@@ -272,7 +273,6 @@
"width": "100px"
},
{
"default": "1.00",
"description": "Rate at which customer's currency is converted to company's base currency",
"doctype": "DocField",
"fieldname": "conversion_rate",

View File

@@ -72,10 +72,13 @@ class TestSalesOrder(unittest.TestCase):
def create_dn_against_so(self, so, delivered_qty=0):
from stock.doctype.delivery_note.test_delivery_note import test_records as dn_test_records
from stock.doctype.delivery_note.test_delivery_note import _insert_purchase_receipt
_insert_purchase_receipt(so.doclist[1].item_code)
dn = webnotes.bean(webnotes.copy_doclist(dn_test_records[0]))
dn.doclist[1].item_code = so.doclist[1].item_code
dn.doclist[1].prevdoc_doctype = "Sales Order"
dn.doclist[1].prevdoc_docname = so.doc.name
dn.doclist[1].against_sales_order = so.doc.name
dn.doclist[1].prevdoc_detail_docname = so.doclist[1].name
if delivered_qty:
dn.doclist[1].qty = delivered_qty
@@ -273,14 +276,13 @@ class TestSalesOrder(unittest.TestCase):
so.doclist[1].reserved_warehouse, 20.0)
def test_warehouse_user(self):
webnotes.session.user = "test@example.com"
webnotes.bean("Profile", "test@example.com").get_controller()\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
webnotes.bean("Profile", "test2@example.com").get_controller()\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
webnotes.session.user = "test@example.com"
from stock.utils import UserNotAllowedForWarehouse
so = webnotes.bean(copy = test_records[0])

View File

@@ -10,7 +10,6 @@ from webnotes.model.bean import copy_doclist
from webnotes.model.code import get_obj
from webnotes import msgprint
sql = webnotes.conn.sql
# ----------
@@ -29,15 +28,15 @@ class DocType:
where_clause = self.doc.sales_partner and " and ifnull(is_sales_partner, 0) = 1 and sales_partner = '%s'" % self.doc.sales_partner or " and ifnull(sales_partner, '') != ''"
if self.doc.send_to in ['All Contact', 'All Customer Contact', 'All Supplier Contact', 'All Sales Partner Contact']:
rec = sql("select CONCAT(ifnull(first_name,''),'',ifnull(last_name,'')), mobile_no from `tabContact` where ifnull(mobile_no,'')!='' and docstatus != 2 %s" % where_clause)
rec = webnotes.conn.sql("select CONCAT(ifnull(first_name,''),'',ifnull(last_name,'')), mobile_no from `tabContact` where ifnull(mobile_no,'')!='' and docstatus != 2 %s" % where_clause)
elif self.doc.send_to == 'All Lead (Open)':
rec = sql("select lead_name, mobile_no from tabLead where ifnull(mobile_no,'')!='' and docstatus != 2 and status = 'Open'")
rec = webnotes.conn.sql("select lead_name, mobile_no from tabLead where ifnull(mobile_no,'')!='' and docstatus != 2 and status = 'Open'")
elif self.doc.send_to == 'All Employee (Active)':
where_clause = self.doc.department and " and department = '%s'" % self.doc.department or ""
where_clause += self.doc.branch and " and branch = '%s'" % self.doc.branch or ""
rec = sql("select employee_name, cell_number from `tabEmployee` where status = 'Active' and docstatus < 2 and ifnull(cell_number,'')!='' %s" % where_clause)
rec = webnotes.conn.sql("select employee_name, cell_number from `tabEmployee` where status = 'Active' and docstatus < 2 and ifnull(cell_number,'')!='' %s" % where_clause)
elif self.doc.send_to == 'All Sales Person':
rec = sql("select sales_person_name, mobile_no from `tabSales Person` where docstatus != 2 and ifnull(mobile_no,'')!=''")
rec = webnotes.conn.sql("select sales_person_name, mobile_no from `tabSales Person` where docstatus != 2 and ifnull(mobile_no,'')!=''")
rec_list = ''
for d in rec:
rec_list += d[0] + ' - ' + d[1] + '\n'

View File

View File

@@ -0,0 +1,3 @@
.funnel-wrapper {
margin: 15px;
}

View File

@@ -0,0 +1,189 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
// License: GNU General Public License v3. See license.txt
wn.pages['sales-funnel'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: 'Sales Funnel',
single_column: true
});
wrapper.crm_funnel = new erpnext.CRMFunnel(wrapper);
wrapper.appframe.add_module_icon("Selling", "sales-funnel", function() {
wn.set_route("selling-home");
});
}
erpnext.CRMFunnel = Class.extend({
init: function(wrapper) {
var me = this;
// 0 setTimeout hack - this gives time for canvas to get width and height
setTimeout(function() {
me.setup(wrapper);
me.get_data();
}, 0);
},
setup: function(wrapper) {
var me = this;
this.elements = {
layout: $(wrapper).find(".layout-main"),
from_date: wrapper.appframe.add_date("From Date"),
to_date: wrapper.appframe.add_date("To Date"),
refresh_btn: wrapper.appframe.add_button("Refresh",
function() { me.get_data(); }, "icon-refresh"),
};
this.elements.no_data = $('<div class="alert alert-warning">No Data</div>')
.toggle(false)
.appendTo(this.elements.layout);
this.elements.funnel_wrapper = $('<div class="funnel-wrapper text-center"></div>')
.appendTo(this.elements.layout);
this.options = {
from_date: wn.datetime.add_months(wn.datetime.get_today(), -1),
to_date: wn.datetime.get_today()
};
// set defaults and bind on change
$.each(this.options, function(k, v) {
me.elements[k].val(wn.datetime.str_to_user(v));
me.elements[k].on("change", function() {
me.options[k] = wn.datetime.user_to_str($(this).val());
me.get_data();
});
});
// bind refresh
this.elements.refresh_btn.on("click", function() {
me.get_data(this);
});
// bind resize
$(window).resize(function() {
me.render();
});
},
get_data: function(btn) {
var me = this;
wn.call({
module: "selling",
page: "crm_funnel",
method: "get_funnel_data",
args: {
from_date: this.options.from_date,
to_date: this.options.to_date
},
btn: btn,
callback: function(r) {
if(!r.exc) {
me.options.data = r.message;
me.render();
}
}
});
},
render: function() {
var me = this;
this.prepare();
var context = this.elements.context,
x_start = 0.0,
x_end = this.options.width,
x_mid = (x_end - x_start) / 2.0,
y = 0,
y_old = 0.0;
if(this.options.total_value === 0) {
this.elements.no_data.toggle(true);
return;
}
this.options.data.forEach(function(d) {
context.fillStyle = d.color;
context.strokeStyle = d.color;
me.draw_triangle(x_start, x_mid, x_end, y, me.options.height);
y_old = y;
// new y
y = y + d.height;
// new x
var half_side = (me.options.height - y) / Math.sqrt(3);
x_start = x_mid - half_side;
x_end = x_mid + half_side;
var y_mid = y_old + (y - y_old) / 2.0;
me.draw_legend(x_mid, y_mid, me.options.width, me.options.height, d.value + " - " + d.title);
});
},
prepare: function() {
var me = this;
this.elements.no_data.toggle(false);
// calculate width and height options
this.options.width = $(this.elements.funnel_wrapper).width() * 2.0 / 3.0;
this.options.height = (Math.sqrt(3) * this.options.width) / 2.0;
// calculate total weightage
// as height decreases, area decreases by the square of the reduction
// hence, compensating by squaring the index value
this.options.total_weightage = this.options.data.reduce(
function(prev, curr, i) { return prev + Math.pow(i+1, 2) * curr.value; }, 0.0);
// calculate height for each data
$.each(this.options.data, function(i, d) {
d.height = me.options.height * d.value * Math.pow(i+1, 2) / me.options.total_weightage;
});
this.elements.canvas = $('<canvas></canvas>')
.appendTo(this.elements.funnel_wrapper.empty())
.attr("width", $(this.elements.funnel_wrapper).width())
.attr("height", this.options.height);
this.elements.context = this.elements.canvas.get(0).getContext("2d");
},
draw_triangle: function(x_start, x_mid, x_end, y, height) {
var context = this.elements.context;
context.beginPath();
context.moveTo(x_start, y);
context.lineTo(x_end, y);
context.lineTo(x_mid, height);
context.lineTo(x_start, y);
context.closePath();
context.fill();
},
draw_legend: function(x_mid, y_mid, width, height, title) {
var context = this.elements.context;
// draw line
context.beginPath();
context.moveTo(x_mid, y_mid);
context.lineTo(width, y_mid);
context.closePath();
context.stroke();
// draw circle
context.beginPath();
context.arc(width, y_mid, 5, 0, Math.PI * 2, false);
context.closePath();
context.fill();
// draw text
context.fillStyle = "black";
context.textBaseline = "middle";
context.font = "1.1em sans-serif";
context.fillText(title, width + 20, y_mid);
}
});

View File

@@ -0,0 +1,33 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes
@webnotes.whitelist()
def get_funnel_data(from_date, to_date):
active_leads = webnotes.conn.sql("""select count(*) from `tabLead`
where (`modified` between %s and %s)
and status != "Do Not Contact" """, (from_date, to_date))[0][0]
active_leads += webnotes.conn.sql("""select count(distinct customer) from `tabContact`
where (`modified` between %s and %s)
and status != "Passive" """, (from_date, to_date))[0][0]
opportunities = webnotes.conn.sql("""select count(*) from `tabOpportunity`
where docstatus = 1 and (`modified` between %s and %s)
and status != "Lost" """, (from_date, to_date))[0][0]
quotations = webnotes.conn.sql("""select count(*) from `tabQuotation`
where docstatus = 1 and (`modified` between %s and %s)
and status != "Lost" """, (from_date, to_date))[0][0]
sales_orders = webnotes.conn.sql("""select count(*) from `tabQuotation`
where docstatus = 1 and (`modified` between %s and %s)""", (from_date, to_date))[0][0]
return [
{ "title": "Active Leads / Customers", "value": active_leads, "color": "#B03B46" },
{ "title": "Opportunities", "value": opportunities, "color": "#F09C00" },
{ "title": "Quotations", "value": quotations, "color": "#006685" },
{ "title": "Sales Orders", "value": sales_orders, "color": "#00AD65" }
]

View File

@@ -0,0 +1,33 @@
[
{
"creation": "2013-10-04 13:17:18",
"docstatus": 0,
"modified": "2013-10-04 13:17:18",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-filter",
"module": "Selling",
"name": "__common__",
"page_name": "sales-funnel",
"standard": "Yes",
"title": "Sales Funnel"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "sales-funnel",
"parentfield": "roles",
"parenttype": "Page",
"role": "Sales Manager"
},
{
"doctype": "Page",
"name": "sales-funnel"
},
{
"doctype": "Page Role"
}
]

View File

@@ -155,6 +155,10 @@ wn.module_page["Selling"] = [
"label":wn._("Sales Analytics"),
page: "sales-analytics"
},
{
"label":wn._("Sales Funnel"),
page: "sales-funnel"
},
]
},
{

View File

@@ -34,6 +34,7 @@ def get_item_details(args):
"plc_conversion_rate": 1.0
}
"""
if isinstance(args, basestring):
args = json.loads(args)
args = webnotes._dict(args)
@@ -73,7 +74,7 @@ def get_item_details(args):
out.update(apply_pos_settings(pos_settings, out))
if args.doctype in ("Sales Invoice", "Delivery Note"):
if item_bean.doc.has_serial_no and not args.serial_no:
if item_bean.doc.has_serial_no == "Yes" and not args.serial_no:
out.serial_no = _get_serial_nos_by_fifo(args, item_bean)
return out

View File

@@ -12,8 +12,8 @@ class WebsitePriceListMissingError(webnotes.ValidationError): pass
def set_cart_count(quotation=None):
if not quotation:
quotation = _get_cart_quotation()
webnotes.add_cookies["cart_count"] = cstr(len(quotation.doclist.get(
{"parentfield": "quotation_details"})) or "")
cart_count = cstr(len(quotation.doclist.get({"parentfield": "quotation_details"})))
webnotes._response.set_cookie("cart_count", cart_count)
@webnotes.whitelist()
def get_cart_quotation(doclist=None):
@@ -47,7 +47,7 @@ def place_order():
sales_order.ignore_permissions = True
sales_order.insert()
sales_order.submit()
webnotes.add_cookies["cart_count"] = ""
webnotes._response.set_cookie("cart_count", "")
return sales_order.doc.name