Merge branch 'develop' into email-digest

This commit is contained in:
Anupam Kumar 2021-04-09 17:24:43 +05:30 committed by GitHub
commit f52f2b2f17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 394 additions and 225 deletions

View File

@ -293,6 +293,11 @@ def validate_accounts(file_name):
accounts_dict = {} accounts_dict = {}
for account in accounts: for account in accounts:
accounts_dict.setdefault(account["account_name"], account) accounts_dict.setdefault(account["account_name"], account)
if not hasattr(account, "parent_account"):
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
msg += "<br><br>"
msg += _("Alternatively, you can download the template and fill your data in.")
frappe.throw(msg, title=_("Parent Account Missing"))
if account["parent_account"] and accounts_dict.get(account["parent_account"]): if account["parent_account"] and accounts_dict.get(account["parent_account"]):
accounts_dict[account["parent_account"]]["is_group"] = 1 accounts_dict[account["parent_account"]]["is_group"] = 1

View File

@ -260,7 +260,10 @@ doc_events = {
"erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction" "erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
], ],
"on_trash": "erpnext.regional.check_deletion_permission" "on_trash": "erpnext.regional.check_deletion_permission",
"validate": [
"erpnext.regional.india.utils.validate_document_name"
]
}, },
"Purchase Invoice": { "Purchase Invoice": {
"validate": [ "validate": [
@ -282,9 +285,6 @@ doc_events = {
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { ('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
'validate': ['erpnext.regional.india.utils.set_place_of_supply'] 'validate': ['erpnext.regional.india.utils.set_place_of_supply']
}, },
('Sales Invoice', 'Purchase Invoice'): {
'validate': ['erpnext.regional.india.utils.validate_document_name']
},
"Contact": { "Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue", "on_trash": "erpnext.support.doctype.issue.issue.update_issue",
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",

View File

@ -15,6 +15,7 @@
"hide_custom": 0, "hide_custom": 0,
"icon": "hr", "icon": "hr",
"idx": 0, "idx": 0,
"is_default": 0,
"is_standard": 1, "is_standard": 1,
"label": "HR", "label": "HR",
"links": [ "links": [
@ -226,42 +227,12 @@
"onboard": 0, "onboard": 0,
"type": "Card Break" "type": "Card Break"
}, },
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
"label": "Leave Application",
"link_to": "Leave Application",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
"label": "Leave Allocation",
"link_to": "Leave Allocation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Leave Type",
"hidden": 0,
"is_query_report": 0,
"label": "Leave Policy",
"link_to": "Leave Policy",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{ {
"dependencies": "", "dependencies": "",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Leave Period", "label": "Holiday List",
"link_to": "Leave Period", "link_to": "Holiday List",
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
@ -280,8 +251,28 @@
"dependencies": "", "dependencies": "",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Holiday List", "label": "Leave Period",
"link_to": "Holiday List", "link_to": "Leave Period",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Leave Type",
"hidden": 0,
"is_query_report": 0,
"label": "Leave Policy",
"link_to": "Leave Policy",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Leave Policy",
"hidden": 0,
"is_query_report": 0,
"label": "Leave Policy Assignment",
"link_to": "Leave Policy Assignment",
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
@ -290,8 +281,18 @@
"dependencies": "Employee", "dependencies": "Employee",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Compensatory Leave Request", "label": "Leave Application",
"link_to": "Compensatory Leave Request", "link_to": "Leave Application",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
"label": "Leave Allocation",
"link_to": "Leave Allocation",
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
@ -317,12 +318,12 @@
"type": "Link" "type": "Link"
}, },
{ {
"dependencies": "Leave Application", "dependencies": "Employee",
"hidden": 0, "hidden": 0,
"is_query_report": 1, "is_query_report": 0,
"label": "Employee Leave Balance", "label": "Compensatory Leave Request",
"link_to": "Employee Leave Balance", "link_to": "Compensatory Leave Request",
"link_type": "Report", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
@ -383,16 +384,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"dependencies": "Attendance",
"hidden": 0,
"is_query_report": 1,
"label": "Monthly Attendance Sheet",
"link_to": "Monthly Attendance Sheet",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -420,6 +411,15 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Travel Request",
"link_to": "Travel Request",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -464,6 +464,15 @@
"onboard": 0, "onboard": 0,
"type": "Card Break" "type": "Card Break"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Driver",
"link_to": "Driver",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{ {
"dependencies": "", "dependencies": "",
"hidden": 0, "hidden": 0,
@ -541,6 +550,24 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Appointment Letter",
"link_to": "Appointment Letter",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Appointment Letter Template",
"link_to": "Appointment Letter Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -625,33 +652,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 1,
"label": "Employee Birthday",
"link_to": "Employee Birthday",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 1,
"label": "Employees working on a holiday",
"link_to": "Employees working on a holiday",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -702,7 +702,74 @@
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Employee Tax and Benefits", "label": "Key Reports",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Attendance",
"hidden": 0,
"is_query_report": 1,
"label": "Monthly Attendance Sheet",
"link_to": "Monthly Attendance Sheet",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Staffing Plan",
"hidden": 0,
"is_query_report": 1,
"label": "Recruitment Analytics",
"link_to": "Recruitment Analytics",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 1,
"label": "Employee Analytics",
"link_to": "Employee Analytics",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 1,
"label": "Employee Leave Balance",
"link_to": "Employee Leave Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 1,
"label": "Employee Leave Balance Summary",
"link_to": "Employee Leave Balance Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee Advance",
"hidden": 0,
"is_query_report": 1,
"label": "Employee Advance Summary",
"link_to": "Employee Advance Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Other Reports",
"onboard": 0, "onboard": 0,
"type": "Card Break" "type": "Card Break"
}, },
@ -710,74 +777,44 @@
"dependencies": "Employee", "dependencies": "Employee",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Employee Tax Exemption Declaration", "label": "Employee Information",
"link_to": "Employee Tax Exemption Declaration", "link_to": "Employee Information",
"link_type": "DocType", "link_type": "Report",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{ {
"dependencies": "Employee", "dependencies": "Employee",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 1,
"label": "Employee Tax Exemption Proof Submission", "label": "Employee Birthday",
"link_to": "Employee Tax Exemption Proof Submission", "link_to": "Employee Birthday",
"link_type": "DocType", "link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee, Payroll Period",
"hidden": 0,
"is_query_report": 0,
"label": "Employee Other Income",
"link_to": "Employee Other Income",
"link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{ {
"dependencies": "Employee", "dependencies": "Employee",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 1,
"label": "Employee Benefit Application", "label": "Employees Working on a Holiday",
"link_to": "Employee Benefit Application", "link_to": "Employees working on a holiday",
"link_type": "DocType", "link_type": "Report",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{ {
"dependencies": "Employee", "dependencies": "Daily Work Summary",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 1,
"label": "Employee Benefit Claim", "label": "Daily Work Summary Replies",
"link_to": "Employee Benefit Claim", "link_to": "Daily Work Summary Replies",
"link_type": "DocType", "link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
"label": "Employee Tax Exemption Category",
"link_to": "Employee Tax Exemption Category",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
"label": "Employee Tax Exemption Sub Category",
"link_to": "Employee Tax Exemption Sub Category",
"link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
} }
], ],
"modified": "2021-01-21 13:38:38.941001", "modified": "2021-03-24 17:35:21.483297",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR", "name": "HR",

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from frappe.utils import cstr from frappe.utils import cstr, flt
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
@ -81,15 +81,27 @@ class TestBOM(unittest.TestCase):
bom = frappe.copy_doc(test_records[2]) bom = frappe.copy_doc(test_records[2])
bom.insert() bom.insert()
# test amounts in selected currency raw_material_cost = 0.0
self.assertEqual(bom.operating_cost, 100) op_cost = 0.0
self.assertEqual(bom.raw_material_cost, 351.68)
self.assertEqual(bom.total_cost, 451.68) for op_row in bom.operations:
op_cost += op_row.operating_cost
for row in bom.items:
raw_material_cost += row.amount
base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
# test amounts in selected currency # test amounts in selected currency
self.assertEqual(bom.base_operating_cost, 6000) self.assertEqual(bom.operating_cost, op_cost)
self.assertEqual(bom.base_raw_material_cost, 21100.80) self.assertEqual(bom.raw_material_cost, raw_material_cost)
self.assertEqual(bom.base_total_cost, 27100.80) self.assertEqual(bom.total_cost, raw_material_cost + op_cost)
# test amounts in selected currency
self.assertEqual(bom.base_operating_cost, base_op_cost)
self.assertEqual(bom.base_raw_material_cost, base_raw_material_cost)
self.assertEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)

View File

@ -47,6 +47,8 @@ class JobCard(Document):
if d.completed_qty: if d.completed_qty:
self.total_completed_qty += d.completed_qty self.total_completed_qty += d.completed_qty
self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
def get_overlap_for(self, args, check_next_available_slot=False): def get_overlap_for(self, args, check_next_available_slot=False):
production_capacity = 1 production_capacity = 1

View File

@ -33,12 +33,16 @@ class TestProject(unittest.TestCase):
def test_project_template_having_parent_child_tasks(self): def test_project_template_having_parent_child_tasks(self):
project_name = "Test Project with Template - Tasks with Parent-Child Relation" project_name = "Test Project with Template - Tasks with Parent-Child Relation"
if frappe.db.get_value('Project', {'project_name': project_name}, 'name'):
project_name = frappe.db.get_value('Project', {'project_name': project_name}, 'name')
frappe.db.sql(""" delete from tabTask where project = %s """, project_name) frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
frappe.delete_doc('Project', project_name) frappe.delete_doc('Project', project_name)
task1 = task_exists("Test Template Task Parent") task1 = task_exists("Test Template Task Parent")
if not task1: if not task1:
task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4) task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=10)
task2 = task_exists("Test Template Task Child 1") task2 = task_exists("Test Template Task Child 1")
if not task2: if not task2:
@ -53,7 +57,7 @@ class TestProject(unittest.TestCase):
tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc') tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc')
self.assertEqual(tasks[0].subject, 'Test Template Task Parent') self.assertEqual(tasks[0].subject, 'Test Template Task Parent')
self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4)) self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 10))
self.assertEqual(tasks[1].subject, 'Test Template Task Child 1') self.assertEqual(tasks[1].subject, 'Test Template Task Child 1')
self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3)) self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3))

