Merge branch 'develop' of https://github.com/frappe/erpnext into loan_top_up_fixes

This commit is contained in:
Deepesh Garg 2020-08-15 18:28:04 +05:30
commit a2249e013d
50 changed files with 496 additions and 242 deletions

View File

@ -302,10 +302,10 @@
"fieldname": "warehouse", "fieldname": "warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Warehouse", "label": "Warehouse",
"mandatory_depends_on": "update_stock",
"oldfieldname": "warehouse", "oldfieldname": "warehouse",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Warehouse", "options": "Warehouse"
"reqd": 1
}, },
{ {
"default": "0", "default": "0",

View File

@ -1621,8 +1621,9 @@ def update_multi_mode_option(doc, pos_profile):
pos_payment_method = pos_payment_method.as_dict() pos_payment_method = pos_payment_method.as_dict()
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
payment_mode[0].default = pos_payment_method.default if payment_mode:
append_payment(payment_mode[0]) payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0])
def get_all_mode_of_payments(doc): def get_all_mode_of_payments(doc):
return frappe.db.sql(""" return frappe.db.sql("""

View File

@ -206,10 +206,19 @@ class TestSalesInvoice(unittest.TestCase):
"rate": 14, "rate": 14,
'included_in_print_rate': 1 'included_in_print_rate': 1
}) })
si.append("taxes", {
"charge_type": "On Item Quantity",
"account_head": "_Test Account Education Cess - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "CESS",
"rate": 5,
'included_in_print_rate': 1
})
si.insert() si.insert()
# with inclusive tax # with inclusive tax
self.assertEqual(si.net_total, 4385.96) self.assertEqual(si.items[0].net_amount, 3947.368421052631)
self.assertEqual(si.net_total, 3947.37)
self.assertEqual(si.grand_total, 5000) self.assertEqual(si.grand_total, 5000)
si.reload() si.reload()
@ -222,8 +231,8 @@ class TestSalesInvoice(unittest.TestCase):
si.save() si.save()
# with inclusive tax and additional discount # with inclusive tax and additional discount
self.assertEqual(si.net_total, 4285.96) self.assertEqual(si.net_total, 3847.37)
self.assertEqual(si.grand_total, 4885.99) self.assertEqual(si.grand_total, 4886)
si.reload() si.reload()
@ -235,7 +244,7 @@ class TestSalesInvoice(unittest.TestCase):
si.save() si.save()
# with inclusive tax and additional discount # with inclusive tax and additional discount
self.assertEqual(si.net_total, 4298.25) self.assertEqual(si.net_total, 3859.65)
self.assertEqual(si.grand_total, 4900.00) self.assertEqual(si.grand_total, 4900.00)
def test_sales_invoice_discount_amount(self): def test_sales_invoice_discount_amount(self):

View File

@ -11,7 +11,7 @@ from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate
class AssetMaintenanceLog(Document): class AssetMaintenanceLog(Document):
def validate(self): def validate(self):
if getdate(self.due_date) < getdate(nowdate()): if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in ["Completed", "Cancelled"]:
self.maintenance_status = "Overdue" self.maintenance_status = "Overdue"
if self.maintenance_status == "Completed" and not self.completion_date: if self.maintenance_status == "Completed" and not self.completion_date:

View File

@ -1,14 +1,15 @@
frappe.listview_settings['Asset Maintenance Log'] = { frappe.listview_settings['Asset Maintenance Log'] = {
add_fields: ["maintenance_status"], add_fields: ["maintenance_status"],
has_indicator_for_draft: 1,
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.maintenance_status=="Pending") { if (doc.maintenance_status=="Planned") {
return [__("Pending"), "orange"]; return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
} else if(doc.maintenance_status=="Completed") { } else if (doc.maintenance_status=="Completed") {
return [__("Completed"), "green"]; return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
} else if(doc.maintenance_status=="Cancelled") { } else if (doc.maintenance_status=="Cancelled") {
return [__("Cancelled"), "red"]; return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
} else if(doc.maintenance_status=="Overdue") { } else if (doc.maintenance_status=="Overdue") {
return [__("Overdue"), "red"]; return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
} }
} }
}; };

View File

