diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py index 0215dbd39e3..61bd03269c6 100644 --- a/controllers/selling_controller.py +++ b/controllers/selling_controller.py @@ -358,4 +358,4 @@ def check_active_sales_items(obj): webnotes.throw(_("Item is neither Sales nor Service Item") + ": " + d.item_code) if d.income_account and not item.default_income_account: webnotes.conn.set_value("Item", d.item_code, "default_income_account", - d.income_account) \ No newline at end of file + d.income_account) diff --git a/selling/doctype/quotation/quotation.py b/selling/doctype/quotation/quotation.py index cf82407485d..dbc496b6d2d 100644 --- a/selling/doctype/quotation/quotation.py +++ b/selling/doctype/quotation/quotation.py @@ -59,9 +59,37 @@ class DocType(SellingController): self.validate_for_items() self.validate_uom_is_integer("stock_uom", "qty") +<<<<<<< HEAD def update_opportunity(self): for opportunity in self.doclist.get_distinct_values("prevdoc_docname"): webnotes.bean("Opportunity", opportunity).get_controller().set_status(update=True) +======= + sales_com_obj = get_obj('Sales Common') + sales_com_obj.check_active_sales_items(self) + sales_com_obj.validate_max_discount(self,'quotation_details') + + 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) +>>>>>>> 8f72d9f679fca88be7710e31b6cfa66defcde81a # declare as order lost #------------------------- diff --git a/selling/doctype/sales_common/sales_common.py b/selling/doctype/sales_common/sales_common.py new file mode 100644 index 00000000000..ad626da4600 --- /dev/null +++ b/selling/doctype/sales_common/sales_common.py @@ -0,0 +1,348 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +from webnotes.utils import cint, cstr, flt +from webnotes.model.doc import addchild +from webnotes.model.bean import getlist +from webnotes.model.code import get_obj +from webnotes import msgprint, _ +from setup.utils import get_company_currency + +get_value = webnotes.conn.get_value + +from utilities.transaction_base import TransactionBase + +class DocType(TransactionBase): + def __init__(self,d,dl): + self.doc, self.doclist = d, dl + + + def get_contact_details(self, obj = '', primary = 0): + cond = " and contact_name = '"+cstr(obj.doc.contact_person)+"'" + if primary: cond = " and is_primary_contact = 'Yes'" + contact = webnotes.conn.sql("select contact_name, contact_no, email_id, contact_address from `tabContact` where customer = '%s' and docstatus != 2 %s" %(obj.doc.customer, cond), as_dict = 1) + if not contact: + return + c = contact[0] + obj.doc.contact_person = c['contact_name'] or '' + obj.doc.contact_no = c['contact_no'] or '' + obj.doc.email_id = c['email_id'] or '' + obj.doc.customer_mobile_no = c['contact_no'] or '' + if c['contact_address']: + obj.doc.customer_address = c['contact_address'] + + def get_invoice_details(self, obj = ''): + if obj.doc.company: + acc_head = webnotes.conn.sql("select name from `tabAccount` where name = '%s' and docstatus != 2" % (cstr(obj.doc.customer) + " - " + webnotes.conn.get_value('Company', obj.doc.company, 'abbr'))) + obj.doc.debit_to = acc_head and acc_head[0][0] or '' + + def get_tax_details(self, item_code, obj): + import json + tax = webnotes.conn.sql("select tax_type, tax_rate from `tabItem Tax` where parent = %s" , item_code) + t = {} + for x in tax: t[x[0]] = flt(x[1]) + ret = { + 'item_tax_rate' : tax and json.dumps(t) or '' + } + return ret + + def get_serial_details(self, serial_no, obj): + import json + item = webnotes.conn.sql("select item_code, make, label,brand, description from `tabSerial No` where name = '%s' and docstatus != 2" %(serial_no), as_dict=1) + tax = webnotes.conn.sql("select tax_type, tax_rate from `tabItem Tax` where parent = %s" , item[0]['item_code']) + t = {} + for x in tax: t[x[0]] = flt(x[1]) + ret = { + 'item_code' : item and item[0]['item_code'] or '', + 'make' : item and item[0]['make'] or '', + 'label' : item and item[0]['label'] or '', + 'brand' : item and item[0]['brand'] or '', + 'description' : item and item[0]['description'] or '', + 'item_tax_rate' : json.dumps(t) + } + return ret + + # To verify whether rate entered in details table does not exceed max discount % + # ======================================================================================= + def validate_max_discount(self,obj, detail_table): + for d in getlist(obj.doclist, detail_table): + discount = webnotes.conn.sql("select max_discount from tabItem where name = '%s'" %(d.item_code),as_dict = 1) + if discount and discount[0]['max_discount'] and (flt(d.adj_rate)>flt(discount[0]['max_discount'])): + msgprint("You cannot give more than " + cstr(discount[0]['max_discount']) + " % discount on Item Code : "+cstr(d.item_code)) + raise Exception + + # 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""" + il = [] + for d in getlist(obj.doclist, obj.fname): + reserved_warehouse = "" + reserved_qty_for_main_item = 0 + + if obj.doc.doctype == "Sales Order": + if (webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == 'Yes' or + self.has_sales_bom(d.item_code)) and not d.reserved_warehouse: + webnotes.throw(_("Please enter Reserved Warehouse for item ") + + d.item_code + _(" as it is stock Item or packing item")) + reserved_warehouse = d.reserved_warehouse + 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 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) + so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.prevdoc_detail_docname) + + if already_delivered_qty + d.qty > so_qty: + reserved_qty_for_main_item = -(so_qty - already_delivered_qty) + else: + reserved_qty_for_main_item = -flt(d.qty) + + if self.has_sales_bom(d.item_code): + for p in getlist(obj.doclist, 'packing_details'): + if p.parent_detail_docname == d.name and p.parent_item == d.item_code: + # the packing details table's qty is already multiplied with parent's qty + il.append(webnotes._dict({ + 'warehouse': p.warehouse, + 'reserved_warehouse': reserved_warehouse, + 'item_code': p.item_code, + 'qty': flt(p.qty), + 'reserved_qty': (flt(p.qty)/flt(d.qty)) * reserved_qty_for_main_item, + 'uom': p.uom, + 'batch_no': cstr(p.batch_no).strip(), + 'serial_no': cstr(p.serial_no).strip(), + 'name': d.name + })) + else: + il.append(webnotes._dict({ + 'warehouse': d.warehouse, + 'reserved_warehouse': reserved_warehouse, + 'item_code': d.item_code, + 'qty': d.qty, + 'reserved_qty': reserved_qty_for_main_item, + 'uom': d.stock_uom, + 'batch_no': cstr(d.batch_no).strip(), + 'serial_no': cstr(d.serial_no).strip(), + 'name': d.name + })) + return il + + 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 parent != %s""", (so_detail, so, dn)) + return qty and flt(qty[0][0]) or 0.0 + + def get_so_qty_and_warehouse(self, so_detail): + so_item = webnotes.conn.sql("""select qty, reserved_warehouse from `tabSales Order Item` + where name = %s and docstatus = 1""", so_detail, as_dict=1) + so_qty = so_item and flt(so_item[0]["qty"]) or 0.0 + so_warehouse = so_item and so_item[0]["reserved_warehouse"] or "" + return so_qty, so_warehouse + + def has_sales_bom(self, item_code): + return webnotes.conn.sql("select name from `tabSales BOM` where new_item_code=%s and docstatus != 2", item_code) + + def get_sales_bom_items(self, item_code): + return webnotes.conn.sql("""select t1.item_code, t1.qty, t1.uom + from `tabSales BOM Item` t1, `tabSales BOM` t2 + where t2.new_item_code=%s and t1.parent = t2.name""", item_code, as_dict=1) + + def get_packing_item_details(self, item): + return webnotes.conn.sql("select item_name, description, stock_uom from `tabItem` where name = %s", item, as_dict = 1)[0] + + def get_bin_qty(self, item, warehouse): + det = webnotes.conn.sql("select actual_qty, projected_qty from `tabBin` where item_code = '%s' and warehouse = '%s'" % (item, warehouse), as_dict = 1) + return det and det[0] or '' + + def update_packing_list_item(self,obj, packing_item_code, qty, warehouse, line): + bin = self.get_bin_qty(packing_item_code, warehouse) + item = self.get_packing_item_details(packing_item_code) + + # check if exists + exists = 0 + for d in getlist(obj.doclist, 'packing_details'): + if d.parent_item == line.item_code and d.item_code == packing_item_code and d.parent_detail_docname == line.name: + pi, exists = d, 1 + break + + if not exists: + pi = addchild(obj.doc, 'packing_details', 'Delivery Note Packing Item', + obj.doclist) + + pi.parent_item = line.item_code + pi.item_code = packing_item_code + pi.item_name = item['item_name'] + pi.parent_detail_docname = line.name + pi.description = item['description'] + pi.uom = item['stock_uom'] + 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: + pi.batch_no = cstr(line.batch_no) + pi.idx = self.packing_list_idx + + # saved, since this function is called on_update of delivery note + pi.save() + + self.packing_list_idx += 1 + + + def make_packing_list(self, obj, fname): + """make packing list for sales bom item""" + self.packing_list_idx = 0 + parent_items = [] + for d in getlist(obj.doclist, fname): + warehouse = fname == "sales_order_details" and d.reserved_warehouse or d.warehouse + if self.has_sales_bom(d.item_code): + for i in self.get_sales_bom_items(d.item_code): + self.update_packing_list_item(obj, i['item_code'], flt(i['qty'])*flt(d.qty), warehouse, d) + + if [d.item_code, d.name] not in parent_items: + parent_items.append([d.item_code, d.name]) + + obj.doclist = self.cleanup_packing_list(obj, parent_items) + + return obj.doclist + + def cleanup_packing_list(self, obj, parent_items): + """Remove all those child items which are no longer present in main item table""" + delete_list = [] + for d in getlist(obj.doclist, 'packing_details'): + if [d.parent_item, d.parent_detail_docname] not in parent_items: + # mark for deletion from doclist + delete_list.append(d.name) + + if not delete_list: + return obj.doclist + + # delete from doclist + obj.doclist = webnotes.doclist(filter(lambda d: d.name not in delete_list, obj.doclist)) + + # delete from db + webnotes.conn.sql("""\ + delete from `tabDelivery Note Packing Item` + where name in (%s)""" + % (", ".join(["%s"] * len(delete_list))), + tuple(delete_list)) + + return obj.doclist + + + def get_month(self,date): + """Get month based on date (required in sales person and sales partner)""" + month_list = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + month_idx = cint(cstr(date).split('-')[1])-1 + return month_list[month_idx] + + + # **** Check for Stop SO as no transactions can be made against Stopped SO. Need to unstop it. *** + 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 + 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: + so_status = webnotes.conn.sql("select status from `tabSales Order` where name = %s",ref_doc_name) + so_status = so_status and so_status[0][0] or '' + if so_status == 'Stopped': + msgprint("You cannot do any transaction against Sales Order : '%s' as it is Stopped." %(ref_doc_name)) + raise Exception + + def check_active_sales_items(self,obj): + for d in getlist(obj.doclist, obj.fname): + if d.item_code: + item = webnotes.conn.sql("""select docstatus, is_sales_item, + is_service_item, default_income_account from tabItem where name = %s""", + d.item_code, as_dict=True)[0] + if item.is_sales_item == 'No' and item.is_service_item == 'No': + msgprint("Item : '%s' is neither Sales nor Service Item" % (d.item_code)) + raise Exception + if d.income_account and not item.default_income_account: + webnotes.conn.set_value("Item", d.item_code, "default_income_account", d.income_account) + + + def check_credit(self,obj,grand_total): + acc_head = webnotes.conn.sql("select name from `tabAccount` where company = '%s' and master_name = '%s'"%(obj.doc.company, obj.doc.customer)) + if acc_head: + tot_outstanding = 0 + dbcr = webnotes.conn.sql("""select sum(debit), sum(credit) from `tabGL Entry` + where account = %s""", acc_head[0][0]) + if dbcr: + tot_outstanding = flt(dbcr[0][0])-flt(dbcr[0][1]) + + 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 + + if filters.has_key('warehouse'): + return webnotes.conn.sql("""select batch_no from `tabStock Ledger Entry` sle + where item_code = '%(item_code)s' + and warehouse = '%(warehouse)s' + and batch_no like '%(txt)s' + and exists(select * from `tabBatch` + where name = sle.batch_no + and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s') + and docstatus != 2) + %(mcond)s + group by batch_no having sum(actual_qty) > 0 + order by batch_no desc + limit %(start)s, %(page_len)s """ % {'item_code': filters['item_code'], + 'warehouse': filters['warehouse'], 'posting_date': filters['posting_date'], + 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield), + 'start': start, 'page_len': page_len}) + else: + return webnotes.conn.sql("""select name from tabBatch + where docstatus != 2 + and item = '%(item_code)s' + and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s') + and name like '%(txt)s' + %(mcond)s + order by name desc + limit %(start)s, %(page_len)s""" % {'item_code': filters['item_code'], + 'posting_date': filters['posting_date'], 'txt': "%%%s%%" % txt, + 'mcond':get_match_cond(doctype, searchfield),'start': start, + 'page_len': page_len}) diff --git a/selling/doctype/sales_order/sales_order.py b/selling/doctype/sales_order/sales_order.py index a9ab8270d1d..7bad121331a 100644 --- a/selling/doctype/sales_order/sales_order.py +++ b/selling/doctype/sales_order/sales_order.py @@ -110,10 +110,10 @@ class DocType(SellingController): self.validate_uom_is_integer("stock_uom", "qty") self.validate_for_items() self.validate_warehouse() - + from stock.doctype.packed_item.packed_item import make_packing_list self.doclist = make_packing_list(self,'sales_order_details') - + self.validate_with_previous_doc() if not self.doc.status: @@ -417,4 +417,4 @@ def make_maintenance_visit(source_name, target_doclist=None): } }, target_doclist) - return [d.fields for d in doclist] \ No newline at end of file + return [d.fields for d in doclist] diff --git a/stock/doctype/delivery_note/delivery_note.py b/stock/doctype/delivery_note/delivery_note.py index 8d325f4bb38..4c8068a86a0 100644 --- a/stock/doctype/delivery_note/delivery_note.py +++ b/stock/doctype/delivery_note/delivery_note.py @@ -69,11 +69,8 @@ class DocType(SellingController): self.check_stop_sales_order("against_sales_order") self.validate_for_items() self.validate_warehouse() - self.validate_uom_is_integer("stock_uom", "qty") - - # Set actual qty for each item in selected warehouse - self.update_current_stock() - + self.validate_uom_is_integer("stock_uom", "qty") + self.update_current_stock() self.validate_with_previous_doc() self.doc.status = 'Draft' diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index 0e05a882ff3..665486f3869 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -366,7 +366,6 @@ class DocType(StockController): def get_item_details(self, arg): arg = json.loads(arg) - item = webnotes.conn.sql("""select stock_uom, description, item_name, purchase_account, cost_center from `tabItem` where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'