Merge branch 'master' of github.com:webnotes/erpnext
This commit is contained in:
commit
b02fba9c77
@ -181,7 +181,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
set_dynamic_labels: function() {
|
set_dynamic_labels: function() {
|
||||||
this._super();
|
this._super();
|
||||||
this.hide_fields(this.frm.doc);
|
this.hide_fields(this.frm.doc);
|
||||||
|
},
|
||||||
|
|
||||||
|
entries_on_form_rendered: function(doc, grid_row) {
|
||||||
|
erpnext.setup_serial_no(grid_row)
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// for backward compatibility: combine new and previous states
|
// for backward compatibility: combine new and previous states
|
||||||
|
@ -65,9 +65,6 @@ class DocType(SellingController):
|
|||||||
self.validate_write_off_account()
|
self.validate_write_off_account()
|
||||||
|
|
||||||
if cint(self.doc.update_stock):
|
if cint(self.doc.update_stock):
|
||||||
sl = get_obj('Stock Ledger')
|
|
||||||
sl.validate_serial_no(self, 'entries')
|
|
||||||
sl.validate_serial_no(self, 'packing_details')
|
|
||||||
self.validate_item_code()
|
self.validate_item_code()
|
||||||
self.update_current_stock()
|
self.update_current_stock()
|
||||||
self.validate_delivery_note()
|
self.validate_delivery_note()
|
||||||
@ -85,14 +82,8 @@ class DocType(SellingController):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if cint(self.doc.update_stock) == 1:
|
if cint(self.doc.update_stock) == 1:
|
||||||
sl_obj = get_obj("Stock Ledger")
|
|
||||||
sl_obj.validate_serial_no_warehouse(self, 'entries')
|
|
||||||
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
|
|
||||||
|
|
||||||
sl_obj.update_serial_record(self, 'entries', is_submit = 1, is_incoming = 0)
|
|
||||||
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
|
|
||||||
|
|
||||||
self.update_stock_ledger(update_stock=1)
|
self.update_stock_ledger(update_stock=1)
|
||||||
|
self.update_serial_nos()
|
||||||
else:
|
else:
|
||||||
# Check for Approving Authority
|
# Check for Approving Authority
|
||||||
if not self.doc.recurring_id:
|
if not self.doc.recurring_id:
|
||||||
@ -120,11 +111,8 @@ class DocType(SellingController):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
if cint(self.doc.update_stock) == 1:
|
if cint(self.doc.update_stock) == 1:
|
||||||
sl = get_obj('Stock Ledger')
|
|
||||||
sl.update_serial_record(self, 'entries', is_submit = 0, is_incoming = 0)
|
|
||||||
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
|
|
||||||
|
|
||||||
self.update_stock_ledger(update_stock = -1)
|
self.update_stock_ledger(update_stock = -1)
|
||||||
|
self.update_serial_nos(cancel = True)
|
||||||
|
|
||||||
sales_com_obj = get_obj(dt = 'Sales Common')
|
sales_com_obj = get_obj(dt = 'Sales Common')
|
||||||
sales_com_obj.check_stop_sales_order(self)
|
sales_com_obj.check_stop_sales_order(self)
|
||||||
@ -494,10 +482,6 @@ class DocType(SellingController):
|
|||||||
|
|
||||||
def make_packing_list(self):
|
def make_packing_list(self):
|
||||||
get_obj('Sales Common').make_packing_list(self,'entries')
|
get_obj('Sales Common').make_packing_list(self,'entries')
|
||||||
sl = get_obj('Stock Ledger')
|
|
||||||
sl.scrub_serial_nos(self)
|
|
||||||
sl.scrub_serial_nos(self, 'packing_details')
|
|
||||||
|
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
if cint(self.doc.update_stock) == 1:
|
if cint(self.doc.update_stock) == 1:
|
||||||
|
@ -645,6 +645,60 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
for i in xrange(count):
|
for i in xrange(count):
|
||||||
base_si = _test(i)
|
base_si = _test(i)
|
||||||
|
|
||||||
|
def test_serialized(self):
|
||||||
|
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||||
|
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||||
|
|
||||||
|
si = webnotes.bean(copy=test_records[0])
|
||||||
|
si.doc.update_stock = 1
|
||||||
|
si.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
si.doclist[1].qty = 1
|
||||||
|
si.doclist[1].serial_no = serial_nos[0]
|
||||||
|
si.insert()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
|
||||||
|
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||||
|
"delivery_document_no"), si.doc.name)
|
||||||
|
|
||||||
|
return si
|
||||||
|
|
||||||
|
def test_serialized_cancel(self):
|
||||||
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||||
|
si = self.test_serialized()
|
||||||
|
si.cancel()
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(si.doclist[1].serial_no)
|
||||||
|
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
|
||||||
|
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||||
|
"delivery_document_no"))
|
||||||
|
|
||||||
|
def test_serialize_status(self):
|
||||||
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
|
||||||
|
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||||
|
|
||||||
|
sr = webnotes.bean("Serial No", serial_nos[0])
|
||||||
|
sr.doc.status = "Not Available"
|
||||||
|
sr.save()
|
||||||
|
|
||||||
|
si = webnotes.bean(copy=test_records[0])
|
||||||
|
si.doc.update_stock = 1
|
||||||
|
si.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
si.doclist[1].qty = 1
|
||||||
|
si.doclist[1].serial_no = serial_nos[0]
|
||||||
|
si.insert()
|
||||||
|
|
||||||
|
self.assertRaises(SerialNoStatusError, si.submit)
|
||||||
|
|
||||||
test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"]
|
test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"]
|
||||||
|
|
||||||
test_records = [
|
test_records = [
|
||||||
|
@ -46,10 +46,10 @@ cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
|
|||||||
if (doc.item_code) {
|
if (doc.item_code) {
|
||||||
filter = {
|
filter = {
|
||||||
'item_code': doc.item_code,
|
'item_code': doc.item_code,
|
||||||
'status': "In Store"
|
'status': "Available"
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
filter = { 'status': "In Store" }
|
filter = { 'status': "Available" }
|
||||||
|
|
||||||
return { filters: filter }
|
return { filters: filter }
|
||||||
}
|
}
|
@ -267,3 +267,32 @@ class SellingController(StockController):
|
|||||||
msgprint(_(self.meta.get_label("order_type")) + " " +
|
msgprint(_(self.meta.get_label("order_type")) + " " +
|
||||||
_("must be one of") + ": " + comma_or(valid_types),
|
_("must be one of") + ": " + comma_or(valid_types),
|
||||||
raise_exception=True)
|
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.delivery_date),
|
||||||
|
cint(sr.doc.warranty_period))
|
||||||
|
sr.doc.status = 'Delivered'
|
||||||
|
|
||||||
|
sr.save()
|
||||||
|
@ -7,64 +7,59 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
An Item is simply a product or service which you sell or buy from your Customers or Suppliers. ERPNext is optimized for itemized management of your sales and purchase. However, you can skip creating Items. If you are in services, you can create an Item for each services that your offer.
|
An Item is your company's product or a service.The term Item is applicable to your core products as well as your raw materials. It can be a product or service that you buy/sell from your customers/ suppliers. ERPNext allows you to manage all sorts of items like raw-materials, sub-assemblies, finished goods, item variants and service items.
|
||||||
|
|
||||||
There are two main categories of Items in ERPNext
|
ERPNext is optimized for itemized management of your sales and purchase. If you are in services, you can create an Item for each services that your offer. Completing the Item Master is very essential for successful implementation of ERPNext.
|
||||||
|
|
||||||
- Stock Items
|
## Item Properties
|
||||||
- Non Stock Items
|
|
||||||
|
|
||||||
As you may have guessed, inventory balances are tracked for stock items and not for
|
- **Item Name:** Item name is the actual name of your product or service.
|
||||||
non-stock items. Non-stock items could be services or consumables that are not tracked.
|
- **Item Code:** Item Code is a short-form to denote your Item. If you have very few Items, it is advisable to keep the Item Name and the Item Code same. This helps new users to recognise and update Item details in all transactions. In case you have lot of Items with long names and the list runs in hundreds, it is advisable to code. To understand naming Item codes see [Item Codification](docs.user.setup.codification.html)
|
||||||
|
- **Item Group:** Item Group is used to categorize an Item under various criterias like products, raw materials, services, sub-assemblies, consumables or all Item groups. Create your default Item Group list under Setup> Item Group and pre-select the option while filling your New Item details under Item Group.
|
||||||
|
- **Default Unit of Measure:** This is the default measuring unit that you will use for your product. It could be in nos, kgs, meters, etc. You can store all the UOM’s that your product will require under Set Up> Master Data > UOM. These can be preselected while filling New Item by using % sign to get a pop up of the UOM list.
|
||||||
|
- **Brand:** If you have more than one brand save them under Set Up> Master Data> Brand and pre-select them while filling a New Item.
|
||||||
|
|
||||||
### Item Groups
|
![Item Properties](img/item-properties.png)
|
||||||
|
|
||||||
ERPNext allows you to classify items into groups. This will help you in getting reports about various classes of items and also help in cataloging your items for the website.
|
### Upload an Image
|
||||||
|
|
||||||
### Warehouses
|
To upload an image for your icon that will appear in all transactions, save the partially filled form. Only after your file is saved a “+” button will appear besides the Image icon. Click on this sign and upload the image.
|
||||||
|
|
||||||
In ERPNext you can create Warehouses to identify where your Items reside.
|
![Item Properties](img/item-add-image.png)
|
||||||
|
|
||||||
There are two main Warehouse Types that are significant in ERPNext.
|
### Item Pricing
|
||||||
|
|
||||||
Stores: These are where your incoming Items are kept before they are consumed or sold. You can have as many “Stores” type Warehouses as you wish. Stores type warehouses are significant because if you set an Item for automatic re-order, ERPNext will check its quantities in all “Stores” type Warehouses when deciding whether to re-order or not.
|
Item Price and Price Lists: ERPNext lets you maintain multiple selling prices for an Item using Price Lists. A Price List is a place where different rate plans can be stored. It’s a name you can give to a set of Item prices. In case you have different zones (based on the shipping costs), for different currencies etc, you can maintain different Price Lists. A Price List is formed when you create different Item Prices. To import Item Price visit “Import Item Price”.
|
||||||
|
|
||||||
Asset: Items marked as type “Fixed Asset” are maintained in Asset Type Warehouses. This helps you to separate them for the Items that are consumed as a part of your regular operations or “Cost of Goods Sold”.
|
## Inventory : Warehouse and Stock Setting
|
||||||
|
|
||||||
### Item Taxes
|
In ERPNext, you can select different type of Warehouses to stock your different Items. This can be selected based on Item types. It could be Fixed Asset Item, Stock Item or even Manufacturing Item.
|
||||||
|
|
||||||
These settings are only required if this particular Item has a different tax rate than what is the rate defined in the standard tax Account.
|
- **Stock Item:** If you are maintaining stock of this Item in your Inventory, ERPNext will make a stock ledger entry for each transaction of this item.
|
||||||
|
- **Default Warehouse:** This is the Warehouse that is automatically selected in your transactions.
|
||||||
|
- **Allowance Percentage:** This is the percent by which you will be allowed to over-bill or over-deliver this Item. If not set, it will select from the Global Defaults.
|
||||||
|
- **Valuation Method:** There are two options to maintain valuation of stock. FIFO (first in - first out) and Moving Average. To understand this topic in detail please visit “ Item Valuation, FIFO and Moving Average”.
|
||||||
|
|
||||||
For example, you have a tax Account, “VAT 10%” and this particular item is exempted from this tax, then you select “VAT 10%” in the first column, and set “0” as the tax rate in the second column.
|
### Serialized and Batched Inventory
|
||||||
|
|
||||||
|
These numbers help to track individual units or batches of Items which you sell. It also tracks warranty and returns. In case any individual Item is recalled by the supplier the number system helps to track individual Item. The numbering system also manages expiry dates. Please note that if you sell your items in thousands, and if the items are very small like pens or erasers, you need not serialize them. In ERPNext, you will have to mention the serial number in some accounting entries. To create serial numbers you will have to manually create all the numbers in your entries. If your product is not a big consumer durable Item, if it has no warranty and has no chances of being recalled, avoid giving serial numbers.
|
||||||
|
|
||||||
|
> Important: Once you mark an item as serialized or batched or neither, you cannot change it after you have made any stock entry.
|
||||||
|
|
||||||
|
- [Disucssion on Serialized Inventory](docs.user.stock.serialized.html)
|
||||||
|
|
||||||
|
### Re Ordering
|
||||||
|
|
||||||
|
- **Re-order level** suggests the amount of stock balance in the Warehouse.
|
||||||
|
- **Re-order Qty** suggests the amount of stock to be ordered to maintain minimum stock levels.
|
||||||
|
- **Minimum Order Qty** is the minimum quantity for which a Material Request / Purchase Order must be made.
|
||||||
|
|
||||||
|
### Item Tax
|
||||||
|
|
||||||
|
These settings are required only if a particular Item has a different tax rate than the rate defined in the standard tax Account. For example, If you have a tax Account, “VAT 10%” and this particular Item is exempted from tax, then you select “VAT 10%” in the first column, and set “0” as the tax rate in the second column.
|
||||||
|
|
||||||
### Inspection
|
### Inspection
|
||||||
|
|
||||||
Inspection Required: If an incoming inspection (at the time of delivery from the Supplier) is mandatory for this Item, mention “Inspection Required” as “Yes”. The system will ensure that a Quality Inspection will be prepared and approved before a Purchase Receipt is submitted.
|
Inspection Required: If an incoming inspection (at the time of delivery from the Supplier) is mandatory for this Item, mention “Inspection Required” as “Yes”. The system will ensure that a Quality Inspection will be prepared and approved before a Purchase Receipt is submitted.
|
||||||
|
|
||||||
Inspection Criteria: If a Quality Inspection is prepared for this Item, then this template of criteria will automatically be updated in the Quality Inspection table of the Quality Inspection.
Examples of Criteria are: Weight, Length, Finish etc.
|
Inspection Criteria: If a Quality Inspection is prepared for this Item, then this template of criteria will automatically be updated in the Quality Inspection table of the Quality Inspection. Examples of Criteria are: Weight, Length, Finish etc.
|
||||||
|
|
||||||
### Item Pricing and Price Lists
|
|
||||||
|
|
||||||
ERPNext lets you maintain multiple selling prices for an Item using Price Lists. A Price List is a name you can give to a set of Item prices.
|
|
||||||

|
|
||||||
Why would you want Price Lists? You have different prices for different zones (based on the shipping costs), for different currencies, regions etc.
|
|
||||||
|
|
||||||
#### Negative Stock
|
|
||||||
|
|
||||||
FIFO is the more accurate system of the two but has a disadvantage. You cannot have negative stock in FIFO. This means that you cannot make forward transactions that would make your stock negative. Why is this? Because sequences are so important to FIFO, you cannot track the value of the stock if it does not exist!
|
|
||||||
|
|
||||||
In Moving Average, since each item has an “average” value, the value of the negative stock is also based on this “average”.
|
|
||||||
|
|
||||||
### Serial Numbers and Batches
|
|
||||||
|
|
||||||
In scenarios where you may have to track individual units or batches of Items you sell, ERPNext allows you to manage Serial Numbers and Batches.
|
|
||||||

|
|
||||||
Why is this useful?
|
|
||||||
|
|
||||||
- To track warranty and returns.
|
|
||||||
- To trace individual Items incase they are recalled by the Supplier.
|
|
||||||
- To manage expiry.
|
|
||||||
|
|
||||||
In ERPNext, Serial Number and Batch are separate entities and all stock transactions for Items that serialized or batches must be tagged with either the Batch or Serial Number.
|
|
||||||
|
|
||||||
> Important: Once you mark an item as serialized or batched or neither, you cannot change it after you have made any stock entry.
|
|
||||||
|
@ -11,10 +11,40 @@ You can also track from which **Supplier** you purchased the **Serial No** and t
|
|||||||
|
|
||||||
If your Item is *serialized* you will have to enter the Serial Nos in the related column with each Serial No in a new line.
|
If your Item is *serialized* you will have to enter the Serial Nos in the related column with each Serial No in a new line.
|
||||||
|
|
||||||
|
### Serial Nos and Inventory
|
||||||
|
|
||||||
|
Inventory of an Item can only be affected if the Serial No is transacted via a Stock transaction (Stock Entry, Purchase Receipt, Delivery Note, Sales Invoice). When a new Serial No is created directly, its warehouse cannot be set.
|
||||||
|
|
||||||
|
### Using Serial Nos
|
||||||
|
|
||||||
|
To add a Serial No to a stock transaction, you can set the Serial No in the serial no field:
|
||||||
|
|
||||||
|
![Serial No Entry](img/serial-no-entry.png)
|
||||||
|
|
||||||
|
### Creation
|
||||||
|
|
||||||
|
Serial Nos can automatically be created from a Stock Entry or Purchase Receipt. If you mention Serial No in the Serial Nos column, it will automatically create those serial Nos.
|
||||||
|
|
||||||
|
### Automatic Series
|
||||||
|
|
||||||
|
If in the Item Master, the Serial No Series is mentioned, you can leave the Serial No column blank in a Stock Entry / Purchase Receipt and Serial Nos will automatically be set from that series.
|
||||||
|
|
||||||
|
#### Step 1: Mention the Series in the Item
|
||||||
|
|
||||||
|
![Automatic Series](img/item-serial-no-series.png)
|
||||||
|
|
||||||
|
#### Step 2: Keep Serial No field blank in your entry
|
||||||
|
|
||||||
|
#### Step 3: Save / Submit your transaction (Serial Nos Automatically Updated)
|
||||||
|
|
||||||
|
![Serial No Created Message](img/serial-no-auto-1.png)
|
||||||
|
|
||||||
|
![Serial No Updated in Transaction](img/serial-no-auto-2.png)
|
||||||
|
|
||||||
|
|
||||||
### Importing and Updating Serial Nos
|
### Importing and Updating Serial Nos
|
||||||
|
|
||||||
Serial Nos cannot be imported from Stock Reconciliation. To import Serial Nos, you will have to use the Data Import Tool. When you import the Serial Nos, the stock level of its corresponding Item will be automatically updated.
|
Serial Nos cannot be imported from Stock Reconciliation. To import Serial Nos, you will have to use the Data Import Tool.
|
||||||
|
|
||||||
|
|
||||||
### Using Serial Numbers for Multiple Purposes
|
### Using Serial Numbers for Multiple Purposes
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
def execute():
|
|
||||||
import webnotes
|
|
||||||
from webnotes.modules import reload_doc
|
|
||||||
reload_doc('stock', 'doctype', 'serial_no')
|
|
||||||
|
|
||||||
webnotes.conn.sql("update `tabSerial No` set sle_exists = 1")
|
|
5
patches/august_2013/p05_update_serial_no_status.py
Normal file
5
patches/august_2013/p05_update_serial_no_status.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import webnotes
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
webnotes.conn.sql("""update `tabSerial No` set status = 'Not Available' where status='Not In Store'""")
|
||||||
|
webnotes.conn.sql("""update `tabSerial No` set status = 'Available' where status='In Store'""")
|
@ -39,7 +39,7 @@ def execute():
|
|||||||
|
|
||||||
status = 'Not in Use'
|
status = 'Not in Use'
|
||||||
if sle and flt(sle[0]['actual_qty']) > 0:
|
if sle and flt(sle[0]['actual_qty']) > 0:
|
||||||
status = 'In Store'
|
status = 'Available'
|
||||||
elif sle and flt(sle[0]['actual_qty']) < 0:
|
elif sle and flt(sle[0]['actual_qty']) < 0:
|
||||||
status = 'Delivered'
|
status = 'Delivered'
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ patch_list = [
|
|||||||
"patches.april_2012.update_role_in_address",
|
"patches.april_2012.update_role_in_address",
|
||||||
"patches.april_2012.update_permlevel_in_address",
|
"patches.april_2012.update_permlevel_in_address",
|
||||||
"patches.april_2012.update_appraisal_permission",
|
"patches.april_2012.update_appraisal_permission",
|
||||||
"patches.april_2012.serial_no_fixes",
|
|
||||||
"patches.april_2012.repost_stock_for_posting_time",
|
"patches.april_2012.repost_stock_for_posting_time",
|
||||||
"patches.may_2012.cleanup_property_setter",
|
"patches.may_2012.cleanup_property_setter",
|
||||||
"patches.may_2012.rename_prev_doctype",
|
"patches.may_2012.rename_prev_doctype",
|
||||||
@ -254,6 +253,7 @@ patch_list = [
|
|||||||
"patches.august_2013.p01_hr_settings",
|
"patches.august_2013.p01_hr_settings",
|
||||||
"patches.august_2013.p02_rename_price_list",
|
"patches.august_2013.p02_rename_price_list",
|
||||||
"patches.august_2013.p03_pos_setting_replace_customer_account",
|
"patches.august_2013.p03_pos_setting_replace_customer_account",
|
||||||
|
"patches.august_2013.p05_update_serial_no_status",
|
||||||
"patches.august_2013.p05_employee_birthdays",
|
"patches.august_2013.p05_employee_birthdays",
|
||||||
"execute:webnotes.reload_doc('accounts', 'Print Format', 'POS Invoice') # 2013-08-16",
|
"execute:webnotes.reload_doc('accounts', 'Print Format', 'POS Invoice') # 2013-08-16",
|
||||||
]
|
]
|
@ -36,4 +36,46 @@ $.extend(erpnext, {
|
|||||||
territory.territory = wn.defaults.get_default("territory");
|
territory.territory = wn.defaults.get_default("territory");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup_serial_no: function(grid_row) {
|
||||||
|
if(grid_row.fields_dict.serial_no.get_status()!=="Write") return;
|
||||||
|
|
||||||
|
var $btn = $('<button class="btn btn-sm btn-default">Add Serial No</button>')
|
||||||
|
.appendTo($("<div>")
|
||||||
|
.css({"margin-bottom": "10px", "margin-top": "-10px"})
|
||||||
|
.appendTo(grid_row.fields_dict.serial_no.$wrapper));
|
||||||
|
|
||||||
|
$btn.on("click", function() {
|
||||||
|
var d = new wn.ui.Dialog({
|
||||||
|
title: "Add Serial No",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Serial No",
|
||||||
|
"label": "Serial No",
|
||||||
|
"get_query": {
|
||||||
|
item_code: grid_row.doc.item_code,
|
||||||
|
warehouse: grid_row.doc.warehouse
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Add"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
d.get_input("add").on("click", function() {
|
||||||
|
var serial_no = d.get_value("serial_no");
|
||||||
|
if(serial_no) {
|
||||||
|
var val = (grid_row.doc.serial_no || "").split("\n").concat([serial_no]).join("\n");
|
||||||
|
grid_row.fields_dict.serial_no.set_model_value(val.trim());
|
||||||
|
}
|
||||||
|
d.hide();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
d.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
@ -105,7 +105,6 @@ class DocType(TransactionBase):
|
|||||||
msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
|
msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
get_obj("Stock Ledger").scrub_serial_nos(self, 'installed_item_details')
|
|
||||||
webnotes.conn.set(self.doc, 'status', 'Draft')
|
webnotes.conn.set(self.doc, 'status', 'Draft')
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
@ -72,8 +72,21 @@ def get_item_details(args):
|
|||||||
if pos_settings:
|
if pos_settings:
|
||||||
out.update(apply_pos_settings(pos_settings, out))
|
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:
|
||||||
|
out.serial_no = _get_serial_nos_by_fifo(args, item_bean)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def _get_serial_nos_by_fifo(args, item_bean):
|
||||||
|
return "\n".join(webnotes.conn.sql_list("""select name from `tabSerial No`
|
||||||
|
where item_code=%(item_code)s and warehouse=%(warehouse)s and status='Available'
|
||||||
|
order by timestamp(purchase_date, purchase_time) asc limit %(qty)s""", {
|
||||||
|
"item_code": args.item_code,
|
||||||
|
"warehouse": args.warehouse,
|
||||||
|
"qty": cint(args.qty)
|
||||||
|
}))
|
||||||
|
|
||||||
def _get_item_code(barcode):
|
def _get_item_code(barcode):
|
||||||
item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode)
|
item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode)
|
||||||
|
|
||||||
|
@ -75,6 +75,10 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
|||||||
this.get_terms();
|
this.get_terms();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
delivery_note_details_on_form_rendered: function(doc, grid_row) {
|
||||||
|
erpnext.setup_serial_no(grid_row)
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// for backward compatibility: combine new and previous states
|
// for backward compatibility: combine new and previous states
|
||||||
|
@ -175,9 +175,6 @@ class DocType(SellingController):
|
|||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.doclist = get_obj('Sales Common').make_packing_list(self,'delivery_note_details')
|
self.doclist = get_obj('Sales Common').make_packing_list(self,'delivery_note_details')
|
||||||
sl = get_obj('Stock Ledger')
|
|
||||||
sl.scrub_serial_nos(self)
|
|
||||||
sl.scrub_serial_nos(self, 'packing_details')
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_packed_qty()
|
self.validate_packed_qty()
|
||||||
@ -185,22 +182,12 @@ class DocType(SellingController):
|
|||||||
# Check for Approving Authority
|
# Check for Approving Authority
|
||||||
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)
|
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)
|
||||||
|
|
||||||
# validate serial no for item table (non-sales-bom item) and packing list (sales-bom item)
|
|
||||||
sl_obj = get_obj("Stock Ledger")
|
|
||||||
sl_obj.validate_serial_no(self, 'delivery_note_details')
|
|
||||||
sl_obj.validate_serial_no_warehouse(self, 'delivery_note_details')
|
|
||||||
sl_obj.validate_serial_no(self, 'packing_details')
|
|
||||||
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
|
|
||||||
|
|
||||||
# update delivery details in serial no
|
|
||||||
sl_obj.update_serial_record(self, 'delivery_note_details', is_submit = 1, is_incoming = 0)
|
|
||||||
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
|
|
||||||
|
|
||||||
# update delivered qty in sales order
|
# update delivered qty in sales order
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
|
||||||
# create stock ledger entry
|
# create stock ledger entry
|
||||||
self.update_stock_ledger(update_stock = 1)
|
self.update_stock_ledger(update_stock = 1)
|
||||||
|
self.update_serial_nos()
|
||||||
|
|
||||||
self.credit_limit()
|
self.credit_limit()
|
||||||
|
|
||||||
@ -211,6 +198,50 @@ class DocType(SellingController):
|
|||||||
webnotes.conn.set(self.doc, 'status', 'Submitted')
|
webnotes.conn.set(self.doc, 'status', 'Submitted')
|
||||||
|
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
sales_com_obj = get_obj(dt = 'Sales Common')
|
||||||
|
sales_com_obj.check_stop_sales_order(self)
|
||||||
|
self.check_next_docstatus()
|
||||||
|
|
||||||
|
self.update_prevdoc_status()
|
||||||
|
|
||||||
|
self.update_stock_ledger(update_stock = -1)
|
||||||
|
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.delivery_date),
|
||||||
|
cint(sr.doc.warranty_period))
|
||||||
|
sr.doc.status = 'Delivered'
|
||||||
|
|
||||||
|
sr.save()
|
||||||
|
|
||||||
def validate_packed_qty(self):
|
def validate_packed_qty(self):
|
||||||
"""
|
"""
|
||||||
Validate that if packed qty exists, it should be equal to qty
|
Validate that if packed qty exists, it should be equal to qty
|
||||||
@ -232,26 +263,6 @@ class DocType(SellingController):
|
|||||||
+ ", Packed: " + cstr(d[2])) for d in packing_error_list])
|
+ ", Packed: " + cstr(d[2])) for d in packing_error_list])
|
||||||
webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1)
|
webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1)
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
sales_com_obj = get_obj(dt = 'Sales Common')
|
|
||||||
sales_com_obj.check_stop_sales_order(self)
|
|
||||||
self.check_next_docstatus()
|
|
||||||
|
|
||||||
# remove delivery details from serial no
|
|
||||||
sl = get_obj('Stock Ledger')
|
|
||||||
sl.update_serial_record(self, 'delivery_note_details', is_submit = 0, is_incoming = 0)
|
|
||||||
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
|
|
||||||
|
|
||||||
self.update_prevdoc_status()
|
|
||||||
|
|
||||||
self.update_stock_ledger(update_stock = -1)
|
|
||||||
webnotes.conn.set(self.doc, 'status', 'Cancelled')
|
|
||||||
self.cancel_packing_slips()
|
|
||||||
|
|
||||||
self.make_cancel_gl_entries()
|
|
||||||
|
|
||||||
|
|
||||||
def check_next_docstatus(self):
|
def check_next_docstatus(self):
|
||||||
submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name))
|
submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name))
|
||||||
if submit_rv:
|
if submit_rv:
|
||||||
@ -263,7 +274,6 @@ class DocType(SellingController):
|
|||||||
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !")
|
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !")
|
||||||
raise Exception , "Validation Error."
|
raise Exception , "Validation Error."
|
||||||
|
|
||||||
|
|
||||||
def cancel_packing_slips(self):
|
def cancel_packing_slips(self):
|
||||||
"""
|
"""
|
||||||
Cancel submitted packing slips related to this delivery note
|
Cancel submitted packing slips related to this delivery note
|
||||||
|
@ -97,6 +97,59 @@ class TestDeliveryNote(unittest.TestCase):
|
|||||||
|
|
||||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||||
|
|
||||||
|
def test_serialized(self):
|
||||||
|
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||||
|
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||||
|
|
||||||
|
dn = webnotes.bean(copy=test_records[0])
|
||||||
|
dn.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
dn.doclist[1].qty = 1
|
||||||
|
dn.doclist[1].serial_no = serial_nos[0]
|
||||||
|
dn.insert()
|
||||||
|
dn.submit()
|
||||||
|
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
|
||||||
|
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||||
|
"delivery_document_no"), dn.doc.name)
|
||||||
|
|
||||||
|
return dn
|
||||||
|
|
||||||
|
def test_serialized_cancel(self):
|
||||||
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||||
|
dn = self.test_serialized()
|
||||||
|
dn.cancel()
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(dn.doclist[1].serial_no)
|
||||||
|
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
|
||||||
|
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||||
|
"delivery_document_no"))
|
||||||
|
|
||||||
|
def test_serialize_status(self):
|
||||||
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
|
||||||
|
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||||
|
|
||||||
|
sr = webnotes.bean("Serial No", serial_nos[0])
|
||||||
|
sr.doc.status = "Not Available"
|
||||||
|
sr.save()
|
||||||
|
|
||||||
|
dn = webnotes.bean(copy=test_records[0])
|
||||||
|
dn.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
dn.doclist[1].qty = 1
|
||||||
|
dn.doclist[1].serial_no = serial_nos[0]
|
||||||
|
dn.insert()
|
||||||
|
|
||||||
|
self.assertRaises(SerialNoStatusError, dn.submit)
|
||||||
|
|
||||||
|
|
||||||
test_records = [
|
test_records = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"creation": "2013-05-03 10:45:46",
|
"creation": "2013-05-03 10:45:46",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"modified": "2013-08-08 14:22:25",
|
"modified": "2013-08-14 11:46:49",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator"
|
"owner": "Administrator"
|
||||||
},
|
},
|
||||||
@ -300,6 +300,14 @@
|
|||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.has_serial_no===\"Yes\"",
|
||||||
|
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"fieldname": "serial_no_series",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Serial Number Series"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.is_stock_item==\"Yes\"",
|
"depends_on": "eval:doc.is_stock_item==\"Yes\"",
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
|
@ -194,4 +194,25 @@ test_records = [
|
|||||||
"is_sub_contracted_item": "No",
|
"is_sub_contracted_item": "No",
|
||||||
"stock_uom": "_Test UOM"
|
"stock_uom": "_Test UOM"
|
||||||
}],
|
}],
|
||||||
|
[{
|
||||||
|
"doctype": "Item",
|
||||||
|
"item_code": "_Test Serialized Item With Series",
|
||||||
|
"item_name": "_Test Serialized Item With Series",
|
||||||
|
"description": "_Test Serialized Item",
|
||||||
|
"item_group": "_Test Item Group Desktops",
|
||||||
|
"is_stock_item": "Yes",
|
||||||
|
"default_warehouse": "_Test Warehouse - _TC",
|
||||||
|
"is_asset_item": "No",
|
||||||
|
"has_batch_no": "No",
|
||||||
|
"has_serial_no": "Yes",
|
||||||
|
"serial_no_series": "ABCD.#####",
|
||||||
|
"is_purchase_item": "Yes",
|
||||||
|
"is_sales_item": "Yes",
|
||||||
|
"is_service_item": "No",
|
||||||
|
"is_sample_item": "No",
|
||||||
|
"inspection_required": "No",
|
||||||
|
"is_pro_applicable": "No",
|
||||||
|
"is_sub_contracted_item": "No",
|
||||||
|
"stock_uom": "_Test UOM"
|
||||||
|
}],
|
||||||
]
|
]
|
@ -127,8 +127,6 @@ class DocType(BuyingController):
|
|||||||
self.validate_inspection()
|
self.validate_inspection()
|
||||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||||
|
|
||||||
get_obj('Stock Ledger').validate_serial_no(self, 'purchase_receipt_details')
|
|
||||||
self.validate_challan_no()
|
self.validate_challan_no()
|
||||||
|
|
||||||
pc_obj = get_obj(dt='Purchase Common')
|
pc_obj = get_obj(dt='Purchase Common')
|
||||||
@ -147,16 +145,6 @@ class DocType(BuyingController):
|
|||||||
for d in getlist(self.doclist,'purchase_receipt_details'):
|
for d in getlist(self.doclist,'purchase_receipt_details'):
|
||||||
d.rejected_warehouse = self.doc.rejected_warehouse
|
d.rejected_warehouse = self.doc.rejected_warehouse
|
||||||
|
|
||||||
get_obj('Stock Ledger').scrub_serial_nos(self)
|
|
||||||
self.scrub_rejected_serial_nos()
|
|
||||||
|
|
||||||
|
|
||||||
def scrub_rejected_serial_nos(self):
|
|
||||||
for d in getlist(self.doclist, 'purchase_receipt_details'):
|
|
||||||
if d.rejected_serial_no:
|
|
||||||
d.rejected_serial_no = cstr(d.rejected_serial_no).strip().replace(',', '\n')
|
|
||||||
d.save()
|
|
||||||
|
|
||||||
def update_stock(self, is_submit):
|
def update_stock(self, is_submit):
|
||||||
pc_obj = get_obj('Purchase Common')
|
pc_obj = get_obj('Purchase Common')
|
||||||
self.values = []
|
self.values = []
|
||||||
@ -207,11 +195,6 @@ class DocType(BuyingController):
|
|||||||
|
|
||||||
# make Stock Entry
|
# make Stock Entry
|
||||||
def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0):
|
def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0):
|
||||||
if rejected:
|
|
||||||
serial_no = cstr(d.rejected_serial_no).strip()
|
|
||||||
else:
|
|
||||||
serial_no = cstr(d.serial_no).strip()
|
|
||||||
|
|
||||||
self.values.append({
|
self.values.append({
|
||||||
'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code,
|
'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code,
|
||||||
'warehouse' : wh,
|
'warehouse' : wh,
|
||||||
@ -227,7 +210,7 @@ class DocType(BuyingController):
|
|||||||
'fiscal_year' : self.doc.fiscal_year,
|
'fiscal_year' : self.doc.fiscal_year,
|
||||||
'is_cancelled' : (is_submit==1) and 'No' or 'Yes',
|
'is_cancelled' : (is_submit==1) and 'No' or 'Yes',
|
||||||
'batch_no' : cstr(d.batch_no).strip(),
|
'batch_no' : cstr(d.batch_no).strip(),
|
||||||
'serial_no' : serial_no,
|
'serial_no' : d.serial_no,
|
||||||
"project" : d.project_name
|
"project" : d.project_name
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -261,17 +244,31 @@ class DocType(BuyingController):
|
|||||||
|
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
|
||||||
# Update Serial Record
|
|
||||||
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 1, is_incoming = 1)
|
|
||||||
|
|
||||||
# Update Stock
|
# Update Stock
|
||||||
self.update_stock(is_submit = 1)
|
self.update_stock(is_submit = 1)
|
||||||
|
|
||||||
|
self.update_serial_nos()
|
||||||
|
|
||||||
# Update last purchase rate
|
# Update last purchase rate
|
||||||
purchase_controller.update_last_purchase_rate(self, 1)
|
purchase_controller.update_last_purchase_rate(self, 1)
|
||||||
|
|
||||||
self.make_gl_entries()
|
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):
|
def check_next_docstatus(self):
|
||||||
submit_rv = 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))
|
submit_rv = 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))
|
||||||
if submit_rv:
|
if submit_rv:
|
||||||
@ -295,10 +292,10 @@ class DocType(BuyingController):
|
|||||||
webnotes.conn.set(self.doc,'status','Cancelled')
|
webnotes.conn.set(self.doc,'status','Cancelled')
|
||||||
|
|
||||||
# 3. Cancel Serial No
|
# 3. Cancel Serial No
|
||||||
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 0, is_incoming = 1)
|
|
||||||
|
|
||||||
# 4.Update Bin
|
# 4.Update Bin
|
||||||
self.update_stock(is_submit = 0)
|
self.update_stock(is_submit = 0)
|
||||||
|
self.update_serial_nos(cancel=True)
|
||||||
|
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
|
||||||
|
@ -77,6 +77,29 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0)
|
self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0)
|
||||||
self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2)
|
self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2)
|
||||||
|
|
||||||
|
def test_serial_no_supplier(self):
|
||||||
|
pr = webnotes.bean(copy=test_records[0])
|
||||||
|
pr.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
pr.doclist[1].qty = 1
|
||||||
|
pr.doclist[1].received_qty = 1
|
||||||
|
pr.insert()
|
||||||
|
pr.submit()
|
||||||
|
self.assertEquals(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
|
||||||
|
"supplier"), pr.doc.supplier)
|
||||||
|
|
||||||
|
return pr
|
||||||
|
|
||||||
|
def test_serial_no_cancel(self):
|
||||||
|
pr = self.test_serial_no_supplier()
|
||||||
|
pr.cancel()
|
||||||
|
|
||||||
|
self.assertFalse(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
|
||||||
|
"warehouse"))
|
||||||
|
|
||||||
|
self.assertEqual(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
|
||||||
|
"status"), "Not Available")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test_dependencies = ["BOM"]
|
test_dependencies = ["BOM"]
|
||||||
|
|
||||||
|
@ -1,69 +1,10 @@
|
|||||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
cur_frm.add_fetch("customer", "customer_name", "customer_name")
|
||||||
if(!doc.status) set_multiple(cdt, cdn, {status:'In Store'});
|
cur_frm.add_fetch("supplier", "supplier_name", "supplier_name")
|
||||||
if(doc.__islocal) hide_field(['supplier_name','address_display'])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
cur_frm.add_fetch("item_code", "item_name", "item_name")
|
||||||
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
cur_frm.add_fetch("item_code", "description", "description")
|
||||||
flds = ['status', 'item_code', 'warehouse', 'purchase_document_type',
|
cur_frm.add_fetch("item_code", "item_group", "item_group")
|
||||||
'purchase_document_no', 'purchase_date', 'purchase_time', 'purchase_rate',
|
cur_frm.add_fetch("item_code", "brand", "brand")
|
||||||
'supplier']
|
|
||||||
for(i=0;i<flds.length;i++) {
|
|
||||||
cur_frm.set_df_property(flds[i], 'read_only', doc.__islocal ? 0 : 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// item details
|
|
||||||
// -------------
|
|
||||||
cur_frm.add_fetch('item_code', 'item_name', 'item_name')
|
|
||||||
cur_frm.add_fetch('item_code', 'item_group', 'item_group')
|
|
||||||
cur_frm.add_fetch('item_code', 'brand', 'brand')
|
|
||||||
cur_frm.add_fetch('item_code', 'description', 'description')
|
|
||||||
cur_frm.add_fetch('item_code', 'warranty_period', 'warranty_period')
|
|
||||||
|
|
||||||
// customer
|
|
||||||
// ---------
|
|
||||||
cur_frm.add_fetch('customer', 'customer_name', 'customer_name')
|
|
||||||
cur_frm.add_fetch('customer', 'address', 'delivery_address')
|
|
||||||
cur_frm.add_fetch('customer', 'territory', 'territory')
|
|
||||||
|
|
||||||
// territory
|
|
||||||
// ----------
|
|
||||||
cur_frm.fields_dict['territory'].get_query = function(doc,cdt,cdn) {
|
|
||||||
return{
|
|
||||||
filters:{'is_group': "No"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supplier
|
|
||||||
//-------------
|
|
||||||
cur_frm.cscript.supplier = function(doc,dt,dn) {
|
|
||||||
if(doc.supplier) return get_server_fields('get_default_supplier_address', JSON.stringify({supplier: doc.supplier}),'', doc, dt, dn, 1);
|
|
||||||
if(doc.supplier) unhide_field(['supplier_name','address_display']);
|
|
||||||
}
|
|
||||||
|
|
||||||
//item code
|
|
||||||
//----------
|
|
||||||
cur_frm.fields_dict['item_code'].get_query = function(doc,cdt,cdn) {
|
|
||||||
return{
|
|
||||||
query:"controllers.queries.item_query",
|
|
||||||
filters:{
|
|
||||||
'has_serial_no': 'Yes'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) {
|
|
||||||
return{
|
|
||||||
query:"controllers.queries.customer_query"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cur_frm.fields_dict.supplier.get_query = function(doc,cdt,cdn) {
|
|
||||||
return{
|
|
||||||
query:"controllers.queries.supplier_query"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -10,10 +10,24 @@ from webnotes import msgprint, _
|
|||||||
|
|
||||||
from controllers.stock_controller import StockController
|
from controllers.stock_controller import StockController
|
||||||
|
|
||||||
|
class SerialNoCannotCreateDirectError(webnotes.ValidationError): pass
|
||||||
|
class SerialNoCannotCannotChangeError(webnotes.ValidationError): pass
|
||||||
|
|
||||||
class DocType(StockController):
|
class DocType(StockController):
|
||||||
def __init__(self, doc, doclist=[]):
|
def __init__(self, doc, doclist=[]):
|
||||||
self.doc = doc
|
self.doc = doc
|
||||||
self.doclist = doclist
|
self.doclist = doclist
|
||||||
|
self.via_stock_ledger = False
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
self.validate_warranty_status()
|
||||||
|
self.validate_amc_status()
|
||||||
|
self.validate_warehouse()
|
||||||
|
self.validate_item()
|
||||||
|
|
||||||
def validate_amc_status(self):
|
def validate_amc_status(self):
|
||||||
"""
|
"""
|
||||||
@ -31,79 +45,41 @@ class DocType(StockController):
|
|||||||
|
|
||||||
|
|
||||||
def validate_warehouse(self):
|
def validate_warehouse(self):
|
||||||
if self.doc.status=='In Store' and not self.doc.warehouse:
|
if not self.doc.fields.get("__islocal"):
|
||||||
msgprint("Warehouse is mandatory if this Serial No is <b>In Store</b>", raise_exception=1)
|
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)
|
||||||
|
if not self.via_stock_ledger and warehouse != self.doc.warehouse:
|
||||||
|
webnotes.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
|
||||||
|
|
||||||
|
if not self.doc.warehouse and self.doc.status=="Available":
|
||||||
|
self.doc.status = "Not Available"
|
||||||
|
|
||||||
def validate_item(self):
|
def validate_item(self):
|
||||||
"""
|
"""
|
||||||
Validate whether serial no is required for this item
|
Validate whether serial no is required for this item
|
||||||
"""
|
"""
|
||||||
item = webnotes.conn.sql("select name, has_serial_no from tabItem where name = '%s'" % self.doc.item_code)
|
item = webnotes.doc("Item", self.doc.item_code)
|
||||||
if not item:
|
if item.has_serial_no!="Yes":
|
||||||
msgprint("Item is not exists in the system", raise_exception=1)
|
webnotes.throw(_("Item must have 'Has Serial No' as 'Yes'") + ": " + self.doc.item_code)
|
||||||
elif item[0][1] == 'No':
|
|
||||||
msgprint("To proceed please select 'Yes' in 'Has Serial No' in Item master: '%s'" % self.doc.item_code, raise_exception=1)
|
|
||||||
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
self.validate_warranty_status()
|
|
||||||
self.validate_amc_status()
|
|
||||||
self.validate_warehouse()
|
|
||||||
self.validate_item()
|
|
||||||
|
|
||||||
def on_update(self):
|
|
||||||
if self.doc.warehouse and self.doc.status == 'In Store' \
|
|
||||||
and cint(self.doc.sle_exists) == 0 and \
|
|
||||||
not webnotes.conn.sql("""select name from `tabStock Ledger Entry`
|
|
||||||
where serial_no = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name):
|
|
||||||
self.make_stock_ledger_entry(1)
|
|
||||||
webnotes.conn.set(self.doc, 'sle_exists', 1)
|
|
||||||
|
|
||||||
self.make_gl_entries()
|
|
||||||
|
|
||||||
def make_stock_ledger_entry(self, qty):
|
|
||||||
from webnotes.model.code import get_obj
|
|
||||||
values = [{
|
|
||||||
'item_code' : self.doc.item_code,
|
|
||||||
'warehouse' : self.doc.warehouse,
|
|
||||||
'posting_date' : self.doc.purchase_date or (self.doc.creation and self.doc.creation.split(' ')[0]) or nowdate(),
|
|
||||||
'posting_time' : self.doc.purchase_time or '00:00',
|
|
||||||
'voucher_type' : 'Serial No',
|
|
||||||
'voucher_no' : self.doc.name,
|
|
||||||
'voucher_detail_no' : '',
|
|
||||||
'actual_qty' : qty,
|
|
||||||
'stock_uom' : webnotes.conn.get_value('Item', self.doc.item_code, 'stock_uom'),
|
|
||||||
'incoming_rate' : self.doc.purchase_rate,
|
|
||||||
'company' : self.doc.company,
|
|
||||||
'fiscal_year' : self.doc.fiscal_year,
|
|
||||||
'is_cancelled' : 'No', # is_cancelled is always 'No' because while deleted it can not find creation entry if it not created directly, voucher no != serial no
|
|
||||||
'batch_no' : '',
|
|
||||||
'serial_no' : self.doc.name
|
|
||||||
}]
|
|
||||||
get_obj('Stock Ledger').update_stock(values)
|
|
||||||
|
|
||||||
|
self.doc.item_group = item.item_group
|
||||||
|
self.doc.description = item.description
|
||||||
|
self.doc.item_name = item.item_name
|
||||||
|
self.doc.brand = item.brand
|
||||||
|
self.doc.warranty_period = item.warranty_period
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
if self.doc.status == 'Delivered':
|
if self.doc.status == 'Delivered':
|
||||||
msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1)
|
msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1)
|
||||||
elif self.doc.status == 'In Store':
|
if self.doc.warehouse:
|
||||||
webnotes.conn.set(self.doc, 'status', 'Not in Use')
|
webnotes.throw(_("Cannot delete Serial No in warehouse. First remove from warehouse, then delete.") + \
|
||||||
self.make_stock_ledger_entry(-1)
|
": " + self.doc.name)
|
||||||
|
|
||||||
if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) \
|
|
||||||
and webnotes.conn.sql("""select name from `tabGL Entry`
|
|
||||||
where voucher_type=%s and voucher_no=%s and ifnull(is_cancelled, 'No')='No'""",
|
|
||||||
(self.doc.doctype, self.doc.name)):
|
|
||||||
self.make_gl_entries(cancel=True)
|
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.on_trash()
|
self.on_trash()
|
||||||
|
|
||||||
def on_restore(self):
|
|
||||||
self.make_stock_ledger_entry(1)
|
|
||||||
self.make_gl_entries()
|
|
||||||
|
|
||||||
def on_rename(self, new, old, merge=False):
|
def on_rename(self, new, old, merge=False):
|
||||||
"""rename serial_no text fields"""
|
"""rename serial_no text fields"""
|
||||||
if merge:
|
if merge:
|
||||||
@ -119,18 +95,3 @@ class DocType(StockController):
|
|||||||
webnotes.conn.sql("""update `tab%s` set serial_no = %s
|
webnotes.conn.sql("""update `tab%s` set serial_no = %s
|
||||||
where name=%s""" % (dt[0], '%s', '%s'),
|
where name=%s""" % (dt[0], '%s', '%s'),
|
||||||
('\n'.join(serial_nos), item[0]))
|
('\n'.join(serial_nos), item[0]))
|
||||||
|
|
||||||
def make_gl_entries(self, cancel=False):
|
|
||||||
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
|
|
||||||
return
|
|
||||||
|
|
||||||
from accounts.general_ledger import make_gl_entries
|
|
||||||
against_stock_account = self.get_company_default("stock_adjustment_account")
|
|
||||||
gl_entries = self.get_gl_entries_for_stock(against_stock_account, self.doc.purchase_rate)
|
|
||||||
|
|
||||||
for entry in gl_entries:
|
|
||||||
entry["posting_date"] = self.doc.purchase_date or (self.doc.creation and
|
|
||||||
self.doc.creation.split(' ')[0]) or nowdate()
|
|
||||||
|
|
||||||
if gl_entries:
|
|
||||||
make_gl_entries(gl_entries, cancel)
|
|
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"creation": "2013-05-16 10:59:15",
|
"creation": "2013-05-16 10:59:15",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"modified": "2013-07-22 15:29:43",
|
"modified": "2013-08-16 10:16:00",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator"
|
"owner": "Administrator"
|
||||||
},
|
},
|
||||||
@ -14,6 +14,7 @@
|
|||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Master",
|
"document_type": "Master",
|
||||||
"icon": "icon-barcode",
|
"icon": "icon-barcode",
|
||||||
|
"in_create": 0,
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "__common__",
|
"name": "__common__",
|
||||||
"search_fields": "item_code,status"
|
"search_fields": "item_code,status"
|
||||||
@ -57,6 +58,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "In Store",
|
"default": "In Store",
|
||||||
|
"description": "Only Serial Nos with status \"Available\" can be delivered.",
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@ -66,8 +68,8 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "\nIn Store\nDelivered\nNot in Use\nPurchase Returned",
|
"options": "\nAvailable\nNot Available\nDelivered\nPurchase Returned\nSales Returned",
|
||||||
"read_only": 1,
|
"read_only": 0,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@ -98,6 +100,22 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Warehouse can only be changed via Stock Entry / Delivery Note / Purchase Receipt",
|
||||||
|
"doctype": "DocField",
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_filter": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Warehouse",
|
||||||
|
"no_copy": 1,
|
||||||
|
"oldfieldname": "warehouse",
|
||||||
|
"oldfieldtype": "Link",
|
||||||
|
"options": "Warehouse",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "column_break1",
|
"fieldname": "column_break1",
|
||||||
@ -134,7 +152,7 @@
|
|||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Item Group",
|
"options": "Item Group",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 0,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -154,7 +172,7 @@
|
|||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "purchase_details",
|
"fieldname": "purchase_details",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Purchase Details",
|
"label": "Purchase / Manufacture Details",
|
||||||
"read_only": 0
|
"read_only": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -168,30 +186,30 @@
|
|||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "purchase_document_type",
|
"fieldname": "purchase_document_type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Purchase Document Type",
|
"label": "Creation Document Type",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "\nPurchase Receipt\nStock Entry",
|
"options": "\nPurchase Receipt\nStock Entry",
|
||||||
"read_only": 0
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "purchase_document_no",
|
"fieldname": "purchase_document_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Purchase Document No",
|
"label": "Creation Document No",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 0
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "purchase_date",
|
"fieldname": "purchase_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"label": "Purchase Date",
|
"label": "Creation Date",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "purchase_date",
|
"oldfieldname": "purchase_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"read_only": 0,
|
"read_only": 1,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
@ -199,10 +217,10 @@
|
|||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "purchase_time",
|
"fieldname": "purchase_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"label": "Incoming Time",
|
"label": "Creation Time",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 0,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
@ -214,8 +232,8 @@
|
|||||||
"oldfieldname": "purchase_rate",
|
"oldfieldname": "purchase_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 0,
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 0,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -225,21 +243,6 @@
|
|||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"width": "50%"
|
"width": "50%"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "warehouse",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_filter": 1,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Warehouse",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "warehouse",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Warehouse",
|
|
||||||
"read_only": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "supplier",
|
"fieldname": "supplier",
|
||||||
@ -259,14 +262,6 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "address_display",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"label": "Supplier Address",
|
|
||||||
"no_copy": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "delivery_details",
|
"fieldname": "delivery_details",
|
||||||
@ -275,13 +270,6 @@
|
|||||||
"oldfieldtype": "Column Break",
|
"oldfieldtype": "Column Break",
|
||||||
"read_only": 0
|
"read_only": 0
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "column_break4",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"read_only": 0,
|
|
||||||
"width": "50%"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "delivery_document_type",
|
"fieldname": "delivery_document_type",
|
||||||
@ -301,15 +289,6 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "customer_address",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"label": "Customer Address",
|
|
||||||
"oldfieldname": "customer_address",
|
|
||||||
"oldfieldtype": "Text",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "delivery_date",
|
"fieldname": "delivery_date",
|
||||||
@ -359,7 +338,7 @@
|
|||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Customer",
|
"options": "Customer",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 0,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -374,28 +353,6 @@
|
|||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "delivery_address",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"label": "Delivery Address",
|
|
||||||
"no_copy": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "territory",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_filter": 1,
|
|
||||||
"label": "Territory",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "territory",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Territory",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"fieldname": "warranty_amc_details",
|
"fieldname": "warranty_amc_details",
|
||||||
@ -485,7 +442,7 @@
|
|||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "link:Company",
|
"options": "link:Company",
|
||||||
"read_only": 0,
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@ -500,26 +457,6 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "trash_reason",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Trash Reason",
|
|
||||||
"oldfieldname": "trash_reason",
|
|
||||||
"oldfieldtype": "Small Text",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "DocField",
|
|
||||||
"fieldname": "sle_exists",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "SLE Exists",
|
|
||||||
"no_copy": 1,
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"cancel": 1,
|
"cancel": 1,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
|
@ -7,99 +7,23 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import webnotes, unittest
|
import webnotes, unittest
|
||||||
|
|
||||||
class TestSerialNo(unittest.TestCase):
|
|
||||||
def test_aii_gl_entries_for_serial_no_in_store(self):
|
|
||||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
|
||||||
|
|
||||||
sr = webnotes.bean(copy=test_records[0])
|
|
||||||
sr.doc.serial_no = "_Test Serial No 1"
|
|
||||||
sr.insert()
|
|
||||||
|
|
||||||
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
|
|
||||||
"stock_in_hand_account")
|
|
||||||
against_stock_account = webnotes.conn.get_value("Company", "_Test Company",
|
|
||||||
"stock_adjustment_account")
|
|
||||||
|
|
||||||
# check stock ledger entries
|
|
||||||
sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry`
|
|
||||||
where voucher_type = 'Serial No' and voucher_no = %s""", sr.doc.name, as_dict=1)[0]
|
|
||||||
self.assertTrue(sle)
|
|
||||||
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
|
|
||||||
["_Test Serialized Item", "_Test Warehouse - _TC", 1.0])
|
|
||||||
|
|
||||||
# check gl entries
|
|
||||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
|
||||||
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
|
|
||||||
order by account desc""", sr.doc.name, as_dict=1)
|
|
||||||
self.assertTrue(gl_entries)
|
|
||||||
|
|
||||||
expected_values = [
|
|
||||||
[stock_in_hand_account, 1000.0, 0.0],
|
|
||||||
[against_stock_account, 0.0, 1000.0]
|
|
||||||
]
|
|
||||||
|
|
||||||
for i, gle in enumerate(gl_entries):
|
|
||||||
self.assertEquals(expected_values[i][0], gle.account)
|
|
||||||
self.assertEquals(expected_values[i][1], gle.debit)
|
|
||||||
self.assertEquals(expected_values[i][2], gle.credit)
|
|
||||||
|
|
||||||
sr.load_from_db()
|
|
||||||
self.assertEquals(sr.doc.sle_exists, 1)
|
|
||||||
|
|
||||||
# save again
|
|
||||||
sr.save()
|
|
||||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
|
||||||
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
|
|
||||||
order by account desc""", sr.doc.name, as_dict=1)
|
|
||||||
|
|
||||||
for i, gle in enumerate(gl_entries):
|
|
||||||
self.assertEquals(expected_values[i][0], gle.account)
|
|
||||||
self.assertEquals(expected_values[i][1], gle.debit)
|
|
||||||
self.assertEquals(expected_values[i][2], gle.credit)
|
|
||||||
|
|
||||||
# trash/cancel
|
|
||||||
sr.submit()
|
|
||||||
sr.cancel()
|
|
||||||
|
|
||||||
gl_count = webnotes.conn.sql("""select count(name) from `tabGL Entry`
|
|
||||||
where voucher_type='Serial No' and voucher_no=%s and ifnull(is_cancelled, 'No') = 'Yes'
|
|
||||||
order by account asc, name asc""", sr.doc.name)
|
|
||||||
|
|
||||||
self.assertEquals(gl_count[0][0], 4)
|
|
||||||
|
|
||||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
|
||||||
|
|
||||||
|
|
||||||
def test_aii_gl_entries_for_serial_no_delivered(self):
|
|
||||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
|
||||||
|
|
||||||
sr = webnotes.bean(copy=test_records[0])
|
|
||||||
sr.doc.serial_no = "_Test Serial No 2"
|
|
||||||
sr.doc.status = "Delivered"
|
|
||||||
sr.insert()
|
|
||||||
|
|
||||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
|
||||||
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
|
|
||||||
order by account desc""", sr.doc.name, as_dict=1)
|
|
||||||
self.assertFalse(gl_entries)
|
|
||||||
|
|
||||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
test_dependencies = ["Item"]
|
||||||
test_records = [
|
test_records = []
|
||||||
[
|
|
||||||
{
|
from stock.doctype.serial_no.serial_no import *
|
||||||
"company": "_Test Company",
|
|
||||||
"doctype": "Serial No",
|
class TestSerialNo(unittest.TestCase):
|
||||||
"serial_no": "_Test Serial No",
|
def test_cannot_create_direct(self):
|
||||||
"status": "In Store",
|
sr = webnotes.new_bean("Serial No")
|
||||||
"item_code": "_Test Serialized Item",
|
sr.doc.item_code = "_Test Serialized Item"
|
||||||
"item_group": "_Test Item Group",
|
sr.doc.warehouse = "_Test Warehouse - _TC"
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
sr.doc.serial_no = "_TCSER0001"
|
||||||
"purchase_rate": 1000.0,
|
sr.doc.purchase_rate = 10
|
||||||
"purchase_time": "11:37:39",
|
self.assertRaises(SerialNoCannotCreateDirectError, sr.insert)
|
||||||
"purchase_date": "2013-02-26",
|
|
||||||
'fiscal_year': "_Test Fiscal Year 2013"
|
sr.doc.warehouse = None
|
||||||
}
|
sr.insert()
|
||||||
]
|
self.assertTrue(sr.doc.name)
|
||||||
]
|
|
||||||
|
sr.doc.warehouse = "_Test Warehouse - _TC"
|
||||||
|
self.assertTrue(SerialNoCannotCannotChangeError, sr.doc.save)
|
@ -222,6 +222,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
|||||||
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
||||||
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mtn_details_on_form_rendered: function(doc, grid_row) {
|
||||||
|
erpnext.setup_serial_no(grid_row)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cur_frm.script_manager.make(erpnext.stock.StockEntry);
|
cur_frm.script_manager.make(erpnext.stock.StockEntry);
|
||||||
|
@ -32,7 +32,6 @@ class DocType(StockController):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_posting_time()
|
self.validate_posting_time()
|
||||||
self.validate_purpose()
|
self.validate_purpose()
|
||||||
self.validate_serial_nos()
|
|
||||||
pro_obj = self.doc.production_order and \
|
pro_obj = self.doc.production_order and \
|
||||||
get_obj('Production Order', self.doc.production_order) or None
|
get_obj('Production Order', self.doc.production_order) or None
|
||||||
|
|
||||||
@ -52,14 +51,14 @@ class DocType(StockController):
|
|||||||
self.set_total_amount()
|
self.set_total_amount()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_serial_no(1)
|
|
||||||
self.update_stock_ledger(0)
|
self.update_stock_ledger(0)
|
||||||
|
self.update_serial_no(1)
|
||||||
self.update_production_order(1)
|
self.update_production_order(1)
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_serial_no(0)
|
|
||||||
self.update_stock_ledger(1)
|
self.update_stock_ledger(1)
|
||||||
|
self.update_serial_no(0)
|
||||||
self.update_production_order(0)
|
self.update_production_order(0)
|
||||||
self.make_cancel_gl_entries()
|
self.make_cancel_gl_entries()
|
||||||
|
|
||||||
@ -75,11 +74,6 @@ class DocType(StockController):
|
|||||||
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
|
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
|
||||||
raise_exception=True)
|
raise_exception=True)
|
||||||
|
|
||||||
def validate_serial_nos(self):
|
|
||||||
sl_obj = get_obj("Stock Ledger")
|
|
||||||
sl_obj.scrub_serial_nos(self)
|
|
||||||
sl_obj.validate_serial_no(self, 'mtn_details')
|
|
||||||
|
|
||||||
def validate_item(self):
|
def validate_item(self):
|
||||||
for item in self.doclist.get({"parentfield": "mtn_details"}):
|
for item in self.doclist.get({"parentfield": "mtn_details"}):
|
||||||
if item.item_code not in self.stock_items:
|
if item.item_code not in self.stock_items:
|
||||||
@ -206,7 +200,7 @@ class DocType(StockController):
|
|||||||
"posting_date": self.doc.posting_date,
|
"posting_date": self.doc.posting_date,
|
||||||
"posting_time": self.doc.posting_time,
|
"posting_time": self.doc.posting_time,
|
||||||
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
|
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
|
||||||
"serial_no": cstr(d.serial_no).strip(),
|
"serial_no": d.serial_no,
|
||||||
"bom_no": d.bom_no,
|
"bom_no": d.bom_no,
|
||||||
})
|
})
|
||||||
# get actual stock at source warehouse
|
# get actual stock at source warehouse
|
||||||
@ -317,27 +311,21 @@ class DocType(StockController):
|
|||||||
|
|
||||||
def update_serial_no(self, is_submit):
|
def update_serial_no(self, is_submit):
|
||||||
"""Create / Update Serial No"""
|
"""Create / Update Serial No"""
|
||||||
from stock.utils import get_valid_serial_nos
|
|
||||||
|
|
||||||
sl_obj = get_obj('Stock Ledger')
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
|
||||||
if is_submit:
|
update_serial_nos_after_submit(self, "Stock Entry", "mtn_details")
|
||||||
sl_obj.validate_serial_no_warehouse(self, 'mtn_details')
|
|
||||||
|
|
||||||
for d in getlist(self.doclist, 'mtn_details'):
|
for d in getlist(self.doclist, 'mtn_details'):
|
||||||
if cstr(d.serial_no).strip():
|
for serial_no in get_serial_nos(d.serial_no):
|
||||||
for x in get_valid_serial_nos(d.serial_no):
|
|
||||||
serial_no = x.strip()
|
|
||||||
if d.s_warehouse:
|
|
||||||
sl_obj.update_serial_delivery_details(self, d, serial_no, is_submit)
|
|
||||||
if d.t_warehouse:
|
|
||||||
sl_obj.update_serial_purchase_details(self, d, serial_no, is_submit,
|
|
||||||
self.doc.purpose)
|
|
||||||
|
|
||||||
if self.doc.purpose == 'Purchase Return':
|
if self.doc.purpose == 'Purchase Return':
|
||||||
serial_doc = Document("Serial No", serial_no)
|
sr = webnotes.bean("Serial No", serial_no)
|
||||||
serial_doc.status = is_submit and 'Purchase Returned' or 'In Store'
|
sr.doc.status = "Purchase Returned" if is_submit else "Available"
|
||||||
serial_doc.docstatus = is_submit and 2 or 0
|
sr.save()
|
||||||
serial_doc.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, is_cancelled=0):
|
def update_stock_ledger(self, is_cancelled=0):
|
||||||
self.values = []
|
self.values = []
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import webnotes, unittest
|
import webnotes, unittest
|
||||||
from webnotes.utils import flt
|
from webnotes.utils import flt
|
||||||
|
from stock.doctype.stock_ledger_entry.stock_ledger_entry import *
|
||||||
|
|
||||||
class TestStockEntry(unittest.TestCase):
|
class TestStockEntry(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@ -180,6 +181,7 @@ class TestStockEntry(unittest.TestCase):
|
|||||||
def _clear_stock(self):
|
def _clear_stock(self):
|
||||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||||
webnotes.conn.sql("""delete from `tabBin`""")
|
webnotes.conn.sql("""delete from `tabBin`""")
|
||||||
|
webnotes.conn.sql("""delete from `tabSerial No`""")
|
||||||
|
|
||||||
self.old_default_company = webnotes.conn.get_default("company")
|
self.old_default_company = webnotes.conn.get_default("company")
|
||||||
webnotes.conn.set_default("company", "_Test Company")
|
webnotes.conn.set_default("company", "_Test Company")
|
||||||
@ -571,12 +573,139 @@ class TestStockEntry(unittest.TestCase):
|
|||||||
|
|
||||||
return se, pr.doc.name
|
return se, pr.doc.name
|
||||||
|
|
||||||
|
def test_serial_no_not_reqd(self):
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doclist[1].serial_no = "ABCD"
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(SerialNoNotRequiredError, se.submit)
|
||||||
|
|
||||||
|
def test_serial_no_reqd(self):
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item"
|
||||||
|
se.doclist[1].qty = 2
|
||||||
|
se.doclist[1].transfer_qty = 2
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(SerialNoRequiredError, se.submit)
|
||||||
|
|
||||||
|
def test_serial_no_qty_more(self):
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item"
|
||||||
|
se.doclist[1].qty = 2
|
||||||
|
se.doclist[1].serial_no = "ABCD\nEFGH\nXYZ"
|
||||||
|
se.doclist[1].transfer_qty = 2
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(SerialNoQtyError, se.submit)
|
||||||
|
|
||||||
|
def test_serial_no_qty_less(self):
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item"
|
||||||
|
se.doclist[1].qty = 2
|
||||||
|
se.doclist[1].serial_no = "ABCD"
|
||||||
|
se.doclist[1].transfer_qty = 2
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(SerialNoQtyError, se.submit)
|
||||||
|
|
||||||
|
def test_serial_no_transfer_in(self):
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item"
|
||||||
|
se.doclist[1].qty = 2
|
||||||
|
se.doclist[1].serial_no = "ABCD\nEFGH"
|
||||||
|
se.doclist[1].transfer_qty = 2
|
||||||
|
se.insert()
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
self.assertTrue(webnotes.conn.exists("Serial No", "ABCD"))
|
||||||
|
self.assertTrue(webnotes.conn.exists("Serial No", "EFGH"))
|
||||||
|
|
||||||
|
def test_serial_no_not_exists(self):
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doc.purpose = "Material Issue"
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item"
|
||||||
|
se.doclist[1].qty = 2
|
||||||
|
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
se.doclist[1].t_warehouse = None
|
||||||
|
se.doclist[1].serial_no = "ABCD\nEFGH"
|
||||||
|
se.doclist[1].transfer_qty = 2
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(SerialNoNotExistsError, se.submit)
|
||||||
|
|
||||||
|
def test_serial_by_series(self):
|
||||||
|
se = make_serialized_item()
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||||
|
|
||||||
|
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[0]))
|
||||||
|
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[1]))
|
||||||
|
|
||||||
|
return se
|
||||||
|
|
||||||
|
def test_serial_item_error(self):
|
||||||
|
self.test_serial_by_series()
|
||||||
|
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doc.purpose = "Material Transfer"
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item"
|
||||||
|
se.doclist[1].qty = 1
|
||||||
|
se.doclist[1].transfer_qty = 1
|
||||||
|
se.doclist[1].serial_no = "ABCD00001"
|
||||||
|
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
|
||||||
|
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(SerialNoItemError, se.submit)
|
||||||
|
|
||||||
|
def test_serial_move(self):
|
||||||
|
se = make_serialized_item()
|
||||||
|
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
|
||||||
|
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doc.purpose = "Material Transfer"
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
se.doclist[1].qty = 1
|
||||||
|
se.doclist[1].transfer_qty = 1
|
||||||
|
se.doclist[1].serial_no = serial_no
|
||||||
|
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
|
||||||
|
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
se.insert()
|
||||||
|
se.submit()
|
||||||
|
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC")
|
||||||
|
|
||||||
|
def test_serial_warehouse_error(self):
|
||||||
|
make_serialized_item()
|
||||||
|
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doc.purpose = "Material Transfer"
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
se.doclist[1].qty = 1
|
||||||
|
se.doclist[1].transfer_qty = 1
|
||||||
|
se.doclist[1].serial_no = "ABCD00001"
|
||||||
|
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
se.doclist[1].t_warehouse = "_Test Warehouse - _TC"
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(SerialNoWarehouseError, se.submit)
|
||||||
|
|
||||||
|
def test_serial_cancel(self):
|
||||||
|
se = self.test_serial_by_series()
|
||||||
|
se.cancel()
|
||||||
|
|
||||||
|
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
|
||||||
|
self.assertFalse(webnotes.conn.get_value("Serial No", serial_no, "warehouse"))
|
||||||
|
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "status"), "Not Available")
|
||||||
|
|
||||||
|
def make_serialized_item():
|
||||||
|
se = webnotes.bean(copy=test_records[0])
|
||||||
|
se.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||||
|
se.doclist[1].qty = 2
|
||||||
|
se.doclist[1].transfer_qty = 2
|
||||||
|
se.insert()
|
||||||
|
se.submit()
|
||||||
|
return se
|
||||||
|
|
||||||
test_records = [
|
test_records = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"doctype": "Stock Entry",
|
"doctype": "Stock Entry",
|
||||||
"posting_date": "2013-01-25",
|
"posting_date": "2013-01-01",
|
||||||
"posting_time": "17:14:24",
|
"posting_time": "17:14:24",
|
||||||
"purpose": "Material Receipt",
|
"purpose": "Material Receipt",
|
||||||
"fiscal_year": "_Test Fiscal Year 2013",
|
"fiscal_year": "_Test Fiscal Year 2013",
|
||||||
|
@ -18,173 +18,9 @@ class DocType:
|
|||||||
self.doc = doc
|
self.doc = doc
|
||||||
self.doclist = doclist
|
self.doclist = doclist
|
||||||
|
|
||||||
|
|
||||||
def scrub_serial_nos(self, obj, table_name = ''):
|
|
||||||
if not table_name:
|
|
||||||
table_name = obj.fname
|
|
||||||
|
|
||||||
for d in getlist(obj.doclist, table_name):
|
|
||||||
if d.serial_no:
|
|
||||||
serial_nos = cstr(d.serial_no).strip().replace(',', '\n').split('\n')
|
|
||||||
d.serial_no = "\n".join(map(lambda x: x.strip(), serial_nos))
|
|
||||||
d.save()
|
|
||||||
|
|
||||||
def validate_serial_no_warehouse(self, obj, fname):
|
|
||||||
for d in getlist(obj.doclist, fname):
|
|
||||||
wh = d.warehouse or d.s_warehouse
|
|
||||||
if cstr(d.serial_no).strip() and wh:
|
|
||||||
serial_nos = get_valid_serial_nos(d.serial_no)
|
|
||||||
for s in serial_nos:
|
|
||||||
s = s.strip()
|
|
||||||
sr_war = webnotes.conn.sql("select warehouse,name from `tabSerial No` where name = '%s'" % (s))
|
|
||||||
if not sr_war:
|
|
||||||
msgprint("Serial No %s does not exists"%s, raise_exception = 1)
|
|
||||||
elif not sr_war[0][0]:
|
|
||||||
msgprint("Warehouse not mentioned in the Serial No <b>%s</b>" % s, raise_exception = 1)
|
|
||||||
elif sr_war[0][0] != wh:
|
|
||||||
msgprint("Serial No : %s for Item : %s doesn't exists in Warehouse : %s" % (s, d.item_code, wh), raise_exception = 1)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_serial_no(self, obj, fname):
|
|
||||||
"""check whether serial no is required"""
|
|
||||||
for d in getlist(obj.doclist, fname):
|
|
||||||
is_stock_item = webnotes.conn.get_value('Item', d.item_code, 'is_stock_item')
|
|
||||||
ar_required = webnotes.conn.get_value('Item', d.item_code, 'has_serial_no')
|
|
||||||
|
|
||||||
# [bug fix] need to strip serial nos of all spaces and new lines for validation
|
|
||||||
serial_no = cstr(d.serial_no).strip()
|
|
||||||
if serial_no:
|
|
||||||
if is_stock_item != 'Yes':
|
|
||||||
msgprint("Serial No is not required for non-stock item: %s" % d.item_code, raise_exception=1)
|
|
||||||
elif ar_required != 'Yes':
|
|
||||||
msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :" + d.item_code + \
|
|
||||||
', otherwise please remove serial no', raise_exception=1)
|
|
||||||
elif ar_required == 'Yes' and not serial_no and d.qty:
|
|
||||||
msgprint("Serial no is mandatory for item: "+ d.item_code, raise_exception = 1)
|
|
||||||
|
|
||||||
# validate rejected serial nos
|
|
||||||
if fname == 'purchase_receipt_details' and flt(d.rejected_qty) > 0 and ar_required == 'Yes' and not d.rejected_serial_no:
|
|
||||||
msgprint("Rejected serial no is mandatory for rejected qty of item: "+ d.item_code, raise_exception = 1)
|
|
||||||
|
|
||||||
|
|
||||||
def set_pur_serial_no_values(self, obj, serial_no, d, s, new_rec, rejected=None):
|
|
||||||
item_details = webnotes.conn.sql("""select item_group, warranty_period
|
|
||||||
from `tabItem` where name = '%s' and (ifnull(end_of_life,'')='' or
|
|
||||||
end_of_life = '0000-00-00' or end_of_life > now()) """ %(d.item_code), as_dict=1)
|
|
||||||
|
|
||||||
s.purchase_document_type = obj.doc.doctype
|
|
||||||
s.purchase_document_no = obj.doc.name
|
|
||||||
s.purchase_date = obj.doc.posting_date
|
|
||||||
s.purchase_time = obj.doc.posting_time
|
|
||||||
s.purchase_rate = d.valuation_rate or d.incoming_rate
|
|
||||||
s.item_code = d.item_code
|
|
||||||
s.item_name = d.item_name
|
|
||||||
s.brand = d.brand
|
|
||||||
s.description = d.description
|
|
||||||
s.item_group = item_details and item_details[0]['item_group'] or ''
|
|
||||||
s.warranty_period = item_details and item_details[0]['warranty_period'] or 0
|
|
||||||
s.supplier = obj.doc.supplier
|
|
||||||
s.supplier_name = obj.doc.supplier_name
|
|
||||||
s.address_display = obj.doc.address_display or obj.doc.supplier_address
|
|
||||||
s.warehouse = rejected and obj.doc.rejected_warehouse \
|
|
||||||
or d.warehouse or d.t_warehouse or ""
|
|
||||||
s.docstatus = 0
|
|
||||||
s.status = 'In Store'
|
|
||||||
s.modified = nowdate()
|
|
||||||
s.modified_by = session['user']
|
|
||||||
s.serial_no = serial_no
|
|
||||||
s.sle_exists = 1
|
|
||||||
s.company = obj.doc.company
|
|
||||||
s.save(new_rec)
|
|
||||||
|
|
||||||
|
|
||||||
def update_serial_purchase_details(self, obj, d, serial_no, is_submit, purpose = '', rejected=None):
|
|
||||||
exists = webnotes.conn.sql("select name, status, docstatus from `tabSerial No` where name = '%s'" % (serial_no))
|
|
||||||
if is_submit:
|
|
||||||
if exists and exists[0][2] != 2 and \
|
|
||||||
purpose not in ['Material Transfer', "Material Receipt", 'Sales Return']:
|
|
||||||
msgprint("Serial No: %s already %s" % (serial_no, exists and exists[0][1]), raise_exception = 1)
|
|
||||||
elif exists:
|
|
||||||
s = Document('Serial No', exists and exists[0][0])
|
|
||||||
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 0, rejected=rejected)
|
|
||||||
else:
|
|
||||||
s = Document('Serial No')
|
|
||||||
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 1, rejected=rejected)
|
|
||||||
else:
|
|
||||||
if exists and exists[0][1] == 'Delivered' and exists[0][2] != 2:
|
|
||||||
msgprint("Serial No: %s is already delivered, you can not cancel the document." % serial_no, raise_exception=1)
|
|
||||||
elif purpose == 'Material Transfer':
|
|
||||||
webnotes.conn.sql("update `tabSerial No` set status = 'In Store', purchase_document_type = '', purchase_document_no = '', warehouse = '%s' where name = '%s'" % (d.s_warehouse, serial_no))
|
|
||||||
elif purpose == 'Sales Return':
|
|
||||||
webnotes.conn.sql("update `tabSerial No` set status = 'Delivered', purchase_document_type = '', purchase_document_no = '' where name = '%s'" % serial_no)
|
|
||||||
else:
|
|
||||||
webnotes.conn.sql("update `tabSerial No` set docstatus = 2, status = 'Not in Use', purchase_document_type = '', purchase_document_no = '', purchase_date = null, purchase_rate = 0, supplier = null, supplier_name = '', address_display = '', warehouse = '' where name = '%s'" % serial_no)
|
|
||||||
|
|
||||||
|
|
||||||
def check_serial_no_exists(self, serial_no, item_code):
|
|
||||||
chk = webnotes.conn.sql("select name, status, docstatus, item_code from `tabSerial No` where name = %s", (serial_no), as_dict=1)
|
|
||||||
if not chk:
|
|
||||||
msgprint("Serial No: %s does not exists in the system" % serial_no, raise_exception=1)
|
|
||||||
elif chk and chk[0]['item_code'] != item_code:
|
|
||||||
msgprint("Serial No: %s not belong to item: %s" % (serial_no, item_code), raise_exception=1)
|
|
||||||
elif chk and chk[0]['docstatus'] == 2:
|
|
||||||
msgprint("Serial No: %s of Item : %s is trashed in the system" % (serial_no, item_code), raise_exception = 1)
|
|
||||||
elif chk and chk[0]['status'] == 'Delivered':
|
|
||||||
msgprint("Serial No: %s of Item : %s is already delivered." % (serial_no, item_code), raise_exception = 1)
|
|
||||||
|
|
||||||
|
|
||||||
def set_delivery_serial_no_values(self, obj, serial_no):
|
|
||||||
s = Document('Serial No', serial_no)
|
|
||||||
s.delivery_document_type = obj.doc.doctype
|
|
||||||
s.delivery_document_no = obj.doc.name
|
|
||||||
s.delivery_date = obj.doc.posting_date
|
|
||||||
s.delivery_time = obj.doc.posting_time
|
|
||||||
s.customer = obj.doc.customer
|
|
||||||
s.customer_name = obj.doc.customer_name
|
|
||||||
s.delivery_address = obj.doc.address_display
|
|
||||||
s.territory = obj.doc.territory
|
|
||||||
s.warranty_expiry_date = cint(s.warranty_period) and \
|
|
||||||
add_days(cstr(obj.doc.posting_date), cint(s.warranty_period)) or s.warranty_expiry_date
|
|
||||||
s.docstatus = 1
|
|
||||||
s.status = 'Delivered'
|
|
||||||
s.modified = nowdate()
|
|
||||||
s.modified_by = session['user']
|
|
||||||
s.save()
|
|
||||||
|
|
||||||
|
|
||||||
def update_serial_delivery_details(self, obj, d, serial_no, is_submit):
|
|
||||||
if is_submit:
|
|
||||||
self.check_serial_no_exists(serial_no, d.item_code)
|
|
||||||
self.set_delivery_serial_no_values(obj, serial_no)
|
|
||||||
else:
|
|
||||||
webnotes.conn.sql("update `tabSerial No` set docstatus = 0, status = 'In Store', delivery_document_type = '', delivery_document_no = '', delivery_date = null, customer = null, customer_name = '', delivery_address = '', territory = null where name = '%s'" % (serial_no))
|
|
||||||
|
|
||||||
|
|
||||||
def update_serial_record(self, obj, fname, is_submit = 1, is_incoming = 0):
|
|
||||||
for d in getlist(obj.doclist, fname):
|
|
||||||
if d.serial_no:
|
|
||||||
serial_nos = get_valid_serial_nos(d.serial_no)
|
|
||||||
for a in serial_nos:
|
|
||||||
serial_no = a.strip()
|
|
||||||
if is_incoming:
|
|
||||||
self.update_serial_purchase_details(obj, d, serial_no, is_submit)
|
|
||||||
else:
|
|
||||||
self.update_serial_delivery_details(obj, d, serial_no, is_submit)
|
|
||||||
|
|
||||||
if fname == 'purchase_receipt_details' and d.rejected_qty and d.rejected_serial_no:
|
|
||||||
serial_nos = get_valid_serial_nos(d.rejected_serial_no)
|
|
||||||
for a in serial_nos:
|
|
||||||
self.update_serial_purchase_details(obj, d, a, is_submit, rejected=True)
|
|
||||||
|
|
||||||
|
|
||||||
def update_stock(self, values, is_amended = 'No'):
|
def update_stock(self, values, is_amended = 'No'):
|
||||||
for v in values:
|
for v in values:
|
||||||
sle_id, valid_serial_nos = '', ''
|
sle_id = ''
|
||||||
# get serial nos
|
|
||||||
if v.get("serial_no", "").strip():
|
|
||||||
valid_serial_nos = get_valid_serial_nos(v["serial_no"],
|
|
||||||
v['actual_qty'], v['item_code'])
|
|
||||||
v["serial_no"] = valid_serial_nos and "\n".join(valid_serial_nos) or ""
|
|
||||||
|
|
||||||
# reverse quantities for cancel
|
# reverse quantities for cancel
|
||||||
if v.get('is_cancelled') == 'Yes':
|
if v.get('is_cancelled') == 'Yes':
|
||||||
|
@ -3,11 +3,21 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import webnotes
|
import webnotes
|
||||||
from webnotes import _, msgprint
|
from webnotes import _, msgprint, ValidationError
|
||||||
from webnotes.utils import cint, flt, getdate
|
from webnotes.utils import cint, flt, getdate, cstr
|
||||||
from webnotes.model.controller import DocListController
|
from webnotes.model.controller import DocListController
|
||||||
|
|
||||||
class InvalidWarehouseCompany(Exception): pass
|
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):
|
class DocType(DocListController):
|
||||||
def __init__(self, doc, doclist=[]):
|
def __init__(self, doc, doclist=[]):
|
||||||
@ -15,6 +25,9 @@ class DocType(DocListController):
|
|||||||
self.doclist = doclist
|
self.doclist = doclist
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
if not hasattr(webnotes, "new_stock_ledger_entries"):
|
||||||
|
webnotes.new_stock_ledger_entries = []
|
||||||
|
webnotes.new_stock_ledger_entries.append(self.doc)
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
self.validate_item()
|
self.validate_item()
|
||||||
self.validate_warehouse_user()
|
self.validate_warehouse_user()
|
||||||
@ -59,7 +72,7 @@ class DocType(DocListController):
|
|||||||
def validate_mandatory(self):
|
def validate_mandatory(self):
|
||||||
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company']
|
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company']
|
||||||
for k in mandatory:
|
for k in mandatory:
|
||||||
if self.doc.fields.get(k)==None:
|
if not self.doc.fields.get(k):
|
||||||
msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1)
|
msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1)
|
||||||
elif k == 'warehouse':
|
elif k == 'warehouse':
|
||||||
if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)):
|
if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)):
|
||||||
@ -67,35 +80,105 @@ class DocType(DocListController):
|
|||||||
|
|
||||||
def validate_item(self):
|
def validate_item(self):
|
||||||
item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus,
|
item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus,
|
||||||
ifnull(is_stock_item, 'No') from tabItem where name=%s""",
|
is_stock_item, has_serial_no, serial_no_series
|
||||||
self.doc.item_code)
|
from tabItem where name=%s""",
|
||||||
|
self.doc.item_code, as_dict=True)[0]
|
||||||
|
|
||||||
# check item exists
|
if item_det.is_stock_item != 'Yes':
|
||||||
if item_det:
|
webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code)
|
||||||
item_det = item_det and item_det[0]
|
|
||||||
else:
|
|
||||||
msgprint("Item: '%s' does not exist in the system. Please check." % self.doc.item_code, raise_exception = 1)
|
|
||||||
|
|
||||||
if item_det[3]!='Yes':
|
|
||||||
webnotes.msgprint("""Item: "%s" is not a Stock Item.""" % self.doc.item_code,
|
|
||||||
raise_exception=1)
|
|
||||||
|
|
||||||
# check if item is trashed
|
|
||||||
if cint(item_det[2])==2:
|
|
||||||
msgprint("Item: '%s' is trashed, cannot make a stock transaction against a trashed item" % self.doc.item_code, raise_exception = 1)
|
|
||||||
|
|
||||||
# check if batch number is required
|
# check if batch number is required
|
||||||
if item_det[1]=='Yes' and self.doc.voucher_type != 'Stock Reconciliation':
|
if item_det.has_batch_no =='Yes' and self.doc.voucher_type != 'Stock Reconciliation':
|
||||||
if not self.doc.batch_no:
|
if not self.doc.batch_no:
|
||||||
msgprint("Batch number is mandatory for Item '%s'" % self.doc.item_code, raise_exception = 1)
|
webnotes.throw("Batch number is mandatory for Item '%s'" % self.doc.item_code)
|
||||||
raise Exception
|
|
||||||
|
|
||||||
# check if batch belongs to item
|
# check if batch belongs to item
|
||||||
if not webnotes.conn.sql("select name from `tabBatch` where item='%s' and name ='%s' and docstatus != 2" % (self.doc.item_code, self.doc.batch_no)):
|
if not webnotes.conn.sql("""select name from `tabBatch`
|
||||||
msgprint("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code), raise_exception = 1)
|
where item='%s' and name ='%s' and docstatus != 2""" % (self.doc.item_code, self.doc.batch_no)):
|
||||||
|
webnotes.throw("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code))
|
||||||
|
|
||||||
|
self.validate_serial_no(item_det)
|
||||||
|
|
||||||
|
def validate_serial_no(self, item_det):
|
||||||
|
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(_("Warehouse does not belong to Item") + \
|
||||||
|
(": %s (%s)" % (self.doc.item_code, serial_no)), 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.status = "Available"
|
||||||
|
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.save()
|
||||||
|
webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name)
|
||||||
|
return sr.doc.name
|
||||||
|
|
||||||
# Nobody can do SL Entries where posting date is before freezing date except authorized person
|
|
||||||
#----------------------------------------------------------------------------------------------
|
|
||||||
def check_stock_frozen_date(self):
|
def check_stock_frozen_date(self):
|
||||||
stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
|
stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
|
||||||
if stock_frozen_upto:
|
if stock_frozen_upto:
|
||||||
@ -107,6 +190,21 @@ class DocType(DocListController):
|
|||||||
if not self.doc.posting_time or self.doc.posting_time == '00:0':
|
if not self.doc.posting_time or self.doc.posting_time == '00:0':
|
||||||
self.doc.posting_time = '00:00'
|
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():
|
def on_doctype_update():
|
||||||
if not webnotes.conn.sql("""show index from `tabStock Ledger Entry`
|
if not webnotes.conn.sql("""show index from `tabStock Ledger Entry`
|
||||||
where Key_name="posting_sort_index" """):
|
where Key_name="posting_sort_index" """):
|
||||||
|
@ -193,10 +193,6 @@ class DocType(TransactionBase):
|
|||||||
if not chk1:
|
if not chk1:
|
||||||
msgprint("Serial no "+x+" does not exist in system.")
|
msgprint("Serial no "+x+" does not exist in system.")
|
||||||
raise Exception
|
raise Exception
|
||||||
else:
|
|
||||||
if status=='In Store' or status=='Note in Use' or status=='Scrapped':
|
|
||||||
msgprint("Serial no "+x+" is '"+status+"'")
|
|
||||||
raise Exception
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_maintenance_detail()
|
self.validate_maintenance_detail()
|
||||||
|
@ -28,6 +28,7 @@ def make(reset=False):
|
|||||||
webnotes.connect()
|
webnotes.connect()
|
||||||
webnotes.print_messages = True
|
webnotes.print_messages = True
|
||||||
webnotes.mute_emails = True
|
webnotes.mute_emails = True
|
||||||
|
webnotes.rollback_on_exception = True
|
||||||
|
|
||||||
if reset:
|
if reset:
|
||||||
setup()
|
setup()
|
||||||
@ -59,8 +60,6 @@ def simulate():
|
|||||||
run_manufacturing(current_date)
|
run_manufacturing(current_date)
|
||||||
run_stock(current_date)
|
run_stock(current_date)
|
||||||
|
|
||||||
webnotes.conn.commit()
|
|
||||||
|
|
||||||
def run_sales(current_date):
|
def run_sales(current_date):
|
||||||
if can_make("Quotation"):
|
if can_make("Quotation"):
|
||||||
for i in xrange(how_many("Quotation")):
|
for i in xrange(how_many("Quotation")):
|
||||||
@ -81,6 +80,7 @@ def run_stock(current_date):
|
|||||||
pr.doc.fiscal_year = "2010"
|
pr.doc.fiscal_year = "2010"
|
||||||
pr.insert()
|
pr.insert()
|
||||||
pr.submit()
|
pr.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
# make delivery notes (if possible)
|
# make delivery notes (if possible)
|
||||||
if can_make("Delivery Note"):
|
if can_make("Delivery Note"):
|
||||||
@ -92,6 +92,7 @@ def run_stock(current_date):
|
|||||||
dn.doc.fiscal_year = "2010"
|
dn.doc.fiscal_year = "2010"
|
||||||
dn.insert()
|
dn.insert()
|
||||||
dn.submit()
|
dn.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
|
|
||||||
def run_purchase(current_date):
|
def run_purchase(current_date):
|
||||||
@ -106,6 +107,7 @@ def run_purchase(current_date):
|
|||||||
sq.doc.fiscal_year = "2010"
|
sq.doc.fiscal_year = "2010"
|
||||||
sq.insert()
|
sq.insert()
|
||||||
sq.submit()
|
sq.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
# make purchase orders
|
# make purchase orders
|
||||||
if can_make("Purchase Order"):
|
if can_make("Purchase Order"):
|
||||||
@ -118,8 +120,12 @@ def run_purchase(current_date):
|
|||||||
po.doc.fiscal_year = "2010"
|
po.doc.fiscal_year = "2010"
|
||||||
po.insert()
|
po.insert()
|
||||||
po.submit()
|
po.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
def run_manufacturing(current_date):
|
def run_manufacturing(current_date):
|
||||||
|
from stock.stock_ledger import NegativeStockError
|
||||||
|
from stock.doctype.stock_entry.stock_entry import IncorrectValuationRateError
|
||||||
|
|
||||||
ppt = webnotes.bean("Production Planning Tool", "Production Planning Tool")
|
ppt = webnotes.bean("Production Planning Tool", "Production Planning Tool")
|
||||||
ppt.doc.company = company
|
ppt.doc.company = company
|
||||||
ppt.doc.use_multi_level_bom = 1
|
ppt.doc.use_multi_level_bom = 1
|
||||||
@ -128,17 +134,20 @@ def run_manufacturing(current_date):
|
|||||||
ppt.run_method("get_items_from_so")
|
ppt.run_method("get_items_from_so")
|
||||||
ppt.run_method("raise_production_order")
|
ppt.run_method("raise_production_order")
|
||||||
ppt.run_method("raise_purchase_request")
|
ppt.run_method("raise_purchase_request")
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
# submit production orders
|
# submit production orders
|
||||||
for pro in webnotes.conn.get_values("Production Order", {"docstatus": 0}):
|
for pro in webnotes.conn.get_values("Production Order", {"docstatus": 0}):
|
||||||
b = webnotes.bean("Production Order", pro[0])
|
b = webnotes.bean("Production Order", pro[0])
|
||||||
b.doc.wip_warehouse = "Work in Progress - WP"
|
b.doc.wip_warehouse = "Work in Progress - WP"
|
||||||
b.submit()
|
b.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
# submit material requests
|
# submit material requests
|
||||||
for pro in webnotes.conn.get_values("Material Request", {"docstatus": 0}):
|
for pro in webnotes.conn.get_values("Material Request", {"docstatus": 0}):
|
||||||
b = webnotes.bean("Material Request", pro[0])
|
b = webnotes.bean("Material Request", pro[0])
|
||||||
b.submit()
|
b.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
# stores -> wip
|
# stores -> wip
|
||||||
if can_make("Stock Entry for WIP"):
|
if can_make("Stock Entry for WIP"):
|
||||||
@ -154,6 +163,7 @@ def run_manufacturing(current_date):
|
|||||||
for st in webnotes.conn.get_values("Stock Entry", {"docstatus":0}):
|
for st in webnotes.conn.get_values("Stock Entry", {"docstatus":0}):
|
||||||
try:
|
try:
|
||||||
webnotes.bean("Stock Entry", st[0]).submit()
|
webnotes.bean("Stock Entry", st[0]).submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
except NegativeStockError: pass
|
except NegativeStockError: pass
|
||||||
except IncorrectValuationRateError: pass
|
except IncorrectValuationRateError: pass
|
||||||
|
|
||||||
@ -170,7 +180,9 @@ def make_stock_entry_from_pro(pro_id, purpose, current_date):
|
|||||||
st.doc.expense_adjustment_account = "Stock in Hand - WP"
|
st.doc.expense_adjustment_account = "Stock in Hand - WP"
|
||||||
try:
|
try:
|
||||||
st.insert()
|
st.insert()
|
||||||
|
webnotes.conn.commit()
|
||||||
st.submit()
|
st.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
except NegativeStockError: pass
|
except NegativeStockError: pass
|
||||||
except IncorrectValuationRateError: pass
|
except IncorrectValuationRateError: pass
|
||||||
|
|
||||||
@ -194,7 +206,9 @@ def make_quotation(current_date):
|
|||||||
}, unique="item_code")
|
}, unique="item_code")
|
||||||
|
|
||||||
b.insert()
|
b.insert()
|
||||||
|
webnotes.conn.commit()
|
||||||
b.submit()
|
b.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
def make_sales_order(current_date):
|
def make_sales_order(current_date):
|
||||||
q = get_random("Quotation", {"status": "Submitted"})
|
q = get_random("Quotation", {"status": "Submitted"})
|
||||||
@ -204,7 +218,9 @@ def make_sales_order(current_date):
|
|||||||
so.doc.transaction_date = current_date
|
so.doc.transaction_date = current_date
|
||||||
so.doc.delivery_date = webnotes.utils.add_days(current_date, 10)
|
so.doc.delivery_date = webnotes.utils.add_days(current_date, 10)
|
||||||
so.insert()
|
so.insert()
|
||||||
|
webnotes.conn.commit()
|
||||||
so.submit()
|
so.submit()
|
||||||
|
webnotes.conn.commit()
|
||||||
|
|
||||||
def add_random_children(bean, template, rows, randomize, unique=None):
|
def add_random_children(bean, template, rows, randomize, unique=None):
|
||||||
for i in xrange(random.randrange(1, rows)):
|
for i in xrange(random.randrange(1, rows)):
|
||||||
|
Loading…
Reference in New Issue
Block a user