View File

@ -737,28 +737,34 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.frm.trigger("item_code", cdt, cdn); this.frm.trigger("item_code", cdt, cdn);
} }
else { else {
var valid_serial_nos = [];
var serialnos = [];
// Replacing all occurences of comma with carriage return // Replacing all occurences of comma with carriage return
item.serial_no = item.serial_no.replace(/,/g, '\n'); item.serial_no = item.serial_no.replace(/,/g, '\n');
serialnos = item.serial_no.split("\n");
for (var i = 0; i < serialnos.length; i++) {
if (serialnos[i] != "") {
valid_serial_nos.push(serialnos[i]);
}
}
item.conversion_factor = item.conversion_factor || 1; item.conversion_factor = item.conversion_factor || 1;
refresh_field("serial_no", item.name, item.parentfield); refresh_field("serial_no", item.name, item.parentfield);
if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
frappe.model.set_value(item.doctype, item.name, setTimeout(() => {
"qty", valid_serial_nos.length / item.conversion_factor); me.update_qty(cdt, cdn);
frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); }, 10000);
} }
} }
} }
}, },
update_qty: function(cdt, cdn) {
var valid_serial_nos = [];
var serialnos = [];
var item = frappe.get_doc(cdt, cdn);
serialnos = item.serial_no.split("\n");
for (var i = 0; i < serialnos.length; i++) {
if (serialnos[i] != "") {
valid_serial_nos.push(serialnos[i]);
}
}
frappe.model.set_value(item.doctype, item.name,
"qty", valid_serial_nos.length / item.conversion_factor);
frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
},
validate: function() { validate: function() {
this.calculate_taxes_and_totals(false); this.calculate_taxes_and_totals(false);
}, },

