[enhance] automatic batch creation, move and split
This commit is contained in:
parent
bb2670d57a
commit
e385b5b97b
@ -177,6 +177,18 @@ class StockController(AccountsController):
|
||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
return stock_ledger
|
||||
|
||||
def make_batches(self):
|
||||
'''Create batches if required. Called before submit'''
|
||||
for d in self.items:
|
||||
has_batch_no, create_new_batch = frappe.db.get_value('Item', d.item_code, ['has_batch_no', 'create_new_batch'])
|
||||
if has_batch_no and not d.batch_no and create_new_batch:
|
||||
d.batch_no = frappe.get_doc(dict(
|
||||
doctype='Batch',
|
||||
item=d.item_code,
|
||||
supplier=getattr(self, 'supplier', None),
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name)).insert().name
|
||||
|
||||
def make_adjustment_entry(self, expected_gle, voucher_obj):
|
||||
from erpnext.accounts.utils import get_stock_and_account_difference
|
||||
account_list = [d.account for d in expected_gle]
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 50 KiB |
BIN
erpnext/docs/assets/img/stock/batch_view.png
Normal file
BIN
erpnext/docs/assets/img/stock/batch_view.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 167 KiB |
BIN
erpnext/docs/assets/img/stock/item_setup_for_batch.png
Normal file
BIN
erpnext/docs/assets/img/stock/item_setup_for_batch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
@ -1,27 +1,45 @@
|
||||
Batch inventory feature in ERPNext allows you to group multiple units of an item,
|
||||
Batch feature in ERPNext allows you to group multiple units of an item,
|
||||
and assign them a unique value/number/tag called Batch No.
|
||||
|
||||
The practice of stocking based on batch is mainly followed in the pharmaceutical industry.
|
||||
Medicines/drugs produced in a particular batched is assigned a unique id.
|
||||
This helps them updating and tracking manufacturing and expiry date for all the units produced under specific batch.
|
||||
This is done based on the Item. If the Item is batched, then a Batch number must be mentioned in every stock transaction. Batch numbers can be maintained manually or automatically
|
||||
|
||||
> Note: To set item as a batch item, "Has Batch No" field should be updated as Yes in the Item master.
|
||||
### Item Setup
|
||||
|
||||
On every stock transaction (Purchase Receipt, Delivery Note, POS Invoice) made for batch item,
|
||||
you should provide item's Batch No.
|
||||
To set item as a batch item, "Has Batch No" field should be checked in the Item master.
|
||||
|
||||
If you want automatic batch creation at the time of Purchase Receipt, you must check "Create New Batches Automatically"
|
||||
|
||||
<img class="screenshot" alt="Item Setup for Batches" src="{{docs_base_url}}/assets/img/stock/item_setup_for_batch.png">
|
||||
|
||||
### Creating Batches
|
||||
|
||||
If you have not selected "Create New Batches Automatically", you will have to make Batches Manually as you go along.
|
||||
|
||||
To create new Batch No. master for an item, go to:
|
||||
|
||||
> Stock > Setup > Batch > New
|
||||
|
||||
Batch master is created before creation of Purchase Receipt.
|
||||
Hence eveytime there is Purchase Receipt or Production entry being made for a batch item,
|
||||
you will first create its Batch No, and then select it in Purcase order or Production Entry.
|
||||
### Splitting and Moving Batches
|
||||
|
||||
<img class="screenshot" alt="batch" src="{{docs_base_url}}/assets/img/stock/batch.png">
|
||||
When you open a batch, you will see all the quantities relating this that batch on the page.
|
||||
|
||||
> Note: In stock transactions, Batch IDs will be filtered based on Item Code, Warehouse,
|
||||
Batch Expiry Date (compared with Posting date of a transaction) and Actual Qty in Warehouse.
|
||||
<img class="screenshot" alt="Batch View" src="{{docs_base_url}}/assets/img/stock/batch_view.png">
|
||||
|
||||
To move the batch from one warehouse to another, you can click on the move button.
|
||||
|
||||
You can also split the batch into smaller one by clicking on "Split". This will create a new Batch based on this Batch and the quantities will be split between the batches.
|
||||
|
||||
### Transacting Items with Batches
|
||||
|
||||
Batch master is created before creation of Purchase Receipt.
|
||||
Hence eveytime there is Purchase Receipt or Production Order being made for a batch item,
|
||||
you will first create its Batch No, and then select it in Purchase order or Production Entry.
|
||||
|
||||
On every stock transaction (Purchase Receipt, Delivery Note, POS Invoice) made for batch item,
|
||||
you should provide item's Batch No.
|
||||
|
||||
> Note: In stock transactions, Batch IDs will be filtered based on Item Code, Warehouse,
|
||||
Batch Expiry Date (compared with Posting date of a transaction) and Actual Qty in Warehouse.
|
||||
While searching for Batch ID without value in Warehouse field, then Actual Qty filter won't be applied.
|
||||
|
||||
{next}
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
{% include 'erpnext/selling/sales_common.js' %}
|
||||
|
||||
cur_frm.add_fetch('customer', 'tax_id', 'tax_id');
|
||||
|
||||
frappe.ui.form.on("Sales Order", {
|
||||
setup: function(frm) {
|
||||
$.extend(frm.cscript, new erpnext.selling.SalesOrderController({frm: frm}));
|
||||
@ -14,6 +12,7 @@ frappe.ui.form.on("Sales Order", {
|
||||
'Material Request': 'Material Request',
|
||||
'Purchase Order': 'Purchase Order'
|
||||
}
|
||||
frm.add_fetch('customer', 'tax_id', 'tax_id');
|
||||
},
|
||||
onload: function(frm) {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
|
@ -1,12 +1,137 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.fields_dict['item'].get_query = function(doc, cdt, cdn) {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{
|
||||
'is_stock_item': 1,
|
||||
'has_batch_no': 1
|
||||
frappe.ui.form.on('Batch', {
|
||||
setup: (frm) => {
|
||||
frm.fields_dict['item'].get_query = function(doc, cdt, cdn) {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{
|
||||
'is_stock_item': 1,
|
||||
'has_batch_no': 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
refresh: (frm) => {
|
||||
if(!frm.is_new()) {
|
||||
frm.add_custom_button(__("View Ledger"), () => {
|
||||
frappe.route_options = {
|
||||
batch_no: frm.doc.name
|
||||
};
|
||||
frappe.set_route("query-report", "Stock Ledger");
|
||||
});
|
||||
frm.trigger('make_dashboard');
|
||||
}
|
||||
},
|
||||
make_dashboard: (frm) => {
|
||||
if(!frm.is_new()) {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
|
||||
args: {batch_no: frm.doc.name},
|
||||
callback: (r) => {
|
||||
if(!r.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
var section = frm.dashboard.add_section(`<h5 style="margin-top: 0px;">
|
||||
${ __("Stock Levels") }</a></h5>`);
|
||||
|
||||
// sort by qty
|
||||
r.message.sort(function(a, b) { a.qty > b.qty ? 1 : -1 });
|
||||
|
||||
var rows = $('<div></div>').appendTo(section);
|
||||
|
||||
// show
|
||||
(r.message || []).forEach(function(d) {
|
||||
if(d.qty > 0) {
|
||||
$(`<div class='row' style='margin-bottom: 10px;'>
|
||||
<div class='col-sm-3 small' style='padding-top: 3px;'>${d.warehouse}</div>
|
||||
<div class='col-sm-3 small text-right' style='padding-top: 3px;'>${d.qty}</div>
|
||||
<div class='col-sm-6'>
|
||||
<button class='btn btn-default btn-xs btn-move' style='margin-right: 7px;'
|
||||
data-qty = "${d.qty}"
|
||||
data-warehouse = "${d.warehouse}">
|
||||
${__('Move')}</button>
|
||||
<button class='btn btn-default btn-xs btn-split'
|
||||
data-qty = "${d.qty}"
|
||||
data-warehouse = "${d.warehouse}">
|
||||
${__('Split')}</button>
|
||||
</div>
|
||||
</div>`).appendTo(rows);
|
||||
}
|
||||
});
|
||||
|
||||
// move - ask for target warehouse and make stock entry
|
||||
rows.find('.btn-move').on('click', function() {
|
||||
var $btn = $(this);
|
||||
frappe.prompt({
|
||||
fieldname: 'to_warehouse',
|
||||
label: __('To Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse'
|
||||
},
|
||||
(data) => {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
|
||||
args: {
|
||||
item_code: frm.doc.item,
|
||||
batch_no: frm.doc.name,
|
||||
qty: $btn.attr('data-qty'),
|
||||
from_warehouse: $btn.attr('data-warehouse'),
|
||||
to_warehouse: data.to_warehouse
|
||||
},
|
||||
callback: (r) => {
|
||||
frappe.show_alert(__('Stock Entry {0} created',
|
||||
['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
|
||||
frm.refresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
__('Select Target Warehouse'),
|
||||
__('Move')
|
||||
)
|
||||
});
|
||||
|
||||
// split - ask for new qty and batch ID (optional)
|
||||
// and make stock entry via batch.batch_split
|
||||
rows.find('.btn-split').on('click', function() {
|
||||
var $btn = $(this);
|
||||
frappe.prompt([{
|
||||
fieldname: 'qty',
|
||||
label: __('New Batch Qty'),
|
||||
fieldtype: 'Float',
|
||||
'default': $btn.attr('data-qty')
|
||||
},
|
||||
{
|
||||
fieldname: 'new_batch_id',
|
||||
label: __('New Batch ID (Optional)'),
|
||||
fieldtype: 'Data',
|
||||
}],
|
||||
(data) => {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.batch.batch.split_batch',
|
||||
args: {
|
||||
item_code: frm.doc.item,
|
||||
batch_no: frm.doc.name,
|
||||
qty: data.qty,
|
||||
warehouse: $btn.attr('data-warehouse'),
|
||||
new_batch_id: data.new_batch_id
|
||||
},
|
||||
callback: (r) => {
|
||||
frm.refresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
__('Split Batch'),
|
||||
__('Split')
|
||||
)
|
||||
})
|
||||
|
||||
frm.dashboard.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:batch_id",
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2013-03-05 14:50:38",
|
||||
"custom": 0,
|
||||
@ -17,12 +18,14 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.__islocal",
|
||||
"fieldname": "batch_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Batch ID",
|
||||
@ -36,7 +39,7 @@
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
@ -52,6 +55,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Item",
|
||||
@ -71,6 +75,66 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "image",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.parent_batch",
|
||||
"fieldname": "parent_batch",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Parent Batch",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Batch",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -82,6 +146,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
@ -109,6 +174,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expiry Date",
|
||||
@ -127,6 +193,153 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "source",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Source",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Supplier",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Supplier",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Source Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Source Document Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "reference_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -138,6 +351,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
@ -165,6 +379,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Batch Description",
|
||||
@ -185,18 +400,19 @@
|
||||
"width": "300px"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-archive",
|
||||
"idx": 1,
|
||||
"image_field": "image",
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 5,
|
||||
"modified": "2016-11-07 05:50:33.973883",
|
||||
"modified": "2017-04-20 03:22:19.888058",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Batch",
|
||||
@ -212,7 +428,6 @@
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
@ -224,9 +439,12 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "DESC",
|
||||
"title_field": "item",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
@ -7,6 +7,24 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Batch(Document):
|
||||
def autoname(self):
|
||||
'''Generate random ID for batch if not specified'''
|
||||
if not self.batch_id:
|
||||
if frappe.db.get_value('Item', self.item, 'create_new_batch'):
|
||||
temp = None
|
||||
while not temp:
|
||||
temp = frappe.generate_hash()[:7].upper()
|
||||
if frappe.db.exists('Batch', temp):
|
||||
temp = None
|
||||
|
||||
self.batch_id = temp
|
||||
else:
|
||||
frappe.throw(_('Batch ID is mandatory'), frappe.MandatoryError)
|
||||
|
||||
self.name = self.batch_id
|
||||
|
||||
def onload(self):
|
||||
self.image = frappe.db.get_value('Item', self.item, 'image')
|
||||
|
||||
def validate(self):
|
||||
self.item_has_batch_enabled()
|
||||
@ -14,3 +32,47 @@ class Batch(Document):
|
||||
def item_has_batch_enabled(self):
|
||||
if frappe.db.get_value("Item",self.item,"has_batch_no") == 0:
|
||||
frappe.throw(_("The selected item cannot have Batch"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_batch_qty(batch_no, warehouse=None):
|
||||
'''Returns batch actual qty if warehouse is passed, or returns dict of qty by warehouse if warehouse is None'''
|
||||
frappe.has_permission('Batch', throw=True)
|
||||
out = 0
|
||||
if batch_no and warehouse:
|
||||
out = float(frappe.db.sql("""select sum(actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where warehouse=%s and batch_no=%s""",
|
||||
(warehouse, batch_no))[0][0] or 0)
|
||||
if batch_no and not warehouse:
|
||||
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where batch_no=%s
|
||||
group by warehouse''', batch_no, as_dict=1)
|
||||
return out
|
||||
|
||||
@frappe.whitelist()
|
||||
def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
|
||||
'''Split the batch into a new batch'''
|
||||
batch = frappe.get_doc(dict(doctype='Batch', item=item_code, batch_id=new_batch_id)).insert()
|
||||
stock_entry = frappe.get_doc(dict(
|
||||
doctype='Stock Entry',
|
||||
purpose='Repack',
|
||||
items=[
|
||||
dict(
|
||||
item_code = item_code,
|
||||
qty = float(qty or 0),
|
||||
s_warehouse = warehouse,
|
||||
batch_no = batch_no
|
||||
),
|
||||
dict(
|
||||
item_code = item_code,
|
||||
qty = float(qty or 0),
|
||||
t_warehouse = warehouse,
|
||||
batch_no = batch.name
|
||||
),
|
||||
]
|
||||
))
|
||||
stock_entry.insert()
|
||||
stock_entry.submit()
|
||||
|
||||
return batch.name
|
||||
|
@ -6,10 +6,75 @@ import frappe
|
||||
from frappe.exceptions import ValidationError
|
||||
import unittest
|
||||
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
|
||||
class TestBatch(unittest.TestCase):
|
||||
def test_item_has_batch_enabled(self):
|
||||
self.assertRaises(ValidationError, frappe.get_doc({
|
||||
"doctype": "Batch",
|
||||
"name": "_test Batch",
|
||||
"item": "_Test Item"
|
||||
}).save)
|
||||
}).save)
|
||||
|
||||
def make_batch_item(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
if not frappe.db.exists('ITEM-BATCH-1'):
|
||||
make_item('ITEM-BATCH-1', dict(has_batch_no = 1, create_new_batch = 1))
|
||||
|
||||
def test_purchase_receipt(self):
|
||||
'''Test automated batch creation from Purchase Receipt'''
|
||||
self.make_batch_item()
|
||||
|
||||
receipt = frappe.get_doc(dict(
|
||||
doctype = 'Purchase Receipt',
|
||||
supplier = '_Test Supplier',
|
||||
items = [
|
||||
dict(
|
||||
item_code = 'ITEM-BATCH-1',
|
||||
qty = 100,
|
||||
rate = 10
|
||||
)
|
||||
]
|
||||
)).insert()
|
||||
receipt.submit()
|
||||
|
||||
self.assertTrue(receipt.items[0].batch_no)
|
||||
self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 100)
|
||||
|
||||
return receipt
|
||||
|
||||
def test_stock_entry(self):
|
||||
'''Test batch creation via Stock Entry (Production Order)'''
|
||||
|
||||
self.make_batch_item()
|
||||
|
||||
stock_entry = frappe.get_doc(dict(
|
||||
doctype = 'Stock Entry',
|
||||
purpose = 'Material Receipt',
|
||||
company = '_Test Company',
|
||||
items = [
|
||||
dict(
|
||||
item_code = 'ITEM-BATCH-1',
|
||||
qty = 90,
|
||||
t_warehouse = '_Test Warehouse - _TC',
|
||||
cost_center = 'Main - _TC',
|
||||
rate = 10
|
||||
)
|
||||
]
|
||||
)).insert()
|
||||
stock_entry.submit()
|
||||
|
||||
self.assertTrue(stock_entry.items[0].batch_no)
|
||||
self.assertEquals(get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90)
|
||||
|
||||
def test_batch_split(self):
|
||||
'''Test batch splitting'''
|
||||
receipt = self.test_purchase_receipt()
|
||||
from erpnext.stock.doctype.batch.batch import split_batch
|
||||
|
||||
new_batch = split_batch(receipt.items[0].batch_no, 'ITEM-BATCH-1', receipt.items[0].warehouse, 22)
|
||||
|
||||
self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78)
|
||||
self.assertEquals(get_batch_qty(new_batch, receipt.items[0].warehouse), 22)
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
"beta": 0,
|
||||
"creation": "2013-05-03 10:45:46",
|
||||
"custom": 0,
|
||||
"default_print_format": "Standard",
|
||||
"default_print_format": "",
|
||||
"description": "A Product or a Service that is bought, sold or kept in stock.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
@ -714,103 +714,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "eval:doc.is_stock_item",
|
||||
"fieldname": "has_batch_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Has Batch No",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "has_batch_no",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "eval:doc.is_stock_item",
|
||||
"description": "Selecting \"Yes\" will give a unique identity to each entity of this item which can be viewed in the Serial No master.",
|
||||
"fieldname": "has_serial_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Has Serial No",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "has_serial_no",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "has_serial_no",
|
||||
"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.",
|
||||
"fieldname": "serial_no_series",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Serial Number Series",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -1150,6 +1053,193 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no",
|
||||
"columns": 0,
|
||||
"depends_on": "is_stock_item",
|
||||
"fieldname": "serial_nos_and_batches",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Serial Nos and Batches",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "eval:doc.is_stock_item",
|
||||
"fieldname": "has_batch_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Has Batch No",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "has_batch_no",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "has_batch_no",
|
||||
"description": "",
|
||||
"fieldname": "create_new_batch",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Automatically Create New Batch",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_37",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "eval:doc.is_stock_item",
|
||||
"description": "",
|
||||
"fieldname": "has_serial_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Has Serial No",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "has_serial_no",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "has_serial_no",
|
||||
"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.",
|
||||
"fieldname": "serial_no_series",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Serial Number Series",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
@ -2954,8 +3044,8 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2017-03-24 15:46:18.569291",
|
||||
"modified_by": "d.ottenbreit@eso-electronic.de",
|
||||
"modified": "2017-04-19 08:14:26.785497",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
"owner": "Administrator",
|
||||
|
@ -50,8 +50,11 @@ class PurchaseReceipt(BuyingController):
|
||||
self.validate_posting_time()
|
||||
super(PurchaseReceipt, self).validate()
|
||||
|
||||
if not self._action=="submit":
|
||||
if self._action=="submit":
|
||||
self.make_batches()
|
||||
else:
|
||||
self.set_status()
|
||||
|
||||
self.po_required()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
@ -62,7 +65,6 @@ class PurchaseReceipt(BuyingController):
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
throw(_("Posting Date cannot be future date"))
|
||||
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(PurchaseReceipt, self).validate_with_previous_doc({
|
||||
"Purchase Order": {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -48,6 +48,9 @@ class StockEntry(StockController):
|
||||
self.validate_with_material_request()
|
||||
self.validate_batch()
|
||||
|
||||
if self._action == 'submit':
|
||||
self.make_batches()
|
||||
|
||||
self.set_actual_qty()
|
||||
self.calculate_rate_and_amount(update_finished_item_rate=False)
|
||||
|
||||
|
@ -6,6 +6,20 @@ from frappe.utils import cint, flt
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_stock_entry(**args):
|
||||
'''Helper function to make a Stock Entry
|
||||
|
||||
:item_code: Item to be moved
|
||||
:qty: Qty to be moved
|
||||
:from_warehouse: Optional
|
||||
:to_warehouse: Optional
|
||||
:rate: Optional
|
||||
:serial_no: Optional
|
||||
:batch_no: Optional
|
||||
:posting_date: Optional
|
||||
:posting_time: Optional
|
||||
:do_not_save: Optional flag
|
||||
:do_not_submit: Optional flag
|
||||
'''
|
||||
s = frappe.new_doc("Stock Entry")
|
||||
args = frappe._dict(args)
|
||||
|
||||
@ -71,6 +85,7 @@ def make_stock_entry(**args):
|
||||
"basic_rate": args.rate or args.basic_rate,
|
||||
"conversion_factor": 1.0,
|
||||
"serial_no": args.serial_no,
|
||||
'batch_no': args.batch_no,
|
||||
'cost_center': args.cost_center,
|
||||
'expense_account': args.expense_account
|
||||
})
|
||||
|
@ -58,7 +58,7 @@ class StockLedgerEntry(Document):
|
||||
|
||||
def validate_item(self):
|
||||
item_det = frappe.db.sql("""select name, has_batch_no, docstatus,
|
||||
is_stock_item, has_variants, stock_uom
|
||||
is_stock_item, has_variants, stock_uom, create_new_batch
|
||||
from tabItem where name=%s""", self.item_code, as_dict=True)
|
||||
|
||||
if not item_det:
|
||||
@ -75,7 +75,7 @@ class StockLedgerEntry(Document):
|
||||
if not self.batch_no:
|
||||
frappe.throw(_("Batch number is mandatory for Item {0}").format(self.item_code))
|
||||
elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
|
||||
frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, self.item_code))
|
||||
frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, self.item_code))
|
||||
|
||||
elif item_det.has_batch_no ==0 and self.batch_no:
|
||||
frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
|
||||
@ -116,7 +116,7 @@ class StockLedgerEntry(Document):
|
||||
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
|
||||
else:
|
||||
from erpnext.accounts.utils import validate_fiscal_year
|
||||
validate_fiscal_year(self.posting_date, self.fiscal_year, self.company,
|
||||
validate_fiscal_year(self.posting_date, self.fiscal_year, self.company,
|
||||
self.meta.get_label("posting_date"), self)
|
||||
|
||||
def block_transactions_against_group_warehouse(self):
|
||||
|
@ -143,13 +143,11 @@ class StockReconciliation(StockController):
|
||||
|
||||
# item should not be serialized
|
||||
if item.has_serial_no == 1:
|
||||
raise frappe.ValidationError, _("Serialized Item {0} cannot be updated \
|
||||
using Stock Reconciliation").format(item_code)
|
||||
raise frappe.ValidationError, _("Serialized Item {0} cannot be updated using Stock Reconciliation, please use Stock Entry").format(item_code)
|
||||
|
||||
# item managed batch-wise not allowed
|
||||
if item.has_batch_no == 1:
|
||||
raise frappe.ValidationError, _("Item: {0} managed batch-wise, can not be reconciled using \
|
||||
Stock Reconciliation, instead use Stock Entry").format(item_code)
|
||||
raise frappe.ValidationError, _("Batched Item {0} cannot be updated using Stock Reconciliation, instead use Stock Entry").format(item_code)
|
||||
|
||||
# docstatus should be < 2
|
||||
validate_cancelled_item(item_code, item.docstatus, verbose=0)
|
||||
|
@ -187,9 +187,11 @@ def get_basic_details(args, item):
|
||||
out.stock_qty = out.qty * out.conversion_factor
|
||||
|
||||
# if default specified in item is for another company, fetch from company
|
||||
for d in [["Account", "income_account", "default_income_account"],
|
||||
for d in [
|
||||
["Account", "income_account", "default_income_account"],
|
||||
["Account", "expense_account", "default_expense_account"],
|
||||
["Cost Center", "cost_center", "cost_center"], ["Warehouse", "warehouse", ""]]:
|
||||
["Cost Center", "cost_center", "cost_center"],
|
||||
["Warehouse", "warehouse", ""]]:
|
||||
company = frappe.db.get_value(d[0], out.get(d[1]), "company")
|
||||
if not out[d[1]] or (company and args.company != company):
|
||||
out[d[1]] = frappe.db.get_value("Company", args.company, d[2]) if d[2] else None
|
||||
@ -359,15 +361,6 @@ def get_serial_nos_by_fifo(args):
|
||||
"qty": abs(cint(args.stock_qty))
|
||||
}))
|
||||
|
||||
def get_actual_batch_qty(batch_no,warehouse,item_code):
|
||||
actual_batch_qty = 0
|
||||
if batch_no:
|
||||
actual_batch_qty = flt(frappe.db.sql("""select sum(actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where warehouse=%s and item_code=%s and batch_no=%s""",
|
||||
(warehouse, item_code, batch_no))[0][0])
|
||||
return actual_batch_qty
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_conversion_factor(item_code, uom):
|
||||
variant_of = frappe.db.get_value("Item", item_code, "variant_of")
|
||||
@ -403,10 +396,10 @@ def get_bin_details_and_serial_nos(item_code, warehouse, stock_qty=None, serial_
|
||||
return bin_details_and_serial_nos
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_batch_qty(batch_no,warehouse,item_code):
|
||||
actual_batch_qty = get_actual_batch_qty(batch_no,warehouse,item_code)
|
||||
def get_batch_qty(batch_no, warehouse, item_code):
|
||||
from frappe.stock.doctype.batch import batch
|
||||
if batch_no:
|
||||
return {'actual_batch_qty': actual_batch_qty}
|
||||
return {'actual_batch_qty': batch.get_batch_qty(batch_no, warehouse)}
|
||||
|
||||
@frappe.whitelist()
|
||||
def apply_price_list(args, as_doc=False):
|
||||
|
@ -37,6 +37,12 @@ frappe.query_reports["Stock Ledger"] = {
|
||||
"fieldtype": "Link",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fieldname":"batch_no",
|
||||
"label": __("Batch No"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"fieldname":"brand",
|
||||
"label": __("Brand"),
|
||||
|
@ -10,9 +10,9 @@ def execute(filters=None):
|
||||
sl_entries = get_stock_ledger_entries(filters)
|
||||
item_details = get_item_details(filters)
|
||||
opening_row = get_opening_balance(filters, columns)
|
||||
|
||||
|
||||
data = []
|
||||
|
||||
|
||||
if opening_row:
|
||||
data.append(opening_row)
|
||||
|
||||
@ -25,7 +25,7 @@ def execute(filters=None):
|
||||
(sle.incoming_rate if sle.actual_qty > 0 else 0.0),
|
||||
sle.valuation_rate, sle.stock_value, sle.voucher_type, sle.voucher_no,
|
||||
sle.batch_no, sle.serial_no, sle.company])
|
||||
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_columns():
|
||||
@ -76,6 +76,8 @@ def get_sle_conditions(filters):
|
||||
conditions.append(get_warehouse_condition(filters.get("warehouse")))
|
||||
if filters.get("voucher_no"):
|
||||
conditions.append("voucher_no=%(voucher_no)s")
|
||||
if filters.get("batch_no"):
|
||||
conditions.append("batch_no=%(batch_no)s")
|
||||
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
@ -90,14 +92,14 @@ def get_opening_balance(filters, columns):
|
||||
"posting_date": filters.from_date,
|
||||
"posting_time": "00:00:00"
|
||||
})
|
||||
|
||||
|
||||
row = [""]*len(columns)
|
||||
row[1] = _("'Opening'")
|
||||
for i, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
|
||||
row[i] = last_entry.get(v, 0)
|
||||
|
||||
|
||||
return row
|
||||
|
||||
|
||||
def get_warehouse_condition(warehouse):
|
||||
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
|
||||
if warehouse_details:
|
||||
|
@ -143,6 +143,7 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
|
||||
for d in doc.get_all_children(parenttype=child_dt):
|
||||
if d.get(uom_field) in integer_uoms:
|
||||
for f in qty_fields:
|
||||
if d.get(f):
|
||||
if cint(d.get(f))!=d.get(f):
|
||||
frappe.throw(_("Quantity cannot be a fraction in row {0}").format(d.idx), UOMMustBeIntegerError)
|
||||
qty = d.get(f)
|
||||
if qty:
|
||||
if abs(int(qty) - float(qty)) > 0.0000001:
|
||||
frappe.throw(_("Quantity ({0}) cannot be a fraction in row {1}").format(qty, d.idx), UOMMustBeIntegerError)
|
||||
|
Loading…
x
Reference in New Issue
Block a user