Merge branch 'develop' into razorpay-subscription
This commit is contained in:
commit
83f80517c6
@ -152,10 +152,9 @@ def build_forest(data):
|
|||||||
return [parent_account]
|
return [parent_account]
|
||||||
elif account_name == child:
|
elif account_name == child:
|
||||||
parent_account_list = return_parent(data, parent_account)
|
parent_account_list = return_parent(data, parent_account)
|
||||||
if not parent_account_list:
|
if not parent_account_list and parent_account:
|
||||||
frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
|
frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
|
||||||
frappe.bold(parent_account)))
|
frappe.bold(parent_account)))
|
||||||
|
|
||||||
return [child] + parent_account_list
|
return [child] + parent_account_list
|
||||||
|
|
||||||
charts_map, paths = {}, []
|
charts_map, paths = {}, []
|
||||||
|
@ -174,7 +174,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
read_only: 0,
|
read_only: 0,
|
||||||
fieldtype:'Date',
|
fieldtype:'Date',
|
||||||
label: __('Release Date'),
|
label: __('Release Date'),
|
||||||
default: me.frm.doc.release_date
|
default: me.frm.doc.release_date,
|
||||||
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'hold_comment',
|
fieldname: 'hold_comment',
|
||||||
|
@ -73,9 +73,9 @@
|
|||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
"column_break_28",
|
"column_break_28",
|
||||||
|
"total_net_weight",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"total_net_weight",
|
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
"tax_category",
|
"tax_category",
|
||||||
"column_break_49",
|
"column_break_49",
|
||||||
@ -1298,7 +1298,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-30 19:13:49.610538",
|
"modified": "2020-04-17 13:05:25.199832",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2013-05-24 19:29:05",
|
"creation": "2013-05-24 19:29:05",
|
||||||
@ -74,9 +75,9 @@
|
|||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
"column_break_32",
|
"column_break_32",
|
||||||
|
"total_net_weight",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"total_net_weight",
|
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_38",
|
"column_break_38",
|
||||||
@ -1577,7 +1578,8 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 181,
|
"idx": 181,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2020-02-10 04:57:11.221180",
|
"links": [],
|
||||||
|
"modified": "2020-04-17 12:38:41.435728",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -1926,16 +1926,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
item.taxes = []
|
item.taxes = []
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
def test_customer_provided_parts_si(self):
|
|
||||||
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
|
||||||
si = create_sales_invoice(item_code='CUST-0987', rate=0)
|
|
||||||
self.assertEqual(si.get("items")[0].allow_zero_valuation_rate, 1)
|
|
||||||
self.assertEqual(si.get("items")[0].amount, 0)
|
|
||||||
|
|
||||||
# test if Sales Invoice with rate is allowed
|
|
||||||
si2 = create_sales_invoice(item_code='CUST-0987', do_not_save=True)
|
|
||||||
self.assertRaises(frappe.ValidationError, si2.save)
|
|
||||||
|
|
||||||
def create_sales_invoice(**args):
|
def create_sales_invoice(**args):
|
||||||
si = frappe.new_doc("Sales Invoice")
|
si = frappe.new_doc("Sales Invoice")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{% if (filters.party_name) { %}
|
{% if (filters.party_name) { %}
|
||||||
{%= filters.party_name %}
|
{%= filters.party_name %}
|
||||||
{% } else if (filters.party) { %}
|
{% } else if (filters.party && filters.party.length) { %}
|
||||||
{%= filters.party %}
|
{%= filters.party %}
|
||||||
{% } else if (filters.account) { %}
|
{% } else if (filters.account) { %}
|
||||||
{%= filters.account %}
|
{%= filters.account %}
|
||||||
|
@ -365,6 +365,7 @@ def get_columns(filters):
|
|||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
|
"label": _("GL Entry"),
|
||||||
"fieldname": "gl_entry",
|
"fieldname": "gl_entry",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "GL Entry",
|
"options": "GL Entry",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2013-05-21 16:16:39",
|
"creation": "2013-05-21 16:16:39",
|
||||||
@ -63,9 +64,9 @@
|
|||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
"column_break_26",
|
"column_break_26",
|
||||||
|
"total_net_weight",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"total_net_weight",
|
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
"tax_category",
|
"tax_category",
|
||||||
"column_break_50",
|
"column_break_50",
|
||||||
@ -170,8 +171,8 @@
|
|||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Fetch items based on Default Supplier.",
|
|
||||||
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
|
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
|
||||||
|
"description": "Fetch items based on Default Supplier.",
|
||||||
"fieldname": "get_items_from_open_material_requests",
|
"fieldname": "get_items_from_open_material_requests",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Get Items from Open Material Requests"
|
"label": "Get Items from Open Material Requests"
|
||||||
@ -1054,7 +1055,8 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2020-01-14 18:54:39.694448",
|
"links": [],
|
||||||
|
"modified": "2020-04-17 13:04:28.185197",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -383,9 +383,6 @@ class StockController(AccountsController):
|
|||||||
# Customer Provided parts will have zero valuation rate
|
# Customer Provided parts will have zero valuation rate
|
||||||
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||||
d.allow_zero_valuation_rate = 1
|
d.allow_zero_valuation_rate = 1
|
||||||
if d.parenttype in ["Delivery Note", "Sales Invoice"] and d.rate:
|
|
||||||
frappe.throw(_("Row #{0}: {1} cannot have {2} as it is a Customer Provided Item")
|
|
||||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold("Rate")))
|
|
||||||
|
|
||||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||||
warehouse_account=None, company=None):
|
warehouse_account=None, company=None):
|
||||||
|
@ -2,15 +2,40 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Tally Migration', {
|
frappe.ui.form.on('Tally Migration', {
|
||||||
onload: function(frm) {
|
onload: function (frm) {
|
||||||
|
let reload_status = true;
|
||||||
frappe.realtime.on("tally_migration_progress_update", function (data) {
|
frappe.realtime.on("tally_migration_progress_update", function (data) {
|
||||||
|
if (reload_status) {
|
||||||
|
frappe.model.with_doc(frm.doc.doctype, frm.doc.name, () => {
|
||||||
|
frm.refresh_header();
|
||||||
|
});
|
||||||
|
reload_status = false;
|
||||||
|
}
|
||||||
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
||||||
if (data.count == data.total) {
|
let error_occurred = data.count === -1;
|
||||||
window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title);
|
if (data.count == data.total || error_occurred) {
|
||||||
|
window.setTimeout((title) => {
|
||||||
|
frm.dashboard.hide_progress(title);
|
||||||
|
frm.reload_doc();
|
||||||
|
if (error_occurred) {
|
||||||
|
frappe.msgprint({
|
||||||
|
message: __("An error has occurred during {0}. Check {1} for more details",
|
||||||
|
[
|
||||||
|
repl("<a href='#Form/Tally Migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
|
||||||
|
tally_document: frm.docname
|
||||||
|
}),
|
||||||
|
"<a href='#List/Error Log' class='variant-click'>Error Log</a>"
|
||||||
|
]
|
||||||
|
),
|
||||||
|
title: __("Tally Migration Error"),
|
||||||
|
indicator: "red"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 2000, data.title);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
refresh: function(frm) {
|
refresh: function (frm) {
|
||||||
if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
|
if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
|
||||||
if (frm.doc.is_master_data_processed) {
|
if (frm.doc.is_master_data_processed) {
|
||||||
if (frm.doc.status != "Importing Master Data") {
|
if (frm.doc.status != "Importing Master Data") {
|
||||||
@ -34,17 +59,17 @@ frappe.ui.form.on('Tally Migration', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
add_button: function(frm, label, method) {
|
add_button: function (frm, label, method) {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
label,
|
label,
|
||||||
() => frm.call({
|
() => {
|
||||||
doc: frm.doc,
|
frm.call({
|
||||||
method: method,
|
doc: frm.doc,
|
||||||
freeze: true,
|
method: method,
|
||||||
callback: () => {
|
freeze: true
|
||||||
frm.remove_custom_button(label);
|
});
|
||||||
}
|
frm.reload_doc();
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"beta": 1,
|
"beta": 1,
|
||||||
"creation": "2019-02-01 14:27:09.485238",
|
"creation": "2019-02-01 14:27:09.485238",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -14,6 +15,7 @@
|
|||||||
"tally_debtors_account",
|
"tally_debtors_account",
|
||||||
"company_section",
|
"company_section",
|
||||||
"tally_company",
|
"tally_company",
|
||||||
|
"default_uom",
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
"erpnext_company",
|
"erpnext_company",
|
||||||
"processed_files_section",
|
"processed_files_section",
|
||||||
@ -43,6 +45,7 @@
|
|||||||
"label": "Status"
|
"label": "Status"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs",
|
||||||
"fieldname": "master_data",
|
"fieldname": "master_data",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -50,6 +53,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Sundry Creditors",
|
"default": "Sundry Creditors",
|
||||||
|
"description": "Creditors Account set in Tally",
|
||||||
"fieldname": "tally_creditors_account",
|
"fieldname": "tally_creditors_account",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Tally Creditors Account",
|
"label": "Tally Creditors Account",
|
||||||
@ -61,6 +65,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Sundry Debtors",
|
"default": "Sundry Debtors",
|
||||||
|
"description": "Debtors Account set in Tally",
|
||||||
"fieldname": "tally_debtors_account",
|
"fieldname": "tally_debtors_account",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Tally Debtors Account",
|
"label": "Tally Debtors Account",
|
||||||
@ -72,6 +77,7 @@
|
|||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Company Name as per Imported Tally Data",
|
||||||
"fieldname": "tally_company",
|
"fieldname": "tally_company",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Tally Company",
|
"label": "Tally Company",
|
||||||
@ -82,9 +88,11 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Your Company set in ERPNext",
|
||||||
"fieldname": "erpnext_company",
|
"fieldname": "erpnext_company",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "ERPNext Company"
|
"label": "ERPNext Company",
|
||||||
|
"read_only_depends_on": "eval:doc.is_master_data_processed == 1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "processed_files_section",
|
"fieldname": "processed_files_section",
|
||||||
@ -155,24 +163,28 @@
|
|||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"fieldname": "is_master_data_processed",
|
"fieldname": "is_master_data_processed",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Master Data Processed",
|
"label": "Is Master Data Processed",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"fieldname": "is_day_book_data_processed",
|
"fieldname": "is_day_book_data_processed",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Day Book Data Processed",
|
"label": "Is Day Book Data Processed",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"fieldname": "is_day_book_data_imported",
|
"fieldname": "is_day_book_data_imported",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Day Book Data Imported",
|
"label": "Is Day Book Data Imported",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"fieldname": "is_master_data_imported",
|
"fieldname": "is_master_data_imported",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Master Data Imported",
|
"label": "Is Master Data Imported",
|
||||||
@ -188,13 +200,23 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Day Book Data exported from Tally that consists of all historic transactions",
|
||||||
"fieldname": "day_book_data",
|
"fieldname": "day_book_data",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Day Book Data"
|
"label": "Day Book Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Unit",
|
||||||
|
"description": "UOM in case unspecified in imported data",
|
||||||
|
"fieldname": "default_uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Default UOM",
|
||||||
|
"options": "UOM"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2019-04-29 05:46:54.394967",
|
"links": [],
|
||||||
|
"modified": "2020-04-16 13:03:28.894919",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "ERPNext Integrations",
|
"module": "ERPNext Integrations",
|
||||||
"name": "Tally Migration",
|
"name": "Tally Migration",
|
||||||
|
@ -4,20 +4,23 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from decimal import Decimal
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup as bs
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from erpnext import encode_company_abbr
|
||||||
|
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.naming import getseries, revert_series_if_last
|
from frappe.model.naming import getseries, revert_series_if_last
|
||||||
from frappe.utils.data import format_datetime
|
from frappe.utils.data import format_datetime
|
||||||
from bs4 import BeautifulSoup as bs
|
|
||||||
from erpnext import encode_company_abbr
|
|
||||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
|
|
||||||
|
|
||||||
PRIMARY_ACCOUNT = "Primary"
|
PRIMARY_ACCOUNT = "Primary"
|
||||||
VOUCHER_CHUNK_SIZE = 500
|
VOUCHER_CHUNK_SIZE = 500
|
||||||
@ -39,13 +42,15 @@ class TallyMigration(Document):
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
master_file = frappe.get_doc("File", {"file_url": data_file})
|
master_file = frappe.get_doc("File", {"file_url": data_file})
|
||||||
|
master_file_path = master_file.get_full_path()
|
||||||
|
|
||||||
with zipfile.ZipFile(master_file.get_full_path()) as zf:
|
if zipfile.is_zipfile(master_file_path):
|
||||||
encoded_content = zf.read(zf.namelist()[0])
|
with zipfile.ZipFile(master_file_path) as zf:
|
||||||
try:
|
encoded_content = zf.read(zf.namelist()[0])
|
||||||
content = encoded_content.decode("utf-8-sig")
|
try:
|
||||||
except UnicodeDecodeError:
|
content = encoded_content.decode("utf-8-sig")
|
||||||
content = encoded_content.decode("utf-16")
|
except UnicodeDecodeError:
|
||||||
|
content = encoded_content.decode("utf-16")
|
||||||
|
|
||||||
master = bs(sanitize(emptify(content)), "xml")
|
master = bs(sanitize(emptify(content)), "xml")
|
||||||
collection = master.BODY.IMPORTDATA.REQUESTDATA
|
collection = master.BODY.IMPORTDATA.REQUESTDATA
|
||||||
@ -58,13 +63,14 @@ class TallyMigration(Document):
|
|||||||
"file_name": key + ".json",
|
"file_name": key + ".json",
|
||||||
"attached_to_doctype": self.doctype,
|
"attached_to_doctype": self.doctype,
|
||||||
"attached_to_name": self.name,
|
"attached_to_name": self.name,
|
||||||
"content": json.dumps(value)
|
"content": json.dumps(value),
|
||||||
|
"is_private": True
|
||||||
}).insert()
|
}).insert()
|
||||||
setattr(self, key, f.file_url)
|
setattr(self, key, f.file_url)
|
||||||
|
|
||||||
def _process_master_data(self):
|
def _process_master_data(self):
|
||||||
def get_company_name(collection):
|
def get_company_name(collection):
|
||||||
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string
|
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
|
||||||
|
|
||||||
def get_coa_customers_suppliers(collection):
|
def get_coa_customers_suppliers(collection):
|
||||||
root_type_map = {
|
root_type_map = {
|
||||||
@ -97,17 +103,17 @@ class TallyMigration(Document):
|
|||||||
# If Ledger doesn't have PARENT field then don't create Account
|
# If Ledger doesn't have PARENT field then don't create Account
|
||||||
# For example "Profit & Loss A/c"
|
# For example "Profit & Loss A/c"
|
||||||
if account.PARENT:
|
if account.PARENT:
|
||||||
yield account.PARENT.string, account["NAME"], 0
|
yield account.PARENT.string.strip(), account["NAME"], 0
|
||||||
|
|
||||||
def get_parent(account):
|
def get_parent(account):
|
||||||
if account.PARENT:
|
if account.PARENT:
|
||||||
return account.PARENT.string
|
return account.PARENT.string.strip()
|
||||||
return {
|
return {
|
||||||
("Yes", "No"): "Application of Funds (Assets)",
|
("Yes", "No"): "Application of Funds (Assets)",
|
||||||
("Yes", "Yes"): "Expenses",
|
("Yes", "Yes"): "Expenses",
|
||||||
("No", "Yes"): "Income",
|
("No", "Yes"): "Income",
|
||||||
("No", "No"): "Source of Funds (Liabilities)",
|
("No", "No"): "Source of Funds (Liabilities)",
|
||||||
}[(account.ISDEEMEDPOSITIVE.string, account.ISREVENUE.string)]
|
}[(account.ISDEEMEDPOSITIVE.string.strip(), account.ISREVENUE.string.strip())]
|
||||||
|
|
||||||
def get_children_and_parent_dict(accounts):
|
def get_children_and_parent_dict(accounts):
|
||||||
children, parents = {}, {}
|
children, parents = {}, {}
|
||||||
@ -145,38 +151,38 @@ class TallyMigration(Document):
|
|||||||
parties, addresses = [], []
|
parties, addresses = [], []
|
||||||
for account in collection.find_all("LEDGER"):
|
for account in collection.find_all("LEDGER"):
|
||||||
party_type = None
|
party_type = None
|
||||||
if account.NAME.string in customers:
|
if account.NAME.string.strip() in customers:
|
||||||
party_type = "Customer"
|
party_type = "Customer"
|
||||||
parties.append({
|
parties.append({
|
||||||
"doctype": party_type,
|
"doctype": party_type,
|
||||||
"customer_name": account.NAME.string,
|
"customer_name": account.NAME.string.strip(),
|
||||||
"tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
|
"tax_id": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
|
||||||
"customer_group": "All Customer Groups",
|
"customer_group": "All Customer Groups",
|
||||||
"territory": "All Territories",
|
"territory": "All Territories",
|
||||||
"customer_type": "Individual",
|
"customer_type": "Individual",
|
||||||
})
|
})
|
||||||
elif account.NAME.string in suppliers:
|
elif account.NAME.string.strip() in suppliers:
|
||||||
party_type = "Supplier"
|
party_type = "Supplier"
|
||||||
parties.append({
|
parties.append({
|
||||||
"doctype": party_type,
|
"doctype": party_type,
|
||||||
"supplier_name": account.NAME.string,
|
"supplier_name": account.NAME.string.strip(),
|
||||||
"pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
|
"pan": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
|
||||||
"supplier_group": "All Supplier Groups",
|
"supplier_group": "All Supplier Groups",
|
||||||
"supplier_type": "Individual",
|
"supplier_type": "Individual",
|
||||||
})
|
})
|
||||||
if party_type:
|
if party_type:
|
||||||
address = "\n".join([a.string for a in account.find_all("ADDRESS")])
|
address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
|
||||||
addresses.append({
|
addresses.append({
|
||||||
"doctype": "Address",
|
"doctype": "Address",
|
||||||
"address_line1": address[:140].strip(),
|
"address_line1": address[:140].strip(),
|
||||||
"address_line2": address[140:].strip(),
|
"address_line2": address[140:].strip(),
|
||||||
"country": account.COUNTRYNAME.string if account.COUNTRYNAME else None,
|
"country": account.COUNTRYNAME.string.strip() if account.COUNTRYNAME else None,
|
||||||
"state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
|
"state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
|
||||||
"gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
|
"gst_state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
|
||||||
"pin_code": account.PINCODE.string if account.PINCODE else None,
|
"pin_code": account.PINCODE.string.strip() if account.PINCODE else None,
|
||||||
"mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
|
"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
|
||||||
"phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
|
"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
|
||||||
"gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None,
|
"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
|
||||||
"links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
|
"links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
|
||||||
})
|
})
|
||||||
return parties, addresses
|
return parties, addresses
|
||||||
@ -184,41 +190,50 @@ class TallyMigration(Document):
|
|||||||
def get_stock_items_uoms(collection):
|
def get_stock_items_uoms(collection):
|
||||||
uoms = []
|
uoms = []
|
||||||
for uom in collection.find_all("UNIT"):
|
for uom in collection.find_all("UNIT"):
|
||||||
uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string})
|
uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string.strip()})
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for item in collection.find_all("STOCKITEM"):
|
for item in collection.find_all("STOCKITEM"):
|
||||||
|
stock_uom = item.BASEUNITS.string.strip() if item.BASEUNITS else self.default_uom
|
||||||
items.append({
|
items.append({
|
||||||
"doctype": "Item",
|
"doctype": "Item",
|
||||||
"item_code" : item.NAME.string,
|
"item_code" : item.NAME.string.strip(),
|
||||||
"stock_uom": item.BASEUNITS.string,
|
"stock_uom": stock_uom.strip(),
|
||||||
"is_stock_item": 0,
|
"is_stock_item": 0,
|
||||||
"item_group": "All Item Groups",
|
"item_group": "All Item Groups",
|
||||||
"item_defaults": [{"company": self.erpnext_company}]
|
"item_defaults": [{"company": self.erpnext_company}]
|
||||||
})
|
})
|
||||||
|
|
||||||
return items, uoms
|
return items, uoms
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
|
||||||
|
collection = self.get_collection(self.master_data)
|
||||||
|
company = get_company_name(collection)
|
||||||
|
self.tally_company = company
|
||||||
|
self.erpnext_company = company
|
||||||
|
|
||||||
self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
|
self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
|
||||||
collection = self.get_collection(self.master_data)
|
chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
|
||||||
|
|
||||||
company = get_company_name(collection)
|
self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
|
||||||
self.tally_company = company
|
parties, addresses = get_parties_addresses(collection, customers, suppliers)
|
||||||
self.erpnext_company = company
|
|
||||||
|
|
||||||
self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
|
self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
|
||||||
chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
|
items, uoms = get_stock_items_uoms(collection)
|
||||||
self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
|
data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
|
||||||
parties, addresses = get_parties_addresses(collection, customers, suppliers)
|
|
||||||
self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
|
|
||||||
items, uoms = get_stock_items_uoms(collection)
|
|
||||||
data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
|
|
||||||
self.publish("Process Master Data", _("Done"), 5, 5)
|
|
||||||
|
|
||||||
self.dump_processed_data(data)
|
self.publish("Process Master Data", _("Done"), 5, 5)
|
||||||
self.is_master_data_processed = 1
|
self.dump_processed_data(data)
|
||||||
self.status = ""
|
|
||||||
self.save()
|
self.is_master_data_processed = 1
|
||||||
|
|
||||||
|
except:
|
||||||
|
self.publish("Process Master Data", _("Process Failed"), -1, 5)
|
||||||
|
self.log()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
def publish(self, title, message, count, total):
|
def publish(self, title, message, count, total):
|
||||||
frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total})
|
frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total})
|
||||||
@ -256,7 +271,6 @@ class TallyMigration(Document):
|
|||||||
except:
|
except:
|
||||||
self.log(address)
|
self.log(address)
|
||||||
|
|
||||||
|
|
||||||
def create_items_uoms(items_file_url, uoms_file_url):
|
def create_items_uoms(items_file_url, uoms_file_url):
|
||||||
uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
|
uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
|
||||||
for uom in json.loads(uoms_file.get_content()):
|
for uom in json.loads(uoms_file.get_content()):
|
||||||
@ -273,25 +287,35 @@ class TallyMigration(Document):
|
|||||||
except:
|
except:
|
||||||
self.log(item)
|
self.log(item)
|
||||||
|
|
||||||
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
|
try:
|
||||||
create_company_and_coa(self.chart_of_accounts)
|
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
|
||||||
self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
|
create_company_and_coa(self.chart_of_accounts)
|
||||||
create_parties_and_addresses(self.parties, self.addresses)
|
|
||||||
self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
|
self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
|
||||||
create_items_uoms(self.items, self.uoms)
|
create_parties_and_addresses(self.parties, self.addresses)
|
||||||
self.publish("Import Master Data", _("Done"), 4, 4)
|
|
||||||
self.status = ""
|
self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
|
||||||
self.is_master_data_imported = 1
|
create_items_uoms(self.items, self.uoms)
|
||||||
self.save()
|
|
||||||
|
self.publish("Import Master Data", _("Done"), 4, 4)
|
||||||
|
|
||||||
|
self.is_master_data_imported = 1
|
||||||
|
|
||||||
|
except:
|
||||||
|
self.publish("Import Master Data", _("Process Failed"), -1, 5)
|
||||||
|
self.log()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
def _process_day_book_data(self):
|
def _process_day_book_data(self):
|
||||||
def get_vouchers(collection):
|
def get_vouchers(collection):
|
||||||
vouchers = []
|
vouchers = []
|
||||||
for voucher in collection.find_all("VOUCHER"):
|
for voucher in collection.find_all("VOUCHER"):
|
||||||
if voucher.ISCANCELLED.string == "Yes":
|
if voucher.ISCANCELLED.string.strip() == "Yes":
|
||||||
continue
|
continue
|
||||||
inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST")
|
inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST")
|
||||||
if voucher.VOUCHERTYPENAME.string not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
|
if voucher.VOUCHERTYPENAME.string.strip() not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
|
||||||
function = voucher_to_invoice
|
function = voucher_to_invoice
|
||||||
else:
|
else:
|
||||||
function = voucher_to_journal_entry
|
function = voucher_to_journal_entry
|
||||||
@ -307,15 +331,15 @@ class TallyMigration(Document):
|
|||||||
accounts = []
|
accounts = []
|
||||||
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
|
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
|
||||||
for entry in ledger_entries:
|
for entry in ledger_entries:
|
||||||
account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center}
|
account = {"account": encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company), "cost_center": self.default_cost_center}
|
||||||
if entry.ISPARTYLEDGER.string == "Yes":
|
if entry.ISPARTYLEDGER.string.strip() == "Yes":
|
||||||
party_details = get_party(entry.LEDGERNAME.string)
|
party_details = get_party(entry.LEDGERNAME.string.strip())
|
||||||
if party_details:
|
if party_details:
|
||||||
party_type, party_account = party_details
|
party_type, party_account = party_details
|
||||||
account["party_type"] = party_type
|
account["party_type"] = party_type
|
||||||
account["account"] = party_account
|
account["account"] = party_account
|
||||||
account["party"] = entry.LEDGERNAME.string
|
account["party"] = entry.LEDGERNAME.string.strip()
|
||||||
amount = Decimal(entry.AMOUNT.string)
|
amount = Decimal(entry.AMOUNT.string.strip())
|
||||||
if amount > 0:
|
if amount > 0:
|
||||||
account["credit_in_account_currency"] = str(abs(amount))
|
account["credit_in_account_currency"] = str(abs(amount))
|
||||||
else:
|
else:
|
||||||
@ -324,21 +348,21 @@ class TallyMigration(Document):
|
|||||||
|
|
||||||
journal_entry = {
|
journal_entry = {
|
||||||
"doctype": "Journal Entry",
|
"doctype": "Journal Entry",
|
||||||
"tally_guid": voucher.GUID.string,
|
"tally_guid": voucher.GUID.string.strip(),
|
||||||
"posting_date": voucher.DATE.string,
|
"posting_date": voucher.DATE.string.strip(),
|
||||||
"company": self.erpnext_company,
|
"company": self.erpnext_company,
|
||||||
"accounts": accounts,
|
"accounts": accounts,
|
||||||
}
|
}
|
||||||
return journal_entry
|
return journal_entry
|
||||||
|
|
||||||
def voucher_to_invoice(voucher):
|
def voucher_to_invoice(voucher):
|
||||||
if voucher.VOUCHERTYPENAME.string in ["Sales", "Credit Note"]:
|
if voucher.VOUCHERTYPENAME.string.strip() in ["Sales", "Credit Note"]:
|
||||||
doctype = "Sales Invoice"
|
doctype = "Sales Invoice"
|
||||||
party_field = "customer"
|
party_field = "customer"
|
||||||
account_field = "debit_to"
|
account_field = "debit_to"
|
||||||
account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
|
account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
|
||||||
price_list_field = "selling_price_list"
|
price_list_field = "selling_price_list"
|
||||||
elif voucher.VOUCHERTYPENAME.string in ["Purchase", "Debit Note"]:
|
elif voucher.VOUCHERTYPENAME.string.strip() in ["Purchase", "Debit Note"]:
|
||||||
doctype = "Purchase Invoice"
|
doctype = "Purchase Invoice"
|
||||||
party_field = "supplier"
|
party_field = "supplier"
|
||||||
account_field = "credit_to"
|
account_field = "credit_to"
|
||||||
@ -351,10 +375,10 @@ class TallyMigration(Document):
|
|||||||
|
|
||||||
invoice = {
|
invoice = {
|
||||||
"doctype": doctype,
|
"doctype": doctype,
|
||||||
party_field: voucher.PARTYNAME.string,
|
party_field: voucher.PARTYNAME.string.strip(),
|
||||||
"tally_guid": voucher.GUID.string,
|
"tally_guid": voucher.GUID.string.strip(),
|
||||||
"posting_date": voucher.DATE.string,
|
"posting_date": voucher.DATE.string.strip(),
|
||||||
"due_date": voucher.DATE.string,
|
"due_date": voucher.DATE.string.strip(),
|
||||||
"items": get_voucher_items(voucher, doctype),
|
"items": get_voucher_items(voucher, doctype),
|
||||||
"taxes": get_voucher_taxes(voucher),
|
"taxes": get_voucher_taxes(voucher),
|
||||||
account_field: account_name,
|
account_field: account_name,
|
||||||
@ -375,15 +399,15 @@ class TallyMigration(Document):
|
|||||||
for entry in inventory_entries:
|
for entry in inventory_entries:
|
||||||
qty, uom = entry.ACTUALQTY.string.strip().split()
|
qty, uom = entry.ACTUALQTY.string.strip().split()
|
||||||
items.append({
|
items.append({
|
||||||
"item_code": entry.STOCKITEMNAME.string,
|
"item_code": entry.STOCKITEMNAME.string.strip(),
|
||||||
"description": entry.STOCKITEMNAME.string,
|
"description": entry.STOCKITEMNAME.string.strip(),
|
||||||
"qty": qty.strip(),
|
"qty": qty.strip(),
|
||||||
"uom": uom.strip(),
|
"uom": uom.strip(),
|
||||||
"conversion_factor": 1,
|
"conversion_factor": 1,
|
||||||
"price_list_rate": entry.RATE.string.split("/")[0],
|
"price_list_rate": entry.RATE.string.strip().split("/")[0],
|
||||||
"cost_center": self.default_cost_center,
|
"cost_center": self.default_cost_center,
|
||||||
"warehouse": self.default_warehouse,
|
"warehouse": self.default_warehouse,
|
||||||
account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string, self.erpnext_company),
|
account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string.strip(), self.erpnext_company),
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@ -391,13 +415,13 @@ class TallyMigration(Document):
|
|||||||
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
|
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
|
||||||
taxes = []
|
taxes = []
|
||||||
for entry in ledger_entries:
|
for entry in ledger_entries:
|
||||||
if entry.ISPARTYLEDGER.string == "No":
|
if entry.ISPARTYLEDGER.string.strip() == "No":
|
||||||
tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company)
|
tax_account = encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company)
|
||||||
taxes.append({
|
taxes.append({
|
||||||
"charge_type": "Actual",
|
"charge_type": "Actual",
|
||||||
"account_head": tax_account,
|
"account_head": tax_account,
|
||||||
"description": tax_account,
|
"description": tax_account,
|
||||||
"tax_amount": entry.AMOUNT.string,
|
"tax_amount": entry.AMOUNT.string.strip(),
|
||||||
"cost_center": self.default_cost_center,
|
"cost_center": self.default_cost_center,
|
||||||
})
|
})
|
||||||
return taxes
|
return taxes
|
||||||
@ -408,15 +432,24 @@ class TallyMigration(Document):
|
|||||||
elif frappe.db.exists({"doctype": "Customer", "customer_name": party}):
|
elif frappe.db.exists({"doctype": "Customer", "customer_name": party}):
|
||||||
return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
|
return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
|
||||||
|
|
||||||
self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
|
try:
|
||||||
collection = self.get_collection(self.day_book_data)
|
self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
|
||||||
self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
|
collection = self.get_collection(self.day_book_data)
|
||||||
vouchers = get_vouchers(collection)
|
|
||||||
self.publish("Process Day Book Data", _("Done"), 3, 3)
|
self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
|
||||||
self.dump_processed_data({"vouchers": vouchers})
|
vouchers = get_vouchers(collection)
|
||||||
self.status = ""
|
|
||||||
self.is_day_book_data_processed = 1
|
self.publish("Process Day Book Data", _("Done"), 3, 3)
|
||||||
self.save()
|
self.dump_processed_data({"vouchers": vouchers})
|
||||||
|
|
||||||
|
self.is_day_book_data_processed = 1
|
||||||
|
|
||||||
|
except:
|
||||||
|
self.publish("Process Day Book Data", _("Process Failed"), -1, 5)
|
||||||
|
self.log()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
def _import_day_book_data(self):
|
def _import_day_book_data(self):
|
||||||
def create_fiscal_years(vouchers):
|
def create_fiscal_years(vouchers):
|
||||||
@ -454,23 +487,31 @@ class TallyMigration(Document):
|
|||||||
"currency": "INR"
|
"currency": "INR"
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
|
try:
|
||||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
|
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
|
||||||
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
|
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
|
||||||
|
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
|
||||||
|
|
||||||
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
|
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
|
||||||
vouchers = json.loads(vouchers_file.get_content())
|
vouchers = json.loads(vouchers_file.get_content())
|
||||||
|
|
||||||
create_fiscal_years(vouchers)
|
create_fiscal_years(vouchers)
|
||||||
create_price_list()
|
create_price_list()
|
||||||
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
|
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
|
||||||
|
|
||||||
total = len(vouchers)
|
total = len(vouchers)
|
||||||
is_last = False
|
is_last = False
|
||||||
for index in range(0, total, VOUCHER_CHUNK_SIZE):
|
|
||||||
if index + VOUCHER_CHUNK_SIZE >= total:
|
for index in range(0, total, VOUCHER_CHUNK_SIZE):
|
||||||
is_last = True
|
if index + VOUCHER_CHUNK_SIZE >= total:
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
|
is_last = True
|
||||||
|
frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
|
||||||
|
|
||||||
|
except:
|
||||||
|
self.log()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
def _import_vouchers(self, start, total, is_last=False):
|
def _import_vouchers(self, start, total, is_last=False):
|
||||||
frappe.flags.in_migrate = True
|
frappe.flags.in_migrate = True
|
||||||
@ -494,25 +535,26 @@ class TallyMigration(Document):
|
|||||||
frappe.flags.in_migrate = False
|
frappe.flags.in_migrate = False
|
||||||
|
|
||||||
def process_master_data(self):
|
def process_master_data(self):
|
||||||
self.status = "Processing Master Data"
|
self.set_status("Processing Master Data")
|
||||||
self.save()
|
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
def import_master_data(self):
|
def import_master_data(self):
|
||||||
self.status = "Importing Master Data"
|
self.set_status("Importing Master Data")
|
||||||
self.save()
|
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
def process_day_book_data(self):
|
def process_day_book_data(self):
|
||||||
self.status = "Processing Day Book Data"
|
self.set_status("Processing Day Book Data")
|
||||||
self.save()
|
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
def import_day_book_data(self):
|
def import_day_book_data(self):
|
||||||
self.status = "Importing Day Book Data"
|
self.set_status("Importing Day Book Data")
|
||||||
self.save()
|
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
def log(self, data=None):
|
def log(self, data=None):
|
||||||
message = "\n".join(["Data", json.dumps(data, default=str, indent=4), "Exception", traceback.format_exc()])
|
data = data or self.status
|
||||||
|
message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
|
||||||
return frappe.log_error(title="Tally Migration Error", message=message)
|
return frappe.log_error(title="Tally Migration Error", message=message)
|
||||||
|
|
||||||
|
def set_status(self, status=""):
|
||||||
|
self.status = status
|
||||||
|
self.save()
|
||||||
|
@ -776,22 +776,16 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
for payment in self.get('loans'):
|
for payment in self.get('loans'):
|
||||||
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
||||||
|
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
|
||||||
|
if payment.total_payment > total_amount:
|
||||||
|
frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
|
||||||
|
against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
|
||||||
|
frappe.bold(total_amount), frappe.bold(payment.loan)))
|
||||||
|
|
||||||
if payment.interest_amount > amounts['interest_amount']:
|
|
||||||
frappe.throw(_("""Row {0}: Paid Interest amount {1} is greater than pending interest amount {2}
|
|
||||||
against loan {3}""").format(payment.idx, frappe.bold(payment.interest_amount),
|
|
||||||
frappe.bold(amounts['interest_amount']), frappe.bold(payment.loan)))
|
|
||||||
|
|
||||||
if payment.principal_amount > amounts['payable_principal_amount']:
|
|
||||||
frappe.throw(_("""Row {0}: Paid Principal amount {1} is greater than pending principal amount {2}
|
|
||||||
against loan {3}""").format(payment.idx, frappe.bold(payment.principal_amount),
|
|
||||||
frappe.bold(amounts['payable_principal_amount']), frappe.bold(payment.loan)))
|
|
||||||
|
|
||||||
payment.total_payment = payment.interest_amount + payment.principal_amount
|
|
||||||
self.total_interest_amount += payment.interest_amount
|
self.total_interest_amount += payment.interest_amount
|
||||||
self.total_principal_amount += payment.principal_amount
|
self.total_principal_amount += payment.principal_amount
|
||||||
|
|
||||||
self.total_loan_repayment = self.total_interest_amount + self.total_principal_amount
|
self.total_loan_repayment += payment.total_payment
|
||||||
|
|
||||||
def get_loan_details(self):
|
def get_loan_details(self):
|
||||||
|
|
||||||
|
@ -149,13 +149,19 @@ class TestLoan(unittest.TestCase):
|
|||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68)
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68)
|
||||||
repayment_entry.save()
|
repayment_entry.save()
|
||||||
|
repayment_entry.submit()
|
||||||
|
|
||||||
penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
|
penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
|
||||||
|
|
||||||
self.assertEquals(flt(repayment_entry.interest_payable, 2), flt(accrued_interest_amount, 2))
|
|
||||||
self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
|
self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
|
||||||
|
|
||||||
repayment_entry.submit()
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
|
loan.load_from_db()
|
||||||
|
|
||||||
|
self.assertEquals(amounts[0], repayment_entry.interest_payable)
|
||||||
|
self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid -
|
||||||
|
penalty_amount - amounts[0], 2))
|
||||||
|
|
||||||
def test_loan_closure_repayment(self):
|
def test_loan_closure_repayment(self):
|
||||||
pledges = []
|
pledges = []
|
||||||
@ -189,15 +195,19 @@ class TestLoan(unittest.TestCase):
|
|||||||
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
|
||||||
"Loan Closure", 13315.0681)
|
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||||
repayment_entry.save()
|
repayment_entry.submit()
|
||||||
|
|
||||||
repayment_entry.amount_paid = repayment_entry.payable_amount
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
self.assertEquals(flt(repayment_entry.interest_payable, 3), flt(accrued_interest_amount, 3))
|
unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \
|
||||||
|
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||||
|
|
||||||
|
self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3),
|
||||||
|
flt(accrued_interest_amount, 3))
|
||||||
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
|
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
|
||||||
|
|
||||||
repayment_entry.submit()
|
|
||||||
loan.load_from_db()
|
loan.load_from_db()
|
||||||
self.assertEquals(loan.status, "Loan Closure Requested")
|
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||||
|
|
||||||
@ -227,57 +237,15 @@ class TestLoan(unittest.TestCase):
|
|||||||
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
|
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
|
||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5),
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5),
|
||||||
"Regular Payment", 89768.7534247)
|
"Regular Payment", 89768.75)
|
||||||
|
|
||||||
repayment_entry.save()
|
|
||||||
repayment_entry.submit()
|
repayment_entry.submit()
|
||||||
|
|
||||||
repayment_entry.load_from_db()
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
self.assertEquals(repayment_entry.interest_payable, 11250.00)
|
self.assertEquals(amounts[0], 11250.00)
|
||||||
self.assertEquals(repayment_entry.payable_principal_amount, 78303.00)
|
self.assertEquals(amounts[1], 78303.00)
|
||||||
|
|
||||||
def test_partial_loan_repayment(self):
|
|
||||||
pledges = []
|
|
||||||
pledges.append({
|
|
||||||
"loan_security": "Test Security 1",
|
|
||||||
"qty": 4000.00,
|
|
||||||
"haircut": 50
|
|
||||||
})
|
|
||||||
|
|
||||||
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
|
|
||||||
|
|
||||||
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
|
|
||||||
posting_date=get_first_day(nowdate()))
|
|
||||||
|
|
||||||
loan.submit()
|
|
||||||
|
|
||||||
self.assertEquals(loan.loan_amount, 1000000)
|
|
||||||
|
|
||||||
first_date = '2019-10-01'
|
|
||||||
last_date = '2019-10-30'
|
|
||||||
|
|
||||||
no_of_days = date_diff(last_date, first_date) + 1
|
|
||||||
|
|
||||||
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
|
||||||
/ (days_in_year(get_datetime().year) * 100)
|
|
||||||
|
|
||||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
|
||||||
|
|
||||||
process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 15))
|
|
||||||
process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 30))
|
|
||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 1), "Regular Payment", 6500)
|
|
||||||
repayment_entry.save()
|
|
||||||
repayment_entry.submit()
|
|
||||||
|
|
||||||
penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year))
|
|
||||||
|
|
||||||
lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name')
|
|
||||||
lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name')
|
|
||||||
|
|
||||||
self.assertTrue(lia1)
|
|
||||||
self.assertTrue(lia2)
|
|
||||||
|
|
||||||
def test_security_shortfall(self):
|
def test_security_shortfall(self):
|
||||||
pledges = []
|
pledges = []
|
||||||
@ -294,7 +262,7 @@ class TestLoan(unittest.TestCase):
|
|||||||
|
|
||||||
make_loan_disbursement_entry(loan.name, loan.loan_amount)
|
make_loan_disbursement_entry(loan.name, loan.loan_amount)
|
||||||
|
|
||||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
||||||
where loan_security='Test Security 2'""")
|
where loan_security='Test Security 2'""")
|
||||||
|
|
||||||
create_process_loan_security_shortfall()
|
create_process_loan_security_shortfall()
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"is_term_loan",
|
"is_term_loan",
|
||||||
"is_paid",
|
|
||||||
"section_break_7",
|
"section_break_7",
|
||||||
"pending_principal_amount",
|
"pending_principal_amount",
|
||||||
"payable_principal_amount",
|
"payable_principal_amount",
|
||||||
|
"paid_principal_amount",
|
||||||
"column_break_14",
|
"column_break_14",
|
||||||
"interest_amount",
|
"interest_amount",
|
||||||
|
"paid_interest_amount",
|
||||||
"section_break_15",
|
"section_break_15",
|
||||||
"process_loan_interest_accrual",
|
"process_loan_interest_accrual",
|
||||||
"repayment_schedule_name",
|
"repayment_schedule_name",
|
||||||
@ -101,13 +102,6 @@
|
|||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company"
|
"options": "Company"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_paid",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Paid",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_from": "loan.is_term_loan",
|
"fetch_from": "loan.is_term_loan",
|
||||||
@ -143,12 +137,24 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Repayment Schedule Name",
|
"label": "Repayment Schedule Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_principal_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Principal Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_interest_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Interest Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-10 18:31:02.369857",
|
"modified": "2020-04-16 11:24:23.258404",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Interest Accrual",
|
"name": "Loan Interest Accrual",
|
||||||
|
@ -30,9 +30,8 @@
|
|||||||
"reference_number",
|
"reference_number",
|
||||||
"column_break_21",
|
"column_break_21",
|
||||||
"reference_date",
|
"reference_date",
|
||||||
"paid_accrual_entries",
|
|
||||||
"partial_paid_entry",
|
|
||||||
"principal_amount_paid",
|
"principal_amount_paid",
|
||||||
|
"repayment_details",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -155,13 +154,6 @@
|
|||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "paid_accrual_entries",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Paid Accrual Entries",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_from": "against_loan.is_term_loan",
|
"fetch_from": "against_loan.is_term_loan",
|
||||||
@ -197,13 +189,6 @@
|
|||||||
"fieldname": "column_break_21",
|
"fieldname": "column_break_21",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "partial_paid_entry",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Partial Paid Entry",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0.0",
|
"default": "0.0",
|
||||||
"fieldname": "principal_amount_paid",
|
"fieldname": "principal_amount_paid",
|
||||||
@ -225,11 +210,18 @@
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Due Date",
|
"label": "Due Date",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "repayment_details",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Repayment Details",
|
||||||
|
"options": "Loan Repayment Detail"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-26 06:18:54.934538",
|
"modified": "2020-04-16 18:14:45.166754",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Repayment",
|
"name": "Loan Repayment",
|
||||||
@ -264,7 +256,6 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
|
@ -19,11 +19,11 @@ class LoanRepayment(AccountsController):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type)
|
amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type)
|
||||||
self.set_missing_values(amounts)
|
self.set_missing_values(amounts)
|
||||||
|
self.validate_amount()
|
||||||
def before_submit(self):
|
self.allocate_amounts(amounts['pending_accrual_entries'])
|
||||||
self.mark_as_paid()
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.update_paid_amount()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@ -38,32 +38,25 @@ class LoanRepayment(AccountsController):
|
|||||||
self.cost_center = erpnext.get_default_cost_center(self.company)
|
self.cost_center = erpnext.get_default_cost_center(self.company)
|
||||||
|
|
||||||
if not self.interest_payable:
|
if not self.interest_payable:
|
||||||
self.interest_payable = amounts['interest_amount']
|
self.interest_payable = flt(amounts['interest_amount'], 2)
|
||||||
|
|
||||||
if not self.penalty_amount:
|
if not self.penalty_amount:
|
||||||
self.penalty_amount = amounts['penalty_amount']
|
self.penalty_amount = flt(amounts['penalty_amount'], 2)
|
||||||
|
|
||||||
if not self.pending_principal_amount:
|
if not self.pending_principal_amount:
|
||||||
self.pending_principal_amount = amounts['pending_principal_amount']
|
self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2)
|
||||||
|
|
||||||
if not self.payable_principal_amount and self.is_term_loan:
|
if not self.payable_principal_amount and self.is_term_loan:
|
||||||
self.payable_principal_amount = amounts['payable_principal_amount']
|
self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2)
|
||||||
|
|
||||||
if not self.payable_amount:
|
if not self.payable_amount:
|
||||||
self.payable_amount = amounts['payable_amount']
|
self.payable_amount = flt(amounts['payable_amount'], 2)
|
||||||
|
|
||||||
if amounts.get('paid_accrual_entries'):
|
|
||||||
self.paid_accrual_entries = frappe.as_json(amounts.get('paid_accrual_entries'))
|
|
||||||
|
|
||||||
if amounts.get('due_date'):
|
if amounts.get('due_date'):
|
||||||
self.due_date = amounts.get('due_date')
|
self.due_date = amounts.get('due_date')
|
||||||
|
|
||||||
def mark_as_paid(self):
|
def validate_amount(self):
|
||||||
paid_entries = []
|
if not self.amount_paid:
|
||||||
paid_amount = self.amount_paid
|
|
||||||
interest_paid = paid_amount
|
|
||||||
|
|
||||||
if not paid_amount:
|
|
||||||
frappe.throw(_("Amount paid cannot be zero"))
|
frappe.throw(_("Amount paid cannot be zero"))
|
||||||
|
|
||||||
if self.amount_paid < self.penalty_amount:
|
if self.amount_paid < self.penalty_amount:
|
||||||
@ -74,37 +67,15 @@ class LoanRepayment(AccountsController):
|
|||||||
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
|
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
|
def update_paid_amount(self):
|
||||||
loan = frappe.get_doc("Loan", self.against_loan)
|
loan = frappe.get_doc("Loan", self.against_loan)
|
||||||
|
|
||||||
if self.paid_accrual_entries:
|
for payment in self.repayment_details:
|
||||||
paid_accrual_entries = json.loads(self.paid_accrual_entries)
|
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
|
||||||
|
SET paid_principal_amount = `paid_principal_amount` + %s,
|
||||||
if paid_amount - self.penalty_amount > 0 and self.paid_accrual_entries:
|
paid_interest_amount = `paid_interest_amount` + %s
|
||||||
|
WHERE name = %s""",
|
||||||
interest_paid = paid_amount - self.penalty_amount
|
(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
|
||||||
|
|
||||||
for lia, interest_amount in iteritems(paid_accrual_entries):
|
|
||||||
if interest_amount <= interest_paid:
|
|
||||||
paid_entries.append(lia)
|
|
||||||
interest_paid -= interest_amount
|
|
||||||
elif interest_paid:
|
|
||||||
self.partial_paid_entry = frappe.as_json({"name": lia, "interest_amount": interest_amount})
|
|
||||||
frappe.db.set_value("Loan Interest Accrual", lia, "interest_amount",
|
|
||||||
interest_amount - interest_paid)
|
|
||||||
interest_paid = 0
|
|
||||||
|
|
||||||
if paid_entries:
|
|
||||||
self.paid_accrual_entries = frappe.as_json(paid_entries)
|
|
||||||
else:
|
|
||||||
self.paid_accrual_entries = ""
|
|
||||||
|
|
||||||
if interest_paid:
|
|
||||||
self.principal_amount_paid = interest_paid
|
|
||||||
|
|
||||||
if paid_entries:
|
|
||||||
frappe.db.sql("""UPDATE `tabLoan Interest Accrual`
|
|
||||||
SET is_paid = 1 where name in (%s)""" #nosec
|
|
||||||
% ", ".join(['%s']*len(paid_entries)), tuple(paid_entries))
|
|
||||||
|
|
||||||
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
||||||
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
||||||
@ -116,21 +87,14 @@ class LoanRepayment(AccountsController):
|
|||||||
update_shortfall_status(self.against_loan, self.principal_amount_paid)
|
update_shortfall_status(self.against_loan, self.principal_amount_paid)
|
||||||
|
|
||||||
def mark_as_unpaid(self):
|
def mark_as_unpaid(self):
|
||||||
|
|
||||||
loan = frappe.get_doc("Loan", self.against_loan)
|
loan = frappe.get_doc("Loan", self.against_loan)
|
||||||
|
|
||||||
if self.paid_accrual_entries:
|
for payment in self.repayment_details:
|
||||||
paid_accrual_entries = json.loads(self.paid_accrual_entries)
|
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
|
||||||
|
SET paid_principal_amount = `paid_principal_amount` - %s,
|
||||||
if self.paid_accrual_entries:
|
paid_interest_amount = `paid_interest_amount` - %s
|
||||||
frappe.db.sql("""UPDATE `tabLoan Interest Accrual`
|
WHERE name = %s""",
|
||||||
SET is_paid = 0 where name in (%s)""" #nosec
|
(payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual))
|
||||||
% ", ".join(['%s']*len(paid_accrual_entries)), tuple(paid_accrual_entries))
|
|
||||||
|
|
||||||
if self.partial_paid_entry:
|
|
||||||
partial_paid_entry = json.loads(self.partial_paid_entry)
|
|
||||||
frappe.db.set_value("Loan Interest Accrual", partial_paid_entry["name"], "interest_amount",
|
|
||||||
partial_paid_entry["interest_amount"])
|
|
||||||
|
|
||||||
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
|
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
|
||||||
WHERE name = %s """, (loan.total_amount_paid - self.amount_paid,
|
WHERE name = %s """, (loan.total_amount_paid - self.amount_paid,
|
||||||
@ -139,6 +103,38 @@ class LoanRepayment(AccountsController):
|
|||||||
if loan.status == "Loan Closure Requested":
|
if loan.status == "Loan Closure Requested":
|
||||||
frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed")
|
frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed")
|
||||||
|
|
||||||
|
def allocate_amounts(self, paid_entries):
|
||||||
|
self.set('repayment_details', [])
|
||||||
|
self.principal_amount_paid = 0
|
||||||
|
|
||||||
|
if self.amount_paid - self.penalty_amount > 0 and paid_entries:
|
||||||
|
interest_paid = self.amount_paid - self.penalty_amount
|
||||||
|
for lia, amounts in iteritems(paid_entries):
|
||||||
|
if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
|
||||||
|
interest_amount = amounts['interest_amount']
|
||||||
|
paid_principal = amounts['payable_principal_amount']
|
||||||
|
self.principal_amount_paid += paid_principal
|
||||||
|
interest_paid -= (interest_amount + paid_principal)
|
||||||
|
elif interest_paid:
|
||||||
|
if interest_paid >= amounts['interest_amount']:
|
||||||
|
interest_amount = amounts['interest_amount']
|
||||||
|
paid_principal = interest_paid - interest_amount
|
||||||
|
self.principal_amount_paid += paid_principal
|
||||||
|
interest_paid = 0
|
||||||
|
else:
|
||||||
|
interest_amount = interest_paid
|
||||||
|
interest_paid = 0
|
||||||
|
paid_principal=0
|
||||||
|
|
||||||
|
self.append('repayment_details', {
|
||||||
|
'loan_interest_accrual': lia,
|
||||||
|
'paid_interest_amount': interest_amount,
|
||||||
|
'paid_principal_amount': paid_principal
|
||||||
|
})
|
||||||
|
|
||||||
|
if interest_paid:
|
||||||
|
self.principal_amount_paid += interest_paid
|
||||||
|
|
||||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||||
gle_map = []
|
gle_map = []
|
||||||
loan_details = frappe.get_doc("Loan", self.against_loan)
|
loan_details = frappe.get_doc("Loan", self.against_loan)
|
||||||
@ -223,7 +219,7 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
|
|||||||
"posting_date": posting_date,
|
"posting_date": posting_date,
|
||||||
"applicant": applicant,
|
"applicant": applicant,
|
||||||
"penalty_amount": penalty_amount,
|
"penalty_amount": penalty_amount,
|
||||||
"interst_payable": interest_payable,
|
"interest_payable": interest_payable,
|
||||||
"payable_principal_amount": payable_principal_amount,
|
"payable_principal_amount": payable_principal_amount,
|
||||||
"amount_paid": amount_paid,
|
"amount_paid": amount_paid,
|
||||||
"loan_type": loan_type
|
"loan_type": loan_type
|
||||||
@ -232,15 +228,22 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
|
|||||||
return lr
|
return lr
|
||||||
|
|
||||||
def get_accrued_interest_entries(against_loan):
|
def get_accrued_interest_entries(against_loan):
|
||||||
accrued_interest_entries = frappe.get_all("Loan Interest Accrual",
|
|
||||||
fields=["name", "interest_amount", "posting_date", "payable_principal_amount"],
|
|
||||||
filters = {
|
|
||||||
"loan": against_loan,
|
|
||||||
"is_paid": 0,
|
|
||||||
"docstatus": 1
|
|
||||||
}, order_by="posting_date")
|
|
||||||
|
|
||||||
return accrued_interest_entries
|
unpaid_accrued_entries = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount,
|
||||||
|
payable_principal_amount - paid_principal_amount as payable_principal_amount
|
||||||
|
FROM
|
||||||
|
`tabLoan Interest Accrual`
|
||||||
|
WHERE
|
||||||
|
loan = %s
|
||||||
|
AND (interest_amount - paid_interest_amount > 0 OR
|
||||||
|
payable_principal_amount - paid_principal_amount > 0)
|
||||||
|
AND
|
||||||
|
docstatus = 1
|
||||||
|
""", (against_loan), as_dict=1)
|
||||||
|
|
||||||
|
return unpaid_accrued_entries
|
||||||
|
|
||||||
# This function returns the amounts that are payable at the time of loan repayment based on posting date
|
# This function returns the amounts that are payable at the time of loan repayment based on posting date
|
||||||
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
|
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
|
||||||
@ -273,8 +276,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
|||||||
total_pending_interest += entry.interest_amount
|
total_pending_interest += entry.interest_amount
|
||||||
payable_principal_amount += entry.payable_principal_amount
|
payable_principal_amount += entry.payable_principal_amount
|
||||||
|
|
||||||
pending_accrual_entries.setdefault(entry.name,
|
pending_accrual_entries.setdefault(entry.name, {
|
||||||
flt(entry.interest_amount) + flt(entry.payable_principal_amount))
|
'interest_amount': flt(entry.interest_amount),
|
||||||
|
'payable_principal_amount': flt(entry.payable_principal_amount)
|
||||||
|
})
|
||||||
|
|
||||||
final_due_date = due_date
|
final_due_date = due_date
|
||||||
|
|
||||||
@ -291,7 +296,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
|||||||
amounts["interest_amount"] = total_pending_interest
|
amounts["interest_amount"] = total_pending_interest
|
||||||
amounts["penalty_amount"] = penalty_amount
|
amounts["penalty_amount"] = penalty_amount
|
||||||
amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
|
amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
|
||||||
amounts["paid_accrual_entries"] = pending_accrual_entries
|
amounts["pending_accrual_entries"] = pending_accrual_entries
|
||||||
|
|
||||||
if final_due_date:
|
if final_due_date:
|
||||||
amounts["due_date"] = final_due_date
|
amounts["due_date"] = final_due_date
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-04-15 18:31:54.026923",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"loan_interest_accrual",
|
||||||
|
"paid_principal_amount",
|
||||||
|
"paid_interest_amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "loan_interest_accrual",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Loan Interest Accrual",
|
||||||
|
"options": "Loan Interest Accrual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_principal_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Principal Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_interest_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Interest Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-04-15 21:50:03.837019",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Loan Management",
|
||||||
|
"name": "Loan Repayment Detail",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -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 LoanRepaymentDetail(Document):
|
||||||
|
pass
|
@ -28,7 +28,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "loan_account",
|
"fieldname": "loan_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Loan Account",
|
"label": "Loan Account",
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -50,21 +49,23 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Principal Amount",
|
"label": "Principal Amount",
|
||||||
"options": "Company:company:default_currency"
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "interest_amount",
|
"fieldname": "interest_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Interest Amount",
|
"label": "Interest Amount",
|
||||||
"options": "Company:company:default_currency"
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "total_payment",
|
"fieldname": "total_payment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Total Payment",
|
"label": "Total Payment",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "loan_repayment_entry",
|
"fieldname": "loan_repayment_entry",
|
||||||
@ -84,7 +85,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-09 20:01:53.546364",
|
"modified": "2020-04-16 13:17:04.798335",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Salary Slip Loan",
|
"name": "Salary Slip Loan",
|
||||||
|
@ -82,7 +82,9 @@ frappe.ui.form.on('Job Card', {
|
|||||||
frm.set_value('current_time' , 0);
|
frm.set_value('current_time' , 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.save();
|
frm.save("Save", () => {}, "", () => {
|
||||||
|
frm.doc.time_logs.pop(-1);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
complete_job: function(frm, completed_time, completed_qty) {
|
complete_job: function(frm, completed_time, completed_qty) {
|
||||||
@ -105,6 +107,24 @@ frappe.ui.form.on('Job Card', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
validate: function(frm) {
|
||||||
|
if ((!frm.doc.time_logs || !frm.doc.time_logs.length) && frm.doc.started_time) {
|
||||||
|
frm.trigger("reset_timer");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
employee: function(frm) {
|
||||||
|
if (frm.doc.job_started && !frm.doc.current_time) {
|
||||||
|
frm.trigger("reset_timer");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset_timer: function(frm) {
|
||||||
|
frm.set_value('started_time' , '');
|
||||||
|
frm.set_value('job_started', 0);
|
||||||
|
frm.set_value('current_time' , 0);
|
||||||
|
},
|
||||||
|
|
||||||
make_dashboard: function(frm) {
|
make_dashboard: function(frm) {
|
||||||
if(frm.doc.__islocal)
|
if(frm.doc.__islocal)
|
||||||
return;
|
return;
|
||||||
@ -137,12 +157,12 @@ frappe.ui.form.on('Job Card', {
|
|||||||
updateStopwatch(current);
|
updateStopwatch(current);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStopwatch(increment) {
|
function updateStopwatch(increment) {
|
||||||
var hours = Math.floor(increment / 3600);
|
var hours = Math.floor(increment / 3600);
|
||||||
var minutes = Math.floor((increment - (hours * 3600)) / 60);
|
var minutes = Math.floor((increment - (hours * 3600)) / 60);
|
||||||
var seconds = increment - (hours * 3600) - (minutes * 60);
|
var seconds = increment - (hours * 3600) - (minutes * 60);
|
||||||
|
|
||||||
$(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
|
$(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
|
||||||
$(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
|
$(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
|
||||||
$(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
|
$(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
|
||||||
@ -205,5 +225,10 @@ frappe.ui.form.on('Job Card', {
|
|||||||
frappe.ui.form.on('Job Card Time Log', {
|
frappe.ui.form.on('Job Card Time Log', {
|
||||||
completed_qty: function(frm) {
|
completed_qty: function(frm) {
|
||||||
frm.events.set_total_completed_qty(frm);
|
frm.events.set_total_completed_qty(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
to_time: function(frm) {
|
||||||
|
frm.set_value('job_started', 0);
|
||||||
|
frm.set_value('started_time', '');
|
||||||
}
|
}
|
||||||
})
|
})
|
@ -9,7 +9,7 @@ from frappe import _
|
|||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
|
from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
|
||||||
get_time, add_to_date, time_diff, add_days, get_datetime_str)
|
get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form)
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||||
|
|
||||||
@ -189,11 +189,15 @@ class JobCard(Document):
|
|||||||
|
|
||||||
def validate_job_card(self):
|
def validate_job_card(self):
|
||||||
if not self.time_logs:
|
if not self.time_logs:
|
||||||
frappe.throw(_("Time logs are required for job card {0}").format(self.name))
|
frappe.throw(_("Time logs are required for {0} {1}")
|
||||||
|
.format(frappe.bold("Job Card"), get_link_to_form("Job Card", self.name)))
|
||||||
|
|
||||||
if self.for_quantity and self.total_completed_qty != self.for_quantity:
|
if self.for_quantity and self.total_completed_qty != self.for_quantity:
|
||||||
frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})"
|
total_completed_qty = frappe.bold(_("Total Completed Qty"))
|
||||||
.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))))
|
qty_to_manufacture = frappe.bold(_("Qty to Manufacture"))
|
||||||
|
|
||||||
|
frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})"
|
||||||
|
.format(total_completed_qty, frappe.bold(self.total_completed_qty), qty_to_manufacture,frappe.bold(self.for_quantity))))
|
||||||
|
|
||||||
def update_work_order(self):
|
def update_work_order(self):
|
||||||
if not self.work_order:
|
if not self.work_order:
|
||||||
|
@ -240,6 +240,8 @@ frappe.ui.form.on("Work Order", {
|
|||||||
});
|
});
|
||||||
}, __("Job Card"), __("Create"));
|
}, __("Job Card"), __("Create"));
|
||||||
|
|
||||||
|
dialog.fields_dict["operations"].grid.wrapper.find('.grid-add-row').hide();
|
||||||
|
|
||||||
var pending_qty = 0;
|
var pending_qty = 0;
|
||||||
frm.doc.operations.forEach(data => {
|
frm.doc.operations.forEach(data => {
|
||||||
if(data.completed_qty != frm.doc.qty) {
|
if(data.completed_qty != frm.doc.qty) {
|
||||||
|
@ -83,7 +83,6 @@
|
|||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Open\nCompleted\nCancelled",
|
"options": "Open\nCompleted\nCancelled",
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -60,9 +60,9 @@
|
|||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
"column_break_33",
|
"column_break_33",
|
||||||
|
"total_net_weight",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"total_net_weight",
|
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
"tax_category",
|
"tax_category",
|
||||||
"column_break_38",
|
"column_break_38",
|
||||||
@ -1196,7 +1196,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-30 19:15:28.605085",
|
"modified": "2020-04-17 12:50:39.640534",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order",
|
"name": "Sales Order",
|
||||||
|
@ -287,7 +287,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
|||||||
if (in_list(['serial_no', 'batch_no'], field)) {
|
if (in_list(['serial_no', 'batch_no'], field)) {
|
||||||
args[field] = value;
|
args[field] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to cur_frm
|
// add to cur_frm
|
||||||
const item = this.frm.add_child('items', args);
|
const item = this.frm.add_child('items', args);
|
||||||
frappe.flags.hide_serial_batch_dialog = true;
|
frappe.flags.hide_serial_batch_dialog = true;
|
||||||
|
@ -66,9 +66,9 @@
|
|||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
"column_break_33",
|
"column_break_33",
|
||||||
|
"total_net_weight",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"total_net_weight",
|
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
"tax_category",
|
"tax_category",
|
||||||
"column_break_39",
|
"column_break_39",
|
||||||
@ -1256,7 +1256,7 @@
|
|||||||
"idx": 146,
|
"idx": 146,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-31 19:17:13.122644",
|
"modified": "2020-04-17 12:51:41.288600",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note",
|
"name": "Delivery Note",
|
||||||
|
@ -434,15 +434,6 @@ class TestDeliveryNote(unittest.TestCase):
|
|||||||
update_delivery_note_status(dn.name, "Closed")
|
update_delivery_note_status(dn.name, "Closed")
|
||||||
self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
|
self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
|
||||||
|
|
||||||
def test_customer_provided_parts_dn(self):
|
|
||||||
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
|
||||||
dn = create_delivery_note(item_code='CUST-0987', rate=0)
|
|
||||||
self.assertEqual(dn.get("items")[0].allow_zero_valuation_rate, 1)
|
|
||||||
|
|
||||||
# test if Delivery Note with rate is allowed against Customer Provided Item
|
|
||||||
dn2 = create_delivery_note(item_code='CUST-0987', do_not_save=True)
|
|
||||||
self.assertRaises(frappe.ValidationError, dn2.save)
|
|
||||||
|
|
||||||
def test_dn_billing_status_case1(self):
|
def test_dn_billing_status_case1(self):
|
||||||
# SO -> DN -> SI
|
# SO -> DN -> SI
|
||||||
so = make_sales_order()
|
so = make_sales_order()
|
||||||
|
@ -175,12 +175,11 @@ class MaterialRequest(BuyingController):
|
|||||||
|
|
||||||
frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
|
frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
|
||||||
|
|
||||||
target_ref_field = 'qty' if self.material_request_type == "Manufacture" else 'stock_qty'
|
|
||||||
self._update_percent_field({
|
self._update_percent_field({
|
||||||
"target_dt": "Material Request Item",
|
"target_dt": "Material Request Item",
|
||||||
"target_parent_dt": self.doctype,
|
"target_parent_dt": self.doctype,
|
||||||
"target_parent_field": "per_ordered",
|
"target_parent_field": "per_ordered",
|
||||||
"target_ref_field": target_ref_field,
|
"target_ref_field": "stock_qty",
|
||||||
"target_field": "ordered_qty",
|
"target_field": "ordered_qty",
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
}, update_modified)
|
}, update_modified)
|
||||||
@ -499,7 +498,7 @@ def raise_work_orders(material_request):
|
|||||||
default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
|
default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
|
||||||
|
|
||||||
for d in mr.items:
|
for d in mr.items:
|
||||||
if (d.qty - d.ordered_qty) >0:
|
if (d.stock_qty - d.ordered_qty) > 0:
|
||||||
if frappe.db.exists("BOM", {"item": d.item_code, "is_default": 1}):
|
if frappe.db.exists("BOM", {"item": d.item_code, "is_default": 1}):
|
||||||
wo_order = frappe.new_doc("Work Order")
|
wo_order = frappe.new_doc("Work Order")
|
||||||
wo_order.update({
|
wo_order.update({
|
||||||
@ -531,7 +530,7 @@ def raise_work_orders(material_request):
|
|||||||
msgprint(_("The following Work Orders were created:") + '\n' + new_line_sep(message))
|
msgprint(_("The following Work Orders were created:") + '\n' + new_line_sep(message))
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
|
frappe.throw(_("Work Order cannot be created for following reason:") + '\n' + new_line_sep(errors))
|
||||||
|
|
||||||
return work_orders
|
return work_orders
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, unittest, erpnext
|
import frappe, unittest, erpnext
|
||||||
from frappe.utils import flt, today
|
from frappe.utils import flt, today
|
||||||
from erpnext.stock.doctype.material_request.material_request import raise_work_orders
|
from erpnext.stock.doctype.material_request.material_request \
|
||||||
|
import raise_work_orders, make_stock_entry, make_purchase_order, make_supplier_quotation
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
class TestMaterialRequest(unittest.TestCase):
|
class TestMaterialRequest(unittest.TestCase):
|
||||||
@ -15,8 +16,6 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
erpnext.set_perpetual_inventory(0)
|
erpnext.set_perpetual_inventory(0)
|
||||||
|
|
||||||
def test_make_purchase_order(self):
|
def test_make_purchase_order(self):
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
|
||||||
|
|
||||||
mr = frappe.copy_doc(test_records[0]).insert()
|
mr = frappe.copy_doc(test_records[0]).insert()
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, make_purchase_order,
|
self.assertRaises(frappe.ValidationError, make_purchase_order,
|
||||||
@ -30,8 +29,6 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
self.assertEqual(len(po.get("items")), len(mr.get("items")))
|
self.assertEqual(len(po.get("items")), len(mr.get("items")))
|
||||||
|
|
||||||
def test_make_supplier_quotation(self):
|
def test_make_supplier_quotation(self):
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation
|
|
||||||
|
|
||||||
mr = frappe.copy_doc(test_records[0]).insert()
|
mr = frappe.copy_doc(test_records[0]).insert()
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, make_supplier_quotation, mr.name)
|
self.assertRaises(frappe.ValidationError, make_supplier_quotation, mr.name)
|
||||||
@ -45,12 +42,9 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def test_make_stock_entry(self):
|
def test_make_stock_entry(self):
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
|
|
||||||
mr = frappe.copy_doc(test_records[0]).insert()
|
mr = frappe.copy_doc(test_records[0]).insert()
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, make_stock_entry,
|
self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name)
|
||||||
mr.name)
|
|
||||||
|
|
||||||
mr = frappe.get_doc("Material Request", mr.name)
|
mr = frappe.get_doc("Material Request", mr.name)
|
||||||
mr.material_request_type = "Material Transfer"
|
mr.material_request_type = "Material Transfer"
|
||||||
@ -62,40 +56,40 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
|
|
||||||
def _insert_stock_entry(self, qty1, qty2, warehouse = None ):
|
def _insert_stock_entry(self, qty1, qty2, warehouse = None ):
|
||||||
se = frappe.get_doc({
|
se = frappe.get_doc({
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"doctype": "Stock Entry",
|
"doctype": "Stock Entry",
|
||||||
"posting_date": "2013-03-01",
|
"posting_date": "2013-03-01",
|
||||||
"posting_time": "00:00:00",
|
"posting_time": "00:00:00",
|
||||||
"purpose": "Material Receipt",
|
"purpose": "Material Receipt",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"conversion_factor": 1.0,
|
"conversion_factor": 1.0,
|
||||||
"doctype": "Stock Entry Detail",
|
"doctype": "Stock Entry Detail",
|
||||||
"item_code": "_Test Item Home Desktop 100",
|
"item_code": "_Test Item Home Desktop 100",
|
||||||
"parentfield": "items",
|
"parentfield": "items",
|
||||||
"basic_rate": 100,
|
"basic_rate": 100,
|
||||||
"qty": qty1,
|
"qty": qty1,
|
||||||
"stock_uom": "_Test UOM 1",
|
"stock_uom": "_Test UOM 1",
|
||||||
"transfer_qty": qty1,
|
"transfer_qty": qty1,
|
||||||
"uom": "_Test UOM 1",
|
"uom": "_Test UOM 1",
|
||||||
"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
|
"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
"cost_center": "_Test Cost Center - _TC"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"conversion_factor": 1.0,
|
"conversion_factor": 1.0,
|
||||||
"doctype": "Stock Entry Detail",
|
"doctype": "Stock Entry Detail",
|
||||||
"item_code": "_Test Item Home Desktop 200",
|
"item_code": "_Test Item Home Desktop 200",
|
||||||
"parentfield": "items",
|
"parentfield": "items",
|
||||||
"basic_rate": 100,
|
"basic_rate": 100,
|
||||||
"qty": qty2,
|
"qty": qty2,
|
||||||
"stock_uom": "_Test UOM 1",
|
"stock_uom": "_Test UOM 1",
|
||||||
"transfer_qty": qty2,
|
"transfer_qty": qty2,
|
||||||
"uom": "_Test UOM 1",
|
"uom": "_Test UOM 1",
|
||||||
"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
|
"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
"cost_center": "_Test Cost Center - _TC"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
se.set_stock_entry_type()
|
se.set_stock_entry_type()
|
||||||
se.insert()
|
se.insert()
|
||||||
@ -198,14 +192,7 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
mr.insert()
|
mr.insert()
|
||||||
mr.submit()
|
mr.submit()
|
||||||
|
|
||||||
# check if per complete is None
|
|
||||||
mr.load_from_db()
|
|
||||||
self.assertEqual(mr.per_ordered, 0)
|
|
||||||
self.assertEqual(mr.get("items")[0].ordered_qty, 0)
|
|
||||||
self.assertEqual(mr.get("items")[1].ordered_qty, 0)
|
|
||||||
|
|
||||||
# map a purchase order
|
# map a purchase order
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
|
||||||
po_doc = make_purchase_order(mr.name)
|
po_doc = make_purchase_order(mr.name)
|
||||||
po_doc.supplier = "_Test Supplier"
|
po_doc.supplier = "_Test Supplier"
|
||||||
po_doc.transaction_date = "2013-07-07"
|
po_doc.transaction_date = "2013-07-07"
|
||||||
@ -276,10 +263,8 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
||||||
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
||||||
|
|
||||||
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0)
|
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
|
||||||
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0)
|
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
|
||||||
|
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
|
|
||||||
# map a stock entry
|
# map a stock entry
|
||||||
se_doc = make_stock_entry(mr.name)
|
se_doc = make_stock_entry(mr.name)
|
||||||
@ -331,8 +316,8 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
||||||
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
||||||
|
|
||||||
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 27.0)
|
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 27.0)
|
||||||
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 1.5)
|
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 1.5)
|
||||||
|
|
||||||
# check if per complete is as expected for Stock Entry cancelled
|
# check if per complete is as expected for Stock Entry cancelled
|
||||||
se.cancel()
|
se.cancel()
|
||||||
@ -344,8 +329,8 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
||||||
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
||||||
|
|
||||||
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0)
|
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
|
||||||
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0)
|
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
|
||||||
|
|
||||||
def test_completed_qty_for_over_transfer(self):
|
def test_completed_qty_for_over_transfer(self):
|
||||||
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
||||||
@ -357,14 +342,7 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
mr.insert()
|
mr.insert()
|
||||||
mr.submit()
|
mr.submit()
|
||||||
|
|
||||||
# check if per complete is None
|
|
||||||
mr.load_from_db()
|
|
||||||
self.assertEqual(mr.per_ordered, 0)
|
|
||||||
self.assertEqual(mr.get("items")[0].ordered_qty, 0)
|
|
||||||
self.assertEqual(mr.get("items")[1].ordered_qty, 0)
|
|
||||||
|
|
||||||
# map a stock entry
|
# map a stock entry
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
|
|
||||||
se_doc = make_stock_entry(mr.name)
|
se_doc = make_stock_entry(mr.name)
|
||||||
se_doc.update({
|
se_doc.update({
|
||||||
@ -425,8 +403,8 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
|
||||||
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
|
||||||
|
|
||||||
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0)
|
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
|
||||||
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0)
|
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
|
||||||
|
|
||||||
def test_incorrect_mapping_of_stock_entry(self):
|
def test_incorrect_mapping_of_stock_entry(self):
|
||||||
# submit material request of type Transfer
|
# submit material request of type Transfer
|
||||||
@ -435,9 +413,6 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
mr.insert()
|
mr.insert()
|
||||||
mr.submit()
|
mr.submit()
|
||||||
|
|
||||||
# map a stock entry
|
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
|
|
||||||
se_doc = make_stock_entry(mr.name)
|
se_doc = make_stock_entry(mr.name)
|
||||||
se_doc.update({
|
se_doc.update({
|
||||||
"posting_date": "2013-03-01",
|
"posting_date": "2013-03-01",
|
||||||
@ -468,8 +443,6 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
mr.insert()
|
mr.insert()
|
||||||
mr.submit()
|
mr.submit()
|
||||||
|
|
||||||
# map a stock entry
|
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
se_doc = make_stock_entry(mr.name)
|
se_doc = make_stock_entry(mr.name)
|
||||||
self.assertEqual(se_doc.get("items")[0].s_warehouse, "_Test Warehouse - _TC")
|
self.assertEqual(se_doc.get("items")[0].s_warehouse, "_Test Warehouse - _TC")
|
||||||
|
|
||||||
@ -483,8 +456,6 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty"))
|
return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty"))
|
||||||
|
|
||||||
def test_make_stock_entry_for_material_issue(self):
|
def test_make_stock_entry_for_material_issue(self):
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
|
|
||||||
mr = frappe.copy_doc(test_records[0]).insert()
|
mr = frappe.copy_doc(test_records[0]).insert()
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, make_stock_entry,
|
self.assertRaises(frappe.ValidationError, make_stock_entry,
|
||||||
@ -503,8 +474,6 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
|
return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
|
||||||
"warehouse": "_Test Warehouse - _TC"}, "indented_qty"))
|
"warehouse": "_Test Warehouse - _TC"}, "indented_qty"))
|
||||||
|
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
|
|
||||||
existing_requested_qty = _get_requested_qty()
|
existing_requested_qty = _get_requested_qty()
|
||||||
|
|
||||||
mr = frappe.copy_doc(test_records[0])
|
mr = frappe.copy_doc(test_records[0])
|
||||||
@ -563,9 +532,37 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
|
item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
|
||||||
self.assertEqual(requested_qty, new_requested_qty)
|
self.assertEqual(requested_qty, new_requested_qty)
|
||||||
|
|
||||||
def test_multi_uom_for_purchase(self):
|
def test_requested_qty_multi_uom(self):
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
existing_requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
|
||||||
|
|
||||||
|
mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture',
|
||||||
|
uom="_Test UOM 1", conversion_factor=12)
|
||||||
|
|
||||||
|
requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
|
||||||
|
|
||||||
|
self.assertEqual(requested_qty, existing_requested_qty + 120)
|
||||||
|
|
||||||
|
work_order = raise_work_orders(mr.name)
|
||||||
|
wo = frappe.get_doc("Work Order", work_order[0])
|
||||||
|
wo.qty = 50
|
||||||
|
wo.wip_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
wo.submit()
|
||||||
|
|
||||||
|
requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
|
||||||
|
self.assertEqual(requested_qty, existing_requested_qty + 70)
|
||||||
|
|
||||||
|
wo.cancel()
|
||||||
|
|
||||||
|
requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
|
||||||
|
self.assertEqual(requested_qty, existing_requested_qty + 120)
|
||||||
|
|
||||||
|
mr.reload()
|
||||||
|
mr.cancel()
|
||||||
|
requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
|
||||||
|
self.assertEqual(requested_qty, existing_requested_qty)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_uom_for_purchase(self):
|
||||||
mr = frappe.copy_doc(test_records[0])
|
mr = frappe.copy_doc(test_records[0])
|
||||||
mr.material_request_type = 'Purchase'
|
mr.material_request_type = 'Purchase'
|
||||||
item = mr.items[0]
|
item = mr.items[0]
|
||||||
@ -607,7 +604,6 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
self.assertEqual(mr.per_ordered, 100)
|
self.assertEqual(mr.per_ordered, 100)
|
||||||
|
|
||||||
def test_customer_provided_parts_mr(self):
|
def test_customer_provided_parts_mr(self):
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
|
||||||
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
||||||
existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC")
|
existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC")
|
||||||
|
|
||||||
@ -633,6 +629,8 @@ def make_material_request(**args):
|
|||||||
mr.append("items", {
|
mr.append("items", {
|
||||||
"item_code": args.item_code or "_Test Item",
|
"item_code": args.item_code or "_Test Item",
|
||||||
"qty": args.qty or 10,
|
"qty": args.qty or 10,
|
||||||
|
"uom": args.uom or "_Test UOM",
|
||||||
|
"conversion_factor": args.conversion_factor or 1,
|
||||||
"schedule_date": args.schedule_date or today(),
|
"schedule_date": args.schedule_date or today(),
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC"
|
"cost_center": args.cost_center or "_Test Cost Center - _TC"
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-02-22 01:28:02",
|
"creation": "2013-02-22 01:28:02",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -374,7 +373,10 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "received_qty",
|
"fieldname": "received_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Received Quantity"
|
"label": "Received Quantity",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@ -410,7 +412,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-07 18:37:54.495112",
|
"modified": "2020-04-16 09:00:00.992835",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Material Request Item",
|
"name": "Material Request Item",
|
||||||
|
@ -111,6 +111,7 @@ class TestPickList(unittest.TestCase):
|
|||||||
|
|
||||||
stock_reconciliation = frappe.get_doc({
|
stock_reconciliation = frappe.get_doc({
|
||||||
'doctype': 'Stock Reconciliation',
|
'doctype': 'Stock Reconciliation',
|
||||||
|
'purpose': 'Stock Reconciliation',
|
||||||
'company': '_Test Company',
|
'company': '_Test Company',
|
||||||
'items': [{
|
'items': [{
|
||||||
'item_code': '_Test Serialized Item',
|
'item_code': '_Test Serialized Item',
|
||||||
|
@ -59,9 +59,9 @@
|
|||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
"column_break_27",
|
"column_break_27",
|
||||||
|
"total_net_weight",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"total_net_weight",
|
|
||||||
"taxes_charges_section",
|
"taxes_charges_section",
|
||||||
"tax_category",
|
"tax_category",
|
||||||
"shipping_col",
|
"shipping_col",
|
||||||
@ -1082,7 +1082,7 @@
|
|||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-06 16:31:37.444891",
|
"modified": "2020-04-17 13:06:26.970288",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
|
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"company",
|
"company",
|
||||||
@ -44,11 +45,11 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Stock Reconciliation",
|
|
||||||
"fieldname": "purpose",
|
"fieldname": "purpose",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Purpose",
|
"label": "Purpose",
|
||||||
"options": "Opening Stock\nStock Reconciliation"
|
"options": "\nOpening Stock\nStock Reconciliation",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "col1",
|
"fieldname": "col1",
|
||||||
@ -153,7 +154,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"max_attachments": 1,
|
"max_attachments": 1,
|
||||||
"modified": "2019-05-26 09:03:09.542141",
|
"modified": "2020-04-08 17:02:47.196206",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Reconciliation",
|
"name": "Stock Reconciliation",
|
||||||
|
@ -240,6 +240,7 @@ def create_batch_or_serial_no_items():
|
|||||||
def create_stock_reconciliation(**args):
|
def create_stock_reconciliation(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
sr = frappe.new_doc("Stock Reconciliation")
|
sr = frappe.new_doc("Stock Reconciliation")
|
||||||
|
sr.purpose = args.purpose or "Stock Reconciliation"
|
||||||
sr.posting_date = args.posting_date or nowdate()
|
sr.posting_date = args.posting_date or nowdate()
|
||||||
sr.posting_time = args.posting_time or nowtime()
|
sr.posting_time = args.posting_time or nowtime()
|
||||||
sr.set_posting_time = 1
|
sr.set_posting_time = 1
|
||||||
|
@ -113,24 +113,30 @@ def get_reserved_qty(item_code, warehouse):
|
|||||||
return flt(reserved_qty[0][0]) if reserved_qty else 0
|
return flt(reserved_qty[0][0]) if reserved_qty else 0
|
||||||
|
|
||||||
def get_indented_qty(item_code, warehouse):
|
def get_indented_qty(item_code, warehouse):
|
||||||
inward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor)
|
# Ordered Qty is always maintained in stock UOM
|
||||||
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
|
inward_qty = frappe.db.sql("""
|
||||||
where mr_item.item_code=%s and mr_item.warehouse=%s
|
select sum(mr_item.stock_qty - mr_item.ordered_qty)
|
||||||
and mr.material_request_type in ('Purchase', 'Manufacture')
|
|
||||||
and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
|
|
||||||
and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
|
|
||||||
|
|
||||||
outward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor)
|
|
||||||
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
|
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
|
||||||
where mr_item.item_code=%s and mr_item.warehouse=%s
|
where mr_item.item_code=%s and mr_item.warehouse=%s
|
||||||
and mr.material_request_type in ('Material Issue', 'Material Transfer')
|
and mr.material_request_type in ('Purchase', 'Manufacture', 'Customer Provided', 'Material Transfer')
|
||||||
and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
|
and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
|
||||||
and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
|
and mr.status!='Stopped' and mr.docstatus=1
|
||||||
|
""", (item_code, warehouse))
|
||||||
|
inward_qty = flt(inward_qty[0][0]) if inward_qty else 0
|
||||||
|
|
||||||
inward_qty, outward_qty = flt(inward_qty[0][0]) if inward_qty else 0, flt(outward_qty[0][0]) if outward_qty else 0
|
outward_qty = frappe.db.sql("""
|
||||||
indented_qty = inward_qty - outward_qty
|
select sum(mr_item.stock_qty - mr_item.ordered_qty)
|
||||||
|
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
|
||||||
|
where mr_item.item_code=%s and mr_item.warehouse=%s
|
||||||
|
and mr.material_request_type = 'Material Issue'
|
||||||
|
and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
|
||||||
|
and mr.status!='Stopped' and mr.docstatus=1
|
||||||
|
""", (item_code, warehouse))
|
||||||
|
outward_qty = flt(outward_qty[0][0]) if outward_qty else 0
|
||||||
|
|
||||||
return indented_qty
|
requested_qty = inward_qty - outward_qty
|
||||||
|
|
||||||
|
return requested_qty
|
||||||
|
|
||||||
def get_ordered_qty(item_code, warehouse):
|
def get_ordered_qty(item_code, warehouse):
|
||||||
ordered_qty = frappe.db.sql("""
|
ordered_qty = frappe.db.sql("""
|
||||||
|
@ -335,8 +335,13 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord
|
|||||||
|
|
||||||
ignore_permissions = False
|
ignore_permissions = False
|
||||||
if is_website_user():
|
if is_website_user():
|
||||||
if not filters: filters = []
|
if not filters: filters = {}
|
||||||
filters.append(("Issue", "customer", "=", customer)) if customer else filters.append(("Issue", "raised_by", "=", user))
|
|
||||||
|
if customer:
|
||||||
|
filters["customer"] = customer
|
||||||
|
else:
|
||||||
|
filters["raised_by"] = user
|
||||||
|
|
||||||
ignore_permissions = True
|
ignore_permissions = True
|
||||||
|
|
||||||
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)
|
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)
|
||||||
|
Loading…
Reference in New Issue
Block a user