Merge branch 'develop' into payroll_entry

This commit is contained in:
Nabin Hait
2017-12-06 19:18:40 +05:30
committed by GitHub
328 changed files with 20877 additions and 3508 deletions

View File

@@ -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')
);
});

View File

@@ -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",

View File

@@ -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'

View File

@@ -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",

View 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()
]);
});

View 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()
]);
});

View File

@@ -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);
}
});
}
};

View File

@@ -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")

View File

@@ -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",

View File

@@ -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();

View File

@@ -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

View File

@@ -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,

View File

@@ -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"

View File

@@ -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",

View File

@@ -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
})