From 373680bbae0895a9af1d6500c63d224cc3347b5a Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 10 Oct 2013 16:04:40 +0530 Subject: [PATCH] Merged master into wsgi --- accounts/doctype/gl_entry/gl_entry.py | 2 +- .../purchase_invoice/purchase_invoice.py | 31 ++- .../doctype/sales_invoice/sales_invoice.py | 2 - .../doctype/sales_invoice/sales_invoice.txt | 3 +- .../item_wise_purchase_register.py | 4 +- .../item_wise_sales_register.py | 14 +- buying/page/buying_home/buying_home.js | 5 + .../__init__.py | 0 .../supplier_addresses_and_contacts.txt | 22 ++ controllers/buying_controller.py | 19 +- controllers/selling_controller.py | 32 +-- hr/doctype/employee/employee.js | 2 +- .../production_order/production_order.py | 29 ++- .../october_2013/p01_fix_serial_no_status.py | 18 ++ ...petual_inventory_stock_transfer_utility.py | 86 +++++++ .../set_stock_value_diff_in_sle.py | 10 + selling/doctype/lead/lead.py | 2 +- selling/doctype/lead/lead.txt | 116 ++++----- selling/doctype/opportunity/opportunity.py | 17 +- selling/doctype/opportunity/opportunity.txt | 16 +- selling/doctype/quotation/quotation.py | 17 +- selling/doctype/sales_order/sales_order.py | 7 +- selling/utils/__init__.py | 2 +- stock/doctype/delivery_note/delivery_note.py | 31 --- .../landed_cost_wizard/landed_cost_wizard.py | 4 +- .../material_request/material_request.py | 12 +- .../purchase_receipt/purchase_receipt.py | 21 +- stock/doctype/serial_no/serial_no.py | 225 ++++++++++++++++-- stock/doctype/stock_entry/stock_entry.py | 24 +- stock/doctype/stock_entry/test_stock_entry.py | 16 +- .../stock_ledger_entry/stock_ledger_entry.py | 138 +---------- stock/utils.py | 7 + .../maintenance_schedules.txt | 4 +- 33 files changed, 520 insertions(+), 418 deletions(-) create mode 100644 buying/report/supplier_addresses_and_contacts/__init__.py create mode 100644 buying/report/supplier_addresses_and_contacts/supplier_addresses_and_contacts.txt create mode 100644 patches/october_2013/p01_fix_serial_no_status.py create mode 100644 patches/october_2013/perpetual_inventory_stock_transfer_utility.py create mode 100644 patches/october_2013/set_stock_value_diff_in_sle.py diff --git a/accounts/doctype/gl_entry/gl_entry.py b/accounts/doctype/gl_entry/gl_entry.py index 22435120e0..7a73b06756 100644 --- a/accounts/doctype/gl_entry/gl_entry.py +++ b/accounts/doctype/gl_entry/gl_entry.py @@ -107,7 +107,7 @@ class DocType: _(" does not belong to the company") + ": " + self.doc.company) def check_negative_balance(account, adv_adj=False): - if not adv_adj: + if not adv_adj and account: account_details = webnotes.conn.get_value("Account", account, ["allow_negative_balance", "debit_or_credit"], as_dict=True) if not account_details["allow_negative_balance"]: diff --git a/accounts/doctype/purchase_invoice/purchase_invoice.py b/accounts/doctype/purchase_invoice/purchase_invoice.py index 46faf3df59..c79bfd6648 100644 --- a/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -337,7 +337,7 @@ class DocType(BuyingController): ) # tax table gl entries - valuation_tax = 0 + valuation_tax = {} for tax in self.doclist.get({"parentfield": "purchase_tax_details"}): if tax.category in ("Total", "Valuation and Total") and flt(tax.tax_amount): gl_entries.append( @@ -352,8 +352,11 @@ class DocType(BuyingController): ) # accumulate valuation tax - if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount): - valuation_tax += (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount) + if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount) \ + and tax.cost_center: + valuation_tax.setdefault(tax.cost_center, 0) + valuation_tax[tax.cost_center] += \ + (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount) # item gl entries stock_item_and_auto_accounting_for_stock = False @@ -394,15 +397,19 @@ class DocType(BuyingController): if stock_item_and_auto_accounting_for_stock and valuation_tax: # credit valuation tax amount in "Expenses Included In Valuation" # this will balance out valuation amount included in cost of goods sold - gl_entries.append( - self.get_gl_dict({ - "account": self.get_company_default("expenses_included_in_valuation"), - "cost_center": self.get_company_default("cost_center"), - "against": self.doc.credit_to, - "credit": valuation_tax, - "remarks": self.doc.remarks or "Accounting Entry for Stock" - }) - ) + expenses_included_in_valuation = \ + self.get_company_default("expenses_included_in_valuation") + + for cost_center, amount in valuation_tax.items(): + gl_entries.append( + self.get_gl_dict({ + "account": expenses_included_in_valuation, + "cost_center": cost_center, + "against": self.doc.credit_to, + "credit": amount, + "remarks": self.doc.remarks or "Accounting Entry for Stock" + }) + ) # writeoff account includes petty difference in the invoice amount # and the amount that is paid diff --git a/accounts/doctype/sales_invoice/sales_invoice.py b/accounts/doctype/sales_invoice/sales_invoice.py index 1dca77b3fb..5c7597c403 100644 --- a/accounts/doctype/sales_invoice/sales_invoice.py +++ b/accounts/doctype/sales_invoice/sales_invoice.py @@ -83,7 +83,6 @@ class DocType(SellingController): def on_submit(self): if cint(self.doc.update_stock) == 1: self.update_stock_ledger() - self.update_serial_nos() else: # Check for Approving Authority if not self.doc.recurring_id: @@ -111,7 +110,6 @@ class DocType(SellingController): def on_cancel(self): if cint(self.doc.update_stock) == 1: self.update_stock_ledger() - self.update_serial_nos(cancel = True) sales_com_obj = get_obj(dt = 'Sales Common') sales_com_obj.check_stop_sales_order(self) diff --git a/accounts/doctype/sales_invoice/sales_invoice.txt b/accounts/doctype/sales_invoice/sales_invoice.txt index 77cf2ccef2..8cd83c4596 100644 --- a/accounts/doctype/sales_invoice/sales_invoice.txt +++ b/accounts/doctype/sales_invoice/sales_invoice.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-24 19:29:05", "docstatus": 0, - "modified": "2013-10-02 14:24:50", + "modified": "2013-10-03 18:54:31", "modified_by": "Administrator", "owner": "Administrator" }, @@ -181,7 +181,6 @@ "search_index": 1 }, { - "default": "Today", "description": "Enter the date by which payments from customer is expected against this invoice.", "doctype": "DocField", "fieldname": "due_date", diff --git a/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index bd0726e37d..1c3cef3115 100644 --- a/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -81,12 +81,12 @@ def get_tax_accounts(item_list, columns): if account_head not in tax_accounts: tax_accounts.append(account_head) - invoice = item_tax.setdefault(parent, {}) if item_wise_tax_detail: try: item_wise_tax_detail = json.loads(item_wise_tax_detail) for item, tax_amount in item_wise_tax_detail.items(): - invoice.setdefault(item, {})[account_head] = flt(tax_amount) + item_tax.setdefault(parent, {}).setdefault(item, {})[account_head] = \ + flt(tax_amount[1]) except ValueError: continue diff --git a/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 77fb6f25e6..48bc463f14 100644 --- a/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -9,7 +9,7 @@ def execute(filters=None): if not filters: filters = {} columns = get_columns() last_col = len(columns) - + item_list = get_items(filters) item_tax, tax_accounts = get_tax_accounts(item_list, columns) @@ -21,7 +21,7 @@ def execute(filters=None): for tax in tax_accounts: row.append(item_tax.get(d.parent, {}).get(d.item_code, {}).get(tax, 0)) - + total_tax = sum(row[last_col:]) row += [total_tax, d.amount + total_tax] @@ -71,19 +71,19 @@ def get_tax_accounts(item_list, columns): tax_details = webnotes.conn.sql("""select parent, account_head, item_wise_tax_detail from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice' and docstatus = 1 and ifnull(account_head, '') != '' - and parent in (%s)""" % ', '.join(['%s']*len(item_list)), tuple([item.parent for item in item_list])) + and parent in (%s)""" % ', '.join(['%s']*len(item_list)), + tuple([item.parent for item in item_list])) for parent, account_head, item_wise_tax_detail in tax_details: if account_head not in tax_accounts: tax_accounts.append(account_head) - - invoice = item_tax.setdefault(parent, {}) + if item_wise_tax_detail: try: item_wise_tax_detail = json.loads(item_wise_tax_detail) for item, tax_amount in item_wise_tax_detail.items(): - invoice.setdefault(item, {})[account_head] = flt(tax_amount) - + item_tax.setdefault(parent, {}).setdefault(item, {})[account_head] = \ + flt(tax_amount[1]) except ValueError: continue diff --git a/buying/page/buying_home/buying_home.js b/buying/page/buying_home/buying_home.js index 5db57f4f02..eec0725de9 100644 --- a/buying/page/buying_home/buying_home.js +++ b/buying/page/buying_home/buying_home.js @@ -145,6 +145,11 @@ wn.module_page["Buying"] = [ route: "query-report/Purchase Order Trends", doctype: "Purchase Order" }, + { + "label":wn._("Supplier Addresses And Contacts"), + route: "query-report/Supplier Addresses and Contacts", + doctype: "Supplier" + }, ] } ] diff --git a/buying/report/supplier_addresses_and_contacts/__init__.py b/buying/report/supplier_addresses_and_contacts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/buying/report/supplier_addresses_and_contacts/supplier_addresses_and_contacts.txt b/buying/report/supplier_addresses_and_contacts/supplier_addresses_and_contacts.txt new file mode 100644 index 0000000000..fac1e9e929 --- /dev/null +++ b/buying/report/supplier_addresses_and_contacts/supplier_addresses_and_contacts.txt @@ -0,0 +1,22 @@ +[ + { + "creation": "2013-10-09 10:38:40", + "docstatus": 0, + "modified": "2013-10-09 10:53:52", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "name": "__common__", + "query": "SELECT\n `tabSupplier`.name as \"Supplier:Link/Supplier:120\",\n\t`tabSupplier`.supplier_name as \"Supplier Name::120\",\n\t`tabSupplier`.supplier_type as \"Supplier Type:Link/Supplier Type:120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2), \n\t\ttabAddress.state, tabAddress.pincode, tabAddress.country\n\t) as 'Address::180',\n concat_ws(', ', `tabContact`.first_name, `tabContact`.last_name) as 'Contact Name::180',\n\t`tabContact`.phone as \"Phone\",\n\t`tabContact`.mobile_no as \"Mobile No\",\n\t`tabContact`.email_id as \"Email Id::120\",\n\t`tabContact`.is_primary_contact as \"Is Primary Contact::120\"\nFROM\n\t`tabSupplier`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.supplier=`tabSupplier`.name\n\t)\n\tleft join `tabContact` on (\n\t\t`tabContact`.supplier=`tabSupplier`.name\n\t)\nWHERE\n\t`tabSupplier`.docstatus<2\nORDER BY\n\t`tabSupplier`.name asc", + "ref_doctype": "Supplier", + "report_name": "Supplier Addresses and Contacts", + "report_type": "Query Report" + }, + { + "doctype": "Report", + "name": "Supplier Addresses and Contacts" + } +] \ No newline at end of file diff --git a/controllers/buying_controller.py b/controllers/buying_controller.py index 7e49e60f8a..25d76aa66d 100644 --- a/controllers/buying_controller.py +++ b/controllers/buying_controller.py @@ -24,7 +24,7 @@ class BuyingController(StockController): self.doc.supplier_name = webnotes.conn.get_value("Supplier", self.doc.supplier, "supplier_name") self.validate_stock_or_nonstock_items() - self.validate_warehouse_belongs_to_company() + self.validate_warehouse() def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) @@ -49,17 +49,20 @@ class BuyingController(StockController): if supplier: self.doc.supplier = supplier break + + def validate_warehouse(self): + from stock.utils import validate_warehouse_user, validate_warehouse_company + + warehouses = list(set([d.warehouse for d in + self.doclist.get({"doctype": self.tname}) if d.warehouse])) + + for w in warehouses: + validate_warehouse_user(w) + validate_warehouse_company(w, self.doc.company) def get_purchase_tax_details(self): self.doclist = self.doc.clear_table(self.doclist, "purchase_tax_details") self.set_taxes("purchase_tax_details", "purchase_other_charges") - - def validate_warehouse_belongs_to_company(self): - for warehouse, company in webnotes.conn.get_values("Warehouse", - self.doclist.get_distinct_values("warehouse"), "company").items(): - if company and company != self.doc.company: - webnotes.msgprint(_("Company mismatch for Warehouse") + (": %s" % (warehouse,)), - raise_exception=WrongWarehouseCompany) def validate_stock_or_nonstock_items(self): if not self.get_stock_items(): diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py index 2816524160..d78cf932b4 100644 --- a/controllers/selling_controller.py +++ b/controllers/selling_controller.py @@ -236,34 +236,4 @@ class SellingController(StockController): self.doc.order_type = "Sales" elif self.doc.order_type not in valid_types: msgprint(_(self.meta.get_label("order_type")) + " " + - _("must be one of") + ": " + comma_or(valid_types), - raise_exception=True) - - def update_serial_nos(self, cancel=False): - from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos - update_serial_nos_after_submit(self, self.doc.doctype, self.fname) - update_serial_nos_after_submit(self, self.doc.doctype, "packing_details") - - for table_fieldname in (self.fname, "packing_details"): - for d in self.doclist.get({"parentfield": table_fieldname}): - for serial_no in get_serial_nos(d.serial_no): - sr = webnotes.bean("Serial No", serial_no) - if cancel: - sr.doc.status = "Available" - for fieldname in ("warranty_expiry_date", "delivery_document_type", - "delivery_document_no", "delivery_date", "delivery_time", "customer", - "customer_name"): - sr.doc.fields[fieldname] = None - else: - sr.doc.delivery_document_type = self.doc.doctype - sr.doc.delivery_document_no = self.doc.name - sr.doc.delivery_date = self.doc.posting_date - sr.doc.delivery_time = self.doc.posting_time - sr.doc.customer = self.doc.customer - sr.doc.customer_name = self.doc.customer_name - if sr.doc.warranty_period: - sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date), - cint(sr.doc.warranty_period)) - sr.doc.status = 'Delivered' - - sr.save() + _("must be one of") + ": " + comma_or(valid_types), raise_exception=True) diff --git a/hr/doctype/employee/employee.js b/hr/doctype/employee/employee.js index 01200e7d22..615e2761b8 100644 --- a/hr/doctype/employee/employee.js +++ b/hr/doctype/employee/employee.js @@ -4,7 +4,6 @@ wn.provide("erpnext.hr"); erpnext.hr.EmployeeController = wn.ui.form.Controller.extend({ setup: function() { - this.setup_leave_approver_select(); this.frm.fields_dict.user_id.get_query = function(doc,cdt,cdn) { return { query:"core.doctype.profile.profile.profile_query"} } this.frm.fields_dict.reports_to.get_query = function(doc,cdt,cdn) { @@ -12,6 +11,7 @@ erpnext.hr.EmployeeController = wn.ui.form.Controller.extend({ }, onload: function() { + this.setup_leave_approver_select(); this.frm.toggle_display(["esic_card_no", "gratuity_lic_id", "pan_number", "pf_number"], wn.control_panel.country==="India"); if(this.frm.doc.__islocal) this.frm.set_value("employee_name", ""); diff --git a/manufacturing/doctype/production_order/production_order.py b/manufacturing/doctype/production_order/production_order.py index a280a82202..6bceca6da8 100644 --- a/manufacturing/doctype/production_order/production_order.py +++ b/manufacturing/doctype/production_order/production_order.py @@ -21,13 +21,14 @@ class DocType: utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped", "In Process", "Completed", "Cancelled"]) - if self.doc.production_item : - item_detail = webnotes.conn.sql("select name from `tabItem` where name = '%s' and docstatus != 2" - % self.doc.production_item, as_dict = 1) - if not item_detail: - msgprint("Item '%s' does not exist or cancelled in the system." - % cstr(self.doc.production_item), raise_exception=1) - + self.validate_bom_no() + self.validate_sales_order() + self.validate_warehouse() + + from utilities.transaction_base import validate_uom_is_integer + validate_uom_is_integer(self.doclist, "stock_uom", ["qty", "produced_qty"]) + + def validate_bom_no(self): if self.doc.bom_no: bom = webnotes.conn.sql("""select name from `tabBOM` where name=%s and docstatus=1 and is_active=1 and item=%s""" @@ -37,16 +38,20 @@ class DocType: May be BOM not exists or inactive or not submitted or for some other item.""" % cstr(self.doc.bom_no), raise_exception=1) + def validate_sales_order(self): if self.doc.sales_order: if not webnotes.conn.sql("""select name from `tabSales Order` where name=%s and docstatus = 1""", self.doc.sales_order): msgprint("Sales Order: %s is not valid" % self.doc.sales_order, raise_exception=1) - + self.validate_production_order_against_so() - - from utilities.transaction_base import validate_uom_is_integer - validate_uom_is_integer(self.doclist, "stock_uom", ["qty", "produced_qty"]) - + + def validate_warehouse(self): + from stock.utils import validate_warehouse_user, validate_warehouse_company + + for w in [self.doc.fg_warehouse, self.doc.wip_warehouse]: + validate_warehouse_user(w) + validate_warehouse_company(w, self.doc.company) def validate_production_order_against_so(self): # already ordered qty diff --git a/patches/october_2013/p01_fix_serial_no_status.py b/patches/october_2013/p01_fix_serial_no_status.py new file mode 100644 index 0000000000..0bfc400a8e --- /dev/null +++ b/patches/october_2013/p01_fix_serial_no_status.py @@ -0,0 +1,18 @@ +# 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 flt + +def execute(): + serial_nos = webnotes.conn.sql("""select name from `tabSerial No` where status!='Not in Use' + and docstatus=0""") + for sr in serial_nos: + sr_bean = webnotes.bean("Serial No", sr[0]) + sr_bean.make_controller().via_stock_ledger = True + sr_bean.run_method("validate") + sr_bean.save() + + webnotes.conn.sql("""update `tabSerial No` set warehouse='' where status in + ('Delivered', 'Purchase Returned')""") \ No newline at end of file diff --git a/patches/october_2013/perpetual_inventory_stock_transfer_utility.py b/patches/october_2013/perpetual_inventory_stock_transfer_utility.py new file mode 100644 index 0000000000..d8cade78eb --- /dev/null +++ b/patches/october_2013/perpetual_inventory_stock_transfer_utility.py @@ -0,0 +1,86 @@ +# 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 nowdate, nowtime, cstr +from accounts.utils import get_fiscal_year + +def execute(): + item_map = {} + for item in webnotes.conn.sql("""select * from tabItem""", as_dict=1): + item_map.setdefault(item.name, item) + + warehouse_map = get_warehosue_map() + naming_series = "STE/13/" + + for company in webnotes.conn.sql("select name from tabCompany"): + stock_entry = [{ + "doctype": "Stock Entry", + "naming_series": naming_series, + "posting_date": nowdate(), + "posting_time": nowtime(), + "purpose": "Material Transfer", + "company": company[0], + "remarks": "Material Transfer to activate perpetual inventory", + "fiscal_year": get_fiscal_year(nowdate())[0] + }] + expense_account = "Cost of Goods Sold - NISL" + cost_center = "Default CC Ledger - NISL" + + for bin in webnotes.conn.sql("""select * from tabBin bin where ifnull(item_code, '')!='' + and ifnull(warehouse, '') in (%s) and ifnull(actual_qty, 0) != 0 + and (select company from tabWarehouse where name=bin.warehouse)=%s""" % + (', '.join(['%s']*len(warehouse_map)), '%s'), + (warehouse_map.keys() + [company[0]]), as_dict=1): + item_details = item_map[bin.item_code] + new_warehouse = warehouse_map[bin.warehouse].get("fixed_asset_warehouse") \ + if cstr(item_details.is_asset_item) == "Yes" \ + else warehouse_map[bin.warehouse].get("current_asset_warehouse") + + if item_details.has_serial_no == "Yes": + serial_no = "\n".join([d[0] for d in webnotes.conn.sql("""select name + from `tabSerial No` where item_code = %s and warehouse = %s + and status in ('Available', 'Sales Returned')""", + (bin.item_code, bin.warehouse))]) + else: + serial_no = None + + stock_entry.append({ + "doctype": "Stock Entry Detail", + "parentfield": "mtn_details", + "s_warehouse": bin.warehouse, + "t_warehouse": new_warehouse, + "item_code": bin.item_code, + "description": item_details.description, + "qty": bin.actual_qty, + "transfer_qty": bin.actual_qty, + "uom": item_details.stock_uom, + "stock_uom": item_details.stock_uom, + "conversion_factor": 1, + "expense_account": expense_account, + "cost_center": cost_center, + "serial_no": serial_no + }) + + webnotes.bean(stock_entry).insert() + +def get_warehosue_map(): + return { + "MAHAPE": { + "current_asset_warehouse": "Mahape-New - NISL", + "fixed_asset_warehouse": "" + }, + "DROP SHIPMENT": { + "current_asset_warehouse": "Drop Shipment-New - NISL", + "fixed_asset_warehouse": "" + }, + "TRANSIT": { + "current_asset_warehouse": "Transit-New - NISL", + "fixed_asset_warehouse": "" + }, + "ASSET - MAHAPE": { + "current_asset_warehouse": "", + "fixed_asset_warehouse": "Assets-New - NISL" + } + } \ No newline at end of file diff --git a/patches/october_2013/set_stock_value_diff_in_sle.py b/patches/october_2013/set_stock_value_diff_in_sle.py new file mode 100644 index 0000000000..25f95e0e6d --- /dev/null +++ b/patches/october_2013/set_stock_value_diff_in_sle.py @@ -0,0 +1,10 @@ +# 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 + +def execute(): + from patches.september_2012 import repost_stock + repost_stock.execute() \ No newline at end of file diff --git a/selling/doctype/lead/lead.py b/selling/doctype/lead/lead.py index b42a380758..3cc603fc34 100644 --- a/selling/doctype/lead/lead.py +++ b/selling/doctype/lead/lead.py @@ -121,4 +121,4 @@ def make_opportunity(source_name, target_doclist=None): } }}, target_doclist) - return [d.fields for d in doclist] \ No newline at end of file + return [d if isinstance(d, dict) else d.fields for d in doclist] diff --git a/selling/doctype/lead/lead.txt b/selling/doctype/lead/lead.txt index 4f481b0a5e..b700a2e146 100644 --- a/selling/doctype/lead/lead.txt +++ b/selling/doctype/lead/lead.txt @@ -2,7 +2,7 @@ { "creation": "2013-04-10 11:45:37", "docstatus": 0, - "modified": "2013-10-03 17:24:33", + "modified": "2013-10-09 15:27:54", "modified_by": "Administrator", "owner": "Administrator" }, @@ -159,8 +159,63 @@ "doctype": "DocField", "fieldname": "communication_history", "fieldtype": "Section Break", - "label": "Communication History", - "options": "icon-comments" + "label": "Communication", + "options": "icon-comments", + "print_hide": 1 + }, + { + "default": "__user", + "doctype": "DocField", + "fieldname": "lead_owner", + "fieldtype": "Link", + "in_filter": 1, + "label": "Lead Owner", + "oldfieldname": "lead_owner", + "oldfieldtype": "Link", + "options": "Profile", + "search_index": 1 + }, + { + "doctype": "DocField", + "fieldname": "col_break123", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "allow_on_submit": 0, + "description": "Your sales person who will contact the lead in future", + "doctype": "DocField", + "fieldname": "contact_by", + "fieldtype": "Link", + "hidden": 0, + "in_filter": 1, + "label": "Next Contact By", + "oldfieldname": "contact_by", + "oldfieldtype": "Link", + "options": "Profile", + "print_hide": 0, + "reqd": 0, + "width": "100px" + }, + { + "allow_on_submit": 0, + "description": "Your sales person will get a reminder on this date to contact the lead", + "doctype": "DocField", + "fieldname": "contact_date", + "fieldtype": "Date", + "in_filter": 1, + "label": "Next Contact Date", + "no_copy": 1, + "oldfieldname": "contact_date", + "oldfieldtype": "Date", + "reqd": 0, + "width": "100px" + }, + { + "doctype": "DocField", + "fieldname": "sec_break123", + "fieldtype": "Section Break", + "options": "Simple" }, { "allow_on_submit": 0, @@ -273,18 +328,6 @@ "oldfieldtype": "Select", "options": "\nClient\nChannel Partner\nConsultant" }, - { - "default": "__user", - "doctype": "DocField", - "fieldname": "lead_owner", - "fieldtype": "Link", - "in_filter": 1, - "label": "Lead Owner", - "oldfieldname": "lead_owner", - "oldfieldtype": "Link", - "options": "Profile", - "search_index": 1 - }, { "doctype": "DocField", "fieldname": "market_segment", @@ -334,49 +377,6 @@ "oldfieldtype": "Column Break", "width": "50%" }, - { - "allow_on_submit": 0, - "description": "Your sales person who will contact the lead in future", - "doctype": "DocField", - "fieldname": "contact_by", - "fieldtype": "Link", - "hidden": 0, - "in_filter": 1, - "label": "Next Contact By", - "oldfieldname": "contact_by", - "oldfieldtype": "Link", - "options": "Profile", - "print_hide": 0, - "reqd": 0, - "width": "100px" - }, - { - "allow_on_submit": 0, - "description": "Your sales person will get a reminder on this date to contact the lead", - "doctype": "DocField", - "fieldname": "contact_date", - "fieldtype": "Date", - "in_filter": 1, - "label": "Next Contact Date", - "no_copy": 1, - "oldfieldname": "contact_date", - "oldfieldtype": "Date", - "reqd": 0, - "width": "100px" - }, - { - "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": "company", diff --git a/selling/doctype/opportunity/opportunity.py b/selling/doctype/opportunity/opportunity.py index e63ce6b060..37672f2afb 100644 --- a/selling/doctype/opportunity/opportunity.py +++ b/selling/doctype/opportunity/opportunity.py @@ -4,7 +4,7 @@ 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, _ @@ -17,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, @@ -70,10 +70,6 @@ class DocType(TransactionBase): 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") @@ -122,7 +110,6 @@ class DocType(TransactionBase): def validate(self): self.set_status() - self.set_last_contact_date() self.validate_item_details() self.validate_uom_is_integer("uom", "qty") self.validate_lead_cust() diff --git a/selling/doctype/opportunity/opportunity.txt b/selling/doctype/opportunity/opportunity.txt index 02caea0a6d..3c860ade84 100644 --- a/selling/doctype/opportunity/opportunity.txt +++ b/selling/doctype/opportunity/opportunity.txt @@ -2,7 +2,7 @@ { "creation": "2013-03-07 18:50:30", "docstatus": 0, - "modified": "2013-10-03 16:30:58", + "modified": "2013-10-09 15:26:29", "modified_by": "Administrator", "owner": "Administrator" }, @@ -404,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", diff --git a/selling/doctype/quotation/quotation.py b/selling/doctype/quotation/quotation.py index 697f805a2c..b6bc45d772 100644 --- a/selling/doctype/quotation/quotation.py +++ b/selling/doctype/quotation/quotation.py @@ -4,7 +4,7 @@ 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 @@ -97,24 +97,9 @@ class DocType(SellingController): 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() - self.set_status() - self.set_last_contact_date() self.validate_order_type() self.validate_for_items() diff --git a/selling/doctype/sales_order/sales_order.py b/selling/doctype/sales_order/sales_order.py index af8ea1ad7a..0cdca58ce9 100644 --- a/selling/doctype/sales_order/sales_order.py +++ b/selling/doctype/sales_order/sales_order.py @@ -126,7 +126,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) @@ -147,14 +147,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, { diff --git a/selling/utils/__init__.py b/selling/utils/__init__.py index 801d82bf40..6e74ac4869 100644 --- a/selling/utils/__init__.py +++ b/selling/utils/__init__.py @@ -74,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 diff --git a/stock/doctype/delivery_note/delivery_note.py b/stock/doctype/delivery_note/delivery_note.py index b27d0345ea..b20e790af7 100644 --- a/stock/doctype/delivery_note/delivery_note.py +++ b/stock/doctype/delivery_note/delivery_note.py @@ -186,7 +186,6 @@ class DocType(SellingController): # create stock ledger entry self.update_stock_ledger() - self.update_serial_nos() self.credit_limit() @@ -204,42 +203,12 @@ class DocType(SellingController): self.update_prevdoc_status() self.update_stock_ledger() - self.update_serial_nos(cancel=True) webnotes.conn.set(self.doc, 'status', 'Cancelled') self.cancel_packing_slips() self.make_cancel_gl_entries() - def update_serial_nos(self, cancel=False): - from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos - update_serial_nos_after_submit(self, "Delivery Note", "delivery_note_details") - update_serial_nos_after_submit(self, "Delivery Note", "packing_details") - - for table_fieldname in ("delivery_note_details", "packing_details"): - for d in self.doclist.get({"parentfield": table_fieldname}): - for serial_no in get_serial_nos(d.serial_no): - sr = webnotes.bean("Serial No", serial_no) - if cancel: - sr.doc.status = "Available" - for fieldname in ("warranty_expiry_date", "delivery_document_type", - "delivery_document_no", "delivery_date", "delivery_time", "customer", - "customer_name"): - sr.doc.fields[fieldname] = None - else: - sr.doc.delivery_document_type = "Delivery Note" - sr.doc.delivery_document_no = self.doc.name - sr.doc.delivery_date = self.doc.posting_date - sr.doc.delivery_time = self.doc.posting_time - sr.doc.customer = self.doc.customer - sr.doc.customer_name = self.doc.customer_name - if sr.doc.warranty_period: - sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date), - cint(sr.doc.warranty_period)) - sr.doc.status = 'Delivered' - - sr.save() - def validate_packed_qty(self): """ Validate that if packed qty exists, it should be equal to qty diff --git a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py index f431537c6c..89a3b81d57 100644 --- a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py +++ b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py @@ -85,7 +85,6 @@ class DocType: pr_bean = webnotes.bean("Purchase Receipt", pr) pr_bean.run_method("update_ordered_qty", is_cancelled="Yes") - pr_bean.run_method("update_serial_nos", cancel=True) webnotes.conn.sql("""delete from `tabStock Ledger Entry` where voucher_type='Purchase Receipt' and voucher_no=%s""", pr) @@ -97,5 +96,4 @@ class DocType: pr_bean = webnotes.bean("Purchase Receipt", pr) pr_bean.run_method("update_ordered_qty") pr_bean.run_method("update_stock") - pr_bean.run_method("update_serial_nos") - pr_bean.run_method("make_gl_entries") + pr_bean.run_method("make_gl_entries") \ No newline at end of file diff --git a/stock/doctype/material_request/material_request.py b/stock/doctype/material_request/material_request.py index 249062f29d..d1672ba12c 100644 --- a/stock/doctype/material_request/material_request.py +++ b/stock/doctype/material_request/material_request.py @@ -68,22 +68,14 @@ class DocType(BuyingController): self.doc.status = "Draft" import utilities - utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped", - "Cancelled"]) + utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Stopped", "Cancelled"]) - # restrict material request type self.validate_value("material_request_type", "in", ["Purchase", "Transfer"]) - # Get Purchase Common Obj pc_obj = get_obj(dt='Purchase Common') - - - # Validate for items pc_obj.validate_for_items(self) - - # Validate qty against SO - self.validate_qty_against_so() + self.validate_qty_against_so() def update_bin(self, is_submit, is_stopped): """ Update Quantity Requested for Purchase in Bin for Material Request of type 'Purchase'""" diff --git a/stock/doctype/purchase_receipt/purchase_receipt.py b/stock/doctype/purchase_receipt/purchase_receipt.py index 7d663b8778..6169b1d916 100644 --- a/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/stock/doctype/purchase_receipt/purchase_receipt.py @@ -246,26 +246,12 @@ class DocType(BuyingController): self.update_stock() - self.update_serial_nos() + from stock.doctype.serial_no.serial_no import update_serial_nos_after_submit + update_serial_nos_after_submit(self, "Purchase Receipt", "purchase_receipt_details") purchase_controller.update_last_purchase_rate(self, 1) self.make_gl_entries() - - def update_serial_nos(self, cancel=False): - from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos - update_serial_nos_after_submit(self, "Purchase Receipt", "purchase_receipt_details") - - for d in self.doclist.get({"parentfield": "purchase_receipt_details"}): - for serial_no in get_serial_nos(d.serial_no): - sr = webnotes.bean("Serial No", serial_no) - if cancel: - sr.doc.supplier = None - sr.doc.supplier_name = None - else: - sr.doc.supplier = self.doc.supplier - sr.doc.supplier_name = self.doc.supplier_name - sr.save() def check_next_docstatus(self): submit_rv = webnotes.conn.sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % (self.doc.name)) @@ -292,7 +278,6 @@ class DocType(BuyingController): self.update_ordered_qty() self.update_stock() - self.update_serial_nos(cancel=True) self.update_prevdoc_status() pc_obj.update_last_purchase_rate(self, 0) @@ -308,7 +293,7 @@ class DocType(BuyingController): def get_rate(self,arg): return get_obj('Purchase Common').get_rate(arg,self) - + def get_gl_entries_for_stock(self, warehouse_account=None): against_stock_account = self.get_company_default("stock_received_but_not_billed") diff --git a/stock/doctype/serial_no/serial_no.py b/stock/doctype/serial_no/serial_no.py index 3b9704fcd5..93836d64e4 100644 --- a/stock/doctype/serial_no/serial_no.py +++ b/stock/doctype/serial_no/serial_no.py @@ -4,14 +4,22 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import cint, getdate, nowdate +from webnotes.utils import cint, getdate, nowdate, cstr, flt, add_days import datetime -from webnotes import msgprint, _ +from webnotes import msgprint, _, ValidationError from controllers.stock_controller import StockController -class SerialNoCannotCreateDirectError(webnotes.ValidationError): pass -class SerialNoCannotCannotChangeError(webnotes.ValidationError): pass +class SerialNoCannotCreateDirectError(ValidationError): pass +class SerialNoCannotCannotChangeError(ValidationError): pass +class SerialNoNotRequiredError(ValidationError): pass +class SerialNoRequiredError(ValidationError): pass +class SerialNoQtyError(ValidationError): pass +class SerialNoItemError(ValidationError): pass +class SerialNoWarehouseError(ValidationError): pass +class SerialNoStatusError(ValidationError): pass +class SerialNoNotExistsError(ValidationError): pass +class SerialNoDuplicateError(ValidationError): pass class DocType(StockController): def __init__(self, doc, doclist=None): @@ -21,13 +29,18 @@ class DocType(StockController): def validate(self): if self.doc.fields.get("__islocal") and self.doc.warehouse: - webnotes.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"), - SerialNoCannotCreateDirectError) + webnotes.throw(_("New Serial No cannot have Warehouse. Warehouse must be \ + set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError) self.validate_warranty_status() self.validate_amc_status() self.validate_warehouse() self.validate_item() + + if self.via_stock_ledger: + self.set_status() + self.set_purchase_details() + self.set_sales_details() def validate_amc_status(self): """ @@ -41,7 +54,8 @@ class DocType(StockController): validate warranty status """ if (self.doc.maintenance_status == 'Out of Warranty' and self.doc.warranty_expiry_date and getdate(self.doc.warranty_expiry_date) >= datetime.date.today()) or (self.doc.maintenance_status == 'Under Warranty' and (not self.doc.warranty_expiry_date or getdate(self.doc.warranty_expiry_date) < datetime.date.today())): - msgprint("Warranty expiry date and maintenance status mismatch. Please verify", raise_exception=1) + msgprint("Warranty expiry date and maintenance status mismatch. Please verify", + raise_exception=1) def validate_warehouse(self): @@ -49,10 +63,11 @@ class DocType(StockController): item_code, warehouse = webnotes.conn.get_value("Serial No", self.doc.name, ["item_code", "warehouse"]) if item_code != self.doc.item_code: - webnotes.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError) + webnotes.throw(_("Item Code cannot be changed for Serial No."), + SerialNoCannotCannotChangeError) if not self.via_stock_ledger and warehouse != self.doc.warehouse: - webnotes.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError) - + webnotes.throw(_("Warehouse cannot be changed for Serial No."), + SerialNoCannotCannotChangeError) def validate_item(self): """ @@ -67,16 +82,89 @@ class DocType(StockController): self.doc.item_name = item.item_name self.doc.brand = item.brand self.doc.warranty_period = item.warranty_period - + + def set_status(self): + last_sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` + where (serial_no like %s or serial_no like %s or serial_no=%s) + and item_code=%s and ifnull(is_cancelled, 'No')='No' + order by name desc limit 1""", + ("%%%s%%" % (self.doc.name+"\n"), "%%%s%%" % ("\n"+self.doc.name), self.doc.name, + self.doc.item_code), as_dict=1) + + if last_sle: + if last_sle[0].voucher_type == "Stock Entry": + document_type = webnotes.conn.get_value("Stock Entry", last_sle[0].voucher_no, + "purpose") + else: + document_type = last_sle[0].voucher_type + + if last_sle[0].actual_qty > 0: + if document_type == "Sales Return": + self.doc.status = "Sales Returned" + else: + self.doc.status = "Available" + else: + if document_type == "Purchase Return": + self.doc.status = "Purchase Returned" + elif last_sle[0].voucher_type in ("Delivery Note", "Sales Invoice"): + self.doc.status = "Delivered" + else: + self.doc.status = "Not Available" + + def set_purchase_details(self): + purchase_sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` + where (serial_no like %s or serial_no like %s or serial_no=%s) + and item_code=%s and actual_qty > 0 + and ifnull(is_cancelled, 'No')='No' order by name asc limit 1""", + ("%%%s%%" % (self.doc.name+"\n"), "%%%s%%" % ("\n"+self.doc.name), self.doc.name, + self.doc.item_code), as_dict=1) + + if purchase_sle: + self.doc.purchase_document_type = purchase_sle[0].voucher_type + self.doc.purchase_document_no = purchase_sle[0].voucher_no + self.doc.purchase_date = purchase_sle[0].posting_date + self.doc.purchase_time = purchase_sle[0].posting_time + self.doc.purchase_rate = purchase_sle[0].incoming_rate + if purchase_sle[0].voucher_type == "Purchase Receipt": + self.doc.supplier, self.doc.supplier_name = \ + webnotes.conn.get_value("Purchase Receipt", purchase_sle[0].voucher_no, + ["supplier", "supplier_name"]) + else: + for fieldname in ("purchase_document_type", "purchase_document_no", + "purchase_date", "purchase_time", "purchase_rate", "supplier", "supplier_name"): + self.doc.fields[fieldname] = None + + def set_sales_details(self): + delivery_sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` + where (serial_no like %s or serial_no like %s or serial_no=%s) + and item_code=%s and actual_qty<0 + and voucher_type in ('Delivery Note', 'Sales Invoice') + and ifnull(is_cancelled, 'No')='No' order by name desc limit 1""", + ("%%%s%%" % (self.doc.name+"\n"), "%%%s%%" % ("\n"+self.doc.name), self.doc.name, + self.doc.item_code), as_dict=1) + if delivery_sle: + self.doc.delivery_document_type = delivery_sle[0].voucher_type + self.doc.delivery_document_no = delivery_sle[0].voucher_no + self.doc.delivery_date = delivery_sle[0].posting_date + self.doc.delivery_time = delivery_sle[0].posting_time + self.doc.customer, self.doc.customer_name = \ + webnotes.conn.get_value(delivery_sle[0].voucher_type, delivery_sle[0].voucher_no, + ["customer", "customer_name"]) + if self.doc.warranty_period: + self.doc.warranty_expiry_date = add_days(cstr(delivery_sle[0].posting_date), + cint(self.doc.warranty_period)) + else: + for fieldname in ("delivery_document_type", "delivery_document_no", + "delivery_date", "delivery_time", "customer", "customer_name", + "warranty_expiry_date"): + self.doc.fields[fieldname] = None + def on_trash(self): if self.doc.status == 'Delivered': - msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1) + webnotes.throw(_("Delivered Serial No ") + self.doc.name + _(" can not be deleted")) if self.doc.warehouse: - webnotes.throw(_("Cannot delete Serial No in warehouse. First remove from warehouse, then delete.") + \ - ": " + self.doc.name) - - def on_cancel(self): - self.on_trash() + webnotes.throw(_("Cannot delete Serial No in warehouse. \ + First remove from warehouse, then delete.") + ": " + self.doc.name) def on_rename(self, new, old, merge=False): """rename serial_no text fields""" @@ -93,3 +181,106 @@ class DocType(StockController): webnotes.conn.sql("""update `tab%s` set serial_no = %s where name=%s""" % (dt[0], '%s', '%s'), ('\n'.join(serial_nos), item[0])) + +def process_serial_no(sle): + item_det = get_item_details(sle.item_code) + validate_serial_no(sle, item_det) + update_serial_nos(sle, item_det) + +def validate_serial_no(sle, item_det): + if item_det.has_serial_no=="No": + if sle.serial_no: + webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + + sle.item_code), SerialNoNotRequiredError) + else: + if sle.serial_no: + serial_nos = get_serial_nos(sle.serial_no) + if cint(sle.actual_qty) != flt(sle.actual_qty): + webnotes.throw(_("Serial No qty cannot be a fraction") + \ + (": %s (%s)" % (sle.item_code, sle.actual_qty))) + if len(serial_nos) and len(serial_nos) != abs(cint(sle.actual_qty)): + webnotes.throw(_("Serial Nos do not match with qty") + \ + (": %s (%s)" % (sle.item_code, sle.actual_qty)), SerialNoQtyError) + + for serial_no in serial_nos: + if webnotes.conn.exists("Serial No", serial_no): + sr = webnotes.bean("Serial No", serial_no) + + if sr.doc.item_code!=sle.item_code: + webnotes.throw(_("Serial No does not belong to Item") + + (": %s (%s)" % (sle.item_code, serial_no)), SerialNoItemError) + + if sr.doc.warehouse and sle.actual_qty > 0: + webnotes.throw(_("Same Serial No") + ": " + sr.doc.name + + _(" can not be received twice"), SerialNoDuplicateError) + + if sle.actual_qty < 0: + if sr.doc.warehouse!=sle.warehouse: + webnotes.throw(_("Serial No") + ": " + serial_no + + _(" does not belong to Warehouse") + ": " + sle.warehouse, + SerialNoWarehouseError) + + if sle.voucher_type in ("Delivery Note", "Sales Invoice") \ + and sr.doc.status != "Available": + webnotes.throw(_("Serial No status must be 'Available' to Deliver") + + ": " + serial_no, SerialNoStatusError) + elif sle.actual_qty < 0: + # transfer out + webnotes.throw(_("Serial No must exist to transfer out.") + \ + ": " + serial_no, SerialNoNotExistsError) + elif not item_det.serial_no_series: + webnotes.throw(_("Serial Number Required for Serialized Item" + ": " + + sle.item_code), SerialNoRequiredError) + +def update_serial_nos(sle, item_det): + if sle.serial_no: + serial_nos = get_serial_nos(sle.serial_no) + for serial_no in serial_nos: + if webnotes.conn.exists("Serial No", serial_no): + sr = webnotes.bean("Serial No", serial_no) + sr.make_controller().via_stock_ledger = True + sr.doc.warehouse = sle.warehouse if sle.actual_qty > 0 else None + sr.save() + elif sle.actual_qty > 0: + make_serial_no(serial_no, sle) + elif sle.actual_qty > 0 and item_det.serial_no_series: + from webnotes.model.doc import make_autoname + serial_nos = [] + for i in xrange(cint(sle.actual_qty)): + serial_nos.append(make_serial_no(make_autoname(item_det.serial_no_series), sle)) + sle.serial_no = "\n".join(serial_nos) + +def get_item_details(item_code): + return webnotes.conn.sql("""select name, has_batch_no, docstatus, + is_stock_item, has_serial_no, serial_no_series + from tabItem where name=%s""", item_code, as_dict=True)[0] + +def get_serial_nos(serial_no): + return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()] + +def make_serial_no(serial_no, sle): + sr = webnotes.new_bean("Serial No") + sr.doc.serial_no = serial_no + sr.doc.item_code = sle.item_code + sr.make_controller().via_stock_ledger = True + sr.insert() + sr.doc.warehouse = sle.warehouse + sr.doc.status = "Available" + sr.save() + webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name) + return sr.doc.name + +def update_serial_nos_after_submit(controller, parenttype, parentfield): + if not hasattr(webnotes, "new_stock_ledger_entries"): + return + + for d in controller.doclist.get({"parentfield": parentfield}): + serial_no = None + for sle in webnotes.new_stock_ledger_entries: + if sle.voucher_detail_no==d.name: + serial_no = sle.serial_no + break + + if d.serial_no != serial_no: + d.serial_no = serial_no + webnotes.conn.set_value(d.doctype, d.name, "serial_no", serial_no) diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index 535d4c8233..0117864a1b 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -51,13 +51,15 @@ class DocType(StockController): def on_submit(self): self.update_stock_ledger() - self.update_serial_no(1) + + from stock.doctype.serial_no.serial_no import update_serial_nos_after_submit + update_serial_nos_after_submit(self, "Stock Entry", "mtn_details") + self.update_production_order(1) self.make_gl_entries() def on_cancel(self): self.update_stock_ledger() - self.update_serial_no(0) self.update_production_order(0) self.make_cancel_gl_entries() @@ -293,24 +295,6 @@ class DocType(StockController): from `tabStock Entry Detail` where parent in ( select name from `tabStock Entry` where `%s`=%s and docstatus=1) group by item_code""" % (ref_fieldname, "%s"), (self.doc.fields.get(ref_fieldname),))) - - def update_serial_no(self, is_submit): - """Create / Update Serial No""" - - from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos - update_serial_nos_after_submit(self, "Stock Entry", "mtn_details") - - for d in getlist(self.doclist, 'mtn_details'): - for serial_no in get_serial_nos(d.serial_no): - if self.doc.purpose == 'Purchase Return': - sr = webnotes.bean("Serial No", serial_no) - sr.doc.status = "Purchase Returned" if is_submit else "Available" - sr.save() - - if self.doc.purpose == "Sales Return": - sr = webnotes.bean("Serial No", serial_no) - sr.doc.status = "Sales Returned" if is_submit else "Delivered" - sr.save() def update_stock_ledger(self): sl_entries = [] diff --git a/stock/doctype/stock_entry/test_stock_entry.py b/stock/doctype/stock_entry/test_stock_entry.py index e2358eba4f..b41a6269ad 100644 --- a/stock/doctype/stock_entry/test_stock_entry.py +++ b/stock/doctype/stock_entry/test_stock_entry.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import webnotes, unittest from webnotes.utils import flt -from stock.doctype.stock_ledger_entry.stock_ledger_entry import * +from stock.doctype.serial_no.serial_no import * from stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory @@ -48,7 +48,7 @@ class TestStockEntry(unittest.TestCase): webnotes.bean("Profile", "test2@example.com").get_controller()\ .add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") - from stock.doctype.stock_ledger_entry.stock_ledger_entry import InvalidWarehouseCompany + from stock.utils import InvalidWarehouseCompany st1 = webnotes.bean(copy=test_records[0]) st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1" st1.insert() @@ -721,6 +721,18 @@ class TestStockEntry(unittest.TestCase): se.insert() self.assertRaises(SerialNoNotExistsError, se.submit) + def test_serial_duplicate(self): + self._clear_stock_account_balance() + self.test_serial_by_series() + + se = webnotes.bean(copy=test_records[0]) + se.doclist[1].item_code = "_Test Serialized Item With Series" + se.doclist[1].qty = 1 + se.doclist[1].serial_no = "ABCD00001" + se.doclist[1].transfer_qty = 1 + se.insert() + self.assertRaises(SerialNoDuplicateError, se.submit) + def test_serial_by_series(self): self._clear_stock_account_balance() se = make_serialized_item() diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 1c3d3e132d..8fef889f72 100644 --- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -3,29 +3,17 @@ from __future__ import unicode_literals import webnotes -from webnotes import _, msgprint, ValidationError +from webnotes import _, msgprint from webnotes.utils import cint, flt, getdate, cstr from webnotes.model.controller import DocListController -class InvalidWarehouseCompany(ValidationError): pass -class SerialNoNotRequiredError(ValidationError): pass -class SerialNoRequiredError(ValidationError): pass -class SerialNoQtyError(ValidationError): pass -class SerialNoItemError(ValidationError): pass -class SerialNoWarehouseError(ValidationError): pass -class SerialNoStatusError(ValidationError): pass -class SerialNoNotExistsError(ValidationError): pass - -def get_serial_nos(serial_no): - return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()] - class DocType(DocListController): def __init__(self, doc, doclist=[]): self.doc = doc self.doclist = doclist def validate(self): - from stock.utils import validate_warehouse_user + from stock.utils import validate_warehouse_user, validate_warehouse_company if not hasattr(webnotes, "new_stock_ledger_entries"): webnotes.new_stock_ledger_entries = [] @@ -33,7 +21,7 @@ class DocType(DocListController): self.validate_mandatory() self.validate_item() validate_warehouse_user(self.doc.warehouse) - self.validate_warehouse_company() + validate_warehouse_company(self.doc.warehouse, self.doc.company) self.scrub_posting_time() from accounts.utils import validate_fiscal_year @@ -42,7 +30,9 @@ class DocType(DocListController): def on_submit(self): self.check_stock_frozen_date() self.actual_amt_check() - self.validate_serial_no() + + from stock.doctype.serial_no.serial_no import process_serial_no + process_serial_no(self.doc) #check for item quantity available in stock def actual_amt_check(self): @@ -63,13 +53,6 @@ class DocType(DocListController): as on %(posting_date)s %(posting_time)s""" % self.doc.fields) sself.doc.fields.pop('batch_bal') - - def validate_warehouse_company(self): - warehouse_company = webnotes.conn.get_value("Warehouse", self.doc.warehouse, "company") - if warehouse_company and warehouse_company != self.doc.company: - webnotes.msgprint(_("Warehouse does not belong to company.") + " (" + \ - self.doc.warehouse + ", " + self.doc.company +")", - raise_exception=InvalidWarehouseCompany) def validate_mandatory(self): mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company'] @@ -81,7 +64,10 @@ class DocType(DocListController): msgprint("Warehouse: '%s' does not exist in the system. Please check." % self.doc.fields.get(k), raise_exception = 1) def validate_item(self): - item_det = self.get_item_details() + item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus, + is_stock_item, has_serial_no, serial_no_series + from tabItem where name=%s""", + self.doc.item_code, as_dict=True)[0] if item_det.is_stock_item != 'Yes': webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code) @@ -99,95 +85,6 @@ class DocType(DocListController): if not self.doc.stock_uom: self.doc.stock_uom = item_det.stock_uom - def get_item_details(self): - return webnotes.conn.sql("""select name, has_batch_no, docstatus, - is_stock_item, has_serial_no, serial_no_series - from tabItem where name=%s""", - self.doc.item_code, as_dict=True)[0] - - def validate_serial_no(self): - item_det = self.get_item_details() - - if item_det.has_serial_no=="No": - if self.doc.serial_no: - webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + self.doc.item), - SerialNoNotRequiredError) - else: - if self.doc.serial_no: - serial_nos = get_serial_nos(self.doc.serial_no) - if cint(self.doc.actual_qty) != flt(self.doc.actual_qty): - webnotes.throw(_("Serial No qty cannot be a fraction") + \ - (": %s (%s)" % (self.doc.item_code, self.doc.actual_qty))) - if len(serial_nos) and len(serial_nos) != abs(cint(self.doc.actual_qty)): - webnotes.throw(_("Serial Nos do not match with qty") + \ - (": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)), SerialNoQtyError) - - # check serial no exists, if yes then source - for serial_no in serial_nos: - if webnotes.conn.exists("Serial No", serial_no): - sr = webnotes.bean("Serial No", serial_no) - - if sr.doc.item_code!=self.doc.item_code: - webnotes.throw(_("Serial No does not belong to Item") + \ - (": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoItemError) - - sr.make_controller().via_stock_ledger = True - - if self.doc.actual_qty < 0: - if sr.doc.warehouse!=self.doc.warehouse: - webnotes.throw(_("Serial No") + ": " + serial_no + - _(" does not belong to Warehouse") + ": " + self.doc.warehouse, - SerialNoWarehouseError) - - if self.doc.voucher_type in ("Delivery Note", "Sales Invoice") \ - and sr.doc.status != "Available": - webnotes.throw(_("Serial No status must be 'Available' to Deliver") - + ": " + serial_no, SerialNoStatusError) - - - sr.doc.warehouse = None - sr.save() - else: - sr.doc.warehouse = self.doc.warehouse - sr.save() - else: - if self.doc.actual_qty < 0: - # transfer out - webnotes.throw(_("Serial No must exist to transfer out.") + \ - ": " + serial_no, SerialNoNotExistsError) - else: - # transfer in - self.make_serial_no(serial_no) - else: - if item_det.serial_no_series: - from webnotes.model.doc import make_autoname - serial_nos = [] - for i in xrange(cint(self.doc.actual_qty)): - serial_nos.append(self.make_serial_no(make_autoname(item_det.serial_no_series))) - self.doc.serial_no = "\n".join(serial_nos) - else: - webnotes.throw(_("Serial Number Required for Serialized Item" + ": " + self.doc.item), - SerialNoRequiredError) - - def make_serial_no(self, serial_no): - sr = webnotes.new_bean("Serial No") - sr.doc.serial_no = serial_no - sr.doc.item_code = self.doc.item_code - sr.doc.purchase_rate = self.doc.incoming_rate - sr.doc.purchase_document_type = self.doc.voucher_type - sr.doc.purchase_document_no = self.doc.voucher_no - sr.doc.purchase_date = self.doc.posting_date - sr.doc.purchase_time = self.doc.posting_time - sr.make_controller().via_stock_ledger = True - sr.insert() - - # set warehouse - sr.doc.warehouse = self.doc.warehouse - sr.doc.status = "Available" - sr.save() - webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name) - return sr.doc.name - def check_stock_frozen_date(self): stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or '' if stock_frozen_upto: @@ -199,21 +96,6 @@ class DocType(DocListController): if not self.doc.posting_time or self.doc.posting_time == '00:0': self.doc.posting_time = '00:00' -def update_serial_nos_after_submit(controller, parenttype, parentfield): - if not hasattr(webnotes, "new_stock_ledger_entries"): - return - - for d in controller.doclist.get({"parentfield": parentfield}): - serial_no = None - for sle in webnotes.new_stock_ledger_entries: - if sle.voucher_detail_no==d.name: - serial_no = sle.serial_no - break - - if d.serial_no != serial_no: - d.serial_no = serial_no - webnotes.conn.set_value(d.doctype, d.name, "serial_no", serial_no) - def on_doctype_update(): if not webnotes.conn.sql("""show index from `tabStock Ledger Entry` where Key_name="posting_sort_index" """): diff --git a/stock/utils.py b/stock/utils.py index 8836c6c991..1f501644ff 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -9,6 +9,7 @@ from webnotes.defaults import get_global_default from webnotes.utils.email_lib import sendmail class UserNotAllowedForWarehouse(webnotes.ValidationError): pass +class InvalidWarehouseCompany(webnotes.ValidationError): pass def get_stock_balance_on(warehouse, posting_date=None): if not posting_date: posting_date = nowdate() @@ -216,6 +217,12 @@ def validate_warehouse_user(warehouse): if warehouse_users and not (webnotes.session.user in warehouse_users): webnotes.throw(_("Not allowed entry in Warehouse") \ + ": " + warehouse, UserNotAllowedForWarehouse) + +def validate_warehouse_company(warehouse, company): + warehouse_company = webnotes.conn.get_value("Warehouse", warehouse, "company") + if warehouse_company and warehouse_company != company: + webnotes.msgprint(_("Warehouse does not belong to company.") + " (" + \ + warehouse + ", " + company +")", raise_exception=InvalidWarehouseCompany) def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no, stock_ledger_entries, item_sales_bom): diff --git a/support/report/maintenance_schedules/maintenance_schedules.txt b/support/report/maintenance_schedules/maintenance_schedules.txt index 525f4834ba..766eb20151 100644 --- a/support/report/maintenance_schedules/maintenance_schedules.txt +++ b/support/report/maintenance_schedules/maintenance_schedules.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-06 14:25:21", "docstatus": 0, - "modified": "2013-05-06 14:32:47", + "modified": "2013-10-09 12:23:27", "modified_by": "Administrator", "owner": "Administrator" }, @@ -10,7 +10,7 @@ "doctype": "Report", "is_standard": "Yes", "name": "__common__", - "query": "SELECT\n ms_item.scheduled_date as \"Schedule Date:Date:120\",\n\tms_item.item_code as \"Item Code:Link/Item:120\",\n\tms_item.item_name as \"Item Name::120\",\n\tms_item.serial_no as \"Serial No::120\",\n\tms_item.incharge_name as \"Incharge::120\",\n\tms.customer_name as \"Customer:Link/Customer:120\",\n\tms.address_display as \"Customer Address::120\",\n\tms.sales_order_no as \"Sales Order:Link/Sales Order:120\",\n\tms.company as \"Company:Link/Company:120\"\n\t\nFROM\n\t`tabMaintenance Schedule` ms, `tabMaintenance Schedule Detail` ms_item\nWHERE\n\tms.name = ms_item.parent and ms.docstatus = 1\nORDER BY\n\tms_item.scheduled_date asc, ms_item.item_code asc", + "query": "SELECT\n ms_sch.scheduled_date as \"Schedule Date:Date:120\",\n\tms_sch.item_code as \"Item Code:Link/Item:120\",\n\tms_sch.item_name as \"Item Name::120\",\n\tms_sch.serial_no as \"Serial No::120\",\n\tms_sch.incharge_name as \"Incharge::120\",\n\tms.customer_name as \"Customer:Link/Customer:120\",\n\tms.address_display as \"Customer Address::120\",\n\tms_item.prevdoc_docname as \"Sales Order:Link/Sales Order:120\",\n\tms.company as \"Company:Link/Company:120\"\n\t\nFROM\n\t`tabMaintenance Schedule` ms, \n `tabMaintenance Schedule Detail` ms_sch, \n `tabMaintenance Schedule Item` ms_item\nWHERE\n\tms.name = ms_sch.parent and ms.name = ms_item.parent and ms.docstatus = 1\nORDER BY\n\tms_sch.scheduled_date asc, ms_sch.item_code asc", "ref_doctype": "Maintenance Schedule", "report_name": "Maintenance Schedules", "report_type": "Query Report"