View File

@ -787,6 +787,8 @@ class GSPConnector():
self.invoice.irn = res.get('Irn') self.invoice.irn = res.get('Irn')
self.invoice.ewaybill = res.get('EwbNo') self.invoice.ewaybill = res.get('EwbNo')
self.invoice.ack_no = res.get('AckNo')
self.invoice.ack_date = res.get('AckDt')
self.invoice.signed_einvoice = dec_signed_invoice self.invoice.signed_einvoice = dec_signed_invoice
self.invoice.signed_qr_code = res.get('SignedQRCode') self.invoice.signed_qr_code = res.get('SignedQRCode')

View File

@ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', {
method: "frappe.client.get_list", method: "frappe.client.get_list",
args: { args: {
doctype: "UOM Conversion Factor", doctype: "UOM Conversion Factor",
filters: { "category": "Length" }, filters: { "category": __("Length") },
fields: ["to_uom"], fields: ["to_uom"],
limit_page_length: 500 limit_page_length: 500
}, },

View File

@ -1,14 +1,14 @@
frappe.provide('erpnext.stock'); frappe.provide('erpnext.stock');
erpnext.stock.ItemDashboard = Class.extend({ erpnext.stock.ItemDashboard = Class.extend({
init: function(opts) { init: function (opts) {
$.extend(this, opts); $.extend(this, opts);
this.make(); this.make();
}, },
make: function() { make: function () {
var me = this; var me = this;
this.start = 0; this.start = 0;
if(!this.sort_by) { if (!this.sort_by) {
this.sort_by = 'projected_qty'; this.sort_by = 'projected_qty';
this.sort_order = 'asc'; this.sort_order = 'asc';
} }
@ -16,22 +16,25 @@ erpnext.stock.ItemDashboard = Class.extend({
this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent); this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent);
this.result = this.content.find('.result'); this.result = this.content.find('.result');
this.content.on('click', '.btn-move', function() { this.content.on('click', '.btn-move', function () {
handle_move_add($(this), "Move") handle_move_add($(this), "Move");
}); });
this.content.on('click', '.btn-add', function() { this.content.on('click', '.btn-add', function () {
handle_move_add($(this), "Add") handle_move_add($(this), "Add");
}); });
this.content.on('click', '.btn-edit', function() { this.content.on('click', '.btn-edit', function () {
let item = unescape($(this).attr('data-item')); let item = unescape($(this).attr('data-item'));
let warehouse = unescape($(this).attr('data-warehouse')); let warehouse = unescape($(this).attr('data-warehouse'));
let company = unescape($(this).attr('data-company')); let company = unescape($(this).attr('data-company'));
frappe.db.get_value('Putaway Rule', frappe.db.get_value('Putaway Rule', {
{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => { 'item_code': item,
frappe.set_route("Form", "Putaway Rule", r.name); 'warehouse': warehouse,
}); 'company': company
}, 'name', (r) => {
frappe.set_route("Form", "Putaway Rule", r.name);
});
}); });
function handle_move_add(element, action) { function handle_move_add(element, action) {
@ -39,23 +42,26 @@ erpnext.stock.ItemDashboard = Class.extend({
let warehouse = unescape(element.attr('data-warehouse')); let warehouse = unescape(element.attr('data-warehouse'));
let actual_qty = unescape(element.attr('data-actual_qty')); let actual_qty = unescape(element.attr('data-actual_qty'));
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry'))); let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
let entry_type = action === "Move" ? "Material Transfer": null; let entry_type = action === "Move" ? "Material Transfer" : null;
if (disable_quick_entry) { if (disable_quick_entry) {
open_stock_entry(item, warehouse, entry_type); open_stock_entry(item, warehouse, entry_type);
} else { } else {
if (action === "Add") { if (action === "Add") {
let rate = unescape($(this).attr('data-rate')); let rate = unescape($(this).attr('data-rate'));
erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); }); erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () {
} me.refresh();
else { });
erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); }); } else {
erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () {
me.refresh();
});
} }
} }
} }
function open_stock_entry(item, warehouse, entry_type) { function open_stock_entry(item, warehouse, entry_type) {
frappe.model.with_doctype('Stock Entry', function() { frappe.model.with_doctype('Stock Entry', function () {
var doc = frappe.model.get_new_doc('Stock Entry'); var doc = frappe.model.get_new_doc('Stock Entry');
if (entry_type) doc.stock_entry_type = entry_type; if (entry_type) doc.stock_entry_type = entry_type;
@ -64,18 +70,18 @@ erpnext.stock.ItemDashboard = Class.extend({
row.s_warehouse = warehouse; row.s_warehouse = warehouse;
frappe.set_route('Form', doc.doctype, doc.name); frappe.set_route('Form', doc.doctype, doc.name);
}) });
} }
// more // more
this.content.find('.btn-more').on('click', function() { this.content.find('.btn-more').on('click', function () {
me.start += me.page_length; me.start += me.page_length;
me.refresh(); me.refresh();
}); });
}, },
refresh: function() { refresh: function () {
if(this.before_refresh) { if (this.before_refresh) {
this.before_refresh(); this.before_refresh();
} }
@ -94,13 +100,13 @@ erpnext.stock.ItemDashboard = Class.extend({
frappe.call({ frappe.call({
method: this.method, method: this.method,
args: args, args: args,
callback: function(r) { callback: function (r) {
me.render(r.message); me.render(r.message);
} }
}); });
}, },
render: function(data) { render: function (data) {
if (this.start===0) { if (this.start === 0) {
this.max_count = 0; this.max_count = 0;
this.result.empty(); this.result.empty();
} }
@ -115,7 +121,7 @@ erpnext.stock.ItemDashboard = Class.extend({
this.max_count = this.max_count; this.max_count = this.max_count;
// show more button // show more button
if (data && data.length===(this.page_length + 1)) { if (data && data.length === (this.page_length + 1)) {
this.content.find('.more').removeClass('hidden'); this.content.find('.more').removeClass('hidden');
// remove the last element // remove the last element
@ -137,15 +143,15 @@ erpnext.stock.ItemDashboard = Class.extend({
} }
}, },
get_item_dashboard_data: function(data, max_count, show_item) { get_item_dashboard_data: function (data, max_count, show_item) {
if(!max_count) max_count = 0; if (!max_count) max_count = 0;
if(!data) data = []; if (!data) data = [];
data.forEach(function(d) { data.forEach(function (d) {
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract; d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
d.pending_qty = 0; d.pending_qty = 0;
d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract; d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
if(d.actual_or_pending > d.actual_qty) { if (d.actual_or_pending > d.actual_qty) {
d.pending_qty = d.actual_or_pending - d.actual_qty; d.pending_qty = d.actual_or_pending - d.actual_qty;
} }
@ -161,16 +167,16 @@ erpnext.stock.ItemDashboard = Class.extend({
return { return {
data: data, data: data,
max_count: max_count, max_count: max_count,
can_write:can_write, can_write: can_write,
show_item: show_item || false show_item: show_item || false
}; };
}, },
get_capacity_dashboard_data: function(data) { get_capacity_dashboard_data: function (data) {
if (!data) data = []; if (!data) data = [];
data.forEach(function(d) { data.forEach(function (d) {
d.color = d.percent_occupied >=80 ? "#f8814f" : "#2490ef"; d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef";
}); });
let can_write = 0; let can_write = 0;
@ -185,53 +191,77 @@ erpnext.stock.ItemDashboard = Class.extend({
} }
}); });
erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) { erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) {
var dialog = new frappe.ui.Dialog({ var dialog = new frappe.ui.Dialog({
title: target ? __('Add Item') : __('Move Item'), title: target ? __('Add Item') : __('Move Item'),
fields: [ fields: [{
{fieldname: 'item_code', label: __('Item'), fieldname: 'item_code',
fieldtype: 'Link', options: 'Item', read_only: 1}, label: __('Item'),
{fieldname: 'source', label: __('Source Warehouse'), fieldtype: 'Link',
fieldtype: 'Link', options: 'Warehouse', read_only: 1}, options: 'Item',
{fieldname: 'target', label: __('Target Warehouse'), read_only: 1
fieldtype: 'Link', options: 'Warehouse', reqd: 1}, },
{fieldname: 'qty', label: __('Quantity'), reqd: 1, {
fieldtype: 'Float', description: __('Available {0}', [actual_qty]) }, fieldname: 'source',
{fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 }, label: __('Source Warehouse'),
fieldtype: 'Link',
options: 'Warehouse',
read_only: 1
},
{
fieldname: 'target',
label: __('Target Warehouse'),
fieldtype: 'Link',
options: 'Warehouse',
reqd: 1
},
{
fieldname: 'qty',
label: __('Quantity'),
reqd: 1,
fieldtype: 'Float',
description: __('Available {0}', [actual_qty])
},
{
fieldname: 'rate',
label: __('Rate'),
fieldtype: 'Currency',
hidden: 1
},
], ],
}) });
dialog.show(); dialog.show();
dialog.get_field('item_code').set_input(item); dialog.get_field('item_code').set_input(item);
if(source) { if (source) {
dialog.get_field('source').set_input(source); dialog.get_field('source').set_input(source);
} else { } else {
dialog.get_field('source').df.hidden = 1; dialog.get_field('source').df.hidden = 1;
dialog.get_field('source').refresh(); dialog.get_field('source').refresh();
} }
if(rate) { if (rate) {
dialog.get_field('rate').set_value(rate); dialog.get_field('rate').set_value(rate);
dialog.get_field('rate').df.hidden = 0; dialog.get_field('rate').df.hidden = 0;
dialog.get_field('rate').refresh(); dialog.get_field('rate').refresh();
} }
if(target) { if (target) {
dialog.get_field('target').df.read_only = 1; dialog.get_field('target').df.read_only = 1;
dialog.get_field('target').value = target; dialog.get_field('target').value = target;
dialog.get_field('target').refresh(); dialog.get_field('target').refresh();
} }
dialog.set_primary_action(__('Submit'), function() { dialog.set_primary_action(__('Submit'), function () {
var values = dialog.get_values(); var values = dialog.get_values();
if(!values) { if (!values) {
return; return;
} }
if(source && values.qty > actual_qty) { if (source && values.qty > actual_qty) {
frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty])); frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty]));
return; return;
} }
if(values.source === values.target) { if (values.source === values.target) {
frappe.msgprint(__('Source and target warehouse must be different')); frappe.msgprint(__('Source and target warehouse must be different'));
} }
@ -239,21 +269,21 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
args: values, args: values,
freeze: true, freeze: true,
callback: function(r) { callback: function (r) {
frappe.show_alert(__('Stock Entry {0} created', frappe.show_alert(__('Stock Entry {0} created',
['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>'])); ['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
dialog.hide(); dialog.hide();
callback(r); callback(r);
}, },
}); });
}); });
$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' $('<p style="margin-left: 10px;"><a class="link-open text-muted small">' +
+ __("Add more items or open full form") + '</a></p>') __("Add more items or open full form") + '</a></p>')
.appendTo(dialog.body) .appendTo(dialog.body)
.find('.link-open') .find('.link-open')
.on('click', function() { .on('click', function () {
frappe.model.with_doctype('Stock Entry', function() { frappe.model.with_doctype('Stock Entry', function () {
var doc = frappe.model.get_new_doc('Stock Entry'); var doc = frappe.model.get_new_doc('Stock Entry');
doc.from_warehouse = dialog.get_value('source'); doc.from_warehouse = dialog.get_value('source');
doc.to_warehouse = dialog.get_value('target'); doc.to_warehouse = dialog.get_value('target');
@ -266,6 +296,6 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
row.transfer_qty = dialog.get_value('qty'); row.transfer_qty = dialog.get_value('qty');
row.basic_rate = dialog.get_value('rate'); row.basic_rate = dialog.get_value('rate');
frappe.set_route('Form', doc.doctype, doc.name); frappe.set_route('Form', doc.doctype, doc.name);
}) });
}); });
} };

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.db_query import DatabaseQuery from frappe.model.db_query import DatabaseQuery
from frappe.utils import flt, cint
@frappe.whitelist() @frappe.whitelist()
def get_data(item_code=None, warehouse=None, item_group=None, def get_data(item_code=None, warehouse=None, item_group=None,
@ -42,11 +43,20 @@ def get_data(item_code=None, warehouse=None, item_group=None,
limit_start=start, limit_start=start,
limit_page_length='21') limit_page_length='21')
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
for item in items: for item in items:
item.update({ item.update({
'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'), 'item_name': frappe.get_cached_value(
'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no') "Item", item.item_code, 'item_name'),
or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'), 'disable_quick_entry': frappe.get_cached_value(
"Item", item.item_code, 'has_batch_no')
or frappe.get_cached_value(
"Item", item.item_code, 'has_serial_no'),
'projected_qty': flt(item.projected_qty, precision),
'reserved_qty': flt(item.reserved_qty, precision),
'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision),
'actual_qty': flt(item.actual_qty, precision),
}) })
return items return items

