mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-30 02:14:48 +00:00
Merge branch 'version-12-hotfix' into quality-procedure
This commit is contained in:
@@ -356,6 +356,7 @@
|
|||||||
"fieldname": "bill_date",
|
"fieldname": "bill_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Supplier Invoice Date",
|
"label": "Supplier Invoice Date",
|
||||||
|
"no_copy": 1,
|
||||||
"oldfieldname": "bill_date",
|
"oldfieldname": "bill_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
@@ -1307,7 +1308,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2020-08-20 11:08:19.611710",
|
"modified": "2020-09-21 12:22:09.164068",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
|
|||||||
check_plaid_status() {
|
check_plaid_status() {
|
||||||
const me = this;
|
const me = this;
|
||||||
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
|
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
|
||||||
if (r && r.enabled == "1") {
|
if (r && r.enabled === "1") {
|
||||||
me.plaid_status = "active"
|
me.plaid_status = "active"
|
||||||
} else {
|
} else {
|
||||||
me.plaid_status = "inactive"
|
me.plaid_status = "inactive"
|
||||||
@@ -214,31 +214,35 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {
|
|||||||
|
|
||||||
init_config() {
|
init_config() {
|
||||||
const me = this;
|
const me = this;
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
|
||||||
.then(result => {
|
.then(result => {
|
||||||
me.plaid_env = result.plaid_env;
|
me.plaid_env = result.plaid_env;
|
||||||
me.plaid_public_key = result.plaid_public_key;
|
me.client_name = result.client_name;
|
||||||
me.client_name = result.client_name;
|
me.link_token = result.link_token;
|
||||||
me.sync_transactions()
|
me.sync_transactions();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sync_transactions() {
|
sync_transactions() {
|
||||||
const me = this;
|
const me = this;
|
||||||
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
|
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
||||||
bank: v['bank'],
|
bank: r.bank,
|
||||||
bank_account: me.parent.bank_account,
|
bank_account: me.parent.bank_account,
|
||||||
freeze: true
|
freeze: true
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
|
let result_title = (result && result.length > 0)
|
||||||
|
? __("{0} bank transaction(s) created", [result.length])
|
||||||
|
: __("This bank account is already synchronized");
|
||||||
|
|
||||||
let result_msg = `
|
let result_msg = `
|
||||||
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
|
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
|
||||||
<h5 class="text-muted">${result_title}</h5>
|
<h5 class="text-muted">${result_title}</h5>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
this.parent.$main_section.append(result_msg)
|
this.parent.$main_section.append(result_msg)
|
||||||
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
|
frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -384,7 +388,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
|
|||||||
})
|
})
|
||||||
|
|
||||||
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
|
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
|
||||||
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
|
{ bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
|
||||||
).then((result) => {
|
).then((result) => {
|
||||||
me.make_dialog(result)
|
me.make_dialog(result)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
filters: {
|
filters: {
|
||||||
'company': company
|
'company': company
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -617,9 +617,19 @@ class ReceivablePayableReport(object):
|
|||||||
elif party_type_field=="supplier":
|
elif party_type_field=="supplier":
|
||||||
self.add_supplier_filters(conditions, values)
|
self.add_supplier_filters(conditions, values)
|
||||||
|
|
||||||
|
if self.filters.cost_center:
|
||||||
|
self.get_cost_center_conditions(conditions)
|
||||||
|
|
||||||
self.add_accounting_dimensions_filters(conditions, values)
|
self.add_accounting_dimensions_filters(conditions, values)
|
||||||
return " and ".join(conditions), values
|
return " and ".join(conditions), values
|
||||||
|
|
||||||
|
def get_cost_center_conditions(self, conditions):
|
||||||
|
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
|
||||||
|
cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
|
||||||
|
|
||||||
|
cost_center_string = '", "'.join(cost_center_list)
|
||||||
|
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
|
||||||
|
|
||||||
def get_order_by_condition(self):
|
def get_order_by_condition(self):
|
||||||
if self.filters.get('group_by_party'):
|
if self.filters.get('group_by_party'):
|
||||||
return "order by party, posting_date"
|
return "order by party, posting_date"
|
||||||
|
|||||||
@@ -252,13 +252,6 @@ frappe.ui.form.on('Asset', {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
available_for_use_date: function(frm) {
|
|
||||||
$.each(frm.doc.finance_books || [], function(i, d) {
|
|
||||||
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
|
|
||||||
});
|
|
||||||
refresh_field("finance_books");
|
|
||||||
},
|
|
||||||
|
|
||||||
is_existing_asset: function(frm) {
|
is_existing_asset: function(frm) {
|
||||||
frm.trigger("toggle_reference_doc");
|
frm.trigger("toggle_reference_doc");
|
||||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||||
@@ -437,6 +430,15 @@ frappe.ui.form.on('Asset Finance Book', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frappe.flags.dont_change_rate = false;
|
frappe.flags.dont_change_rate = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
depreciation_start_date: function(frm, cdt, cdn) {
|
||||||
|
const book = locals[cdt][cdn];
|
||||||
|
if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
|
||||||
|
frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
|
||||||
|
book.depreciation_start_date = "";
|
||||||
|
frm.refresh_field("finance_books");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe, erpnext, math, json
|
import frappe, erpnext, math, json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from six import string_types
|
from six import string_types
|
||||||
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days
|
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days, get_last_day
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
from erpnext.assets.doctype.asset.depreciation \
|
from erpnext.assets.doctype.asset.depreciation \
|
||||||
@@ -84,6 +84,11 @@ class Asset(AccountsController):
|
|||||||
if not self.available_for_use_date:
|
if not self.available_for_use_date:
|
||||||
frappe.throw(_("Available for use date is required"))
|
frappe.throw(_("Available for use date is required"))
|
||||||
|
|
||||||
|
for d in self.finance_books:
|
||||||
|
if d.depreciation_start_date == self.available_for_use_date:
|
||||||
|
frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
|
||||||
|
title=_("Incorrect Date"))
|
||||||
|
|
||||||
def set_missing_values(self):
|
def set_missing_values(self):
|
||||||
if not self.asset_category:
|
if not self.asset_category:
|
||||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||||
@@ -308,7 +313,7 @@ class Asset(AccountsController):
|
|||||||
if not row.depreciation_start_date:
|
if not row.depreciation_start_date:
|
||||||
if not self.available_for_use_date:
|
if not self.available_for_use_date:
|
||||||
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
||||||
row.depreciation_start_date = self.available_for_use_date
|
row.depreciation_start_date = get_last_day(self.available_for_use_date)
|
||||||
|
|
||||||
if not self.is_existing_asset:
|
if not self.is_existing_asset:
|
||||||
self.opening_accumulated_depreciation = 0
|
self.opening_accumulated_depreciation = 0
|
||||||
|
|||||||
@@ -374,19 +374,18 @@ class TestAsset(unittest.TestCase):
|
|||||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||||
asset = frappe.get_doc('Asset', asset_name)
|
asset = frappe.get_doc('Asset', asset_name)
|
||||||
asset.calculate_depreciation = 1
|
asset.calculate_depreciation = 1
|
||||||
asset.available_for_use_date = nowdate()
|
asset.available_for_use_date = '2020-01-01'
|
||||||
asset.purchase_date = nowdate()
|
asset.purchase_date = '2020-01-01'
|
||||||
asset.append("finance_books", {
|
asset.append("finance_books", {
|
||||||
"expected_value_after_useful_life": 10000,
|
"expected_value_after_useful_life": 10000,
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 10,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 1
|
||||||
"depreciation_start_date": nowdate()
|
|
||||||
})
|
})
|
||||||
asset.insert()
|
asset.insert()
|
||||||
asset.submit()
|
asset.submit()
|
||||||
|
|
||||||
post_depreciation_entries(date=add_months(nowdate(), 10))
|
post_depreciation_entries(date=add_months('2020-01-01', 4))
|
||||||
|
|
||||||
scrap_asset(asset.name)
|
scrap_asset(asset.name)
|
||||||
|
|
||||||
@@ -395,9 +394,9 @@ class TestAsset(unittest.TestCase):
|
|||||||
self.assertTrue(asset.journal_entry_for_scrap)
|
self.assertTrue(asset.journal_entry_for_scrap)
|
||||||
|
|
||||||
expected_gle = (
|
expected_gle = (
|
||||||
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
|
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
|
||||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||||
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
|
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||||
@@ -469,8 +468,7 @@ class TestAsset(unittest.TestCase):
|
|||||||
"expected_value_after_useful_life": 10000,
|
"expected_value_after_useful_life": 10000,
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10
|
||||||
"depreciation_start_date": "2020-06-06"
|
|
||||||
})
|
})
|
||||||
asset.insert()
|
asset.insert()
|
||||||
accumulated_depreciation_after_full_schedule = \
|
accumulated_depreciation_after_full_schedule = \
|
||||||
|
|||||||
@@ -1,347 +1,99 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-05-08 14:44:37.095570",
|
"creation": "2018-05-08 14:44:37.095570",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"finance_book",
|
||||||
|
"depreciation_method",
|
||||||
|
"total_number_of_depreciations",
|
||||||
|
"column_break_5",
|
||||||
|
"frequency_of_depreciation",
|
||||||
|
"depreciation_start_date",
|
||||||
|
"expected_value_after_useful_life",
|
||||||
|
"value_after_depreciation",
|
||||||
|
"rate_of_depreciation"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "finance_book",
|
"fieldname": "finance_book",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Finance Book",
|
"label": "Finance Book",
|
||||||
"length": 0,
|
"options": "Finance Book"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Finance Book",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "depreciation_method",
|
"fieldname": "depreciation_method",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Depreciation Method",
|
"label": "Depreciation Method",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "total_number_of_depreciations",
|
"fieldname": "total_number_of_depreciations",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Total Number of Depreciations",
|
"label": "Total Number of Depreciations",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "column_break_5",
|
"fieldname": "column_break_5",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "frequency_of_depreciation",
|
"fieldname": "frequency_of_depreciation",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Frequency of Depreciation (Months)",
|
"label": "Frequency of Depreciation (Months)",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "depreciation_start_date",
|
"fieldname": "depreciation_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Depreciation Posting Date",
|
||||||
"label": "Depreciation Start Date",
|
"reqd": 1
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "expected_value_after_useful_life",
|
"fieldname": "expected_value_after_useful_life",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Expected Value After Useful Life",
|
"label": "Expected Value After Useful Life",
|
||||||
"length": 0,
|
"options": "Company:company:default_currency"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company:company:default_currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "value_after_depreciation",
|
"fieldname": "value_after_depreciation",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Value After Depreciation",
|
"label": "Value After Depreciation",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
||||||
"description": "In Percentage",
|
"description": "In Percentage",
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "rate_of_depreciation",
|
"fieldname": "rate_of_depreciation",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"hidden": 0,
|
"label": "Rate of Depreciation"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Rate of Depreciation",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2019-04-09 19:45:14.523488",
|
"modified": "2020-09-16 12:11:30.631788",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -32,8 +32,7 @@ class TestAssetMovement(unittest.TestCase):
|
|||||||
"next_depreciation_date": "2020-12-31",
|
"next_depreciation_date": "2020-12-31",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10
|
||||||
"depreciation_start_date": "2020-06-06"
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if asset.docstatus == 0:
|
if asset.docstatus == 0:
|
||||||
@@ -82,8 +81,7 @@ class TestAssetMovement(unittest.TestCase):
|
|||||||
"next_depreciation_date": "2020-12-31",
|
"next_depreciation_date": "2020-12-31",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10
|
||||||
"depreciation_start_date": "2020-06-06"
|
|
||||||
})
|
})
|
||||||
if asset.docstatus == 0:
|
if asset.docstatus == 0:
|
||||||
asset.submit()
|
asset.submit()
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from erpnext.stock.doctype.material_request.material_request import make_purchas
|
|||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
||||||
from erpnext.controllers.status_updater import OverAllowanceError
|
from erpnext.controllers.status_updater import OverAllowanceError
|
||||||
|
from erpnext.stock.doctype.batch.test_batch import make_new_batch
|
||||||
|
from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials
|
||||||
|
|
||||||
class TestPurchaseOrder(unittest.TestCase):
|
class TestPurchaseOrder(unittest.TestCase):
|
||||||
def test_make_purchase_receipt(self):
|
def test_make_purchase_receipt(self):
|
||||||
@@ -200,6 +202,53 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
|
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_update_child_with_tax_template(self):
|
||||||
|
tax_template = "_Test Account Excise Duty @ 10"
|
||||||
|
item = "_Test Item Home Desktop 100"
|
||||||
|
|
||||||
|
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
||||||
|
item_doc = frappe.get_doc("Item", item)
|
||||||
|
item_doc.append("taxes", {
|
||||||
|
"item_tax_template": tax_template,
|
||||||
|
"valid_from": nowdate()
|
||||||
|
})
|
||||||
|
item_doc.save()
|
||||||
|
else:
|
||||||
|
# update valid from
|
||||||
|
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
|
||||||
|
where parent = %(item)s and item_tax_template = %(tax)s""",
|
||||||
|
{"item": item, "tax": tax_template})
|
||||||
|
|
||||||
|
po = create_purchase_order(item_code=item, qty=1, do_not_save=1)
|
||||||
|
|
||||||
|
po.append("taxes", {
|
||||||
|
"account_head": "_Test Account Excise Duty - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Excise Duty",
|
||||||
|
"doctype": "Purchase Taxes and Charges",
|
||||||
|
"rate": 10
|
||||||
|
})
|
||||||
|
po.insert()
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
self.assertEqual(po.taxes[0].tax_amount, 50)
|
||||||
|
self.assertEqual(po.taxes[0].total, 550)
|
||||||
|
|
||||||
|
items = json.dumps([
|
||||||
|
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
|
||||||
|
{'item_code' : item, 'rate' : 100, 'qty' : 1} # added item
|
||||||
|
])
|
||||||
|
update_child_qty_rate('Purchase Order', items, po.name)
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.taxes[0].tax_amount, 60)
|
||||||
|
self.assertEqual(po.taxes[0].total, 660)
|
||||||
|
|
||||||
|
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
|
||||||
|
where parent = %(item)s and item_tax_template = %(tax)s""",
|
||||||
|
{"item": item, "tax": tax_template})
|
||||||
|
|
||||||
def test_update_qty(self):
|
def test_update_qty(self):
|
||||||
po = create_purchase_order()
|
po = create_purchase_order()
|
||||||
|
|
||||||
@@ -580,7 +629,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
def test_exploded_items_in_subcontracted(self):
|
def test_exploded_items_in_subcontracted(self):
|
||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 1"
|
||||||
make_subcontracted_item(item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
|
|
||||||
po = create_purchase_order(item_code=item_code, qty=1,
|
po = create_purchase_order(item_code=item_code, qty=1,
|
||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||||
@@ -602,7 +651,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
def test_backflush_based_on_stock_entry(self):
|
def test_backflush_based_on_stock_entry(self):
|
||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 1"
|
||||||
make_subcontracted_item(item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
make_item('Sub Contracted Raw Material 1', {
|
make_item('Sub Contracted Raw Material 1', {
|
||||||
'is_stock_item': 1,
|
'is_stock_item': 1,
|
||||||
'is_sub_contracted_item': 1
|
'is_sub_contracted_item': 1
|
||||||
@@ -661,6 +710,76 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
update_backflush_based_on("BOM")
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
|
def test_backflushed_based_on_for_multiple_batches(self):
|
||||||
|
item_code = "_Test Subcontracted FG Item 2"
|
||||||
|
make_item('Sub Contracted Raw Material 2', {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'is_sub_contracted_item': 1
|
||||||
|
})
|
||||||
|
|
||||||
|
make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1,
|
||||||
|
raw_materials=["Sub Contracted Raw Material 2"])
|
||||||
|
|
||||||
|
update_backflush_based_on("Material Transferred for Subcontract")
|
||||||
|
|
||||||
|
order_qty = 500
|
||||||
|
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||||
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||||
|
|
||||||
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
|
item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100)
|
||||||
|
|
||||||
|
rm_items = [
|
||||||
|
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item",
|
||||||
|
"qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}]
|
||||||
|
|
||||||
|
rm_item_string = json.dumps(rm_items)
|
||||||
|
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]:
|
||||||
|
make_new_batch(batch_id=batch, item_code=item_code)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(po.name)
|
||||||
|
|
||||||
|
# partial receipt
|
||||||
|
pr.get('items')[0].qty = 30
|
||||||
|
pr.get('items')[0].batch_no = "ABCD1"
|
||||||
|
|
||||||
|
purchase_order = po.name
|
||||||
|
purchase_order_item = po.items[0].name
|
||||||
|
|
||||||
|
for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items():
|
||||||
|
pr.append("items", {
|
||||||
|
"item_code": pr.get('items')[0].item_code,
|
||||||
|
"item_name": pr.get('items')[0].item_name,
|
||||||
|
"uom": pr.get('items')[0].uom,
|
||||||
|
"stock_uom": pr.get('items')[0].stock_uom,
|
||||||
|
"warehouse": pr.get('items')[0].warehouse,
|
||||||
|
"conversion_factor": pr.get('items')[0].conversion_factor,
|
||||||
|
"cost_center": pr.get('items')[0].cost_center,
|
||||||
|
"rate": pr.get('items')[0].rate,
|
||||||
|
"qty": qty,
|
||||||
|
"batch_no": batch_no,
|
||||||
|
"purchase_order": purchase_order,
|
||||||
|
"purchase_order_item": purchase_order_item
|
||||||
|
})
|
||||||
|
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
pr1 = make_purchase_receipt(po.name)
|
||||||
|
pr1.get('items')[0].qty = 300
|
||||||
|
pr1.get('items')[0].batch_no = "ABCD1"
|
||||||
|
pr1.save()
|
||||||
|
|
||||||
|
pr_key = ("Sub Contracted Raw Material 2", po.name)
|
||||||
|
consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key)
|
||||||
|
|
||||||
|
self.assertTrue(pr1.supplied_items[0].consumed_qty > 0)
|
||||||
|
self.assertTrue(pr1.supplied_items[0].consumed_qty, flt(552.0) - flt(consumed_qty))
|
||||||
|
|
||||||
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
||||||
@@ -712,27 +831,33 @@ def make_pr_against_po(po, received_qty=0):
|
|||||||
pr.submit()
|
pr.submit()
|
||||||
return pr
|
return pr
|
||||||
|
|
||||||
def make_subcontracted_item(item_code):
|
def make_subcontracted_item(**args):
|
||||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
if not frappe.db.exists('Item', item_code):
|
args = frappe._dict(args)
|
||||||
make_item(item_code, {
|
|
||||||
|
if not frappe.db.exists('Item', args.item_code):
|
||||||
|
make_item(args.item_code, {
|
||||||
'is_stock_item': 1,
|
'is_stock_item': 1,
|
||||||
'is_sub_contracted_item': 1
|
'is_sub_contracted_item': 1,
|
||||||
|
'has_batch_no': args.get("has_batch_no") or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
if not frappe.db.exists('Item', "Test Extra Item 1"):
|
if not args.raw_materials:
|
||||||
make_item("Test Extra Item 1", {
|
if not frappe.db.exists('Item', "Test Extra Item 1"):
|
||||||
'is_stock_item': 1,
|
make_item("Test Extra Item 1", {
|
||||||
})
|
'is_stock_item': 1,
|
||||||
|
})
|
||||||
|
|
||||||
if not frappe.db.exists('Item', "Test Extra Item 2"):
|
if not frappe.db.exists('Item', "Test Extra Item 2"):
|
||||||
make_item("Test Extra Item 2", {
|
make_item("Test Extra Item 2", {
|
||||||
'is_stock_item': 1,
|
'is_stock_item': 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
if not frappe.db.get_value('BOM', {'item': item_code}, 'name'):
|
args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
|
||||||
make_bom(item = item_code, raw_materials = ['_Test FG Item', 'Test Extra Item 1'])
|
|
||||||
|
if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
|
||||||
|
make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
|
||||||
|
|
||||||
def update_backflush_based_on(based_on):
|
def update_backflush_based_on(based_on):
|
||||||
doc = frappe.get_doc('Buying Settings')
|
doc = frappe.get_doc('Buying Settings')
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_t
|
|||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from six import text_type
|
from six import text_type
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||||
from erpnext.stock.get_item_details import get_item_warehouse
|
from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
|
||||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||||
|
|
||||||
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
|
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
|
||||||
@@ -1127,6 +1127,18 @@ def get_supplier_block_status(party_name):
|
|||||||
}
|
}
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
def set_child_tax_template_and_map(item, child_item, parent_doc):
|
||||||
|
args = {
|
||||||
|
'item_code': item.item_code,
|
||||||
|
'posting_date': parent_doc.transaction_date,
|
||||||
|
'tax_category': parent_doc.get('tax_category'),
|
||||||
|
'company': parent_doc.get('company')
|
||||||
|
}
|
||||||
|
|
||||||
|
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
|
||||||
|
if child_item.get("item_tax_template"):
|
||||||
|
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
|
||||||
|
|
||||||
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
|
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
|
||||||
"""
|
"""
|
||||||
Returns a Sales Order Item child item containing the default values
|
Returns a Sales Order Item child item containing the default values
|
||||||
@@ -1140,6 +1152,7 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
|
|||||||
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
|
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
|
||||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
|
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
|
||||||
child_item.uom = item.stock_uom
|
child_item.uom = item.stock_uom
|
||||||
|
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||||
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
||||||
if not child_item.warehouse:
|
if not child_item.warehouse:
|
||||||
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
||||||
@@ -1162,6 +1175,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
|
|||||||
child_item.uom = item.stock_uom
|
child_item.uom = item.stock_uom
|
||||||
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
||||||
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
||||||
|
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||||
return child_item
|
return child_item
|
||||||
|
|
||||||
def validate_and_delete_children(parent, data):
|
def validate_and_delete_children(parent, data):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.utils import flt,cint, cstr, getdate
|
from frappe.utils import flt,cint, cstr, getdate
|
||||||
|
from six import iteritems
|
||||||
from erpnext.accounts.party import get_party_details
|
from erpnext.accounts.party import get_party_details
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
|
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
|
||||||
@@ -289,10 +289,10 @@ class BuyingController(StockController):
|
|||||||
title=_("Limit Crossed"))
|
title=_("Limit Crossed"))
|
||||||
|
|
||||||
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||||
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
# backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||||
|
|
||||||
for raw_material in transferred_raw_materials + non_stock_items:
|
for raw_material in transferred_raw_materials + non_stock_items:
|
||||||
rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order)
|
rm_item_key = (raw_material.rm_item_code, item.purchase_order)
|
||||||
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
||||||
|
|
||||||
consumed_qty = raw_material_data.get('qty', 0)
|
consumed_qty = raw_material_data.get('qty', 0)
|
||||||
@@ -321,6 +321,8 @@ class BuyingController(StockController):
|
|||||||
set_serial_nos(raw_material, consumed_serial_nos, qty)
|
set_serial_nos(raw_material, consumed_serial_nos, qty)
|
||||||
|
|
||||||
if raw_material.batch_nos:
|
if raw_material.batch_nos:
|
||||||
|
backflushed_batch_qty_map = raw_material_data.get('consumed_batch', {})
|
||||||
|
|
||||||
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
||||||
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
|
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
|
||||||
for batch_data in batches_qty:
|
for batch_data in batches_qty:
|
||||||
@@ -337,6 +339,7 @@ class BuyingController(StockController):
|
|||||||
if not rm.main_item_code:
|
if not rm.main_item_code:
|
||||||
rm.main_item_code = fg_item_doc.item_code
|
rm.main_item_code = fg_item_doc.item_code
|
||||||
|
|
||||||
|
rm.reference_name = fg_item_doc.name
|
||||||
rm.required_qty = qty
|
rm.required_qty = qty
|
||||||
rm.consumed_qty = qty
|
rm.consumed_qty = qty
|
||||||
|
|
||||||
@@ -864,39 +867,49 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
|
|||||||
return raw_materials
|
return raw_materials
|
||||||
|
|
||||||
def get_backflushed_subcontracted_raw_materials(purchase_orders):
|
def get_backflushed_subcontracted_raw_materials(purchase_orders):
|
||||||
common_query = """
|
purchase_receipts = frappe.get_all("Purchase Receipt Item",
|
||||||
SELECT
|
fields = ["purchase_order", "item_code", "name", "parent"],
|
||||||
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
|
filters={"docstatus": 1, "purchase_order": ("in", list(purchase_orders))})
|
||||||
SUM(prsi.consumed_qty) AS qty,
|
|
||||||
{serial_no_concat_syntax} AS serial_nos,
|
|
||||||
{batch_no_concat_syntax} AS batch_nos
|
|
||||||
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
|
||||||
WHERE
|
|
||||||
pr.name = pri.parent
|
|
||||||
AND pr.name = prsi.parent
|
|
||||||
AND pri.purchase_order IN %s
|
|
||||||
AND pri.item_code = prsi.main_item_code
|
|
||||||
AND pr.docstatus = 1
|
|
||||||
GROUP BY prsi.rm_item_code, pri.purchase_order
|
|
||||||
"""
|
|
||||||
|
|
||||||
backflushed_raw_materials = frappe.db.multisql({
|
distinct_purchase_receipts = {}
|
||||||
'mariadb': common_query.format(
|
for pr in purchase_receipts:
|
||||||
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
|
key = (pr.purchase_order, pr.item_code, pr.parent)
|
||||||
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
|
distinct_purchase_receipts.setdefault(key, []).append(pr.name)
|
||||||
),
|
|
||||||
'postgres': common_query.format(
|
|
||||||
serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
|
|
||||||
batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
|
|
||||||
)
|
|
||||||
}, (purchase_orders, ), as_dict=1)
|
|
||||||
|
|
||||||
backflushed_raw_materials_map = frappe._dict()
|
backflushed_raw_materials_map = frappe._dict()
|
||||||
for item in backflushed_raw_materials:
|
for args, references in iteritems(distinct_purchase_receipts):
|
||||||
backflushed_raw_materials_map.setdefault(item.item_key, item)
|
purchase_receipt_supplied_items = get_supplied_items(args[1], args[2], references)
|
||||||
|
|
||||||
|
for data in purchase_receipt_supplied_items:
|
||||||
|
pr_key = (data.rm_item_code, args[0])
|
||||||
|
if pr_key not in backflushed_raw_materials_map:
|
||||||
|
backflushed_raw_materials_map.setdefault(pr_key, frappe._dict({
|
||||||
|
"qty": 0.0,
|
||||||
|
"serial_no": [],
|
||||||
|
"batch_no": [],
|
||||||
|
"consumed_batch": {}
|
||||||
|
}))
|
||||||
|
|
||||||
|
row = backflushed_raw_materials_map.get(pr_key)
|
||||||
|
row.qty += data.consumed_qty
|
||||||
|
|
||||||
|
for field in ["serial_no", "batch_no"]:
|
||||||
|
if data.get(field):
|
||||||
|
row[field].append(data.get(field))
|
||||||
|
|
||||||
|
if data.get("batch_no"):
|
||||||
|
if data.get("batch_no") in row.consumed_batch:
|
||||||
|
row.consumed_batch[data.get("batch_no")] += data.consumed_qty
|
||||||
|
else:
|
||||||
|
row.consumed_batch[data.get("batch_no")] = data.consumed_qty
|
||||||
|
|
||||||
return backflushed_raw_materials_map
|
return backflushed_raw_materials_map
|
||||||
|
|
||||||
|
def get_supplied_items(item_code, purchase_receipt, references):
|
||||||
|
return frappe.get_all("Purchase Receipt Item Supplied",
|
||||||
|
fields=["rm_item_code", "consumed_qty", "serial_no", "batch_no"],
|
||||||
|
filters={"main_item_code": item_code, "parent": purchase_receipt, "reference_name": ("in", references)})
|
||||||
|
|
||||||
def get_asset_item_details(asset_items):
|
def get_asset_item_details(asset_items):
|
||||||
asset_items_data = {}
|
asset_items_data = {}
|
||||||
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
|
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
|
||||||
@@ -1034,14 +1047,12 @@ def get_backflushed_batch_qty_map(purchase_order, fg_item):
|
|||||||
|
|
||||||
return backflushed_batch_qty_map
|
return backflushed_batch_qty_map
|
||||||
|
|
||||||
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map, po):
|
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batches, po):
|
||||||
# Returns available batches to be backflushed based on requirements
|
# Returns available batches to be backflushed based on requirements
|
||||||
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
||||||
if not transferred_batches:
|
if not transferred_batches:
|
||||||
transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
|
transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
|
||||||
|
|
||||||
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
|
|
||||||
|
|
||||||
available_batches = []
|
available_batches = []
|
||||||
|
|
||||||
for (batch, transferred_qty) in transferred_batches.items():
|
for (batch, transferred_qty) in transferred_batches.items():
|
||||||
|
|||||||
@@ -2,81 +2,90 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
import plaid
|
||||||
from frappe import _
|
import requests
|
||||||
from frappe.utils.password import get_decrypted_password
|
from plaid.errors import APIError, ItemError, InvalidRequestError
|
||||||
from plaid import Client
|
|
||||||
from plaid.errors import APIError, ItemError
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import requests
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
class PlaidConnector():
|
class PlaidConnector():
|
||||||
def __init__(self, access_token=None):
|
def __init__(self, access_token=None):
|
||||||
|
|
||||||
plaid_settings = frappe.get_single("Plaid Settings")
|
|
||||||
|
|
||||||
self.config = {
|
|
||||||
"plaid_client_id": plaid_settings.plaid_client_id,
|
|
||||||
"plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
|
|
||||||
"plaid_public_key": plaid_settings.plaid_public_key,
|
|
||||||
"plaid_env": plaid_settings.plaid_env
|
|
||||||
}
|
|
||||||
|
|
||||||
self.client = Client(client_id=self.config.get("plaid_client_id"),
|
|
||||||
secret=self.config.get("plaid_secret"),
|
|
||||||
public_key=self.config.get("plaid_public_key"),
|
|
||||||
environment=self.config.get("plaid_env")
|
|
||||||
)
|
|
||||||
|
|
||||||
self.access_token = access_token
|
self.access_token = access_token
|
||||||
|
self.settings = frappe.get_single("Plaid Settings")
|
||||||
|
self.products = ["auth", "transactions"]
|
||||||
|
self.client_name = frappe.local.site
|
||||||
|
self.client = plaid.Client(
|
||||||
|
client_id=self.settings.plaid_client_id,
|
||||||
|
secret=self.settings.get_password("plaid_secret"),
|
||||||
|
environment=self.settings.plaid_env,
|
||||||
|
api_version="2019-05-29"
|
||||||
|
)
|
||||||
|
|
||||||
def get_access_token(self, public_token):
|
def get_access_token(self, public_token):
|
||||||
if public_token is None:
|
if public_token is None:
|
||||||
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
|
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
|
||||||
|
|
||||||
response = self.client.Item.public_token.exchange(public_token)
|
response = self.client.Item.public_token.exchange(public_token)
|
||||||
access_token = response['access_token']
|
access_token = response["access_token"]
|
||||||
|
|
||||||
return access_token
|
return access_token
|
||||||
|
|
||||||
|
def get_link_token(self):
|
||||||
|
token_request = {
|
||||||
|
"client_name": self.client_name,
|
||||||
|
"client_id": self.settings.plaid_client_id,
|
||||||
|
"secret": self.settings.plaid_secret,
|
||||||
|
"products": self.products,
|
||||||
|
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
||||||
|
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
||||||
|
"country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
|
||||||
|
"user": {
|
||||||
|
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.client.LinkToken.create(token_request)
|
||||||
|
except InvalidRequestError:
|
||||||
|
frappe.log_error(frappe.get_traceback(), _("Plaid invalid request error"))
|
||||||
|
frappe.msgprint(_("Please check your Plaid client ID and secret values"))
|
||||||
|
except APIError as e:
|
||||||
|
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
|
||||||
|
frappe.throw(_(str(e)), title=_("Authentication Failed"))
|
||||||
|
else:
|
||||||
|
return response["link_token"]
|
||||||
|
|
||||||
def auth(self):
|
def auth(self):
|
||||||
try:
|
try:
|
||||||
self.client.Auth.get(self.access_token)
|
self.client.Auth.get(self.access_token)
|
||||||
print("Authentication successful.....")
|
|
||||||
except ItemError as e:
|
except ItemError as e:
|
||||||
if e.code == 'ITEM_LOGIN_REQUIRED':
|
if e.code == "ITEM_LOGIN_REQUIRED":
|
||||||
pass
|
|
||||||
else:
|
|
||||||
pass
|
pass
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
if e.code == 'PLANNED_MAINTENANCE':
|
if e.code == "PLANNED_MAINTENANCE":
|
||||||
pass
|
|
||||||
else:
|
|
||||||
pass
|
pass
|
||||||
except requests.Timeout:
|
except requests.Timeout:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
|
||||||
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
|
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
|
||||||
frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'})
|
frappe.throw(_(str(e)), title=_("Authentication Failed"))
|
||||||
|
|
||||||
def get_transactions(self, start_date, end_date, account_id=None):
|
def get_transactions(self, start_date, end_date, account_id=None):
|
||||||
|
self.auth()
|
||||||
|
kwargs = dict(
|
||||||
|
access_token=self.access_token,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date
|
||||||
|
)
|
||||||
|
if account_id:
|
||||||
|
kwargs.update(dict(account_ids=[account_id]))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.auth()
|
response = self.client.Transactions.get(**kwargs)
|
||||||
if account_id:
|
transactions = response["transactions"]
|
||||||
account_ids = [account_id]
|
while len(transactions) < response["total_transactions"]:
|
||||||
|
|
||||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
|
|
||||||
|
|
||||||
else:
|
|
||||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)
|
|
||||||
|
|
||||||
transactions = response['transactions']
|
|
||||||
|
|
||||||
while len(transactions) < response['total_transactions']:
|
|
||||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
|
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
|
||||||
transactions.extend(response['transactions'])
|
transactions.extend(response["transactions"])
|
||||||
return transactions
|
return transactions
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
frappe.provide("erpnext.integrations");
|
frappe.provide("erpnext.integrations");
|
||||||
|
|
||||||
frappe.ui.form.on('Plaid Settings', {
|
frappe.ui.form.on('Plaid Settings', {
|
||||||
enabled: function(frm) {
|
enabled: function (frm) {
|
||||||
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
|
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
|
||||||
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
|
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
|
||||||
frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
|
|
||||||
frm.toggle_reqd('plaid_env', frm.doc.enabled);
|
frm.toggle_reqd('plaid_env', frm.doc.enabled);
|
||||||
},
|
},
|
||||||
refresh: function(frm) {
|
|
||||||
if(frm.doc.enabled) {
|
refresh: function (frm) {
|
||||||
|
if (frm.doc.enabled) {
|
||||||
frm.add_custom_button('Link a new bank account', () => {
|
frm.add_custom_button('Link a new bank account', () => {
|
||||||
new erpnext.integrations.plaidLink(frm);
|
new erpnext.integrations.plaidLink(frm);
|
||||||
});
|
});
|
||||||
@@ -22,17 +22,16 @@ frappe.ui.form.on('Plaid Settings', {
|
|||||||
erpnext.integrations.plaidLink = class plaidLink {
|
erpnext.integrations.plaidLink = class plaidLink {
|
||||||
constructor(parent) {
|
constructor(parent) {
|
||||||
this.frm = parent;
|
this.frm = parent;
|
||||||
this.product = ["transactions", "auth"];
|
|
||||||
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
||||||
this.init_config();
|
this.init_config();
|
||||||
}
|
}
|
||||||
|
|
||||||
init_config() {
|
async init_config() {
|
||||||
const me = this;
|
this.product = ["auth", "transactions"];
|
||||||
me.plaid_env = me.frm.doc.plaid_env;
|
this.plaid_env = this.frm.doc.plaid_env;
|
||||||
me.plaid_public_key = me.frm.doc.plaid_public_key;
|
this.client_name = frappe.boot.sitename;
|
||||||
me.client_name = frappe.boot.sitename;
|
this.token = await this.frm.call("get_link_token").then(resp => resp.message);
|
||||||
me.init_plaid();
|
this.init_plaid();
|
||||||
}
|
}
|
||||||
|
|
||||||
init_plaid() {
|
init_plaid() {
|
||||||
@@ -69,17 +68,17 @@ erpnext.integrations.plaidLink = class plaidLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onScriptLoaded(me) {
|
onScriptLoaded(me) {
|
||||||
me.linkHandler = window.Plaid.create({
|
me.linkHandler = Plaid.create({
|
||||||
clientName: me.client_name,
|
clientName: me.client_name,
|
||||||
|
product: me.product,
|
||||||
env: me.plaid_env,
|
env: me.plaid_env,
|
||||||
key: me.plaid_public_key,
|
token: me.token,
|
||||||
onSuccess: me.plaid_success,
|
onSuccess: me.plaid_success
|
||||||
product: me.product
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onScriptError(error) {
|
onScriptError(error) {
|
||||||
frappe.msgprint('There was an issue loading the link-initialize.js script');
|
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
|
||||||
frappe.msgprint(error);
|
frappe.msgprint(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,21 +86,25 @@ erpnext.integrations.plaidLink = class plaidLink {
|
|||||||
const me = this;
|
const me = this;
|
||||||
|
|
||||||
frappe.prompt({
|
frappe.prompt({
|
||||||
fieldtype:"Link",
|
fieldtype: "Link",
|
||||||
options: "Company",
|
options: "Company",
|
||||||
label:__("Company"),
|
label: __("Company"),
|
||||||
fieldname:"company",
|
fieldname: "company",
|
||||||
reqd:1
|
reqd: 1
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
me.company = data.company;
|
me.company = data.company;
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
|
||||||
.then((result) => {
|
token: token,
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
|
response: response
|
||||||
bank: result, company: me.company});
|
}).then((result) => {
|
||||||
})
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
|
||||||
.then(() => {
|
response: response,
|
||||||
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
|
bank: result,
|
||||||
|
company: me.company
|
||||||
});
|
});
|
||||||
|
}).then(() => {
|
||||||
|
frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
|
||||||
|
});
|
||||||
}, __("Select a company"), __("Continue"));
|
}, __("Select a company"), __("Continue"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
|
||||||
"creation": "2018-10-25 10:02:48.656165",
|
"creation": "2018-10-25 10:02:48.656165",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@@ -12,7 +11,6 @@
|
|||||||
"plaid_client_id",
|
"plaid_client_id",
|
||||||
"plaid_secret",
|
"plaid_secret",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
"plaid_public_key",
|
|
||||||
"plaid_env"
|
"plaid_env"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -41,12 +39,6 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Plaid Secret"
|
"label": "Plaid Secret"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "plaid_public_key",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Plaid Public Key"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "plaid_env",
|
"fieldname": "plaid_env",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -69,8 +61,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"modified": "2020-09-12 02:31:44.542385",
|
||||||
"modified": "2020-02-07 15:21:11.616231",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "ERPNext Integrations",
|
"module": "ERPNext Integrations",
|
||||||
"name": "Plaid Settings",
|
"name": "Plaid Settings",
|
||||||
|
|||||||
@@ -2,30 +2,36 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
|
||||||
import json
|
import json
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
import frappe
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
|
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
|
||||||
from frappe.utils import getdate, formatdate, today, add_months
|
from frappe import _
|
||||||
from frappe.desk.doctype.tag.tag import add_tag
|
from frappe.desk.doctype.tag.tag import add_tag
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import add_months, formatdate, getdate, today
|
||||||
|
|
||||||
|
|
||||||
class PlaidSettings(Document):
|
class PlaidSettings(Document):
|
||||||
pass
|
@staticmethod
|
||||||
|
def get_link_token():
|
||||||
|
plaid = PlaidConnector()
|
||||||
|
return plaid.get_link_token()
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def plaid_configuration():
|
def get_plaid_configuration():
|
||||||
if frappe.db.get_single_value("Plaid Settings", "enabled"):
|
if frappe.db.get_single_value("Plaid Settings", "enabled"):
|
||||||
plaid_settings = frappe.get_single("Plaid Settings")
|
plaid_settings = frappe.get_single("Plaid Settings")
|
||||||
return {
|
return {
|
||||||
"plaid_public_key": plaid_settings.plaid_public_key,
|
|
||||||
"plaid_env": plaid_settings.plaid_env,
|
"plaid_env": plaid_settings.plaid_env,
|
||||||
|
"link_token": plaid_settings.get_link_token(),
|
||||||
"client_name": frappe.local.site
|
"client_name": frappe.local.site
|
||||||
}
|
}
|
||||||
else:
|
|
||||||
return "disabled"
|
return "disabled"
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_institution(token, response):
|
def add_institution(token, response):
|
||||||
@@ -33,6 +39,7 @@ def add_institution(token, response):
|
|||||||
|
|
||||||
plaid = PlaidConnector()
|
plaid = PlaidConnector()
|
||||||
access_token = plaid.get_access_token(token)
|
access_token = plaid.get_access_token(token)
|
||||||
|
bank = None
|
||||||
|
|
||||||
if not frappe.db.exists("Bank", response["institution"]["name"]):
|
if not frappe.db.exists("Bank", response["institution"]["name"]):
|
||||||
try:
|
try:
|
||||||
@@ -44,7 +51,6 @@ def add_institution(token, response):
|
|||||||
bank.insert()
|
bank.insert()
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.throw(frappe.get_traceback())
|
frappe.throw(frappe.get_traceback())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
bank = frappe.get_doc("Bank", response["institution"]["name"])
|
bank = frappe.get_doc("Bank", response["institution"]["name"])
|
||||||
bank.plaid_access_token = access_token
|
bank.plaid_access_token = access_token
|
||||||
@@ -52,6 +58,7 @@ def add_institution(token, response):
|
|||||||
|
|
||||||
return bank
|
return bank
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_bank_accounts(response, bank, company):
|
def add_bank_accounts(response, bank, company):
|
||||||
try:
|
try:
|
||||||
@@ -92,9 +99,8 @@ def add_bank_accounts(response, bank, company):
|
|||||||
new_account.insert()
|
new_account.insert()
|
||||||
|
|
||||||
result.append(new_account.name)
|
result.append(new_account.name)
|
||||||
|
|
||||||
except frappe.UniqueValidationError:
|
except frappe.UniqueValidationError:
|
||||||
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name))
|
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.throw(frappe.get_traceback())
|
frappe.throw(frappe.get_traceback())
|
||||||
|
|
||||||
@@ -103,6 +109,7 @@ def add_bank_accounts(response, bank, company):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def add_account_type(account_type):
|
def add_account_type(account_type):
|
||||||
try:
|
try:
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
@@ -122,10 +129,11 @@ def add_account_subtype(account_subtype):
|
|||||||
except Exception:
|
except Exception:
|
||||||
frappe.throw(frappe.get_traceback())
|
frappe.throw(frappe.get_traceback())
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def sync_transactions(bank, bank_account):
|
def sync_transactions(bank, bank_account):
|
||||||
''' Sync transactions based on the last integration date as the start date, after sync is completed
|
"""Sync transactions based on the last integration date as the start date, after sync is completed
|
||||||
add the transaction date of the oldest transaction as the last integration date '''
|
add the transaction date of the oldest transaction as the last integration date."""
|
||||||
|
|
||||||
last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
|
last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
|
||||||
if last_transaction_date:
|
if last_transaction_date:
|
||||||
@@ -148,10 +156,10 @@ def sync_transactions(bank, bank_account):
|
|||||||
len(result), bank_account, start_date, end_date))
|
len(result), bank_account, start_date, end_date))
|
||||||
|
|
||||||
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
|
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
||||||
|
|
||||||
|
|
||||||
def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||||
access_token = None
|
access_token = None
|
||||||
|
|
||||||
@@ -169,6 +177,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
|||||||
|
|
||||||
return transactions
|
return transactions
|
||||||
|
|
||||||
|
|
||||||
def new_bank_transaction(transaction):
|
def new_bank_transaction(transaction):
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
@@ -183,8 +192,8 @@ def new_bank_transaction(transaction):
|
|||||||
|
|
||||||
status = "Pending" if transaction["pending"] == "True" else "Settled"
|
status = "Pending" if transaction["pending"] == "True" else "Settled"
|
||||||
|
|
||||||
|
tags = []
|
||||||
try:
|
try:
|
||||||
tags = []
|
|
||||||
tags += transaction["category"]
|
tags += transaction["category"]
|
||||||
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
|
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -217,6 +226,7 @@ def new_bank_transaction(transaction):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def automatic_synchronization():
|
def automatic_synchronization():
|
||||||
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
||||||
|
|
||||||
@@ -224,4 +234,8 @@ def automatic_synchronization():
|
|||||||
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
|
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
|
||||||
|
|
||||||
for plaid_account in plaid_accounts:
|
for plaid_account in plaid_accounts:
|
||||||
frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name)
|
frappe.enqueue(
|
||||||
|
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
||||||
|
bank=plaid_account.bank,
|
||||||
|
bank_account=plaid_account.name
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import frappe
|
|
||||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import plaid_configuration, add_account_type, add_account_subtype, new_bank_transaction, add_bank_accounts
|
|
||||||
import json
|
import json
|
||||||
from frappe.utils.response import json_handler
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||||
|
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import (
|
||||||
|
add_account_subtype, add_account_type, add_bank_accounts,
|
||||||
|
new_bank_transaction, get_plaid_configuration)
|
||||||
|
from frappe.utils.response import json_handler
|
||||||
|
|
||||||
|
|
||||||
class TestPlaidSettings(unittest.TestCase):
|
class TestPlaidSettings(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -31,7 +34,7 @@ class TestPlaidSettings(unittest.TestCase):
|
|||||||
|
|
||||||
def test_plaid_disabled(self):
|
def test_plaid_disabled(self):
|
||||||
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
|
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
|
||||||
self.assertTrue(plaid_configuration() == "disabled")
|
self.assertTrue(get_plaid_configuration() == "disabled")
|
||||||
|
|
||||||
def test_add_account_type(self):
|
def test_add_account_type(self):
|
||||||
add_account_type("brokerage")
|
add_account_type("brokerage")
|
||||||
@@ -64,7 +67,7 @@ class TestPlaidSettings(unittest.TestCase):
|
|||||||
'mask': '0000',
|
'mask': '0000',
|
||||||
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
|
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
|
||||||
'name': 'Plaid Checking'
|
'name': 'Plaid Checking'
|
||||||
}],
|
}],
|
||||||
'institution': {
|
'institution': {
|
||||||
'institution_id': 'ins_6',
|
'institution_id': 'ins_6',
|
||||||
'name': 'Citi'
|
'name': 'Citi'
|
||||||
@@ -100,7 +103,7 @@ class TestPlaidSettings(unittest.TestCase):
|
|||||||
'mask': '0000',
|
'mask': '0000',
|
||||||
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
|
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
|
||||||
'name': 'Plaid Checking'
|
'name': 'Plaid Checking'
|
||||||
}],
|
}],
|
||||||
'institution': {
|
'institution': {
|
||||||
'institution_id': 'ins_6',
|
'institution_id': 'ins_6',
|
||||||
'name': 'Citi'
|
'name': 'Citi'
|
||||||
|
|||||||
@@ -4,17 +4,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc("erpnext_integrations", "doctype", "plaid_settings")
|
frappe.reload_doc("erpnext_integrations", "doctype", "plaid_settings")
|
||||||
plaid_settings = frappe.get_single("Plaid Settings")
|
plaid_settings = frappe.get_single("Plaid Settings")
|
||||||
if plaid_settings.enabled:
|
if plaid_settings.enabled:
|
||||||
if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env \
|
if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env and frappe.conf.plaid_secret):
|
||||||
and frappe.conf.plaid_public_key and frappe.conf.plaid_secret):
|
|
||||||
plaid_settings.enabled = 0
|
plaid_settings.enabled = 0
|
||||||
else:
|
else:
|
||||||
plaid_settings.update({
|
plaid_settings.update({
|
||||||
"plaid_client_id": frappe.conf.plaid_client_id,
|
"plaid_client_id": frappe.conf.plaid_client_id,
|
||||||
"plaid_public_key": frappe.conf.plaid_public_key,
|
|
||||||
"plaid_env": frappe.conf.plaid_env,
|
"plaid_env": frappe.conf.plaid_env,
|
||||||
"plaid_secret": frappe.conf.plaid_secret
|
"plaid_secret": frappe.conf.plaid_secret
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -224,6 +224,10 @@ erpnext.utils.set_taxes = function(frm, triggered_from_field) {
|
|||||||
party = frm.doc.party_name;
|
party = frm.doc.party_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!frm.doc.company) {
|
||||||
|
frappe.throw(_("Kindly select the company first"));
|
||||||
|
}
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.accounts.party.set_taxes",
|
method: "erpnext.accounts.party.set_taxes",
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
@@ -422,11 +422,12 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
def test_update_child_qty_rate_with_workflow(self):
|
def test_update_child_qty_rate_with_workflow(self):
|
||||||
from frappe.model.workflow import apply_workflow
|
from frappe.model.workflow import apply_workflow
|
||||||
|
|
||||||
|
frappe.set_user("Administrator")
|
||||||
workflow = make_sales_order_workflow()
|
workflow = make_sales_order_workflow()
|
||||||
so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
|
so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
|
||||||
|
frappe.set_user("Administrator")
|
||||||
apply_workflow(so, 'Approve')
|
apply_workflow(so, 'Approve')
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
user = 'test@example.com'
|
user = 'test@example.com'
|
||||||
test_user = frappe.get_doc('User', user)
|
test_user = frappe.get_doc('User', user)
|
||||||
test_user.add_roles("Sales User", "Test Junior Approver")
|
test_user.add_roles("Sales User", "Test Junior Approver")
|
||||||
@@ -1009,7 +1010,7 @@ def make_sales_order_workflow():
|
|||||||
"is_active": 1,
|
"is_active": 1,
|
||||||
"send_email_alert": 0,
|
"send_email_alert": 0,
|
||||||
})
|
})
|
||||||
workflow.append('states', dict( state = 'Pending', allow_edit = 'All' ))
|
workflow.append('states', dict( state = 'Pending', allow_edit = 'Administrator' ))
|
||||||
workflow.append('states', dict( state = 'Approved', allow_edit = 'Test Approver', doc_status = 1 ))
|
workflow.append('states', dict( state = 'Approved', allow_edit = 'Test Approver', doc_status = 1 ))
|
||||||
workflow.append('transitions', dict(
|
workflow.append('transitions', dict(
|
||||||
state = 'Pending', action = 'Approve', next_state = 'Approved', allowed = 'Test Junior Approver', allow_self_approval = 1,
|
state = 'Pending', action = 'Approve', next_state = 'Approved', allowed = 'Test Junior Approver', allow_self_approval = 1,
|
||||||
|
|||||||
@@ -449,8 +449,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
|||||||
|
|
||||||
$(this.frm.msgbox.body).find('.btn-primary').on('click', () => {
|
$(this.frm.msgbox.body).find('.btn-primary').on('click', () => {
|
||||||
this.frm.msgbox.hide();
|
this.frm.msgbox.hide();
|
||||||
const frm = this.events.get_frm();
|
const frm = this.frm;
|
||||||
frm.doc = this.doc;
|
|
||||||
frm.print_preview.lang_code = frm.doc.language;
|
frm.print_preview.lang_code = frm.doc.language;
|
||||||
frm.print_preview.printit(true);
|
frm.print_preview.printit(true);
|
||||||
});
|
});
|
||||||
@@ -688,8 +687,8 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
|||||||
if(this.frm.doc.docstatus != 1 ){
|
if(this.frm.doc.docstatus != 1 ){
|
||||||
await this.frm.save();
|
await this.frm.save();
|
||||||
}
|
}
|
||||||
const frm = this.events.get_frm();
|
|
||||||
frm.doc = this.doc;
|
const frm = this.frm;
|
||||||
frm.print_preview.lang_code = frm.doc.language;
|
frm.print_preview.lang_code = frm.doc.language;
|
||||||
frm.print_preview.printit(true);
|
frm.print_preview.printit(true);
|
||||||
});
|
});
|
||||||
@@ -948,8 +947,12 @@ class POSCart {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onchange: () => {
|
onchange: () => {
|
||||||
this.events.on_customer_change(this.customer_field.get_value());
|
let customer = this.customer_field.get_value();
|
||||||
this.events.get_loyalty_details();
|
frappe.db.get_value("Customer", customer, "language", (r) => {
|
||||||
|
this.frm.doc.language = r ? r.language : "en-US";
|
||||||
|
this.events.on_customer_change(customer);
|
||||||
|
this.events.get_loyalty_details();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parent: this.wrapper.find('.customer-field'),
|
parent: this.wrapper.find('.customer-field'),
|
||||||
|
|||||||
@@ -241,3 +241,18 @@ class TestBatch(unittest.TestCase):
|
|||||||
batch.insert()
|
batch.insert()
|
||||||
|
|
||||||
return batch
|
return batch
|
||||||
|
|
||||||
|
def make_new_batch(**args):
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
batch = frappe.get_doc({
|
||||||
|
"doctype": "Batch",
|
||||||
|
"batch_id": args.batch_id,
|
||||||
|
"item": args.item_code,
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
except frappe.DuplicateEntryError:
|
||||||
|
batch = frappe.get_doc("Batch", args.batch_id)
|
||||||
|
|
||||||
|
return batch
|
||||||
@@ -152,7 +152,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
|
|
||||||
update_backflush_based_on("Material Transferred for Subcontract")
|
update_backflush_based_on("Material Transferred for Subcontract")
|
||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 1"
|
||||||
make_subcontracted_item(item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
|
|
||||||
po = create_purchase_order(item_code=item_code, qty=1,
|
po = create_purchase_order(item_code=item_code, qty=1,
|
||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||||
@@ -471,8 +471,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
"expected_value_after_useful_life": 10,
|
"expected_value_after_useful_life": 10,
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 1,
|
"frequency_of_depreciation": 1
|
||||||
"depreciation_start_date": frappe.utils.nowdate()
|
|
||||||
})
|
})
|
||||||
asset.submit()
|
asset.submit()
|
||||||
|
|
||||||
|
|||||||
@@ -420,6 +420,9 @@ def get_item_details(item_code):
|
|||||||
from tabItem where name=%s""", item_code, as_dict=True)[0]
|
from tabItem where name=%s""", item_code, as_dict=True)[0]
|
||||||
|
|
||||||
def get_serial_nos(serial_no):
|
def get_serial_nos(serial_no):
|
||||||
|
if isinstance(serial_no, list):
|
||||||
|
return serial_no
|
||||||
|
|
||||||
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
|
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
|
||||||
if s.strip()]
|
if s.strip()]
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,24 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query('source_warehouse_address', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
link_doctype: 'Warehouse',
|
||||||
|
link_name: frm.doc.from_warehouse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('target_warehouse_address', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
link_doctype: 'Warehouse',
|
||||||
|
link_name: frm.doc.to_warehouse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
frappe.db.get_value('Stock Settings', {name: 'Stock Settings'}, 'sample_retention_warehouse', (r) => {
|
frappe.db.get_value('Stock Settings', {name: 'Stock Settings'}, 'sample_retention_warehouse', (r) => {
|
||||||
if (r.sample_retention_warehouse) {
|
if (r.sample_retention_warehouse) {
|
||||||
var filters = [
|
var filters = [
|
||||||
|
|||||||
@@ -165,9 +165,12 @@ class StockReconciliation(StockController):
|
|||||||
validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
|
validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
|
||||||
|
|
||||||
# item should not be serialized
|
# item should not be serialized
|
||||||
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
|
if item.has_serial_no and not row.serial_no and not item.serial_no_series and flt(row.qty) > 0:
|
||||||
raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code))
|
raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code))
|
||||||
|
|
||||||
|
if flt(row.qty) == 0 and row.serial_no:
|
||||||
|
row.serial_no = ''
|
||||||
|
|
||||||
# item managed batch-wise not allowed
|
# item managed batch-wise not allowed
|
||||||
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
|
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
|
||||||
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
|
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
|
||||||
@@ -235,7 +238,7 @@ class StockReconciliation(StockController):
|
|||||||
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
|
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
|
||||||
|
|
||||||
def issue_existing_serial_and_batch(self, sl_entries):
|
def issue_existing_serial_and_batch(self, sl_entries):
|
||||||
from erpnext.stock.stock_ledger import get_previous_sle
|
from erpnext.stock.stock_ledger import get_stock_ledger_entries
|
||||||
|
|
||||||
for row in self.items:
|
for row in self.items:
|
||||||
serial_nos = get_serial_nos(row.serial_no) or []
|
serial_nos = get_serial_nos(row.serial_no) or []
|
||||||
@@ -261,12 +264,14 @@ class StockReconciliation(StockController):
|
|||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
args = self.get_sle_for_items(row, [serial_no])
|
args = self.get_sle_for_items(row, [serial_no])
|
||||||
|
|
||||||
previous_sle = get_previous_sle({
|
previous_sle = get_stock_ledger_entries({
|
||||||
"item_code": row.item_code,
|
"item_code": row.item_code,
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
"posting_time": self.posting_time,
|
"posting_time": self.posting_time,
|
||||||
"serial_no": serial_no
|
"serial_no": serial_no
|
||||||
})
|
}, "<", "desc", "limit 1")
|
||||||
|
|
||||||
|
previous_sle = previous_sle and previous_sle[0] or {}
|
||||||
|
|
||||||
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
|
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
|
||||||
# If serial no exists in different warehouse
|
# If serial no exists in different warehouse
|
||||||
|
|||||||
@@ -460,7 +460,13 @@ def get_stock_ledger_entries(previous_sle, operator=None,
|
|||||||
conditions += " and " + previous_sle.get("warehouse_condition")
|
conditions += " and " + previous_sle.get("warehouse_condition")
|
||||||
|
|
||||||
if check_serial_no and previous_sle.get("serial_no"):
|
if check_serial_no and previous_sle.get("serial_no"):
|
||||||
conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
|
serial_no = previous_sle.get("serial_no")
|
||||||
|
conditions += """ and (
|
||||||
|
serial_no = '{0}'
|
||||||
|
OR serial_no like '{0}\n%%'
|
||||||
|
OR serial_no like '%%\n{0}'
|
||||||
|
OR serial_no like '%%\n{0}\n%%'
|
||||||
|
) and actual_qty > 0""".format(serial_no)
|
||||||
|
|
||||||
if not previous_sle.get("posting_date"):
|
if not previous_sle.get("posting_date"):
|
||||||
previous_sle["posting_date"] = "1900-01-01"
|
previous_sle["posting_date"] = "1900-01-01"
|
||||||
|
|||||||
@@ -96,11 +96,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None
|
|||||||
|
|
||||||
if with_valuation_rate:
|
if with_valuation_rate:
|
||||||
if with_serial_no:
|
if with_serial_no:
|
||||||
serial_nos = last_entry.get("serial_no")
|
serial_nos = get_serial_nos_data_after_transactions(args)
|
||||||
|
|
||||||
if (serial_nos and
|
|
||||||
len(get_serial_nos_data(serial_nos)) < last_entry.qty_after_transaction):
|
|
||||||
serial_nos = get_serial_nos_data_after_transactions(args)
|
|
||||||
|
|
||||||
return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
|
return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
|
||||||
if last_entry else (0.0, 0.0, 0.0))
|
if last_entry else (0.0, 0.0, 0.0))
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ frappe
|
|||||||
gocardless-pro==1.11.0
|
gocardless-pro==1.11.0
|
||||||
googlemaps==3.1.1
|
googlemaps==3.1.1
|
||||||
pandas==0.24.2
|
pandas==0.24.2
|
||||||
plaid-python==3.4.0
|
plaid-python==6.0.0
|
||||||
PyGithub==1.44.1
|
PyGithub==1.44.1
|
||||||
python-stdnum==1.12
|
python-stdnum==1.12
|
||||||
Unidecode==1.1.1
|
Unidecode==1.1.1
|
||||||
|
|||||||
Reference in New Issue
Block a user