Merge branch 'develop' of https://github.com/frappe/erpnext into feat-customer-credit-limit

This commit is contained in:
Mangesh-Khairnar 2019-09-02 23:18:56 +05:30
commit 4bcc221ee3
61 changed files with 3038 additions and 5293 deletions

View File

@ -239,8 +239,6 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
jv.cancel() jv.cancel()
frappe.delete_doc('Journal Entry', jv.name)
frappe.delete_doc('Cost Center', cost_center)
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "Project": if budget_against_field == "Project":

View File

@ -49,7 +49,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete # cancel and delete
for d in [si_redeem, si_original]: for d in [si_redeem, si_original]:
d.cancel() d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_earned_multiple_tier(self): def test_loyalty_points_earned_multiple_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty") frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
@ -91,7 +90,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete # cancel and delete
for d in [si_redeem, si_original]: for d in [si_redeem, si_original]:
d.cancel() d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_cancel_sales_invoice(self): def test_cancel_sales_invoice(self):
''' cancelling the sales invoice should cancel the earned points''' ''' cancelling the sales invoice should cancel the earned points'''
@ -143,7 +141,6 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel() d.cancel()
except frappe.TimestampMismatchError: except frappe.TimestampMismatchError:
frappe.get_doc('Sales Invoice', d.name).cancel() frappe.get_doc('Sales Invoice', d.name).cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_for_dashboard(self): def test_loyalty_points_for_dashboard(self):
doc = frappe.get_doc('Customer', 'Test Loyalty Customer') doc = frappe.get_doc('Customer', 'Test Loyalty Customer')

View File

@ -947,10 +947,15 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
paid_amount = abs(outstanding_amount) paid_amount = abs(outstanding_amount)
if bank_amount: if bank_amount:
received_amount = bank_amount received_amount = bank_amount
else:
received_amount = paid_amount * doc.conversion_rate
else: else:
received_amount = abs(outstanding_amount) received_amount = abs(outstanding_amount)
if bank_amount: if bank_amount:
paid_amount = bank_amount paid_amount = bank_amount
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.conversion_rate
pe = frappe.new_doc("Payment Entry") pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type pe.payment_type = payment_type

View File