View File

@ -494,7 +494,8 @@ def make_item_variant():
test_records = frappe.get_test_records('Item') test_records = frappe.get_test_records('Item')
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=None): def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None,
customer=None, is_purchase_item=None, opening_stock=None, company=None):
if not frappe.db.exists("Item", item_code): if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item") item = frappe.new_doc("Item")
item.item_code = item_code item.item_code = item_code
@ -509,7 +510,7 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None,
item.customer = customer or '' item.customer = customer or ''
item.append("item_defaults", { item.append("item_defaults", {
"default_warehouse": warehouse or '_Test Warehouse - _TC', "default_warehouse": warehouse or '_Test Warehouse - _TC',
"company": "_Test Company" "company": company or "_Test Company"
}) })
item.save() item.save()
else: else:

View File

@ -346,7 +346,7 @@ def create_delivery_note(source_name, target_doc=None):
if dn_item: if dn_item:
dn_item.warehouse = location.warehouse dn_item.warehouse = location.warehouse
dn_item.qty = location.picked_qty dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
dn_item.batch_no = location.batch_no dn_item.batch_no = location.batch_no
dn_item.serial_no = location.serial_no dn_item.serial_no = location.serial_no

View File

@ -9,6 +9,7 @@ test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
import EmptyStockReconciliationItemsError import EmptyStockReconciliationItemsError
@ -291,6 +292,61 @@ class TestPickList(unittest.TestCase):
self.assertEqual(pick_list.locations[1].qty, 5) self.assertEqual(pick_list.locations[1].qty, 5)
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name) self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
def test_pick_list_for_items_with_multiple_UOM(self):
purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10)
purchase_receipt.submit()
sales_order = frappe.get_doc({
'doctype': 'Sales Order',
'customer': '_Test Customer',
'company': '_Test Company',
'items': [{
'item_code': '_Test Item',
'qty': 1,
'conversion_factor': 5,
'delivery_date': frappe.utils.today()
}, {
'item_code': '_Test Item',
'qty': 1,
'conversion_factor': 1,
'delivery_date': frappe.utils.today()
}],
}).insert()
sales_order.submit()
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Item',
'qty': 1,
'stock_qty': 5,
'conversion_factor': 5,
'sales_order': sales_order.name,
'sales_order_item': sales_order.items[0].name ,
}, {
'item_code': '_Test Item',
'qty': 1,
'stock_qty': 1,
'conversion_factor': 1,
'sales_order': sales_order.name,
'sales_order_item': sales_order.items[1].name ,
}]
})
pick_list.set_item_locations()
pick_list.submit()
delivery_note = create_delivery_note(pick_list.name)
self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
pick_list.cancel()
sales_order.cancel()
purchase_receipt.cancel()
# def test_pick_list_skips_items_in_expired_batch(self): # def test_pick_list_skips_items_in_expired_batch(self):
# pass # pass

View File

@ -179,11 +179,15 @@ class TestStockEntry(unittest.TestCase):
def test_material_transfer_gl_entry(self): def test_material_transfer_gl_entry(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1", item_code = 'Hand Sanitizer - 001'
create_item(item_code =item_code, is_stock_item = 1,
is_purchase_item=1, opening_stock=1000, valuation_rate=10, company=company, warehouse="Stores - TCP1")
mtn = make_stock_entry(item_code=item_code, source="Stores - TCP1",
target="Finished Goods - TCP1", qty=45, company=company) target="Finished Goods - TCP1", qty=45, company=company)
self.check_stock_ledger_entries("Stock Entry", mtn.name, self.check_stock_ledger_entries("Stock Entry", mtn.name,
[["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]]) [[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]])
source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse) source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)