mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-08 23:52:57 +00:00
Merge branch 'develop' into payroll_entry
This commit is contained in:
@@ -70,31 +70,38 @@ frappe.ui.form.on('Batch', {
|
||||
// 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')
|
||||
const fields = [
|
||||
{
|
||||
fieldname: 'to_warehouse',
|
||||
label: __('To Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse'
|
||||
}
|
||||
];
|
||||
|
||||
frappe.prompt(
|
||||
fields,
|
||||
(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,
|
||||
source_document: frm.doc.reference_name,
|
||||
reference_doctype: frm.doc.reference_doctype
|
||||
},
|
||||
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')
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1183,6 +1183,38 @@
|
||||
"set_only_once": 1,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 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_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -1221,8 +1253,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "has_batch_no",
|
||||
"description": "",
|
||||
"fieldname": "create_new_batch",
|
||||
"fieldname": "retain_sample",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@@ -1231,7 +1262,39 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Automatically Create New Batch",
|
||||
"label": "Retain Sample",
|
||||
"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_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: (doc.retain_sample && doc.has_batch_no)",
|
||||
"description": "Maximum sample quantity that can be retained",
|
||||
"fieldname": "sample_quantity",
|
||||
"fieldtype": "Int",
|
||||
"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": "Max Sample Quantity",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@@ -3360,7 +3423,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2017-11-20 12:18:07.259756",
|
||||
"modified": "2017-12-04 15:37:58.413290",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -97,6 +97,7 @@ class Item(WebsiteGenerator):
|
||||
self.validate_website_image()
|
||||
self.make_thumbnail()
|
||||
self.validate_fixed_asset()
|
||||
self.validate_retain_sample()
|
||||
|
||||
if not self.get("__islocal"):
|
||||
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
||||
@@ -256,6 +257,12 @@ class Item(WebsiteGenerator):
|
||||
if asset:
|
||||
frappe.throw(_('"Is Fixed Asset" cannot be unchecked, as Asset record exists against the item'))
|
||||
|
||||
def validate_retain_sample(self):
|
||||
if self.retain_sample and not frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse'):
|
||||
frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first"));
|
||||
if self.retain_sample and not self.has_batch_no:
|
||||
frappe.throw(_(" {0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(self.item_code))
|
||||
|
||||
def get_context(self, context):
|
||||
context.show_search=True
|
||||
context.search_link = '/product_search'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:price_list_name",
|
||||
@@ -14,6 +15,7 @@
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -43,6 +45,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -70,6 +73,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -100,6 +104,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -129,6 +134,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -157,6 +163,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -185,6 +192,37 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "price_not_uom_dependant",
|
||||
"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": "Price Not UOM Dependant",
|
||||
"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_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -212,6 +250,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -242,18 +281,18 @@
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-tags",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2017-02-20 13:27:30.431064",
|
||||
"modified": "2017-12-01 16:55:00.243382",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Price List",
|
||||
|
||||
23
erpnext/stock/doctype/price_list/test_price_list.js
Normal file
23
erpnext/stock/doctype/price_list/test_price_list.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Price List", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Price List
|
||||
() => frappe.tests.make('Price List', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
58
erpnext/stock/doctype/price_list/test_price_list_uom.js
Normal file
58
erpnext/stock/doctype/price_list/test_price_list_uom.js
Normal file
@@ -0,0 +1,58 @@
|
||||
QUnit.module('Price List');
|
||||
|
||||
QUnit.test("test price list with uom dependancy", function(assert) {
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
|
||||
() => frappe.set_route('Form', 'Price List', 'Standard Buying'),
|
||||
() => {
|
||||
cur_frm.set_value('price_not_uom_dependant','1');
|
||||
frappe.timeout(1);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
|
||||
() => frappe.timeout(1),
|
||||
|
||||
() => {
|
||||
return frappe.tests.make('Item Price', [
|
||||
{price_list:'Standard Buying'},
|
||||
{item_code: 'Test Product 3'},
|
||||
{price_list_rate: 200}
|
||||
]);
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
|
||||
() => {
|
||||
return frappe.tests.make('Purchase Order', [
|
||||
{supplier: 'Test Supplier'},
|
||||
{currency: 'INR'},
|
||||
{buying_price_list: 'Standard Buying'},
|
||||
{items: [
|
||||
[
|
||||
{"item_code": 'Test Product 3'},
|
||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 2)},
|
||||
{"uom": 'Nos'},
|
||||
{"conversion_factor": 3}
|
||||
]
|
||||
]},
|
||||
|
||||
]);
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(0.3),
|
||||
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 3', "Item code correct");
|
||||
assert.ok(cur_frm.doc.items[0].price_list_rate == 200, "Price list rate correct");
|
||||
},
|
||||
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(1),
|
||||
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
@@ -98,6 +98,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
if(flt(this.frm.doc.per_billed) < 100) {
|
||||
cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice, __("Make"));
|
||||
}
|
||||
cur_frm.add_custom_button(__('Retention Stock Entry'), this.make_retention_stock_entry, __("Make"));
|
||||
|
||||
if(!this.frm.doc.subscription) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
@@ -137,7 +138,26 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
|
||||
reopen_purchase_receipt: function() {
|
||||
cur_frm.cscript.update_status("Submitted");
|
||||
}
|
||||
},
|
||||
|
||||
make_retention_stock_entry: function() {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.stock_entry.stock_entry.move_sample_to_retention_warehouse",
|
||||
args:{
|
||||
"company": cur_frm.doc.company,
|
||||
"items": cur_frm.doc.items
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
var doc = frappe.model.sync(r.message)[0];
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
}
|
||||
else {
|
||||
frappe.msgprint(__("Retention Stock Entry already created or Sample Quantity not provided"));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -206,3 +226,36 @@ frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) {
|
||||
}
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Purchase Receipt Item', {
|
||||
item_code: function(frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
frappe.db.get_value('Item', {name: d.item_code}, 'sample_quantity', (r) => {
|
||||
frappe.model.set_value(cdt, cdn, "sample_quantity", r.sample_quantity);
|
||||
});
|
||||
},
|
||||
sample_quantity: function(frm, cdt, cdn) {
|
||||
validate_sample_quantity(frm, cdt, cdn);
|
||||
},
|
||||
batch_no: function(frm, cdt, cdn) {
|
||||
validate_sample_quantity(frm, cdt, cdn);
|
||||
},
|
||||
});
|
||||
|
||||
var validate_sample_quantity = function(frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
if (d.sample_quantity) {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.stock_entry.stock_entry.validate_sample_quantity',
|
||||
args: {
|
||||
batch_no: d.batch_no,
|
||||
item_code: d.item_code,
|
||||
sample_quantity: d.sample_quantity,
|
||||
qty: d.qty
|
||||
},
|
||||
callback: (r) => {
|
||||
frappe.model.set_value(cdt, cdn, "sample_quantity", r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -12,6 +12,7 @@ from erpnext import set_perpetual_inventory
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
|
||||
|
||||
class TestPurchaseReceipt(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
|
||||
@@ -259,7 +260,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
item_code = frappe.db.get_value('Item', {'has_serial_no': 1})
|
||||
if not item_code:
|
||||
item = make_item("Test Serial Item 1", dict(has_serial_no = 1))
|
||||
item = make_item("Test Serial Item 1", dict(has_serial_no=1))
|
||||
item_code = item.name
|
||||
|
||||
serial_no = random_string(5)
|
||||
@@ -273,11 +274,13 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
serial_no=serial_no, basic_rate=100, do_not_submit=True)
|
||||
self.assertRaises(SerialNoDuplicateError, se.submit)
|
||||
|
||||
|
||||
def get_gl_entries(voucher_type, voucher_no):
|
||||
return frappe.db.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
order by account desc""", (voucher_type, voucher_no), as_dict=1)
|
||||
|
||||
|
||||
def make_purchase_receipt(**args):
|
||||
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
|
||||
pr = frappe.new_doc("Purchase Receipt")
|
||||
|
||||
@@ -607,6 +607,69 @@
|
||||
"unique": 0,
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "retain_sample",
|
||||
"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": "Retain Sample",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "item_code.retain_sample",
|
||||
"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_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "retain_sample",
|
||||
"fieldname": "sample_quantity",
|
||||
"fieldtype": "Int",
|
||||
"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": "Sample Quantity",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "item_code.sample_quantity",
|
||||
"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_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -2227,7 +2290,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-11-30 14:19:14.276376",
|
||||
"modified": "2017-12-06 13:50:08.201145",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -86,6 +86,12 @@ frappe.ui.form.on('Stock Entry', {
|
||||
if(frm.doc.company) {
|
||||
frm.trigger("toggle_display_account_head");
|
||||
}
|
||||
|
||||
if(frm.doc.docstatus==1 && frm.doc.purpose == "Material Receipt") {
|
||||
frm.add_custom_button(__('Make Retention Stock Entry'), function () {
|
||||
frm.trigger("make_retention_stock_entry");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
purpose: function(frm) {
|
||||
@@ -122,6 +128,25 @@ frappe.ui.form.on('Stock Entry', {
|
||||
});
|
||||
},
|
||||
|
||||
make_retention_stock_entry: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.stock_entry.stock_entry.move_sample_to_retention_warehouse",
|
||||
args:{
|
||||
"company": frm.doc.company,
|
||||
"items": frm.doc.items
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
var doc = frappe.model.sync(r.message)[0];
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
}
|
||||
else {
|
||||
frappe.msgprint(__("Retention Stock Entry already created or Sample Quantity not provided"));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggle_display_account_head: function(frm) {
|
||||
var enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company);
|
||||
frm.fields_dict["items"].grid.set_column_disp(["cost_center", "expense_account"], enabled);
|
||||
@@ -327,9 +352,33 @@ frappe.ui.form.on('Stock Entry Detail', {
|
||||
},
|
||||
cost_center: function(frm, cdt, cdn) {
|
||||
erpnext.utils.copy_value_in_all_row(frm.doc, cdt, cdn, "items", "cost_center");
|
||||
}
|
||||
},
|
||||
sample_quantity: function(frm, cdt, cdn) {
|
||||
validate_sample_quantity(frm, cdt, cdn);
|
||||
},
|
||||
batch_no: function(frm, cdt, cdn) {
|
||||
validate_sample_quantity(frm, cdt, cdn);
|
||||
},
|
||||
});
|
||||
|
||||
var validate_sample_quantity = function(frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
if (d.sample_quantity && frm.doc.purpose == "Material Receipt") {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.stock_entry.stock_entry.validate_sample_quantity',
|
||||
args: {
|
||||
batch_no: d.batch_no,
|
||||
item_code: d.item_code,
|
||||
sample_quantity: d.sample_quantity,
|
||||
qty: d.transfer_qty
|
||||
},
|
||||
callback: (r) => {
|
||||
frappe.model.set_value(cdt, cdn, "sample_quantity", r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.form.on('Landed Cost Taxes and Charges', {
|
||||
amount: function(frm) {
|
||||
frm.events.calculate_amount(frm);
|
||||
@@ -575,6 +624,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
|
||||
this.frm.fields_dict["items"].grid.set_column_disp("s_warehouse", doc.purpose!='Material Receipt');
|
||||
this.frm.fields_dict["items"].grid.set_column_disp("t_warehouse", doc.purpose!='Material Issue');
|
||||
this.frm.fields_dict["items"].grid.set_column_disp("retain_sample", doc.purpose=='Material Receipt');
|
||||
this.frm.fields_dict["items"].grid.set_column_disp("sample_quantity", doc.purpose=='Material Receipt');
|
||||
|
||||
this.frm.cscript.toggle_enable_bom();
|
||||
|
||||
|
||||
@@ -9,13 +9,14 @@ from frappe.utils import cstr, cint, flt, comma_or, getdate, nowdate, formatdate
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError
|
||||
from erpnext.stock.get_item_details import get_bin_details, get_default_cost_center, get_conversion_factor
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_no, set_batch_nos
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_no, set_batch_nos, get_batch_qty
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
import json
|
||||
|
||||
class IncorrectValuationRateError(frappe.ValidationError): pass
|
||||
class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass
|
||||
class OperationsNotCompleteError(frappe.ValidationError): pass
|
||||
class MaxSampleAlreadyRetainedError(frappe.ValidationError): pass
|
||||
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
|
||||
@@ -472,7 +473,7 @@ class StockEntry(StockController):
|
||||
def get_item_details(self, args=None, for_update=False):
|
||||
item = frappe.db.sql("""select stock_uom, description, image, item_name,
|
||||
expense_account, buying_cost_center, item_group, has_serial_no,
|
||||
has_batch_no
|
||||
has_batch_no, sample_quantity
|
||||
from `tabItem`
|
||||
where name = %s
|
||||
and disabled=0
|
||||
@@ -499,7 +500,8 @@ class StockEntry(StockController):
|
||||
'basic_rate' : 0,
|
||||
'serial_no' : '',
|
||||
'has_serial_no' : item.has_serial_no,
|
||||
'has_batch_no' : item.has_batch_no
|
||||
'has_batch_no' : item.has_batch_no,
|
||||
'sample_quantity' : item.sample_quantity
|
||||
})
|
||||
for d in [["Account", "expense_account", "default_expense_account"],
|
||||
["Cost Center", "cost_center", "cost_center"]]:
|
||||
@@ -803,6 +805,40 @@ class StockEntry(StockController):
|
||||
if getdate(self.posting_date) > getdate(expiry_date):
|
||||
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def move_sample_to_retention_warehouse(company, items):
|
||||
if isinstance(items, basestring):
|
||||
items = json.loads(items)
|
||||
retention_warehouse = frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse')
|
||||
stock_entry = frappe.new_doc("Stock Entry")
|
||||
stock_entry.company = company
|
||||
stock_entry.purpose = "Material Transfer"
|
||||
for item in items:
|
||||
if item.get('sample_quantity') and item.get('batch_no'):
|
||||
sample_quantity = validate_sample_quantity(item.get('item_code'), item.get('sample_quantity'), item.get('transfer_qty') or item.get('qty'), item.get('batch_no'))
|
||||
if sample_quantity:
|
||||
sample_serial_nos = ''
|
||||
if item.get('serial_no'):
|
||||
serial_nos = (item.get('serial_no')).split()
|
||||
if serial_nos and len(serial_nos) > item.get('sample_quantity'):
|
||||
serial_no_list = serial_nos[:-(len(serial_nos)-item.get('sample_quantity'))]
|
||||
sample_serial_nos = '\n'.join(serial_no_list)
|
||||
stock_entry.append("items", {
|
||||
"item_code": item.get('item_code'),
|
||||
"s_warehouse": item.get('t_warehouse'),
|
||||
"t_warehouse": retention_warehouse,
|
||||
"qty": item.get('sample_quantity'),
|
||||
"basic_rate": item.get('valuation_rate'),
|
||||
'uom': item.get('uom'),
|
||||
'stock_uom': item.get('stock_uom'),
|
||||
"conversion_factor": 1.0,
|
||||
"serial_no": sample_serial_nos,
|
||||
'batch_no': item.get('batch_no')
|
||||
})
|
||||
if stock_entry.get('items'):
|
||||
return stock_entry.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_production_order_details(production_order):
|
||||
production_order = frappe.get_doc("Production Order", production_order)
|
||||
@@ -893,5 +929,24 @@ def get_warehouse_details(args):
|
||||
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
|
||||
"basic_rate" : get_incoming_rate(args)
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_sample_quantity(item_code, sample_quantity, qty, batch_no = None):
|
||||
if cint(qty) < cint(sample_quantity):
|
||||
frappe.throw(_("Sample quantity {0} cannot be more than received quantity {1}").format(sample_quantity, qty), alert=True)
|
||||
retention_warehouse = frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse')
|
||||
retainted_qty = 0
|
||||
if batch_no:
|
||||
retainted_qty = get_batch_qty(batch_no, retention_warehouse, item_code)
|
||||
max_retain_qty = frappe.get_value('Item', item_code, 'sample_quantity')
|
||||
if retainted_qty >= max_retain_qty:
|
||||
frappe.msgprint(_("Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}.").
|
||||
format(retainted_qty, batch_no, item_code, batch_no), alert=True)
|
||||
sample_quantity = 0
|
||||
qty_diff = max_retain_qty-retainted_qty
|
||||
if cint(sample_quantity) > cint(qty_diff):
|
||||
frappe.msgprint(_("Maximum Samples - {0} can be retained for Batch {1} and Item {2}.").
|
||||
format(max_retain_qty, batch_no, item_code), alert=True)
|
||||
sample_quantity = qty_diff
|
||||
return sample_quantity
|
||||
@@ -20,6 +20,16 @@ def make_stock_entry(**args):
|
||||
:do_not_save: Optional flag
|
||||
:do_not_submit: Optional flag
|
||||
'''
|
||||
|
||||
def process_serial_numbers(serial_nos_list):
|
||||
serial_nos_list = [
|
||||
'\n'.join(serial_num['serial_no'] for serial_num in serial_nos_list)
|
||||
]
|
||||
|
||||
uniques = list(set(serial_nos_list[0].split('\n')))
|
||||
|
||||
return '\n'.join(uniques)
|
||||
|
||||
s = frappe.new_doc("Stock Entry")
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -77,6 +87,25 @@ def make_stock_entry(**args):
|
||||
if not args.cost_center:
|
||||
args.cost_center = frappe.get_value('Company', s.company, 'cost_center')
|
||||
|
||||
if not args.expense_account:
|
||||
args.expense_account = frappe.get_value('Company', s.company, 'stock_adjustment_account')
|
||||
|
||||
# We can find out the serial number using the batch source document
|
||||
serial_number = args.serial_no
|
||||
|
||||
if not args.serial_no and args.qty and args.batch_no:
|
||||
serial_number_list = frappe.get_list(
|
||||
doctype='Stock Ledger Entry',
|
||||
fields=['serial_no'],
|
||||
filters={
|
||||
'batch_no': args.batch_no,
|
||||
'warehouse': args.from_warehouse
|
||||
}
|
||||
)
|
||||
serial_number = process_serial_numbers(serial_number_list)
|
||||
|
||||
args.serial_no = serial_number
|
||||
|
||||
s.append("items", {
|
||||
"item_code": args.item,
|
||||
"s_warehouse": args.source,
|
||||
|
||||
@@ -15,6 +15,7 @@ from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make
|
||||
from frappe.tests.test_permissions import set_user_permission_doctypes
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse
|
||||
|
||||
def get_sle(**args):
|
||||
condition, values = "", []
|
||||
@@ -613,6 +614,61 @@ class TestStockEntry(unittest.TestCase):
|
||||
s2.submit()
|
||||
s2.cancel()
|
||||
|
||||
def test_retain_sample(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
|
||||
create_warehouse("Test Warehouse for Sample Retention")
|
||||
frappe.db.set_value("Stock Settings", None, "sample_retention_warehouse", "Test Warehouse for Sample Retention - _TC")
|
||||
|
||||
item = frappe.new_doc("Item")
|
||||
item.item_code = "Retain Sample Item"
|
||||
item.item_name = "Retain Sample Item"
|
||||
item.description = "Retain Sample Item"
|
||||
item.item_group = "All Item Groups"
|
||||
item.is_stock_item = 1
|
||||
item.has_batch_no = 1
|
||||
item.create_new_batch = 1
|
||||
item.retain_sample = 1
|
||||
item.sample_quantity = 4
|
||||
item.save()
|
||||
|
||||
receipt_entry = frappe.new_doc("Stock Entry")
|
||||
receipt_entry.company = "_Test Company"
|
||||
receipt_entry.purpose = "Material Receipt"
|
||||
receipt_entry.append("items", {
|
||||
"item_code": item.item_code,
|
||||
"t_warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 40,
|
||||
"basic_rate": 12,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"sample_quantity": 4
|
||||
})
|
||||
receipt_entry.insert()
|
||||
receipt_entry.submit()
|
||||
|
||||
retention_data = move_sample_to_retention_warehouse(receipt_entry.company, receipt_entry.get("items"))
|
||||
retention_entry = frappe.new_doc("Stock Entry")
|
||||
retention_entry.company = retention_data.company
|
||||
retention_entry.purpose = retention_data.purpose
|
||||
retention_entry.append("items", {
|
||||
"item_code": item.item_code,
|
||||
"t_warehouse": "Test Warehouse for Sample Retention - _TC",
|
||||
"s_warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 4,
|
||||
"basic_rate": 12,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"batch_no": receipt_entry.get("items")[0].batch_no
|
||||
})
|
||||
retention_entry.insert()
|
||||
retention_entry.submit()
|
||||
|
||||
qty_in_usable_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "_Test Warehouse - _TC", "_Test Item")
|
||||
qty_in_retention_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "Test Warehouse for Sample Retention - _TC", "_Test Item")
|
||||
|
||||
self.assertEquals(qty_in_usable_warehouse, 36)
|
||||
self.assertEquals(qty_in_retention_warehouse, 4)
|
||||
|
||||
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -140,9 +140,8 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "clean_description_html",
|
||||
"fieldtype": "Check",
|
||||
"fieldname": "sample_retention_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@@ -150,9 +149,10 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Convert Item Description to Clean HTML",
|
||||
"label": "Sample Retention Warehouse",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Warehouse",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
@@ -284,6 +284,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "clean_description_html",
|
||||
"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": "Convert Item Description to Clean HTML",
|
||||
"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_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -648,7 +679,7 @@
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-11-14 16:19:50.274518",
|
||||
"modified": "2017-11-17 01:35:49.562613",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
||||
@@ -155,6 +155,7 @@ def get_basic_details(args, item):
|
||||
"conversion_rate": 1.0,
|
||||
"selling_price_list": None,
|
||||
"price_list_currency": None,
|
||||
"price_list_uom_dependant": None,
|
||||
"plc_conversion_rate": 1.0,
|
||||
"doctype": "",
|
||||
"name": "",
|
||||
@@ -311,8 +312,8 @@ def get_price_list_rate(args, item_doc, out):
|
||||
|
||||
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
|
||||
/ flt(args.conversion_rate)
|
||||
|
||||
out.price_list_rate = flt(out.price_list_rate * (args.conversion_factor or 1.0))
|
||||
if args.price_list_uom_dependant == 0:
|
||||
out.price_list_rate = flt(out.price_list_rate * (args.conversion_factor or 1.0))
|
||||
|
||||
if not out.price_list_rate and args.transaction_type=="buying":
|
||||
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
||||
@@ -504,6 +505,7 @@ def apply_price_list(args, as_doc=False):
|
||||
"conversion_rate": 1.0,
|
||||
"selling_price_list": None,
|
||||
"price_list_currency": None,
|
||||
"price_list_uom_dependant": None,
|
||||
"plc_conversion_rate": 1.0,
|
||||
"doctype": "",
|
||||
"name": "",
|
||||
@@ -530,7 +532,7 @@ def apply_price_list(args, as_doc=False):
|
||||
children.append(item_details)
|
||||
|
||||
if as_doc:
|
||||
args.price_list_currency = parent.price_list_currency
|
||||
args.price_list_currency = parent.price_list_currency,
|
||||
args.plc_conversion_rate = parent.plc_conversion_rate
|
||||
if args.get('items'):
|
||||
for i, item in enumerate(args.get('items')):
|
||||
@@ -565,11 +567,23 @@ def get_price_list_currency(price_list):
|
||||
|
||||
return result.currency
|
||||
|
||||
def get_price_list_uom_dependant(price_list):
|
||||
if price_list:
|
||||
result = frappe.db.get_value("Price List", {"name": price_list,
|
||||
"enabled": 1}, ["name", "price_not_uom_dependant"], as_dict=True)
|
||||
|
||||
if not result:
|
||||
throw(_("Price List {0} is disabled or does not exist").format(price_list))
|
||||
|
||||
return result.price_not_uom_dependant
|
||||
|
||||
|
||||
def get_price_list_currency_and_exchange_rate(args):
|
||||
if not args.price_list:
|
||||
return {}
|
||||
|
||||
price_list_currency = get_price_list_currency(args.price_list)
|
||||
price_list_uom_dependant = get_price_list_uom_dependant(args.price_list)
|
||||
plc_conversion_rate = args.plc_conversion_rate
|
||||
|
||||
if (not plc_conversion_rate) or (price_list_currency and args.price_list_currency \
|
||||
@@ -580,6 +594,7 @@ def get_price_list_currency_and_exchange_rate(args):
|
||||
|
||||
return frappe._dict({
|
||||
"price_list_currency": price_list_currency,
|
||||
"price_list_uom_dependant": price_list_uom_dependant,
|
||||
"plc_conversion_rate": plc_conversion_rate
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user