@ -227,7 +227,7 @@ def get_contacts(customers):
customers = [frappe._dict({'name': customers})] customers = [frappe._dict({'name': customers})]
for data in customers: for data in customers:
contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` contact = frappe.db.sql(""" select email_id, phone from `tabContact`
where is_primary_contact=1 and name in where is_primary_contact=1 and name in
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Contact')""", data.name, as_dict=1) and parenttype = 'Contact')""", data.name, as_dict=1)

View File

@ -818,7 +818,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[i][2], gle.credit) self.assertEqual(expected_gl_entries[i][2], gle.credit)
si.cancel() si.cancel()
frappe.delete_doc('Sales Invoice', si.name)
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)

View File

@ -48,7 +48,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
#delete invoices to avoid clashing #delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds(self): def test_single_threshold_tds(self):
invoices = [] invoices = []
@ -83,7 +82,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing # delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds_with_previous_vouchers(self): def test_single_threshold_tds_with_previous_vouchers(self):
invoices = [] invoices = []
@ -102,7 +100,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing # delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def create_purchase_invoice(**args): def create_purchase_invoice(**args):
# return sales invoice doc object # return sales invoice doc object

View File

@ -816,7 +816,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
contact = me.contacts[data.name]; contact = me.contacts[data.name];
if(reg.test(data.name.toLowerCase()) if(reg.test(data.name.toLowerCase())
|| reg.test(data.customer_name.toLowerCase()) || reg.test(data.customer_name.toLowerCase())
|| (contact && reg.test(contact["mobile_no"]))
|| (contact && reg.test(contact["phone"])) || (contact && reg.test(contact["phone"]))
|| (data.customer_group && reg.test(data.customer_group.toLowerCase()))){ || (data.customer_group && reg.test(data.customer_group.toLowerCase()))){
return data; return data;
@ -834,7 +833,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if(contact && !c['phone']) { if(contact && !c['phone']) {
c["phone"] = contact["phone"]; c["phone"] = contact["phone"];
c["email_id"] = contact["email_id"]; c["email_id"] = contact["email_id"];
c["mobile_no"] = contact["mobile_no"];
} }
me.customers_mapper.push({ me.customers_mapper.push({
@ -844,10 +842,9 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
customer_group: c.customer_group, customer_group: c.customer_group,
territory: c.territory, territory: c.territory,
phone: contact ? contact["phone"] : '', phone: contact ? contact["phone"] : '',
mobile_no: contact ? contact["mobile_no"] : '',
email_id: contact ? contact["email_id"] : '', email_id: contact ? contact["email_id"] : '',
searchtext: ['customer_name', 'customer_group', 'name', 'value', searchtext: ['customer_name', 'customer_group', 'name', 'value',
'label', 'email_id', 'phone', 'mobile_no'] 'label', 'email_id', 'phone']
.map(key => c[key]).join(' ') .map(key => c[key]).join(' ')
.toLowerCase() .toLowerCase()
}); });

View File

@ -456,8 +456,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle) self.assertEqual(gle, expected_gle)
si.cancel() si.cancel()
frappe.delete_doc("Sales Invoice", si.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_expected_value_after_useful_life(self): def test_asset_expected_value_after_useful_life(self):

View File

@ -0,0 +1,8 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Buying Settings', {
// refresh: function(frm) {
// }
});

View File

@ -1,379 +1,111 @@
{ {
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-06-25 11:04:03", "creation": "2013-06-25 11:04:03",
"custom": 0,
"description": "Settings for Buying Module", "description": "Settings for Buying Module",
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
"editable_grid": 0, "field_order": [
"supp_master_name",
"supplier_group",
"buying_price_list",
"column_break_3",
"po_required",
"pr_required",
"maintain_same_rate",
"allow_multiple_items",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
"over_transfer_allowance"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Supplier Name", "default": "Supplier Name",
"fieldname": "supp_master_name", "fieldname": "supp_master_name",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier Naming By", "label": "Supplier Naming By",
"length": 0, "options": "Supplier Name\nNaming Series"
"no_copy": 0,
"options": "Supplier Name\nNaming Series",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "supplier_group", "fieldname": "supplier_group",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Supplier Group", "label": "Default Supplier Group",
"length": 0, "options": "Supplier Group"
"no_copy": 0,
"options": "Supplier Group",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "buying_price_list", "fieldname": "buying_price_list",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Buying Price List", "label": "Default Buying Price List",
"length": 0, "options": "Price List"
"no_copy": 0,
"options": "Price List",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "po_required", "fieldname": "po_required",
"fieldtype": "Select", "fieldtype": "Select",
"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": "Purchase Order Required", "label": "Purchase Order Required",
"length": 0, "options": "No\nYes"
"no_copy": 0,
"options": "No\nYes",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "pr_required", "fieldname": "pr_required",
"fieldtype": "Select", "fieldtype": "Select",
"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": "Purchase Receipt Required", "label": "Purchase Receipt Required",
"length": 0, "options": "No\nYes"
"no_copy": 0,
"options": "No\nYes",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "maintain_same_rate", "fieldname": "maintain_same_rate",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Maintain same rate throughout purchase cycle"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Maintain same rate throughout purchase cycle",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_multiple_items", "fieldname": "allow_multiple_items",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Allow Item to be added multiple times in a transaction"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Item to be added multiple times in a transaction",
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subcontract", "fieldname": "subcontract",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Subcontract"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subcontract",
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Material Transferred for Subcontract", "default": "Material Transferred for Subcontract",
"fieldname": "backflush_raw_materials_of_subcontract_based_on", "fieldname": "backflush_raw_materials_of_subcontract_based_on",
"fieldtype": "Select", "fieldtype": "Select",
"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": "Backflush Raw Materials of Subcontract Based On", "label": "Backflush Raw Materials of Subcontract Based On",
"length": 0, "options": "BOM\nMaterial Transferred for Subcontract"
"no_copy": 0, },
"options": "BOM\nMaterial Transferred for Subcontract", {
"permlevel": 0, "depends_on": "eval:doc.backflush_raw_materials_of_subcontract_based_on == \"BOM\"",
"precision": "", "description": "Percentage you are allowed to transfer more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to transfer 110 units.",
"print_hide": 0, "fieldname": "over_transfer_allowance",
"print_hide_if_no_value": 0, "fieldtype": "Float",
"read_only": 0, "label": "Over Transfer Allowance (%)"
"remember_last_selected_value": 0, },
"report_hide": 0, {
"reqd": 0, "fieldname": "column_break_11",
"search_index": 0, "fieldtype": "Column Break"
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-cog", "icon": "fa fa-cog",
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "modified": "2019-08-20 13:13:09.055189",
"max_attachments": 0,
"modified": "2018-07-31 07:52:38.062488",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying Settings", "name": "Buying Settings",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 0,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ]
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0
} }

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestBuyingSettings(unittest.TestCase):
pass

View File

@ -477,6 +477,7 @@ def make_rm_stock_entry(purchase_order, rm_items):
rm_item_code = rm_item_data["rm_item_code"] rm_item_code = rm_item_data["rm_item_code"]
items_dict = { items_dict = {
rm_item_code: { rm_item_code: {
"po_detail": rm_item_data.get("name"),
"item_name": rm_item_data["item_name"], "item_name": rm_item_data["item_name"],
"description": item_wh.get(rm_item_code, {}).get('description', ""), "description": item_wh.get(rm_item_code, {}).get('description', ""),
'qty': rm_item_data["qty"], 'qty': rm_item_data["qty"],

View File

@ -1,404 +1,134 @@
{ {
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:42", "creation": "2013-02-22 01:27:42",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"field_order": [
"main_item_code",
"rm_item_code",
"required_qty",
"supplied_qty",
"rate",
"amount",
"column_break_6",
"bom_detail_no",
"reference_name",
"conversion_factor",
"stock_uom",
"reserve_warehouse"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "main_item_code", "fieldname": "main_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code", "label": "Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "main_item_code", "oldfieldname": "main_item_code",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "rm_item_code", "fieldname": "rm_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Raw Material Item Code", "label": "Raw Material Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "rm_item_code", "oldfieldname": "rm_item_code",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "required_qty", "fieldname": "required_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Required Qty",
"label": "Supplied Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "required_qty", "oldfieldname": "required_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Rate", "label": "Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "rate", "oldfieldname": "rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency"
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount", "fieldname": "amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"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": "Amount", "label": "Amount",
"length": 0,
"no_copy": 0,
"oldfieldname": "amount", "oldfieldname": "amount",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6", "fieldname": "column_break_6",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bom_detail_no", "fieldname": "bom_detail_no",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "BOM Detail No", "label": "BOM Detail No",
"length": 0,
"no_copy": 0,
"oldfieldname": "bom_detail_no", "oldfieldname": "bom_detail_no",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Name", "label": "Reference Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "reference_name", "oldfieldname": "reference_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conversion_factor", "fieldname": "conversion_factor",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Conversion Factor", "label": "Conversion Factor",
"length": 0,
"no_copy": 0,
"oldfieldname": "conversion_factor", "oldfieldname": "conversion_factor",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_uom", "fieldname": "stock_uom",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Stock Uom", "label": "Stock Uom",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_uom", "oldfieldname": "stock_uom",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "UOM", "options": "UOM",
"permlevel": 0, "read_only": 1
"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,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "reserve_warehouse", "fieldname": "reserve_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Reserve Warehouse", "label": "Reserve Warehouse",
"length": 0, "options": "Warehouse"
"no_copy": 0, },
"options": "Warehouse", {
"permlevel": 0, "fieldname": "supplied_qty",
"precision": "", "fieldtype": "Float",
"print_hide": 0, "in_list_view": 1,
"print_hide_if_no_value": 0, "label": "Supplied Qty",
"read_only": 0, "read_only": 1
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 1, "hide_toolbar": 1,
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "modified": "2019-08-20 13:37:32.702068",
"modified": "2019-01-07 16:51:58.016007",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item Supplied", "name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com", "owner": "dhanalekshmi@webnotestech.com",
"permissions": [], "permissions": []
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -73,6 +73,10 @@ def set_caller_information(doc, state):
# contact_name or lead_name # contact_name or lead_name
display_name_field = '{}_name'.format(fieldname) display_name_field = '{}_name'.format(fieldname)
# Contact now has all the nos saved in child table
if doc.doctype == 'Contact':
numbers = [d.phone for d in doc.phone_nos]
for number in numbers: for number in numbers:
number = strip_number(number) number = strip_number(number)
if not number: continue if not number: continue

View File

@ -14,6 +14,12 @@ def get_data():
"dependencies": ["Item", "Supplier"], "dependencies": ["Item", "Supplier"],
"description": _("Purchase Orders given to Suppliers."), "description": _("Purchase Orders given to Suppliers."),
}, },
{
"type": "doctype",
"name": "Purchase Invoice",
"onboard": 1,
"dependencies": ["Item", "Supplier"]
},
{ {
"type": "doctype", "type": "doctype",
"name": "Material Request", "name": "Material Request",

View File

@ -304,12 +304,6 @@ def get_data():
"name": "Customers Without Any Sales Transactions", "name": "Customers Without Any Sales Transactions",
"doctype": "Customer" "doctype": "Customer"
}, },
{
"type": "report",
"is_query_report": True,
"name": "Sales Partners Commission",
"doctype": "Customer"
},
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,

View File

@ -30,6 +30,12 @@ def get_data():
"onboard": 1, "onboard": 1,
"dependencies": ["Item"], "dependencies": ["Item"],
}, },
{
"type": "doctype",
"name": "Pick List",
"onboard": 1,
"dependencies": ["Item"],
},
{ {
"type": "doctype", "type": "doctype",
"name": "Delivery Trip" "name": "Delivery Trip"

View File

@ -263,7 +263,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"): if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self) ret = get_item_details(args, self, overwrite_warehouse=False)
for fieldname, value in ret.items(): for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None: if item.meta.get_field(fieldname) and value is not None:

View File

@ -394,8 +394,8 @@ class SellingController(StockController):
elif self.doctype == "Delivery Note": elif self.doctype == "Delivery Note":
e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or ''] e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice] f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
elif self.doctype == "Sales Order": elif self.doctype in ["Sales Order", "Quotation"]:
e = [d.item_code, d.description, d.warehouse, d.batch_no or ''] e = [d.item_code, d.description, d.warehouse, '']
f = [d.item_code, d.description] f = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1: if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:

View File

@ -45,15 +45,16 @@ class TestOpportunity(unittest.TestCase):
# create new customer and create new contact against 'new.opportunity@example.com' # create new customer and create new contact against 'new.opportunity@example.com'
customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True) customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True)
frappe.get_doc({ contact = frappe.get_doc({
"doctype": "Contact", "doctype": "Contact",
"email_id": new_lead_email_id,
"first_name": "_Test Opportunity Customer", "first_name": "_Test Opportunity Customer",
"links": [{ "links": [{
"link_doctype": "Customer", "link_doctype": "Customer",
"link_name": customer.name "link_name": customer.name
}] }]
}).insert(ignore_permissions=True) })
contact.add_email(new_lead_email_id)
contact.insert(ignore_permissions=True)
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True) opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.party_name) self.assertTrue(opp_doc.party_name)

View File

@ -3,7 +3,6 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document from frappe.model.document import Document
import requests import requests
import frappe import frappe

View File

@ -58,7 +58,7 @@ class ShopifySettings(Document):
d.raise_for_status() d.raise_for_status()
self.update_webhook_table(method, d.json()) self.update_webhook_table(method, d.json())
except Exception as e: except Exception as e:
make_shopify_log(status="Warning", message=e.message, exception=False) make_shopify_log(status="Warning", message=e, exception=False)
def unregister_webhooks(self): def unregister_webhooks(self):
session = get_request_session() session = get_request_session()
@ -71,7 +71,7 @@ class ShopifySettings(Document):
res.raise_for_status() res.raise_for_status()
deleted_webhooks.append(d) deleted_webhooks.append(d)
except Exception as e: except Exception as e:
frappe.log_error(message=frappe.get_traceback(), title=e.message[:140]) frappe.log_error(message=frappe.get_traceback(), title=e)
for d in deleted_webhooks: for d in deleted_webhooks:
self.remove(d) self.remove(d)

View File

@ -45,7 +45,6 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
expense_claim2.cancel() expense_claim2.cancel()
frappe.delete_doc("Expense Claim", expense_claim2.name)
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200) self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)

View File

@ -95,7 +95,6 @@ def process_expired_allocation():
'expire_carry_forwarded_leaves_after_days': (">", 0) 'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name']) }, fieldname=['name'])
if leave_type_records:
leave_type = [record[0] for record in leave_type_records] leave_type = [record[0] for record in leave_type_records]
expired_allocation = frappe.db.sql_list("""SELECT name expired_allocation = frappe.db.sql_list("""SELECT name

View File

@ -30,7 +30,7 @@ class LoanApplication(Document):
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100) monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
if monthly_interest_rate: if monthly_interest_rate:
min_repayment_amount = self.loan_amount*monthly_interest_rate min_repayment_amount = self.loan_amount*monthly_interest_rate
if self.repayment_amount - min_repayment_amount <= 0: if (self.repayment_amount - min_repayment_amount) <= 0:
frappe.throw(_("Repayment Amount must be greater than " \ frappe.throw(_("Repayment Amount must be greater than " \
+ str(flt(min_repayment_amount, 2)))) + str(flt(min_repayment_amount, 2))))
self.repayment_periods = math.ceil((math.log(self.repayment_amount) - self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
@ -58,6 +58,9 @@ def make_loan(source_name, target_doc = None):
doclist = get_mapped_doc("Loan Application", source_name, { doclist = get_mapped_doc("Loan Application", source_name, {
"Loan Application": { "Loan Application": {
"doctype": "Loan", "doctype": "Loan",
"field_map": {
"repayment_amount": "monthly_repayment_amount"
},
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }

View File

@ -68,12 +68,13 @@ def make_contact(supplier):
contact = frappe.get_doc({ contact = frappe.get_doc({
'doctype': 'Contact', 'doctype': 'Contact',
'first_name': supplier.supplier_name, 'first_name': supplier.supplier_name,
'email_id': supplier.supplier_email,
'is_primary_contact': 1, 'is_primary_contact': 1,
'links': [ 'links': [
{'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
] ]
}).insert() })
contact.add_email(supplier.supplier_email)
contact.insert()
else: else:
contact = frappe.get_doc('Contact', contact_name) contact = frappe.get_doc('Contact', contact_name)

View File

@ -4,16 +4,17 @@
frappe.ui.form.on("Work Order", { frappe.ui.form.on("Work Order", {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Stock Entry': 'Make Stock Entry', 'Stock Entry': 'Start',
} 'Pick List': 'Create Pick List',
};
// Set query for warehouses // Set query for warehouses
frm.set_query("wip_warehouse", function(doc) { frm.set_query("wip_warehouse", function() {
return { return {
filters: { filters: {
'company': frm.doc.company, 'company': frm.doc.company,
} }
} };
}); });
frm.set_query("source_warehouse", function() { frm.set_query("source_warehouse", function() {
@ -21,7 +22,7 @@ frappe.ui.form.on("Work Order", {
filters: { filters: {
'company': frm.doc.company, 'company': frm.doc.company,
} }
} };
}); });
frm.set_query("source_warehouse", "required_items", function() { frm.set_query("source_warehouse", "required_items", function() {
@ -29,7 +30,7 @@ frappe.ui.form.on("Work Order", {
filters: { filters: {
'company': frm.doc.company, 'company': frm.doc.company,
} }
} };
}); });
frm.set_query("sales_order", function() { frm.set_query("sales_order", function() {
@ -37,7 +38,7 @@ frappe.ui.form.on("Work Order", {
filters: { filters: {
"status": ["not in", ["Closed", "On Hold"]] "status": ["not in", ["Closed", "On Hold"]]
} }
} };
}); });
frm.set_query("fg_warehouse", function() { frm.set_query("fg_warehouse", function() {
@ -46,7 +47,7 @@ frappe.ui.form.on("Work Order", {
'company': frm.doc.company, 'company': frm.doc.company,
'is_group': 0 'is_group': 0
} }
} };
}); });
frm.set_query("scrap_warehouse", function() { frm.set_query("scrap_warehouse", function() {
@ -55,7 +56,7 @@ frappe.ui.form.on("Work Order", {
'company': frm.doc.company, 'company': frm.doc.company,
'is_group': 0 'is_group': 0
} }
} };
}); });
// Set query for BOM // Set query for BOM
@ -64,8 +65,10 @@ frappe.ui.form.on("Work Order", {
return { return {
query: "erpnext.controllers.queries.bom", query: "erpnext.controllers.queries.bom",
filters: {item: cstr(frm.doc.production_item)} filters: {item: cstr(frm.doc.production_item)}
};
} else {
frappe.msgprint(__("Please enter Production Item first"));
} }
} else msgprint(__("Please enter Production Item first"));
}); });
// Set query for FG Item // Set query for FG Item
@ -76,7 +79,7 @@ frappe.ui.form.on("Work Order", {
['is_stock_item', '=',1], ['is_stock_item', '=',1],
['default_bom', '!=', ''] ['default_bom', '!=', '']
] ]
} };
}); });
// Set query for FG Item // Set query for FG Item
@ -85,12 +88,12 @@ frappe.ui.form.on("Work Order", {
filters:[ filters:[
['Project', 'status', 'not in', 'Completed, Cancelled'] ['Project', 'status', 'not in', 'Completed, Cancelled']
] ]
} };
}); });
// formatter for work order operation // formatter for work order operation
frm.set_indicator_formatter('operation', frm.set_indicator_formatter('operation',
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" }); function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange"; });
}, },
onload: function(frm) { onload: function(frm) {
@ -133,7 +136,7 @@ frappe.ui.form.on("Work Order", {
if(not_completed && not_completed.length) { if(not_completed && not_completed.length) {
frm.add_custom_button(__('Create Job Card'), () => { frm.add_custom_button(__('Create Job Card'), () => {
frm.trigger("make_job_card") frm.trigger("make_job_card");
}).addClass('btn-primary'); }).addClass('btn-primary');
} }
} }
@ -151,7 +154,7 @@ frappe.ui.form.on("Work Order", {
condition: (d) => { condition: (d) => {
if (d.allow_alternative_item) {return true;} if (d.allow_alternative_item) {return true;}
} }
}) });
}); });
} }
} }
@ -285,13 +288,13 @@ frappe.ui.form.on("Work Order", {
if(!frm.doc.skip_transfer){ if(!frm.doc.skip_transfer){
var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty; var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty;
if(pending_complete) { if(pending_complete) {
var title = __('{0} items in progress', [pending_complete]);
var width = ((pending_complete / frm.doc.qty * 100) - added_min); var width = ((pending_complete / frm.doc.qty * 100) - added_min);
title = __('{0} items in progress', [pending_complete]);
bars.push({ bars.push({
'title': title, 'title': title,
'width': (width > 100 ? "99.5" : width) + '%', 'width': (width > 100 ? "99.5" : width) + '%',
'progress_class': 'progress-bar-warning' 'progress_class': 'progress-bar-warning'
}) });
message = message + '. ' + title; message = message + '. ' + title;
} }
} }
@ -377,7 +380,7 @@ frappe.ui.form.on("Work Order", {
filters: [ filters: [
["Sales Order","name", "in", r.message] ["Sales Order","name", "in", r.message]
] ]
} };
}); });
} }
}); });
@ -401,10 +404,10 @@ frappe.ui.form.on("Work Order Item", {
frappe.model.set_value(row.doctype, row.name, frappe.model.set_value(row.doctype, row.name,
"available_qty_at_source_warehouse", r.message); "available_qty_at_source_warehouse", r.message);
} }
}) });
} }
} }
}) });
frappe.ui.form.on("Work Order Operation", { frappe.ui.form.on("Work Order Operation", {
workstation: function(frm, cdt, cdn) { workstation: function(frm, cdt, cdn) {
@ -421,7 +424,7 @@ frappe.ui.form.on("Work Order Operation", {
erpnext.work_order.calculate_cost(frm.doc); erpnext.work_order.calculate_cost(frm.doc);
erpnext.work_order.calculate_total_cost(frm); erpnext.work_order.calculate_total_cost(frm);
} }
}) });
} }
}, },
time_in_mins: function(frm, cdt, cdn) { time_in_mins: function(frm, cdt, cdn) {
@ -451,6 +454,9 @@ erpnext.work_order = {
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
&& frm.doc.status != 'Stopped') { && frm.doc.status != 'Stopped') {
frm.has_start_btn = true; frm.has_start_btn = true;
frm.add_custom_button(__('Create Pick List'), function() {
erpnext.work_order.create_pick_list(frm);
});
var start_btn = frm.add_custom_button(__('Start'), function() { var start_btn = frm.add_custom_button(__('Start'), function() {
erpnext.work_order.make_se(frm, 'Material Transfer for Manufacture'); erpnext.work_order.make_se(frm, 'Material Transfer for Manufacture');
}); });
@ -519,8 +525,8 @@ erpnext.work_order = {
calculate_total_cost: function(frm) { calculate_total_cost: function(frm) {
var variable_cost = frm.doc.actual_operating_cost ? var variable_cost = frm.doc.actual_operating_cost ?
flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost) flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost);
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost));
}, },
set_default_warehouse: function(frm) { set_default_warehouse: function(frm) {
@ -530,43 +536,70 @@ erpnext.work_order = {
callback: function(r) { callback: function(r) {
if (!r.exe) { if (!r.exe) {
frm.set_value("wip_warehouse", r.message.wip_warehouse); frm.set_value("wip_warehouse", r.message.wip_warehouse);
frm.set_value("fg_warehouse", r.message.fg_warehouse) frm.set_value("fg_warehouse", r.message.fg_warehouse);
} }
} }
}); });
} }
}, },
get_max_transferable_qty: (frm, purpose) => {
let max = 0;
if (frm.doc.skip_transfer) return max;
if (purpose === 'Manufacture') {
max = flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty);
} else {
max = flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
}
return flt(max, precision('qty'));
},
show_prompt_for_qty_input: function(frm, purpose) {
let max = this.get_max_transferable_qty(frm, purpose);
return new Promise((resolve, reject) => {
frappe.prompt({
fieldtype: 'Float',
label: __('Qty for {0}', [purpose]),
fieldname: 'qty',
description: __('Max: {0}', [max]),
default: max
}, data => {
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
reject();
}
data.purpose = purpose;
resolve(data);
}, __('Select Quantity'), __('Create'));
});
},
make_se: function(frm, purpose) { make_se: function(frm, purpose) {
if(!frm.doc.skip_transfer){ this.show_prompt_for_qty_input(frm, purpose)
var max = (purpose === "Manufacture") ? .then(data => {
flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) : return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry', {
flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing); 'work_order_id': frm.doc.name,
} else { 'purpose': purpose,
var max = flt(frm.doc.qty) - flt(frm.doc.produced_qty); 'qty': data.qty
} });
}).then(stock_entry => {
max = flt(max, precision("qty")); frappe.model.sync(stock_entry);
frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty", frappe.set_route('Form', stock_entry.doctype, stock_entry.name);
description: __("Max: {0}", [max]), 'default': max }, function(data) });
{
if(data.qty > max) { },
frappe.msgprint(__("Quantity must not be more than {0}", [max]));
return; create_pick_list: function(frm, purpose='Material Transfer for Manufacture') {
} this.show_prompt_for_qty_input(frm, purpose)
frappe.call({ .then(data => {
method:"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.create_pick_list', {
args: { 'source_name': frm.doc.name,
"work_order_id": frm.doc.name, 'for_qty': data.qty
"purpose": purpose, });
"qty": data.qty }).then(pick_list => {
}, frappe.model.sync(pick_list);
callback: function(r) { frappe.set_route('Form', pick_list.doctype, pick_list.name);
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
}); });
}, __("Select Quantity"), __('Create'));
}, },
make_consumption_se: function(frm, backflush_raw_materials_based_on) { make_consumption_se: function(frm, backflush_raw_materials_based_on) {
@ -606,6 +639,6 @@ erpnext.work_order = {
frm.reload_doc(); frm.reload_doc();
} }
} }
}) });
}
} }
};

View File

@ -72,6 +72,7 @@
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Series", "label": "Series",
"no_copy": 1,
"options": "MFG-WO-.YYYY.-", "options": "MFG-WO-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"reqd": 1, "reqd": 1,
@ -467,7 +468,7 @@
"idx": 1, "idx": 1,
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-07-31 00:13:38.218277", "modified": "2019-08-28 12:29:35.315239",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order", "name": "Work Order",

View File

@ -19,6 +19,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
from frappe.utils.csvutils import getlink from frappe.utils.csvutils import getlink
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
from erpnext.utilities.transaction_base import validate_uom_is_integer from erpnext.utilities.transaction_base import validate_uom_is_integer
from frappe.model.mapper import get_mapped_doc
class OverProductionError(frappe.ValidationError): pass class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass
@ -707,3 +708,46 @@ def get_work_order_operation_data(work_order, operation, workstation):
for d in work_order.operations: for d in work_order.operations:
if d.operation == operation and d.workstation == workstation: if d.operation == operation and d.workstation == workstation:
return d return d
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None, for_qty=None):
for_qty = for_qty or json.loads(target_doc).get('for_qty')
max_finished_goods_qty = frappe.db.get_value('Work Order', source_name, 'qty')
def update_item_quantity(source, target, source_parent):
pending_to_issue = flt(source.required_qty) - flt(source.transferred_qty)
desire_to_transfer = flt(source.required_qty) / max_finished_goods_qty * flt(for_qty)
qty = 0
if desire_to_transfer <= pending_to_issue:
qty = desire_to_transfer
elif pending_to_issue > 0:
qty = pending_to_issue
if qty:
target.qty = qty
target.stock_qty = qty
target.uom = frappe.get_value('Item', source.item_code, 'stock_uom')
target.stock_uom = target.uom
target.conversion_factor = 1
else:
target.delete()
doc = get_mapped_doc('Work Order', source_name, {
'Work Order': {
'doctype': 'Pick List',
'validation': {
'docstatus': ['=', 1]
}
},
'Work Order Item': {
'doctype': 'Pick List Item',
'postprocess': update_item_quantity,
'condition': lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty)
},
}, target_doc)
doc.for_qty = for_qty
doc.set_item_locations()
return doc

View File

@ -6,7 +6,7 @@ def get_data():
'fieldname': 'work_order', 'fieldname': 'work_order',
'transactions': [ 'transactions': [
{ {
'items': ['Stock Entry', 'Job Card'] 'items': ['Pick List', 'Stock Entry', 'Job Card']
} }
] ]
} }

View File

@ -96,7 +96,7 @@ class Timesheet(Document):
for time in self.time_logs: for time in self.time_logs:
if time.from_time and time.to_time: if time.from_time and time.to_time:
if flt(std_working_hours) > 0: if flt(std_working_hours) and date_diff(time.to_time, time.from_time):
time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time) time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time)
else: else:
if not time.hours: if not time.hours:

View File

@ -14,20 +14,33 @@
style="margin-top:-3px; margin-right: -5px;"> style="margin-top:-3px; margin-right: -5px;">
{%= __("Edit") %}</a> {%= __("Edit") %}</a>
</p> </p>
{% if (contact_list[i].phone || contact_list[i].mobile_no || {% if (contact_list[i].phones || contact_list[i].email_ids) { %}
contact_list[i].email_id) { %}
<p> <p>
{% if(contact_list[i].phone) { %} {% if(contact_list[i].phone) { %}
{%= __("Phone") %}: {%= contact_list[i].phone %}<br> {%= __("Phone") %}: {%= contact_list[i].phone %}<span class="text-muted"> ({%= __("Primary") %})</span><br>
{% } %} {% endif %}
{% if(contact_list[i].mobile_no) { %} {% if(contact_list[i].phone_nos) { %}
{%= __("Mobile No.") %}: {%= contact_list[i].mobile_no %}<br> {% for(var j=0, k=contact_list[i].phone_nos.length; j<k; j++) { %}
{%= __("Phone") %}: {%= contact_list[i].phone_nos[j].phone %}<br>
{% } %} {% } %}
{% endif %}
</p>
<p>
{% if(contact_list[i].email_id) { %} {% if(contact_list[i].email_id) { %}
{%= __("Email Address") %}: {%= contact_list[i].email_id %} {%= __("Email") %}: {%= contact_list[i].email_id %}<span class="text-muted"> ({%= __("Primary") %})</span><br>
{% endif %}
{% if(contact_list[i].email_ids) { %}
{% for(var j=0, k=contact_list[i].email_ids.length; j<k; j++) { %}
{%= __("Email") %}: {%= contact_list[i].email_ids[j].email_id %}<br>
{% } %} {% } %}
{% endif %}
</p> </p>
{% endif %} {% endif %}
<p>
{% if (contact_list[i].address) { %}
{%= __("Address") %}: {%= contact_list[i].address %}<br>
{% endif %}
</p>
</div> </div>
{% } %} {% } %}
{% if(!contact_list.length) { %} {% if(!contact_list.length) { %}

View File

@ -342,14 +342,15 @@ def make_contact(args, is_primary_contact=1):
contact = frappe.get_doc({ contact = frappe.get_doc({
'doctype': 'Contact', 'doctype': 'Contact',
'first_name': args.get('name'), 'first_name': args.get('name'),
'mobile_no': args.get('mobile_no'),
'email_id': args.get('email_id'),
'is_primary_contact': is_primary_contact, 'is_primary_contact': is_primary_contact,
'links': [{ 'links': [{
'link_doctype': args.get('doctype'), 'link_doctype': args.get('doctype'),
'link_name': args.get('name') 'link_name': args.get('name')
}] }]
}).insert() })
contact.add_email(args.get('email_id'))
contact.add_phone(args.get('mobile_no'))
contact.insert()
return contact return contact

View File

@ -7,6 +7,7 @@ frappe.ui.form.on("Sales Order", {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery', 'Delivery Note': 'Delivery',
'Pick List': 'Pick List',
'Sales Invoice': 'Invoice', 'Sales Invoice': 'Invoice',
'Material Request': 'Material Request', 'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order', 'Purchase Order': 'Purchase Order',
@ -110,6 +111,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
let allow_delivery = false; let allow_delivery = false;
if (doc.docstatus==1) { if (doc.docstatus==1) {
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
if(this.frm.has_perm("submit")) { if(this.frm.has_perm("submit")) {
if(doc.status === 'On Hold') { if(doc.status === 'On Hold') {
// un-hold // un-hold
@ -233,6 +236,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this.order_type(doc); this.order_type(doc);
}, },
create_pick_list() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.create_pick_list",
frm: this.frm
})
},
make_work_order() { make_work_order() {
var me = this; var me = this;
this.frm.call({ this.frm.call({

View File

@ -559,7 +559,7 @@ def make_project(source_name, target_doc=None):
return doc return doc
@frappe.whitelist() @frappe.whitelist()
def make_delivery_note(source_name, target_doc=None): def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
def set_missing_values(source, target): def set_missing_values(source, target):
target.ignore_pricing_rule = 1 target.ignore_pricing_rule = 1
target.run_method("set_missing_values") target.run_method("set_missing_values")
@ -584,23 +584,13 @@ def make_delivery_note(source_name, target_doc=None):
or item.get("buying_cost_center") \ or item.get("buying_cost_center") \
or item_group.get("buying_cost_center") or item_group.get("buying_cost_center")
target_doc = get_mapped_doc("Sales Order", source_name, { mapper = {
"Sales Order": { "Sales Order": {
"doctype": "Delivery Note", "doctype": "Delivery Note",
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
}, },
"Sales Order Item": {
"doctype": "Delivery Note Item",
"field_map": {
"rate": "rate",
"name": "so_detail",
"parent": "against_sales_order",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
},
"Sales Taxes and Charges": { "Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"add_if_empty": True "add_if_empty": True
@ -609,7 +599,21 @@ def make_delivery_note(source_name, target_doc=None):
"doctype": "Sales Team", "doctype": "Sales Team",
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, set_missing_values) }
if not skip_item_mapping:
mapper["Sales Order Item"] = {
"doctype": "Delivery Note Item",
"field_map": {
"rate": "rate",
"name": "so_detail",
"parent": "against_sales_order",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
}
target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
return target_doc return target_doc
@ -987,3 +991,33 @@ def make_raw_material_request(items, company, sales_order, project=None):
def make_inter_company_purchase_order(source_name, target_doc=None): def make_inter_company_purchase_order(source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
return make_inter_company_transaction("Sales Order", source_name, target_doc) return make_inter_company_transaction("Sales Order", source_name, target_doc)
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
def update_item_quantity(source, target, source_parent):
target.qty = flt(source.qty) - flt(source.delivered_qty)
target.stock_qty = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.conversion_factor)
doc = get_mapped_doc('Sales Order', source_name, {
'Sales Order': {
'doctype': 'Pick List',
'validation': {
'docstatus': ['=', 1]
}
},
'Sales Order Item': {
'doctype': 'Pick List Item',
'field_map': {
'parent': 'sales_order',
'name': 'sales_order_item'
},
'postprocess': update_item_quantity,
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
},
}, target_doc)
doc.purpose = 'Delivery against Sales Order'
doc.set_item_locations()
return doc

View File

@ -17,7 +17,7 @@ def get_data():
'transactions': [ 'transactions': [
{ {
'label': _('Fulfillment'), 'label': _('Fulfillment'),
'items': ['Sales Invoice', 'Delivery Note'] 'items': ['Sales Invoice', 'Pick List', 'Delivery Note']
}, },
{ {
'label': _('Purchasing'), 'label': _('Purchasing'),

View File

@ -31,7 +31,7 @@ class SMSCenter(Document):
self.sales_partner.replace("'", "\'") or " and ifnull(dl.link_name, '') != ''" self.sales_partner.replace("'", "\'") or " and ifnull(dl.link_name, '') != ''"
if self.send_to in ['All Contact', 'All Customer Contact', 'All Supplier Contact', 'All Sales Partner Contact']: if self.send_to in ['All Contact', 'All Customer Contact', 'All Supplier Contact', 'All Sales Partner Contact']:
rec = frappe.db.sql("""select CONCAT(ifnull(c.first_name,''), ' ', ifnull(c.last_name,'')), rec = frappe.db.sql("""select CONCAT(ifnull(c.first_name,''), ' ', ifnull(c.last_name,'')),
c.mobile_no from `tabContact` c, `tabDynamic Link` dl where ifnull(c.mobile_no,'')!='' and c.phone from `tabContact` c, `tabDynamic Link` dl where ifnull(c.phone,'')!='' and
c.docstatus != 2 and dl.parent = c.name%s""" % where_clause) c.docstatus != 2 and dl.parent = c.name%s""" % where_clause)
elif self.send_to == 'All Lead (Open)': elif self.send_to == 'All Lead (Open)':

View File

@ -26,6 +26,10 @@ class ItemGroup(NestedSet, WebsiteGenerator):
def validate(self): def validate(self):
super(ItemGroup, self).validate() super(ItemGroup, self).validate()
if not self.parent_item_group and not frappe.flags.in_test:
self.parent_item_group = 'All Item Groups'
self.make_route() self.make_route()
def on_update(self): def on_update(self):

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ frappe.ui.form.on('Material Request', {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Stock Entry': 'Issue Material', 'Stock Entry': 'Issue Material',
'Pick List': 'Pick List',
'Purchase Order': 'Purchase Order', 'Purchase Order': 'Purchase Order',
'Request for Quotation': 'Request for Quotation', 'Request for Quotation': 'Request for Quotation',
'Supplier Quotation': 'Supplier Quotation', 'Supplier Quotation': 'Supplier Quotation',
@ -55,8 +56,13 @@ frappe.ui.form.on('Material Request', {
if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') { if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') {
if (flt(frm.doc.per_ordered, 2) < 100) { if (flt(frm.doc.per_ordered, 2) < 100) {
// make let add_create_pick_list_button = () => {
frm.add_custom_button(__('Pick List'),
() => frm.events.create_pick_list(frm), __('Create'));
}
if (frm.doc.material_request_type === "Material Transfer") { if (frm.doc.material_request_type === "Material Transfer") {
add_create_pick_list_button();
frm.add_custom_button(__("Transfer Material"), frm.add_custom_button(__("Transfer Material"),
() => frm.events.make_stock_entry(frm), __('Create')); () => frm.events.make_stock_entry(frm), __('Create'));
} }
@ -258,6 +264,13 @@ frappe.ui.form.on('Material Request', {
}); });
}, },
create_pick_list: (frm) => {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.material_request.material_request.create_pick_list",
frm: frm
});
},
raise_work_orders: function(frm) { raise_work_orders: function(frm) {
frappe.call({ frappe.call({
method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders", method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders",

View File

@ -502,3 +502,28 @@ def raise_work_orders(material_request):
frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors)) frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
return work_orders return work_orders
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
doc = get_mapped_doc('Material Request', source_name, {
'Material Request': {
'doctype': 'Pick List',
'field_map': {
'material_request_type': 'purpose'
},
'validation': {
'docstatus': ['=', 1]
}
},
'Material Request Item': {
'doctype': 'Pick List Item',
'field_map': {
'name': 'material_request_item',
'qty': 'stock_qty'
},
},
}, target_doc)
doc.set_item_locations()
return doc

View File

@ -8,7 +8,7 @@ def get_data():
'transactions': [ 'transactions': [
{ {
'label': _('Related'), 'label': _('Related'),
'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', "Stock Entry"] 'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', 'Stock Entry', 'Pick List']
}, },
{ {
'label': _('Manufacturing'), 'label': _('Manufacturing'),

View File

@ -0,0 +1,180 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Pick List', {
setup: (frm) => {
frm.custom_make_buttons = {
'Delivery Note': 'Delivery Note',
'Stock Entry': 'Stock Entry',
};
frm.set_query('parent_warehouse', () => {
return {
filters: {
'is_group': 1,
'company': frm.doc.company
}
};
});
frm.set_query('work_order', () => {
return {
query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders',
filters: {
'company': frm.doc.company
}
};
});
frm.set_query('material_request', () => {
return {
filters: {
'material_request_type': ['=', frm.doc.purpose]
}
};
});
frm.set_query('item_code', 'locations', () => {
return {
filters: {
is_stock_item: 1
}
};
});
},
get_item_locations: (frm) => {
if (!frm.doc.locations || !frm.doc.locations.length) {
frappe.msgprint(__('First add items in the Item Locations table'));
} else {
frm.call('set_item_locations');
}
},
refresh: (frm) => {
frm.trigger('add_get_items_button');
if (frm.doc.docstatus === 1) {
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.target_document_exists', {
'pick_list_name': frm.doc.name,
'purpose': frm.doc.purpose
}).then(target_document_exists => {
if (target_document_exists) return;
if (frm.doc.purpose === 'Delivery against Sales Order') {
frm.add_custom_button(__('Delivery Note'), () => frm.trigger('create_delivery_note'), __('Create'));
} else {
frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create'));
}
});
}
},
work_order: (frm) => {
frappe.db.get_value('Work Order',
frm.doc.work_order,
['qty', 'material_transferred_for_manufacturing']
).then(data => {
let qty_data = data.message;
let max = qty_data.qty - qty_data.material_transferred_for_manufacturing;
frappe.prompt({
fieldtype: 'Float',
label: __('Qty of Finished Goods Item'),
fieldname: 'qty',
description: __('Max: {0}', [max]),
default: max
}, (data) => {
frm.set_value('for_qty', data.qty);
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
return;
}
frm.clear_table('locations');
erpnext.utils.map_current_doc({
method: 'erpnext.manufacturing.doctype.work_order.work_order.create_pick_list',
target: frm,
source_name: frm.doc.work_order
});
}, __('Select Quantity'), __('Get Items'));
});
},
material_request: (frm) => {
erpnext.utils.map_current_doc({
method: 'erpnext.stock.doctype.material_request.material_request.create_pick_list',
target: frm,
source_name: frm.doc.material_request
});
},
purpose: (frm) => {
frm.clear_table('locations');
frm.trigger('add_get_items_button');
},
create_delivery_note: (frm) => {
frappe.model.open_mapped_doc({
method: 'erpnext.stock.doctype.pick_list.pick_list.create_delivery_note',
frm: frm
});
},
create_stock_entry: (frm) => {
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.create_stock_entry', {
'pick_list': frm.doc,
}).then(stock_entry => {
frappe.model.sync(stock_entry);
frappe.set_route("Form", 'Stock Entry', stock_entry.name);
});
},
add_get_items_button: (frm) => {
let purpose = frm.doc.purpose;
if (purpose != 'Delivery against Sales Order' || frm.doc.docstatus !== 0) return;
let get_query_filters = {
docstatus: 1,
per_delivered: ['<', 100],
status: ['!=', ''],
customer: frm.doc.customer
};
frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => {
if (!frm.doc.customer) {
frappe.msgprint(__('Please select Customer first'));
return;
}
erpnext.utils.map_current_doc({
method: 'erpnext.selling.doctype.sales_order.sales_order.create_pick_list',
source_doctype: 'Sales Order',
target: frm,
setters: {
company: frm.doc.company,
customer: frm.doc.customer
},
date_field: 'transaction_date',
get_query_filters: get_query_filters
});
});
}
});
frappe.ui.form.on('Pick List Item', {
item_code: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
if (row.item_code) {
get_item_details(row.item_code).then(data => {
frappe.model.set_value(cdt, cdn, 'uom', data.stock_uom);
frappe.model.set_value(cdt, cdn, 'stock_uom', data.stock_uom);
frappe.model.set_value(cdt, cdn, 'conversion_factor', 1);
});
}
},
uom: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
if (row.uom) {
get_item_details(row.item_code, row.uom).then(data => {
frappe.model.set_value(cdt, cdn, 'conversion_factor', data.conversion_factor);
});
}
},
qty: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
},
conversion_factor: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
}
});
function get_item_details(item_code, uom=null) {
return frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.get_item_details', {
item_code,
uom
});
}

View File

@ -0,0 +1,184 @@
{
"autoname": "naming_series:",
"creation": "2019-07-11 16:03:13.681045",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"company",
"purpose",
"customer",
"work_order",
"material_request",
"for_qty",
"column_break_4",
"parent_warehouse",
"get_item_locations",
"section_break_6",
"locations",
"amended_from"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"description": "Items under this warehouse will be suggested",
"fieldname": "parent_warehouse",
"fieldtype": "Link",
"label": "Parent Warehouse",
"options": "Warehouse"
},
{
"depends_on": "eval:doc.purpose==='Delivery against Sales Order'",
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer",
"options": "Customer"
},
{
"depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'",
"fieldname": "work_order",
"fieldtype": "Link",
"label": "Work Order",
"options": "Work Order"
},
{
"fieldname": "locations",
"fieldtype": "Table",
"label": "Item Locations",
"options": "Pick List Item"
},
{
"depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'",
"description": "Qty of raw materials will be decided based on the qty of the Finished Goods Item",
"fieldname": "for_qty",
"fieldtype": "Float",
"label": "Qty of Finished Goods Item",
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Pick List",
"print_hide": 1,
"read_only": 1
},
{
"default": "Material Transfer for Manufacture",
"fieldname": "purpose",
"fieldtype": "Select",
"label": "Purpose",
"options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery against Sales Order"
},
{
"depends_on": "eval:['Material Transfer', 'Material Issue'].includes(doc.purpose)",
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"options": "Material Request"
},
{
"depends_on": "eval:doc.docstatus===0",
"fieldname": "get_item_locations",
"fieldtype": "Button",
"label": "Get Item Locations"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "STO-PICK-.YYYY.-",
"reqd": 1,
"set_only_once": 1
}
],
"is_submittable": 1,
"modified": "2019-08-29 21:10:11.572387",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock User",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing User",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,432 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from six import iteritems
from frappe.model.document import Document
from frappe import _
from collections import OrderedDict
from frappe.utils import floor, flt, today, cint
from frappe.model.mapper import get_mapped_doc, map_child_doc
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note as create_delivery_note_from_sales_order
# TODO: Prioritize SO or WO group warehouse
class PickList(Document):
def before_save(self):
self.set_item_locations()
def before_submit(self):
for item in self.locations:
if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
continue
if len(item.serial_no.split('\n')) == item.picked_qty:
continue
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
.format(frappe.bold(item.item_code), frappe.bold(item.idx)))
def set_item_locations(self):
items = self.aggregate_item_qty()
self.item_location_map = frappe._dict()
from_warehouses = None
if self.parent_warehouse:
from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse)
# reset
self.delete_key('locations')
for item_doc in items:
item_code = item_doc.item_code
self.item_location_map.setdefault(item_code,
get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code)))
locations = get_items_with_location_and_quantity(item_doc, self.item_location_map)
item_doc.idx = None
item_doc.name = None
for row in locations:
row.update({
'picked_qty': row.stock_qty
})
location = item_doc.as_dict()
location.update(row)
self.append('locations', location)
def aggregate_item_qty(self):
locations = self.get('locations')
self.item_count_map = {}
# aggregate qty for same item
item_map = OrderedDict()
for item in locations:
item_code = item.item_code
reference = item.sales_order_item or item.material_request_item
key = (item_code, item.uom, reference)
item.idx = None
item.name = None
if item_map.get(key):
item_map[key].qty += item.qty
item_map[key].stock_qty += item.stock_qty
else:
item_map[key] = item
# maintain count of each item (useful to limit get query)
self.item_count_map.setdefault(item_code, 0)
self.item_count_map[item_code] += item.stock_qty
return item_map.values()
def get_items_with_location_and_quantity(item_doc, item_location_map):
available_locations = item_location_map.get(item_doc.item_code)
locations = []
remaining_stock_qty = item_doc.stock_qty
while remaining_stock_qty > 0 and available_locations:
item_location = available_locations.pop(0)
item_location = frappe._dict(item_location)
stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty
qty = stock_qty / (item_doc.conversion_factor or 1)
uom_must_be_whole_number = frappe.db.get_value('UOM', item_doc.uom, 'must_be_whole_number')
if uom_must_be_whole_number:
qty = floor(qty)
stock_qty = qty * item_doc.conversion_factor
if not stock_qty: break
serial_nos = None
if item_location.serial_no:
serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
locations.append(frappe._dict({
'qty': qty,
'stock_qty': stock_qty,
'warehouse': item_location.warehouse,
'serial_no': serial_nos,
'batch_no': item_location.batch_no
}))
remaining_stock_qty -= stock_qty
qty_diff = item_location.qty - stock_qty
# if extra quantity is available push current warehouse to available locations
if qty_diff > 0:
item_location.qty = qty_diff
if item_location.serial_no:
# set remaining serial numbers
item_location.serial_no = item_location.serial_no[-qty_diff:]
available_locations = [item_location] + available_locations
# update available locations for the item
item_location_map[item_doc.item_code] = available_locations
return locations
def get_available_item_locations(item_code, from_warehouses, required_qty):
locations = []
if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty)
elif frappe.get_cached_value('Item', item_code, 'has_batch_no'):
locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty)
else:
locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty)
total_qty_available = sum(location.get('qty') for location in locations)
remaining_qty = required_qty - total_qty_available
if remaining_qty > 0:
frappe.msgprint(_('{0} units of {1} is not available.')
.format(remaining_qty, frappe.get_desk_link('Item', item_code)))
return locations
def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty):
filters = frappe._dict({
'item_code': item_code,
'warehouse': ['!=', '']
})
if from_warehouses:
filters.warehouse = ['in', from_warehouses]
serial_nos = frappe.get_all('Serial No',
fields=['name', 'warehouse'],
filters=filters,
limit=required_qty,
order_by='purchase_date',
as_list=1)
warehouse_serial_nos_map = frappe._dict()
for serial_no, warehouse in serial_nos:
warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no)
locations = []
for warehouse, serial_nos in iteritems(warehouse_serial_nos_map):
locations.append({
'qty': len(serial_nos),
'warehouse': warehouse,
'serial_no': serial_nos
})
return locations
def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty):
warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else ''
batch_locations = frappe.db.sql("""
SELECT
sle.`warehouse`,
sle.`batch_no`,
SUM(sle.`actual_qty`) AS `qty`
FROM
`tabStock Ledger Entry` sle, `tabBatch` batch
WHERE
sle.batch_no = batch.name
and sle.`item_code`=%(item_code)s
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
`warehouse`,
`batch_no`,
`item_code`
HAVING `qty` > 0
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
""".format(warehouse_condition=warehouse_condition), { #nosec
'item_code': item_code,
'today': today(),
'warehouses': from_warehouses
}, as_dict=1)
return batch_locations
def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty):
# gets all items available in different warehouses
filters = frappe._dict({
'item_code': item_code,
'actual_qty': ['>', 0]
})
if from_warehouses:
filters.warehouse = ['in', from_warehouses]
item_locations = frappe.get_all('Bin',
fields=['warehouse', 'actual_qty as qty'],
filters=filters,
limit=required_qty,
order_by='creation')
return item_locations
@frappe.whitelist()
def create_delivery_note(source_name, target_doc=None):
pick_list = frappe.get_doc('Pick List', source_name)
sales_orders = [d.sales_order for d in pick_list.locations]
sales_orders = set(sales_orders)
delivery_note = None
for sales_order in sales_orders:
delivery_note = create_delivery_note_from_sales_order(sales_order,
delivery_note, skip_item_mapping=True)
item_table_mapper = {
'doctype': 'Delivery Note Item',
'field_map': {
'rate': 'rate',
'name': 'so_detail',
'parent': 'against_sales_order',
},
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
}
for location in pick_list.locations:
sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item)
dn_item = map_child_doc(sales_order_item, delivery_note, item_table_mapper)
if dn_item:
dn_item.warehouse = location.warehouse
dn_item.qty = location.picked_qty
dn_item.batch_no = location.batch_no
dn_item.serial_no = location.serial_no
update_delivery_note_item(sales_order_item, dn_item, delivery_note)
set_delivery_note_missing_values(delivery_note)
delivery_note.pick_list = pick_list.name
return delivery_note
@frappe.whitelist()
def create_stock_entry(pick_list):
pick_list = frappe.get_doc(json.loads(pick_list))
if stock_entry_exists(pick_list.get('name')):
return frappe.msgprint(_('Stock Entry has been already created against this Pick List'))
stock_entry = frappe.new_doc('Stock Entry')
stock_entry.pick_list = pick_list.get('name')
stock_entry.purpose = pick_list.get('purpose')
stock_entry.set_stock_entry_type()
if pick_list.get('work_order'):
stock_entry = update_stock_entry_based_on_work_order(pick_list, stock_entry)
elif pick_list.get('material_request'):
stock_entry = update_stock_entry_based_on_material_request(pick_list, stock_entry)
else:
stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
stock_entry.set_incoming_rate()
stock_entry.set_actual_qty()
stock_entry.calculate_rate_and_amount(update_finished_item_rate=False)
return stock_entry.as_dict()
@frappe.whitelist()
def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filters, as_dict):
return frappe.db.sql("""
SELECT
`name`, `company`, `planned_start_date`
FROM
`tabWork Order`
WHERE
`status` not in ('Completed', 'Stopped')
AND `qty` > `material_transferred_for_manufacturing`
AND `docstatus` = 1
AND `company` = %(company)s
AND `name` like %(txt)s
ORDER BY
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
LIMIT
%(start)s, %(page_length)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace('%', ''),
'start': start,
'page_length': frappe.utils.cint(page_length),
'company': filters.get('company')
}, as_dict=as_dict)
@frappe.whitelist()
def target_document_exists(pick_list_name, purpose):
if purpose == 'Delivery against Sales Order':
return frappe.db.exists('Delivery Note', {
'pick_list': pick_list_name
})
return stock_entry_exists(pick_list_name)
@frappe.whitelist()
def get_item_details(item_code, uom=None):
details = frappe.db.get_value('Item', item_code, ['stock_uom', 'name'], as_dict=1)
details.uom = uom or details.stock_uom
if uom:
details.update(get_conversion_factor(item_code, uom))
return details
def update_delivery_note_item(source, target, delivery_note):
cost_center = frappe.db.get_value('Project', delivery_note.project, 'cost_center')
if not cost_center:
cost_center = get_cost_center(source.item_code, 'Item', delivery_note.company)
if not cost_center:
cost_center = get_cost_center(source.item_group, 'Item Group', delivery_note.company)
target.cost_center = cost_center
def get_cost_center(for_item, from_doctype, company):
'''Returns Cost Center for Item or Item Group'''
return frappe.db.get_value('Item Default',
fieldname=['buying_cost_center'],
filters={
'parent': for_item,
'parenttype': from_doctype,
'company': company
})
def set_delivery_note_missing_values(target):
target.run_method('set_missing_values')
target.run_method('set_po_nos')
target.run_method('calculate_taxes_and_totals')
def stock_entry_exists(pick_list_name):
return frappe.db.exists('Stock Entry', {
'pick_list': pick_list_name
})
def update_stock_entry_based_on_work_order(pick_list, stock_entry):
work_order = frappe.get_doc("Work Order", pick_list.get('work_order'))
stock_entry.work_order = work_order.name
stock_entry.company = work_order.company
stock_entry.from_bom = 1
stock_entry.bom_no = work_order.bom_no
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = pick_list.for_qty
if work_order.bom_no:
stock_entry.inspection_required = frappe.db.get_value('BOM',
work_order.bom_no, 'inspection_required')
is_wip_warehouse_group = frappe.db.get_value('Warehouse', work_order.wip_warehouse, 'is_group')
if not (is_wip_warehouse_group and work_order.skip_transfer):
wip_warehouse = work_order.wip_warehouse
else:
wip_warehouse = None
stock_entry.to_warehouse = wip_warehouse
stock_entry.project = work_order.project
for location in pick_list.locations:
item = frappe._dict()
update_common_item_properties(item, location)
item.t_warehouse = wip_warehouse
stock_entry.append('items', item)
return stock_entry
def update_stock_entry_based_on_material_request(pick_list, stock_entry):
for location in pick_list.locations:
target_warehouse = None
if location.material_request_item:
target_warehouse = frappe.get_value('Material Request Item',
location.material_request_item, 'warehouse')
item = frappe._dict()
update_common_item_properties(item, location)
item.t_warehouse = target_warehouse
stock_entry.append('items', item)
return stock_entry
def update_stock_entry_items_with_no_reference(pick_list, stock_entry):
for location in pick_list.locations:
item = frappe._dict()
update_common_item_properties(item, location)
stock_entry.append('items', item)
return stock_entry
def update_common_item_properties(item, location):
item.item_code = location.item_code
item.s_warehouse = location.warehouse
item.qty = location.picked_qty * location.conversion_factor
item.transfer_qty = location.picked_qty
item.uom = location.uom
item.conversion_factor = location.conversion_factor
item.stock_uom = location.stock_uom
item.material_request = location.material_request
item.serial_no = location.serial_no
item.batch_no = location.batch_no
item.material_request_item = location.material_request_item

View File

@ -0,0 +1,12 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'pick_list',
'transactions': [
{
'items': ['Stock Entry', 'Delivery Note']
},
]
}

View File

@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
import EmptyStockReconciliationItemsError
class TestPickList(unittest.TestCase):
def test_pick_list_picks_warehouse_for_each_item(self):
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Home Desktop 100',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 5
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Item Home Desktop 100',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
def test_pick_list_splits_row_according_to_warhouse_availability(self):
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Warehouse Group Wise Reorder',
'warehouse': '_Test Warehouse Group-C1 - _TC',
'valuation_rate': 100,
'qty': 5
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Warehouse Group Wise Reorder',
'warehouse': '_Test Warehouse 2 - _TC',
'valuation_rate': 400,
'qty': 10
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Item Warehouse Group Wise Reorder',
'qty': 1000,
'stock_qty': 1000,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Warehouse Group Wise Reorder')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse Group-C1 - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[1].item_code, '_Test Item Warehouse Group Wise Reorder')
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse 2 - _TC')
self.assertEqual(pick_list.locations[1].qty, 10)
def test_pick_list_shows_serial_no_for_serialized_item(self):
stock_reconciliation = frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'items': [{
'item_code': '_Test Serialized Item',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 5,
'serial_no': '123450\n123451\n123452\n123453\n123454'
}]
})
stock_reconciliation.submit()
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Serialized Item',
'qty': 1000,
'stock_qty': 1000,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Serialized Item')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[0].serial_no, '123450\n123451\n123452\n123453\n123454')
def test_pick_list_for_items_from_multiple_sales_orders(self):
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Home Desktop 100',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 10
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
sales_order = frappe.get_doc({
'doctype': "Sales Order",
'customer': '_Test Customer',
'company': '_Test Company',
'items': [{
'item_code': '_Test Item Home Desktop 100',
'qty': 10,
'delivery_date': frappe.utils.today()
}],
})
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 Home Desktop 100',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}, {
'item_code': '_Test Item Home Desktop 100',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
'sales_order': sales_order.name,
'sales_order_item': sales_order.items[0].name,
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item')
self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100')
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[1].qty, 5)
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
# def test_pick_list_skips_items_in_expired_batch(self):
# pass
# def test_pick_list_from_sales_order(self):
# pass
# def test_pick_list_from_work_order(self):
# pass
# def test_pick_list_from_material_request(self):
# pass

View File

@ -0,0 +1,182 @@
{
"creation": "2019-07-11 16:01:22.832885",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"column_break_2",
"description",
"section_break_5",
"warehouse",
"quantity_section",
"qty",
"stock_qty",
"picked_qty",
"column_break_11",
"uom",
"conversion_factor",
"stock_uom",
"serial_no_and_batch_section",
"serial_no",
"column_break_20",
"batch_no",
"column_break_15",
"sales_order",
"sales_order_item",
"material_request",
"material_request_item"
],
"fields": [
{
"default": "1",
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty"
},
{
"fieldname": "picked_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Picked Qty"
},
{
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Warehouse",
"options": "Warehouse",
"read_only": 1
},
{
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fetch_from": "item_code.description",
"fieldname": "description",
"fieldtype": "Text",
"label": "Description",
"read_only": 1
},
{
"depends_on": "serial_no",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No"
},
{
"depends_on": "batch_no",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"read_only": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "uom",
"fieldtype": "Link",
"label": "UOM",
"options": "UOM"
},
{
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "UOM Conversion Factor",
"read_only": 1
},
{
"fieldname": "stock_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Stock Qty",
"read_only": 1
},
{
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
},
{
"fieldname": "quantity_section",
"fieldtype": "Section Break",
"label": "Quantity"
},
{
"fieldname": "column_break_15",
"fieldtype": "Section Break",
"label": "Reference"
},
{
"fieldname": "sales_order",
"fieldtype": "Link",
"label": "Sales Order",
"options": "Sales Order",
"read_only": 1
},
{
"fieldname": "sales_order_item",
"fieldtype": "Data",
"label": "Sales Order Item",
"read_only": 1
},
{
"fieldname": "serial_no_and_batch_section",
"fieldtype": "Section Break",
"label": "Serial No and Batch"
},
{
"fieldname": "column_break_20",
"fieldtype": "Column Break"
},
{
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"options": "Material Request",
"read_only": 1
},
{
"fieldname": "material_request_item",
"fieldtype": "Data",
"label": "Material Request Item",
"read_only": 1
}
],
"istable": 1,
"modified": "2019-08-29 21:28:39.539007",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List Item",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class PickListItem(Document):
pass

View File

@ -329,6 +329,11 @@ class TestPurchaseReceipt(unittest.TestCase):
location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location') location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location')
self.assertEquals(location, "Test Location") self.assertEquals(location, "Test Location")
frappe.db.set_value("Asset", asset, "purchase_receipt", "")
frappe.db.set_value("Purchase Receipt Item", pr.items[0].name, "asset", "")
pr.load_from_db()
pr.cancel() pr.cancel()
serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or []
self.assertEquals(len(serial_nos), 0) self.assertEquals(len(serial_nos), 0)

View File

@ -17,6 +17,7 @@
"purchase_order", "purchase_order",
"delivery_note_no", "delivery_note_no",
"sales_invoice_no", "sales_invoice_no",
"pick_list",
"purchase_receipt_no", "purchase_receipt_no",
"col2", "col2",
"posting_date", "posting_date",
@ -613,12 +614,19 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "pick_list",
"fieldtype": "Link",
"label": "Pick List",
"options": "Pick List",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-07-14 17:41:39.257508", "modified": "2019-08-22 17:11:42.074154",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry", "name": "Stock Entry",

View File

@ -525,15 +525,21 @@ class StockEntry(StockController):
backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings", backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings",
"backflush_raw_materials_of_subcontract_based_on") "backflush_raw_materials_of_subcontract_based_on")
qty_allowance = flt(frappe.db.get_single_value("Buying Settings",
"over_transfer_allowance"))
if (self.purpose == "Send to Subcontractor" and self.purchase_order and if (self.purpose == "Send to Subcontractor" and self.purchase_order and
backflush_raw_materials_based_on == 'BOM'): backflush_raw_materials_based_on == 'BOM'):
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order) purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
for se_item in self.items: for se_item in self.items:
item_code = se_item.original_item or se_item.item_code item_code = se_item.original_item or se_item.item_code
precision = cint(frappe.db.get_default("float_precision")) or 3 precision = cint(frappe.db.get_default("float_precision")) or 3
total_allowed = sum([flt(d.required_qty) for d in purchase_order.supplied_items \ required_qty = sum([flt(d.required_qty) for d in purchase_order.supplied_items \
if d.rm_item_code == item_code]) if d.rm_item_code == item_code])
if not total_allowed:
total_allowed = required_qty + (required_qty * (qty_allowance/100))
if not required_qty:
frappe.throw(_("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}") frappe.throw(_("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}")
.format(se_item.item_code, self.purchase_order)) .format(se_item.item_code, self.purchase_order))
total_supplied = frappe.db.sql("""select sum(transfer_qty) total_supplied = frappe.db.sql("""select sum(transfer_qty)
@ -1110,6 +1116,7 @@ class StockEntry(StockController):
se_child.allow_alternative_item = item_dict[d].get("allow_alternative_item", 0) se_child.allow_alternative_item = item_dict[d].get("allow_alternative_item", 0)
se_child.subcontracted_item = item_dict[d].get("main_item_code") se_child.subcontracted_item = item_dict[d].get("main_item_code")
se_child.original_item = item_dict[d].get("original_item") se_child.original_item = item_dict[d].get("original_item")
se_child.po_detail = item_dict[d].get("po_detail")
if item_dict[d].get("idx"): if item_dict[d].get("idx"):
se_child.idx = item_dict[d].get("idx") se_child.idx = item_dict[d].get("idx")
@ -1161,7 +1168,14 @@ class StockEntry(StockController):
where po.name = poitemsup.parent where po.name = poitemsup.parent
and po.name = %s""", self.purchase_order)) and po.name = %s""", self.purchase_order))
#Update reserved sub contracted quantity in bin based on Supplied Item Details #Update Supplied Qty in PO Supplied Items
frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
SET pos.supplied_qty = (SELECT ifnull(sum(transfer_qty), 0) FROM `tabStock Entry Detail` sed
WHERE pos.name = sed.po_detail and sed.docstatus = 1)
WHERE pos.docstatus = 1""")
#Update reserved sub contracted quantity in bin based on Supplied Item Details and
for d in self.get("items"): for d in self.get("items"):
item_code = d.get('original_item') or d.get('item_code') item_code = d.get('original_item') or d.get('item_code')
reserve_warehouse = item_wh.get(item_code) reserve_warehouse = item_wh.get(item_code)

View File

@ -59,6 +59,7 @@
"reference_section", "reference_section",
"against_stock_entry", "against_stock_entry",
"ste_detail", "ste_detail",
"po_detail",
"column_break_51", "column_break_51",
"transferred_qty", "transferred_qty",
"reference_purchase_receipt", "reference_purchase_receipt",
@ -480,11 +481,20 @@
"label": "Project", "label": "Project",
"options": "Project", "options": "Project",
"read_only": 1 "read_only": 1
},
{
"fieldname": "po_detail",
"fieldtype": "Data",
"hidden": 1,
"label": "PO Supplied Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-07-12 11:34:53.190749", "modified": "2019-08-20 14:01:02.319754",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Detail", "name": "Stock Entry Detail",

View File

@ -152,7 +152,6 @@ class TestStockReconciliation(unittest.TestCase):
for d in to_delete_records: for d in to_delete_records:
stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel() stock_doc.cancel()
frappe.delete_doc("Stock Reconciliation", stock_doc.name)
for d in serial_nos + serial_nos1: for d in serial_nos + serial_nos1:
if frappe.db.exists("Serial No", d): if frappe.db.exists("Serial No", d):
@ -203,9 +202,6 @@ class TestStockReconciliation(unittest.TestCase):
stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel() stock_doc.cancel()
frappe.delete_doc("Batch", sr.items[0].batch_no)
for d in to_delete_records:
frappe.delete_doc("Stock Reconciliation", d)
def insert_existing_sle(): def insert_existing_sle():
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

View File

@ -22,7 +22,7 @@ sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']
purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
@frappe.whitelist() @frappe.whitelist()
def get_item_details(args, doc=None): def get_item_details(args, doc=None, overwrite_warehouse=True):
""" """
args = { args = {
"item_code": "", "item_code": "",
@ -44,11 +44,12 @@ def get_item_details(args, doc=None):
"set_warehouse": "" "set_warehouse": ""
} }
""" """
args = process_args(args) args = process_args(args)
item = frappe.get_cached_doc("Item", args.item_code) item = frappe.get_cached_doc("Item", args.item_code)
validate_item_details(args, item) validate_item_details(args, item)
out = get_basic_details(args, item) out = get_basic_details(args, item, overwrite_warehouse)
get_item_tax_template(args, item, out) get_item_tax_template(args, item, out)
out["item_tax_rate"] = get_item_tax_map(args.company, args.get("item_tax_template") if out.get("item_tax_template") is None \ out["item_tax_rate"] = get_item_tax_map(args.company, args.get("item_tax_template") if out.get("item_tax_template") is None \
@ -178,7 +179,7 @@ def validate_item_details(args, item):
throw(_("Item {0} must be a Sub-contracted Item").format(item.name)) throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
def get_basic_details(args, item): def get_basic_details(args, item, overwrite_warehouse=True):
""" """
:param args: { :param args: {
"item_code": "", "item_code": "",
@ -225,15 +226,27 @@ def get_basic_details(args, item):
item_group_defaults = get_item_group_defaults(item.name, args.company) item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company) brand_defaults = get_brand_defaults(item.name, args.company)
warehouse = (args.get("set_warehouse") or item_defaults.get("default_warehouse") or if overwrite_warehouse or not args.warehouse:
item_group_defaults.get("default_warehouse") or brand_defaults.get("default_warehouse") or args.warehouse) warehouse = (
args.get("set_warehouse") or
item_defaults.get("default_warehouse") or
item_group_defaults.get("default_warehouse") or
brand_defaults.get("default_warehouse") or
args.warehouse
)
if not warehouse: if not warehouse:
defaults = frappe.defaults.get_defaults() or {} defaults = frappe.defaults.get_defaults() or {}
if defaults.get("default_warehouse") and frappe.db.exists("Warehouse", warehouse_exists = frappe.db.exists("Warehouse", {
{'name': defaults.default_warehouse, 'company': args.company}): 'name': defaults.default_warehouse,
'company': args.company
})
if defaults.get("default_warehouse") and warehouse_exists:
warehouse = defaults.default_warehouse warehouse = defaults.default_warehouse
else:
warehouse = args.warehouse
if args.get('doctype') == "Material Request" and not args.get('material_request_type'): if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
args['material_request_type'] = frappe.db.get_value('Material Request', args['material_request_type'] = frappe.db.get_value('Material Request',
args.get('name'), 'material_request_type', cache=True) args.get('name'), 'material_request_type', cache=True)

View File

View File

@ -0,0 +1,23 @@
{
"align_labels_right": 1,
"creation": "2019-08-02 07:27:42.533305",
"custom_format": 0,
"disabled": 0,
"doc_type": "Pick List",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div class=\\\"print-heading\\\">\\t\\t\\t\\t<h2>Pick List<br><small>{{ doc.name }}</small>\\t\\t\\t\\t</h2></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"customer\", \"label\": \"Customer\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"purpose\", \"label\": \"Purpose\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_name\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"warehouse\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"stock_qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"serial_no\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"batch_no\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"locations\", \"label\": \"Item Locations\"}]",
"idx": 0,
"line_breaks": 1,
"modified": "2019-08-30 15:58:27.807219",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
"owner": "Administrator",
"print_format_builder": 1,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 1,
"standard": "Yes"
}

View File

@ -227,9 +227,9 @@ class update_entries_after(object):
elif actual_qty < 0: elif actual_qty < 0:
# In case of delivery/stock issue, get average purchase rate # In case of delivery/stock issue, get average purchase rate
# of serial nos of current entry # of serial nos of current entry
stock_value_change = -1 * flt(frappe.db.sql("""select sum(purchase_rate) stock_value_change = -1 * flt(frappe.get_all("Serial No",
from `tabSerial No` where name in (%s)""" % (", ".join(["%s"]*len(serial_no))), fields=["sum(purchase_rate)"],
tuple(serial_no))[0][0]) filters = {'name': ('in', serial_no)}, as_list=1)[0][0])
new_stock_qty = self.qty_after_transaction + actual_qty new_stock_qty = self.qty_after_transaction + actual_qty