@ -94,7 +94,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) { if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
this.frm.add_custom_button(__('Update Items'), () => { this.frm.add_custom_button(__('Update Items'), () => {
erpnext.utils.update_child_items({ erpnext.utils.update_child_items({
frm: frm, frm: this.frm,
child_docname: "items", child_docname: "items",
child_doctype: "Purchase Order Detail", child_doctype: "Purchase Order Detail",
cannot_add_row: false, cannot_add_row: false,

View File

@ -985,7 +985,7 @@ def validate_inclusive_tax(tax, doc):
# all rows about the reffered tax should be inclusive # all rows about the reffered tax should be inclusive
_on_previous_row_error("1 - %d" % (tax.row_id,)) _on_previous_row_error("1 - %d" % (tax.row_id,))
elif tax.get("category") == "Valuation": elif tax.get("category") == "Valuation":
frappe.throw(_("Valuation type charges can not marked as Inclusive")) frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):

View File

@ -161,8 +161,9 @@ class calculate_taxes_and_totals(object):
for item in self.doc.get("items"): for item in self.doc.get("items"):
item_tax_map = self._load_item_tax_rate(item.item_tax_rate) item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
cumulated_tax_fraction = 0 cumulated_tax_fraction = 0
total_inclusive_tax_amount_per_qty = 0
for i, tax in enumerate(self.doc.get("taxes")): for i, tax in enumerate(self.doc.get("taxes")):
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map) tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
if i==0: if i==0:
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
@ -172,9 +173,12 @@ class calculate_taxes_and_totals(object):
+ tax.tax_fraction_for_current_item + tax.tax_fraction_for_current_item
cumulated_tax_fraction += tax.tax_fraction_for_current_item cumulated_tax_fraction += tax.tax_fraction_for_current_item
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
if cumulated_tax_fraction and not self.discount_amount_applied and item.qty: if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction)) amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
item.discount_percentage = flt(item.discount_percentage, item.discount_percentage = flt(item.discount_percentage,
item.precision("discount_percentage")) item.precision("discount_percentage"))
@ -190,6 +194,7 @@ class calculate_taxes_and_totals(object):
from tax inclusive amount from tax inclusive amount
""" """
current_tax_fraction = 0 current_tax_fraction = 0
inclusive_tax_amount_per_qty = 0
if cint(tax.included_in_print_rate): if cint(tax.included_in_print_rate):
tax_rate = self._get_tax_rate(tax, item_tax_map) tax_rate = self._get_tax_rate(tax, item_tax_map)
@ -205,9 +210,14 @@ class calculate_taxes_and_totals(object):
current_tax_fraction = (tax_rate / 100.0) * \ current_tax_fraction = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
if getattr(tax, "add_deduct_tax", None): elif tax.charge_type == "On Item Quantity":
current_tax_fraction *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0 inclusive_tax_amount_per_qty = flt(tax_rate)
return current_tax_fraction
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
current_tax_fraction *= -1.0
inclusive_tax_amount_per_qty *= -1.0
return current_tax_fraction, inclusive_tax_amount_per_qty
def _get_tax_rate(self, tax, item_tax_map): def _get_tax_rate(self, tax, item_tax_map):
if tax.account_head in item_tax_map: if tax.account_head in item_tax_map:
@ -321,7 +331,7 @@ class calculate_taxes_and_totals(object):
current_tax_amount = (tax_rate / 100.0) * \ current_tax_amount = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
elif tax.charge_type == "On Item Quantity": elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.stock_qty current_tax_amount = tax_rate * item.qty
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
@ -472,7 +482,7 @@ class calculate_taxes_and_totals(object):
actual_taxes_dict = {} actual_taxes_dict = {}
for tax in self.doc.get("taxes"): for tax in self.doc.get("taxes"):
if tax.charge_type == "Actual": if tax.charge_type in ["Actual", "On Item Quantity"]:
tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax) tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
actual_taxes_dict.setdefault(tax.idx, tax_amount) actual_taxes_dict.setdefault(tax.idx, tax_amount)
elif tax.row_id in actual_taxes_dict: elif tax.row_id in actual_taxes_dict:

View File

@ -50,9 +50,9 @@
"contact_email", "contact_email",
"contact_mobile", "contact_mobile",
"more_info", "more_info",
"company",
"campaign", "campaign",
"column_break1", "column_break1",
"company",
"transaction_date", "transaction_date",
"amended_from", "amended_from",
"lost_reasons" "lost_reasons"
@ -344,7 +344,7 @@
"collapsible": 1, "collapsible": 1,
"fieldname": "more_info", "fieldname": "more_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Source", "label": "More Information",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-file-text" "options": "fa fa-file-text"
}, },
@ -411,7 +411,7 @@
"fieldname": "lost_reasons", "fieldname": "lost_reasons",
"fieldtype": "Table MultiSelect", "fieldtype": "Table MultiSelect",
"label": "Lost Reasons", "label": "Lost Reasons",
"options": "Lost Reason Detail", "options": "Opportunity Lost Reason Detail",
"read_only": 1 "read_only": 1
}, },
{ {
@ -424,7 +424,7 @@
"icon": "fa fa-info-sign", "icon": "fa fa-info-sign",
"idx": 195, "idx": 195,
"links": [], "links": [],
"modified": "2020-08-11 14:49:13.496297", "modified": "2020-08-11 17:34:35.066961",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Opportunity", "name": "Opportunity",

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2020-07-16 16:11:39.830389",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"lost_reason"
],
"fields": [
{
"fieldname": "lost_reason",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Opportunity Lost Reason",
"options": "Opportunity Lost Reason"
}
],
"istable": 1,
"links": [],
"modified": "2020-07-26 17:58:26.313242",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity Lost Reason Detail",
"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) 2020, 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 OpportunityLostReasonDetail(Document):
pass

View File

@ -78,7 +78,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "HR", "label": "HR",
"modified": "2020-06-16 19:20:50.976045", "modified": "2020-08-11 17:04:38.655417",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR", "name": "HR",
@ -88,7 +88,7 @@
"pin_to_top": 0, "pin_to_top": 0,
"shortcuts": [ "shortcuts": [
{ {
"color": "#9deca2", "color": "#cef6d1",
"format": "{} Active", "format": "{} Active",
"label": "Employee", "label": "Employee",
"link_to": "Employee", "link_to": "Employee",
@ -96,18 +96,19 @@
"type": "DocType" "type": "DocType"
}, },
{ {
"label": "Attendance", "color": "#ffe8cd",
"link_to": "Attendance",
"stats_filter": "",
"type": "DocType"
},
{
"format": "{} Open", "format": "{} Open",
"label": "Leave Application", "label": "Leave Application",
"link_to": "Leave Application", "link_to": "Leave Application",
"stats_filter": "{\"status\":\"Open\"}", "stats_filter": "{\"status\":\"Open\"}",
"type": "DocType" "type": "DocType"
}, },
{
"label": "Attendance",
"link_to": "Attendance",
"stats_filter": "",
"type": "DocType"
},
{ {
"label": "Job Applicant", "label": "Job Applicant",
"link_to": "Job Applicant", "link_to": "Job Applicant",

View File

@ -5,20 +5,23 @@ cur_frm.add_fetch('employee','employee_name','employee_name');
frappe.ui.form.on("Leave Allocation", { frappe.ui.form.on("Leave Allocation", {
onload: function(frm) { onload: function(frm) {
// Ignore cancellation of doctype on cancel all.
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today()); if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today());
frm.set_query("employee", function() { frm.set_query("employee", function() {
return { return {
query: "erpnext.controllers.queries.employee_query" query: "erpnext.controllers.queries.employee_query"
} };
}); });
frm.set_query("leave_type", function() { frm.set_query("leave_type", function() {
return { return {
filters: { filters: {
is_lwp: 0 is_lwp: 0
} }
} };
}) });
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@ -19,6 +19,10 @@ frappe.ui.form.on("Leave Application", {
frm.set_query("employee", erpnext.queries.employee); frm.set_query("employee", erpnext.queries.employee);
}, },
onload: function(frm) { onload: function(frm) {
// Ignore cancellation of doctype on cancel all.
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
if (!frm.doc.posting_date) { if (!frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.get_today()); frm.set_value("posting_date", frappe.datetime.get_today());
} }

View File

@ -2,6 +2,10 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Leave Encashment', { frappe.ui.form.on('Leave Encashment', {
onload: function(frm) {
// Ignore cancellation of doctype on cancel all.
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
},
setup: function(frm) { setup: function(frm) {
frm.set_query("leave_type", function() { frm.set_query("leave_type", function() {
return { return {
@ -33,7 +37,7 @@ frappe.ui.form.on('Leave Encashment', {
doc: frm.doc, doc: frm.doc,
callback: function(r) { callback: function(r) {
frm.refresh_fields(); frm.refresh_fields();
} }
}); });
} }
} }

View File

@ -16,14 +16,16 @@ from six import string_types
class LoanApplication(Document): class LoanApplication(Document):
def validate(self): def validate(self):
validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount,
self.repayment_periods, self.is_term_loan)
self.validate_loan_type()
self.set_pledge_amount() self.set_pledge_amount()
self.set_loan_amount() self.set_loan_amount()
self.validate_loan_amount() self.validate_loan_amount()
if self.is_term_loan:
validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount,
self.repayment_periods, self.is_term_loan)
self.validate_loan_type()
self.get_repayment_details() self.get_repayment_details()
self.check_sanctioned_amount_limit() self.check_sanctioned_amount_limit()
@ -106,7 +108,7 @@ class LoanApplication(Document):
if self.is_secured_loan and self.proposed_pledges: if self.is_secured_loan and self.proposed_pledges:
self.maximum_loan_amount = 0 self.maximum_loan_amount = 0
for security in self.proposed_pledges: for security in self.proposed_pledges:
self.maximum_loan_amount += security.post_haircut_amount self.maximum_loan_amount += flt(security.post_haircut_amount)
if not self.loan_amount and self.is_secured_loan and self.proposed_pledges: if not self.loan_amount and self.is_secured_loan and self.proposed_pledges:
self.loan_amount = self.maximum_loan_amount self.loan_amount = self.maximum_loan_amount

View File

@ -718,3 +718,6 @@ erpnext.patches.v13_0.delete_report_requested_items_to_order
erpnext.patches.v12_0.update_item_tax_template_company erpnext.patches.v12_0.update_item_tax_template_company
erpnext.patches.v13_0.move_branch_code_to_bank_account erpnext.patches.v13_0.move_branch_code_to_bank_account
erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
erpnext.patches.v13_0.stock_entry_enhancements
erpnext.patches.v12_0.update_state_code_for_daman_and_diu
erpnext.patches.v12_0.rename_lost_reason_detail

View File

@ -0,0 +1,17 @@
from __future__ import unicode_literals
import frappe
def execute():
if frappe.db.exists("DocType", "Lost Reason Detail"):
frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail")
frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail")
frappe.db.sql("""INSERT INTO `tabOpportunity Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Opportunity'""")
frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Quotation'""")
frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`)
SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason`
FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL""")
frappe.delete_doc("DocType", "Lost Reason Detail")

View File

@ -19,7 +19,7 @@ def create_stock_entry_types():
for purpose in ["Material Issue", "Material Receipt", "Material Transfer", for purpose in ["Material Issue", "Material Receipt", "Material Transfer",
"Material Transfer for Manufacture", "Material Consumption for Manufacture", "Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture", "Manufacture",
"Repack", "Send to Subcontractor", "Send to Warehouse", "Receive at Warehouse"]: "Repack", "Send to Subcontractor"]:
ste_type = frappe.get_doc({ ste_type = frappe.get_doc({
'doctype': 'Stock Entry Type', 'doctype': 'Stock Entry Type',

View File

@ -0,0 +1,22 @@
import frappe
from erpnext.regional.india import states
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
# Update options in gst_state custom field
gst_state = frappe.get_doc('Custom Field', 'Address-gst_state')
gst_state.options = '\n'.join(states)
gst_state.save()
# Update gst_state and state code in existing address
frappe.db.sql("""
UPDATE `tabAddress`
SET
gst_state = 'Dadra and Nagar Haveli and Daman and Diu',
gst_state_number = 26
WHERE gst_state = 'Daman and Diu'
""")

View File

@ -0,0 +1,27 @@
# Copyright(c) 2020, Frappe Technologies Pvt.Ltd.and Contributors
# License: GNU General Public License v3.See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("stock", "doctype", "stock_entry")
if frappe.db.has_column("Stock Entry", "add_to_transit"):
frappe.db.sql("""
UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer',
add_to_transit = 1 WHERE stock_entry_type = 'Send to Warehouse'
""")
frappe.db.sql("""UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer'
WHERE stock_entry_type = 'Receive at Warehouse'
""")
frappe.reload_doc("stock", "doctype", "warehouse_type")
if not frappe.db.exists('Warehouse Type', 'Transit'):
doc = frappe.new_doc('Warehouse Type')
doc.name = 'Transit'
doc.insert()

View File

@ -8,7 +8,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Taxation", "label": "Taxation",
"links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]" "links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -38,7 +38,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Payroll", "label": "Payroll",
"modified": "2020-06-19 12:23:06.034046", "modified": "2020-08-10 19:38:45.976209",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Payroll", "name": "Payroll",

View File

@ -94,13 +94,6 @@ frappe.ui.form.on("Timesheet", {
} }
}, },
company: function(frm) {
frappe.db.get_value('Company', { 'company_name' : frm.doc.company }, 'standard_working_hours')
.then(({ message }) => {
(frappe.working_hours = message.standard_working_hours || 0);
});
},
make_invoice: function(frm) { make_invoice: function(frm) {
let dialog = new frappe.ui.Dialog({ let dialog = new frappe.ui.Dialog({
title: __("Select Item (optional)"), title: __("Select Item (optional)"),

View File

@ -163,9 +163,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
$.each(me.frm.doc["items"] || [], function(n, item) { $.each(me.frm.doc["items"] || [], function(n, item) {
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
var cumulated_tax_fraction = 0.0; var cumulated_tax_fraction = 0.0;
var total_inclusive_tax_amount_per_qty = 0;
$.each(me.frm.doc["taxes"] || [], function(i, tax) { $.each(me.frm.doc["taxes"] || [], function(i, tax) {
tax.tax_fraction_for_current_item = me.get_current_tax_fraction(tax, item_tax_map); var current_tax_fraction = me.get_current_tax_fraction(tax, item_tax_map);
tax.tax_fraction_for_current_item = current_tax_fraction[0];
var inclusive_tax_amount_per_qty = current_tax_fraction[1];
if(i==0) { if(i==0) {
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
@ -176,10 +178,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} }
cumulated_tax_fraction += tax.tax_fraction_for_current_item; cumulated_tax_fraction += tax.tax_fraction_for_current_item;
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty);
}); });
if(cumulated_tax_fraction && !me.discount_amount_applied) { if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) {
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction)); var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty;
item.net_amount = flt(amount / (1 + cumulated_tax_fraction));
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
me.set_in_company_currency(item, ["net_rate", "net_amount"]); me.set_in_company_currency(item, ["net_rate", "net_amount"]);
@ -191,6 +195,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
// Get tax fraction for calculating tax exclusive amount // Get tax fraction for calculating tax exclusive amount
// from tax inclusive amount // from tax inclusive amount
var current_tax_fraction = 0.0; var current_tax_fraction = 0.0;
var inclusive_tax_amount_per_qty = 0;
if(cint(tax.included_in_print_rate)) { if(cint(tax.included_in_print_rate)) {
var tax_rate = this._get_tax_rate(tax, item_tax_map); var tax_rate = this._get_tax_rate(tax, item_tax_map);
@ -205,13 +210,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} else if(tax.charge_type == "On Previous Row Total") { } else if(tax.charge_type == "On Previous Row Total") {
current_tax_fraction = (tax_rate / 100.0) * current_tax_fraction = (tax_rate / 100.0) *
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item; this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
} else if (tax.charge_type == "On Item Quantity") {
inclusive_tax_amount_per_qty = flt(tax_rate);
} }
} }
if(tax.add_deduct_tax) { if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
current_tax_fraction *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; current_tax_fraction *= -1;
inclusive_tax_amount_per_qty *= -1;
} }
return current_tax_fraction; return [current_tax_fraction, inclusive_tax_amount_per_qty];
}, },
_get_tax_rate: function(tax, item_tax_map) { _get_tax_rate: function(tax, item_tax_map) {
@ -360,8 +368,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} else if(tax.charge_type == "On Previous Row Total") { } else if(tax.charge_type == "On Previous Row Total") {
current_tax_amount = (tax_rate / 100.0) * current_tax_amount = (tax_rate / 100.0) *
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item; this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item;
} else if (tax.charge_type == "On Item Quantity") {
current_tax_amount = tax_rate * item.qty;
} }
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
return current_tax_amount; return current_tax_amount;
@ -573,7 +582,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
var actual_taxes_dict = {}; var actual_taxes_dict = {};
$.each(this.frm.doc["taxes"] || [], function(i, tax) { $.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (tax.charge_type == "Actual") { if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) {
var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount; var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
actual_taxes_dict[tax.idx] = tax_amount; actual_taxes_dict[tax.idx] = tax_amount;

View File

@ -10,8 +10,7 @@ states = [
'Bihar', 'Bihar',
'Chandigarh', 'Chandigarh',
'Chhattisgarh', 'Chhattisgarh',
'Dadra and Nagar Haveli', 'Dadra and Nagar Haveli and Daman and Diu',
'Daman and Diu',
'Delhi', 'Delhi',
'Goa', 'Goa',
'Gujarat', 'Gujarat',
@ -50,8 +49,7 @@ state_numbers = {
"Bihar": "10", "Bihar": "10",
"Chandigarh": "04", "Chandigarh": "04",
"Chhattisgarh": "22", "Chhattisgarh": "22",
"Dadra and Nagar Haveli": "26", "Dadra and Nagar Haveli and Daman and Diu": "26",
"Daman and Diu": "25",
"Delhi": "07", "Delhi": "07",
"Goa": "30", "Goa": "30",
"Gujarat": "24", "Gujarat": "24",

View File

@ -134,15 +134,10 @@
"state_code": "DL", "state_code": "DL",
"state_name": "Delhi" "state_name": "Delhi"
}, },
{
"state_number": "25",
"state_code": "DD",
"state_name": "Daman and Diu"
},
{ {
"state_number": "26", "state_number": "26",
"state_code": "DN", "state_code": "DN",
"state_name": "Dadra and Nagar Haveli" "state_name": "Dadra and Nagar Haveli and Daman and Diu"
}, },
{ {
"state_number": "22", "state_number": "22",

View File

@ -185,6 +185,14 @@ class Customer(TransactionBase):
if self.get("__islocal") or not self.credit_limits: if self.get("__islocal") or not self.credit_limits:
return return
past_credit_limits = [d.credit_limit
for d in frappe.db.get_all("Customer Credit Limit", filters={'parent': self.name}, fields=["credit_limit"], order_by="company")]
current_credit_limits = [d.credit_limit for d in sorted(self.credit_limits, key=lambda k: k.company)]
if past_credit_limits == current_credit_limits:
return
company_record = [] company_record = []
for limit in self.credit_limits: for limit in self.credit_limits:
if limit.company in company_record: if limit.company in company_record:

View File

@ -923,7 +923,7 @@
"fieldname": "lost_reasons", "fieldname": "lost_reasons",
"fieldtype": "Table MultiSelect", "fieldtype": "Table MultiSelect",
"label": "Lost Reasons", "label": "Lost Reasons",
"options": "Lost Reason Detail", "options": "Quotation Lost Reason Detail",
"read_only": 1 "read_only": 1
} }
], ],
@ -932,7 +932,7 @@
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"max_attachments": 1, "max_attachments": 1,
"modified": "2020-07-18 04:59:09.960118", "modified": "2020-07-26 17:46:19.951223",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation", "name": "Quotation",

View File

@ -68,7 +68,7 @@ class Quotation(SellingController):
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None): def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
if not self.has_sales_order(): if not self.has_sales_order():
get_lost_reasons = frappe.get_list('Opportunity Lost Reason', get_lost_reasons = frappe.get_list('Quotation Lost Reason',
fields = ["name"]) fields = ["name"])
lost_reasons_lst = [reason.get('name') for reason in get_lost_reasons] lost_reasons_lst = [reason.get('name') for reason in get_lost_reasons]
frappe.db.set(self, 'status', 'Lost') frappe.db.set(self, 'status', 'Lost')

View File

@ -494,13 +494,18 @@ frappe.ui.form.on(cur_frm.doctype, {
var dialog = new frappe.ui.Dialog({ var dialog = new frappe.ui.Dialog({
title: __("Set as Lost"), title: __("Set as Lost"),
fields: [ fields: [
{"fieldtype": "Table MultiSelect", {
"label": __("Lost Reasons"), "fieldtype": "Table MultiSelect",
"fieldname": "lost_reason", "label": __("Lost Reasons"),
"options": "Lost Reason Detail", "fieldname": "lost_reason",
"reqd": 1}, "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail',
"reqd": 1
{"fieldtype": "Text", "label": __("Detailed Reason"), "fieldname": "detailed_reason"}, },
{
"fieldtype": "Text",
"label": __("Detailed Reason"),
"fieldname": "detailed_reason"
},
], ],
primary_action: function() { primary_action: function() {
var values = dialog.get_values(); var values = dialog.get_values();

View File

@ -34,6 +34,16 @@ frappe.ui.form.on("Company", {
frm.set_query("default_buying_terms", function() { frm.set_query("default_buying_terms", function() {
return { filters: { buying: 1 } }; return { filters: { buying: 1 } };
}); });
frm.set_query("default_in_transit_warehouse", function() {
return {
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
};
});
}, },
company_name: function(frm) { company_name: function(frm) {

View File

@ -25,6 +25,7 @@
"default_selling_terms", "default_selling_terms",
"default_buying_terms", "default_buying_terms",
"default_warehouse_for_sales_return", "default_warehouse_for_sales_return",
"default_in_transit_warehouse",
"column_break_10", "column_break_10",
"country", "country",
"create_chart_of_accounts_based_on", "create_chart_of_accounts_based_on",
@ -733,6 +734,12 @@
"fieldname": "enable_perpetual_inventory_for_non_stock_items", "fieldname": "enable_perpetual_inventory_for_non_stock_items",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Enable Perpetual Inventory For Non Stock Items" "label": "Enable Perpetual Inventory For Non Stock Items"
},
{
"fieldname": "default_in_transit_warehouse",
"fieldtype": "Link",
"label": "Default In Transit Warehouse",
"options": "Warehouse"
} }
], ],
"icon": "fa fa-building", "icon": "fa fa-building",
@ -740,7 +747,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-06-24 12:45:31.462195", "modified": "2020-08-06 00:38:08.311216",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@ -140,7 +140,8 @@ class Company(NestedSet):
{"warehouse_name": _("All Warehouses"), "is_group": 1}, {"warehouse_name": _("All Warehouses"), "is_group": 1},
{"warehouse_name": _("Stores"), "is_group": 0}, {"warehouse_name": _("Stores"), "is_group": 0},
{"warehouse_name": _("Work In Progress"), "is_group": 0}, {"warehouse_name": _("Work In Progress"), "is_group": 0},
{"warehouse_name": _("Finished Goods"), "is_group": 0}]: {"warehouse_name": _("Finished Goods"), "is_group": 0},
{"warehouse_name": _("Goods In Transit"), "is_group": 0, "warehouse_type": "Transit"}]:
if not frappe.db.exists("Warehouse", "{0} - {1}".format(wh_detail["warehouse_name"], self.abbr)): if not frappe.db.exists("Warehouse", "{0} - {1}".format(wh_detail["warehouse_name"], self.abbr)):
warehouse = frappe.get_doc({ warehouse = frappe.get_doc({
@ -149,7 +150,8 @@ class Company(NestedSet):
"is_group": wh_detail["is_group"], "is_group": wh_detail["is_group"],
"company": self.name, "company": self.name,
"parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) \ "parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) \
if not wh_detail["is_group"] else "" if not wh_detail["is_group"] else "",
"warehouse_type" : wh_detail["warehouse_type"] if "warehouse_type" in wh_detail else None
}) })
warehouse.flags.ignore_permissions = True warehouse.flags.ignore_permissions = True
warehouse.flags.ignore_mandatory = True warehouse.flags.ignore_mandatory = True

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2020-07-14 09:21:44.057724",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"lost_reason"
],
"fields": [
{
"fieldname": "lost_reason",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Quotation Lost Reason",
"options": "Quotation Lost Reason"
}
],
"istable": 1,
"links": [],
"modified": "2020-07-26 17:58:56.373775",
"modified_by": "Administrator",
"module": "Setup",
"name": "Quotation Lost Reason Detail",
"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) 2020, 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 QuotationLostReasonDetail(Document):
pass

View File

@ -95,8 +95,6 @@ def install(country=None):
{'doctype': 'Stock Entry Type', 'name': 'Send to Subcontractor', 'purpose': 'Send to Subcontractor'}, {'doctype': 'Stock Entry Type', 'name': 'Send to Subcontractor', 'purpose': 'Send to Subcontractor'},
{'doctype': 'Stock Entry Type', 'name': 'Material Transfer for Manufacture', 'purpose': 'Material Transfer for Manufacture'}, {'doctype': 'Stock Entry Type', 'name': 'Material Transfer for Manufacture', 'purpose': 'Material Transfer for Manufacture'},
{'doctype': 'Stock Entry Type', 'name': 'Material Consumption for Manufacture', 'purpose': 'Material Consumption for Manufacture'}, {'doctype': 'Stock Entry Type', 'name': 'Material Consumption for Manufacture', 'purpose': 'Material Consumption for Manufacture'},
{'doctype': 'Stock Entry Type', 'name': 'Send to Warehouse', 'purpose': 'Send to Warehouse'},
{'doctype': 'Stock Entry Type', 'name': 'Receive at Warehouse', 'purpose': 'Receive at Warehouse'},
# Designation # Designation
{'doctype': 'Designation', 'designation_name': _('CEO')}, {'doctype': 'Designation', 'designation_name': _('CEO')},
@ -244,7 +242,10 @@ def install(country=None):
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")}, {"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")}, {"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")}, {"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")} {"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")},
# Warehouse Type
{'doctype': 'Warehouse Type', 'name': 'Transit'},
] ]
from erpnext.setup.setup_wizard.data.industry_type import get_industry_types from erpnext.setup.setup_wizard.data.industry_type import get_industry_types

View File

@ -123,6 +123,7 @@
"weightage", "weightage",
"slideshow", "slideshow",
"website_image", "website_image",
"website_image_alt",
"thumbnail", "thumbnail",
"cb72", "cb72",
"website_warehouse", "website_warehouse",
@ -1054,15 +1055,21 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Default Manufacturer Part No", "label": "Default Manufacturer Part No",
"read_only": 1 "read_only": 1
},
{
"fieldname": "website_image_alt",
"fieldtype": "Data",
"label": "Image Description"
} }
], ],
"has_web_view": 1, "has_web_view": 1,
"icon": "fa fa-tag", "icon": "fa fa-tag",
"idx": 2, "idx": 2,
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1,
"links": [], "links": [],
"max_attachments": 1, "max_attachments": 1,
"modified": "2020-07-31 21:21:10.956453", "modified": "2020-08-07 14:24:58.384992",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -343,7 +343,7 @@ class Item(WebsiteGenerator):
if variant: if variant:
context.variant = frappe.get_doc("Item", variant) context.variant = frappe.get_doc("Item", variant)
for fieldname in ("website_image", "web_long_description", "description", for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
"website_specifications"): "website_specifications"):
if context.variant.get(fieldname): if context.variant.get(fieldname):
value = context.variant.get(fieldname) value = context.variant.get(fieldname)

View File

@ -11,6 +11,7 @@
"naming_series", "naming_series",
"title", "title",
"material_request_type", "material_request_type",
"transfer_status",
"customer", "customer",
"column_break_2", "column_break_2",
"schedule_date", "schedule_date",
@ -303,13 +304,22 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Set From Warehouse", "label": "Set From Warehouse",
"options": "Warehouse" "options": "Warehouse"
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.add_to_transit == 1",
"fieldname": "transfer_status",
"fieldtype": "Select",
"label": "Transfer Status",
"options": "\nNot Started\nIn Transit\nCompleted",
"read_only": 1
} }
], ],
"icon": "fa fa-ticket", "icon": "fa fa-ticket",
"idx": 70, "idx": 70,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-01 20:21:09.990867", "modified": "2020-08-10 13:27:54.891058",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Material Request", "name": "Material Request",

View File

@ -1,8 +1,16 @@
frappe.listview_settings['Material Request'] = { frappe.listview_settings['Material Request'] = {
add_fields: ["material_request_type", "status", "per_ordered", "per_received"], add_fields: ["material_request_type", "status", "per_ordered", "per_received", "transfer_status"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.status=="Stopped") { if(doc.status=="Stopped") {
return [__("Stopped"), "red", "status,=,Stopped"]; return [__("Stopped"), "red", "status,=,Stopped"];
} else if(doc.transfer_status && doc.docstatus != 2) {
if (doc.transfer_status == "Not Started") {
return [__("Not Started"), "orange"];
} else if (doc.transfer_status == "In Transit") {
return [__("In Transit"), "yellow"];
} else if (doc.transfer_status == "Completed") {
return [__("Completed"), "green"];
}
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 0) { } else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 0) {
return [__("Pending"), "orange", "per_ordered,=,0"]; return [__("Pending"), "orange", "per_ordered,=,0"];
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) { } else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) {

View File

@ -19,7 +19,6 @@ frappe.ui.form.on('Stock Entry', {
filters: [ filters: [
['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'docstatus', '=', 1],
['Stock Entry', 'per_transferred', '<','100'], ['Stock Entry', 'per_transferred', '<','100'],
['Stock Entry', 'purpose', '=', 'Send to Warehouse']
] ]
} }
}); });
@ -171,9 +170,9 @@ frappe.ui.form.on('Stock Entry', {
} }
} }
if (frm.doc.docstatus === 1 && frm.doc.purpose == 'Send to Warehouse') { if (frm.doc.docstatus === 1) {
if (frm.doc.per_transferred < 100) { if (frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer' && frm.doc.per_transferred < 100) {
frm.add_custom_button(__('Receive at Warehouse Entry'), function() { frm.add_custom_button('End Transit', function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry", method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry",
frm: frm frm: frm
@ -266,6 +265,7 @@ frappe.ui.form.on('Stock Entry', {
stock_entry_type: function(frm){ stock_entry_type: function(frm){
frm.remove_custom_button('Bill of Materials', "Get items from"); frm.remove_custom_button('Bill of Materials', "Get items from");
frm.events.show_bom_custom_button(frm); frm.events.show_bom_custom_button(frm);
frm.trigger('add_to_transit');
}, },
purpose: function(frm) { purpose: function(frm) {
@ -532,6 +532,26 @@ frappe.ui.form.on('Stock Entry', {
target_warehouse_address: function(frm) { target_warehouse_address: function(frm) {
erpnext.utils.get_address_display(frm, 'target_warehouse_address', 'target_address_display', false); erpnext.utils.get_address_display(frm, 'target_warehouse_address', 'target_address_display', false);
},
add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
frm.set_value('stock_entry_type', 'Material Transfer');
frm.fields_dict.to_warehouse.get_query = function() {
return {
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
};
};
frappe.db.get_value('Company', frm.doc.company, 'default_in_transit_warehouse', (r) => {
if (r.default_in_transit_warehouse) {
frm.set_value('to_warehouse', r.default_in_transit_warehouse);
}
});
}
} }
}) })
@ -754,6 +774,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
} }
erpnext.hide_company(); erpnext.hide_company();
erpnext.utils.add_item(this.frm); erpnext.utils.add_item(this.frm);
this.frm.trigger('add_to_transit');
}, },
scan_barcode: function() { scan_barcode: function() {
@ -919,8 +940,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
doc.purpose!='Material Issue'); doc.purpose!='Material Issue');
this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue'); this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue');
this.frm.toggle_reqd("outgoing_stock_entry",
doc.purpose == 'Receive at Warehouse' ? 1: 0);
}, },
supplier: function(doc) { supplier: function(doc) {

View File

@ -13,6 +13,7 @@
"stock_entry_type", "stock_entry_type",
"outgoing_stock_entry", "outgoing_stock_entry",
"purpose", "purpose",
"add_to_transit",
"work_order", "work_order",
"purchase_order", "purchase_order",
"delivery_note_no", "delivery_note_no",
@ -116,11 +117,12 @@
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "eval:doc.purpose == 'Receive at Warehouse'", "depends_on": "eval:doc.purpose == 'Material Transfer'",
"fieldname": "outgoing_stock_entry", "fieldname": "outgoing_stock_entry",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Stock Entry (Outward GIT)", "label": "Stock Entry (Outward GIT)",
"options": "Stock Entry" "options": "Stock Entry",
"read_only": 1
}, },
{ {
"bold": 1, "bold": 1,
@ -132,7 +134,7 @@
"label": "Purpose", "label": "Purpose",
"oldfieldname": "purpose", "oldfieldname": "purpose",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse", "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"read_only": 1 "read_only": 1
}, },
{ {
@ -630,13 +632,21 @@
{ {
"fieldname": "print_settings_col_break", "fieldname": "print_settings_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry",
"fieldname": "add_to_transit",
"fieldtype": "Check",
"label": "Add to Transit",
"no_copy": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-23 12:56:52.881752", "modified": "2020-08-11 19:10:07.954981",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry", "name": "Stock Entry",

View File

@ -97,6 +97,11 @@ class StockEntry(StockController):
if self.work_order and self.purpose == "Manufacture": if self.work_order and self.purpose == "Manufacture":
self.update_so_in_serial_number() self.update_so_in_serial_number()
if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('In Transit')
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
self.set_material_request_transfer_status('Completed')
def on_cancel(self): def on_cancel(self):
if self.purchase_order and self.purpose == "Send to Subcontractor": if self.purchase_order and self.purpose == "Send to Subcontractor":
@ -116,6 +121,11 @@ class StockEntry(StockController):
self.update_quality_inspection() self.update_quality_inspection()
self.delete_auto_created_batches() self.delete_auto_created_batches()
if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('Not Started')
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
self.set_material_request_transfer_status('In Transit')
def set_job_card_data(self): def set_job_card_data(self):
if self.job_card and not self.work_order: if self.job_card and not self.work_order:
data = frappe.db.get_value('Job Card', data = frappe.db.get_value('Job Card',
@ -133,7 +143,7 @@ class StockEntry(StockController):
def validate_purpose(self): def validate_purpose(self):
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
"Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor", "Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor",
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"] "Material Consumption for Manufacture"]
if self.purpose not in valid_purposes: if self.purpose not in valid_purposes:
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes))) frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
@ -199,7 +209,8 @@ class StockEntry(StockController):
item.set(f, item_details.get(f)) item.set(f, item_details.get(f))
if not item.transfer_qty and item.qty: if not item.transfer_qty and item.qty:
item.transfer_qty = item.qty * item.conversion_factor item.transfer_qty = flt(flt(item.qty) * flt(item.conversion_factor),
self.precision("transfer_qty", item))
if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture") if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
and not item.serial_no and not item.serial_no
@ -258,10 +269,10 @@ class StockEntry(StockController):
"""perform various (sometimes conditional) validations on warehouse""" """perform various (sometimes conditional) validations on warehouse"""
source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture", source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture",
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"] "Material Consumption for Manufacture"]
target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor", target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor",
"Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"] "Material Transfer for Manufacture"]
validate_for_manufacture = any([d.bom_no for d in self.get("items")]) validate_for_manufacture = any([d.bom_no for d in self.get("items")])
@ -809,7 +820,7 @@ class StockEntry(StockController):
def set_items_for_stock_in(self): def set_items_for_stock_in(self):
self.items = [] self.items = []
if self.outgoing_stock_entry and self.purpose == 'Receive at Warehouse': if self.outgoing_stock_entry and self.purpose == 'Material Transfer':
doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry) doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry)
if doc.per_transferred == 100: if doc.per_transferred == 100:
@ -1210,13 +1221,25 @@ class StockEntry(StockController):
def validate_with_material_request(self): def validate_with_material_request(self):
for item in self.get("items"): for item in self.get("items"):
if item.material_request: material_request = item.material_request or None
material_request_item = item.material_request_item or None
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
parent_se = frappe.get_value("Stock Entry Detail", item.ste_detail, ['material_request','material_request_item'],as_dict=True)
if parent_se:
material_request = parent_se.material_request
material_request_item = parent_se.material_request_item
if material_request:
mreq_item = frappe.db.get_value("Material Request Item", mreq_item = frappe.db.get_value("Material Request Item",
{"name": item.material_request_item, "parent": item.material_request}, {"name": material_request_item, "parent": material_request},
["item_code", "warehouse", "idx"], as_dict=True) ["item_code", "warehouse", "idx"], as_dict=True)
if mreq_item.item_code != item.item_code or \ if mreq_item.item_code != item.item_code:
mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse): frappe.throw(_("Item for row {0} does not match Material Request").format(item.idx),
frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx), frappe.MappingMismatchError)
elif self.purpose == "Material Transfer" and self.add_to_transit:
continue
elif mreq_item.warehouse != (item.s_warehouse if self.purpose == "Material Issue" else item.t_warehouse):
frappe.throw(_("Warehouse for row {0} does not match Material Request").format(item.idx),
frappe.MappingMismatchError) frappe.MappingMismatchError)
def validate_batch(self): def validate_batch(self):
@ -1284,7 +1307,7 @@ class StockEntry(StockController):
to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order)) to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order))
def update_transferred_qty(self): def update_transferred_qty(self):
if self.purpose == 'Receive at Warehouse': if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
stock_entries = {} stock_entries = {}
stock_entries_child_list = [] stock_entries_child_list = []
for d in self.items: for d in self.items:
@ -1342,6 +1365,20 @@ class StockEntry(StockController):
'reference_type': reference_type, 'reference_type': reference_type,
'reference_name': reference_name 'reference_name': reference_name
}) })
def set_material_request_transfer_status(self, status):
material_requests = []
if self.outgoing_stock_entry:
parent_se = frappe.get_value("Stock Entry", self.outgoing_stock_entry, 'add_to_transit')
for item in self.items:
material_request = item.material_request or None
if self.purpose == "Material Transfer" and material_request not in material_requests:
if self.outgoing_stock_entry and parent_se:
material_request = frappe.get_value("Stock Entry Detail", item.ste_detail, 'material_request')
if material_request and material_request not in material_requests:
material_requests.append(material_request)
frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
@frappe.whitelist() @frappe.whitelist()
def move_sample_to_retention_warehouse(company, items): def move_sample_to_retention_warehouse(company, items):
@ -1381,12 +1418,19 @@ def move_sample_to_retention_warehouse(company, items):
@frappe.whitelist() @frappe.whitelist()
def make_stock_in_entry(source_name, target_doc=None): def make_stock_in_entry(source_name, target_doc=None):
def set_missing_values(source, target): def set_missing_values(source, target):
target.purpose = 'Receive at Warehouse'
target.set_stock_entry_type() target.set_stock_entry_type()
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.t_warehouse = '' target_doc.t_warehouse = ''
if source_doc.material_request_item and source_doc.material_request :
add_to_transit = frappe.db.get_value('Stock Entry', source_name, 'add_to_transit')
if add_to_transit:
warehouse = frappe.get_value('Material Request Item', source_doc.material_request_item, 'warehouse')
target_doc.t_warehouse = warehouse
target_doc.s_warehouse = source_doc.t_warehouse target_doc.s_warehouse = source_doc.t_warehouse
target_doc.qty = source_doc.qty - source_doc.transferred_qty target_doc.qty = source_doc.qty - source_doc.transferred_qty

View File

@ -737,34 +737,6 @@ class TestStockEntry(unittest.TestCase):
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1) self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(se.get("items")[0].amount, 0) self.assertEqual(se.get("items")[0].amount, 0)
def test_goods_in_transit(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
warehouse = "_Test Warehouse FG 1 - _TC"
if not frappe.db.exists('Warehouse', warehouse):
create_warehouse("_Test Warehouse FG 1")
outward_entry = make_stock_entry(item_code="_Test Item",
purpose="Send to Warehouse",
source="_Test Warehouse - _TC",
target="_Test Warehouse 1 - _TC", qty=50, basic_rate=100)
inward_entry1 = make_stock_in_entry(outward_entry.name)
inward_entry1.items[0].t_warehouse = warehouse
inward_entry1.items[0].qty = 25
inward_entry1.submit()
doc = frappe.get_doc('Stock Entry', outward_entry.name)
self.assertEqual(doc.per_transferred, 50)
inward_entry2 = make_stock_in_entry(outward_entry.name)
inward_entry2.items[0].t_warehouse = warehouse
inward_entry2.items[0].qty = 25
inward_entry2.submit()
doc = frappe.get_doc('Stock Entry', outward_entry.name)
self.assertEqual(doc.per_transferred, 100)
def test_gle_for_opening_stock_entry(self): def test_gle_for_opening_stock_entry(self):
mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True) mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)

View File

@ -1,156 +1,83 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "Prompt", "autoname": "Prompt",
"beta": 0,
"creation": "2019-03-13 16:23:46.636769", "creation": "2019-03-13 16:23:46.636769",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"purpose"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Material Issue", "default": "Material Issue",
"fetch_if_empty": 0,
"fieldname": "purpose", "fieldname": "purpose",
"fieldtype": "Select", "fieldtype": "Select",
"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": "Purpose", "label": "Purpose",
"length": 0, "options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"no_copy": 0,
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0, "set_only_once": 1
"set_only_once": 1,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-08-10 23:24:37.160817",
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-26 12:02:42.144377",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Type", "name": "Stock Entry Type",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Manufacturing Manager", "role": "Manufacturing Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock User", "role": "Stock User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -23,7 +23,7 @@
}) })
</script> </script>
{% else %} {% else %}
{{ product_image(website_image or image or 'no-image.jpg') }} {{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }}
{% endif %} {% endif %}
<!-- Simple image preview --> <!-- Simple image preview -->

View File

@ -7,9 +7,9 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro product_image(website_image, css_class="") %} {% macro product_image(website_image, css_class="", alt="") %}
<div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;"> <div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;">
<img itemprop="image" class="website-image h-100 w-100" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}"> <img itemprop="image" class="website-image h-100 w-100" alt="{{ alt }}" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
</div> </div>
{% endmacro %} {% endmacro %}

View File

@ -9,6 +9,33 @@
<p class="hero-subtitle">{{ greeting_subtitle }}</p> <p class="hero-subtitle">{{ greeting_subtitle }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="search-container">
<div class="website-search" id="search-container">
<div class="dropdown">
<div class="search-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-search">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</div>
<input type="search" class="form-control" placeholder="Search the docs (Press ? to focus)" />
<div class="overflow-hidden shadow dropdown-menu w-100">
</div>
</div>
</div>
<button class="navbar-toggler" type="button"
data-toggle="collapse"
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div> </div>
</section> </section>
@ -54,5 +81,21 @@
</div> </div>
</section> </section>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{%- block script -%}
<script>
frappe.ready(() => {
frappe.setup_search('#search-container', 'kb');
});
</script>
{%- endblock -%}
{%- block style -%}
<style>
.search-container {
margin-top: 1.2rem;
max-width: 500px;
}
</style>
{%- endblock -%}