Merge pull request #3 from frappe/version-13-beta-pre-release

Version 13 beta pre release
This commit is contained in:
Jannat Patel 2020-12-22 13:11:02 +05:30 committed by GitHub
commit 8224b1c1e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
592 changed files with 32529 additions and 10610 deletions

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
# Root editor config file
root = true
# Common settings
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
# python, js indentation settings
[{*.py,*.js}]
indent_style = tab
indent_size = 4

View File

@ -5,7 +5,7 @@
"es6": true
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 9,
"sourceType": "module"
},
"extends": "eslint:recommended",
@ -15,6 +15,14 @@
"tab",
{ "SwitchCase": 1 }
],
"brace-style": [
"error",
"1tbs"
],
"space-unary-ops": [
"error",
{ "words": true }
],
"linebreak-style": [
"error",
"unix"
@ -44,12 +52,10 @@
"no-control-regex": [
"off"
],
"spaced-comment": [
"warn"
],
"no-trailing-spaces": [
"warn"
]
"space-before-blocks": "warn",
"keyword-spacing": "warn",
"comma-spacing": "warn",
"key-spacing": "warn"
},
"root": true,
"globals": {

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum
url: https://discuss.erpnext.com/
about: For general QnA, discussions and community help.

View File

@ -9,5 +9,6 @@
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"]
"install_apps": ["erpnext"],
"throttle_user_limit": 100
}

View File

@ -5,7 +5,7 @@
<p>ERP made simple</p>
</p>
[![Build Status](https://travis-ci.com/frappe/erpnext.svg)](https://travis-ci.com/frappe/erpnext)
[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext)
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '13.0.0-beta.4'
__version__ = '13.0.0-beta.6'
def get_default_company(user=None):
'''Get default company for user'''

View File

@ -6,9 +6,8 @@ import frappe, json
from frappe import _
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
from frappe.utils.dashboard import cache_source
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()

View File

@ -23,7 +23,7 @@
{
"hidden": 0,
"label": "Reports",
"links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n }\n]"
"links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"DATEV Export\",\n \"name\": \"DATEV\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
@ -43,7 +43,7 @@
{
"hidden": 0,
"label": "Bank Statement",
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
@ -79,6 +79,11 @@
"hidden": 0,
"label": "Profitability",
"links": "[\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Gross Profit\",\n \"name\": \"Gross Profit\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profitability Analysis\",\n \"name\": \"Profitability Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Value-Added Tax (VAT UAE)",
"links": "[\n {\n \"country\": \"United Arab Emirates\",\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"United Arab Emirates\",\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n\n]"
}
],
"category": "Modules",
@ -98,7 +103,7 @@
"idx": 0,
"is_standard": 1,
"label": "Accounting",
"modified": "2020-10-08 20:31:46.022470",
"modified": "2020-11-11 18:35:11.542909",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@ -108,7 +113,7 @@
"pin_to_top": 0,
"shortcuts": [
{
"label": "Chart of Accounts",
"label": "Chart Of Accounts",
"link_to": "Account",
"type": "DocType"
},

View File

@ -101,7 +101,7 @@ class Account(NestedSet):
return
if not frappe.db.get_value("Account",
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
elif self.parent_account:
descendants = get_descendants_of('Company', self.company)
if not descendants: return
@ -164,9 +164,19 @@ class Account(NestedSet):
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
for company in descendants:
company_bold = frappe.bold(company)
parent_acc_name_bold = frappe.bold(parent_acc_name)
if not parent_acc_name_map.get(company):
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
.format(company, parent_acc_name))
frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
.format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
# validate if parent of child company account to be added is a group
if (frappe.db.get_value("Account", self.parent_account, "is_group")
and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
msg += "<br><br>"
msg += _("Please convert the parent account in corresponding child company to a group account.")
frappe.throw(msg, title=_("Invalid Parent Account"))
filters = {
"account_name": self.account_name,
@ -309,8 +319,9 @@ def update_account_number(name, account_name, account_number=None, from_descenda
allow_child_account_creation = _("Allow Account Creation Against Child Company")
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
message += "<br>" + _("Renaming it is only allowed via parent company {0}, \
to avoid mismatch.").format(frappe.bold(ancestor)) + "<br><br>"
message += "<br>"
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
message += "<br><br>"
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
frappe.throw(message, title=_("Rename Not Allowed"))

View File

@ -245,6 +245,9 @@ def get():
"account_number": "2200"
},
_("Duties and Taxes"): {
_("TDS Payable"): {
"account_number": "2310"
},
"account_type": "Tax",
"is_group": 1,
"account_number": "2300"

View File

@ -111,6 +111,17 @@ class TestAccount(unittest.TestCase):
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
def test_add_account_to_a_group(self):
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 1)
acc = frappe.new_doc("Account")
acc.account_name = "Test Group Account"
acc.parent_account = "Office Rent - _TC3"
acc.company = "_Test Company 3"
self.assertRaises(frappe.ValidationError, acc.insert)
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 0)
def test_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
@ -160,7 +171,8 @@ class TestAccount(unittest.TestCase):
for doc in to_delete:
frappe.delete_doc("Account", doc)
def _make_test_records(verbose):
def _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects
accounts = [

View File

@ -9,11 +9,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase):
def setUp(self):
make_pos_profile()
add_transactions()
add_payments()
@ -27,6 +29,9 @@ class TestBankTransaction(unittest.TestCase):
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
frappe.db.sql("""delete from `tabPayment Entry`""")
# Delete POS Profile
frappe.db.sql("delete from `tabPOS Profile`")
frappe.flags.test_bank_transactions_created = False
frappe.flags.test_payments_created = False

View File

@ -158,8 +158,11 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "cost_center")
budget = make_budget(budget_against="Cost Center")
month = now_datetime().month
if month > 10:
month = 10
for i in range(now_datetime().month):
for i in range(month):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
@ -177,8 +180,11 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "project")
budget = make_budget(budget_against="Project")
month = now_datetime().month
if month > 10:
month = 10
for i in range(now_datetime().month):
for i in range(month):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")

View File

@ -94,8 +94,7 @@ frappe.ui.form.on('Chart of Accounts Importer', {
callback: function(r) {
if(r.message===false) {
frm.set_value("company", "");
frappe.throw(__(`Transactions against the company already exist!
Chart Of accounts can be imported for company with no transactions`));
frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."));
} else {
frm.trigger("refresh");
}

View File

@ -28,22 +28,22 @@ def test_create_test_data():
"item_group": "_Test Item Group",
"item_name": "_Test Tesla Car",
"apply_warehouse_wise_reorder_level": 0,
"warehouse":"Stores - TCP1",
"warehouse":"Stores - _TC",
"gst_hsn_code": "999800",
"valuation_rate": 5000,
"standard_rate":5000,
"item_defaults": [{
"company": "_Test Company with perpetual inventory",
"default_warehouse": "Stores - TCP1",
"company": "_Test Company",
"default_warehouse": "Stores - _TC",
"default_price_list":"_Test Price List",
"expense_account": "Cost of Goods Sold - TCP1",
"buying_cost_center": "Main - TCP1",
"selling_cost_center": "Main - TCP1",
"income_account": "Sales - TCP1"
"expense_account": "Cost of Goods Sold - _TC",
"buying_cost_center": "Main - _TC",
"selling_cost_center": "Main - _TC",
"income_account": "Sales - _TC"
}],
"show_in_website": 1,
"route":"-test-tesla-car",
"website_warehouse": "Stores - TCP1"
"website_warehouse": "Stores - _TC"
})
item.insert()
# create test item price
@ -65,12 +65,12 @@ def test_create_test_data():
"items": [{
"item_code": "_Test Tesla Car"
}],
"warehouse":"Stores - TCP1",
"warehouse":"Stores - _TC",
"coupon_code_based":1,
"selling": 1,
"rate_or_discount": "Discount Percentage",
"discount_percentage": 30,
"company": "_Test Company with perpetual inventory",
"company": "_Test Company",
"currency":"INR",
"for_price_list":"_Test Price List"
})
@ -85,7 +85,7 @@ def test_create_test_data():
})
sales_partner.insert()
# create test item coupon code
if not frappe.db.exists("Coupon Code","SAVE30"):
if not frappe.db.exists("Coupon Code", "SAVE30"):
coupon_code = frappe.get_doc({
"doctype": "Coupon Code",
"coupon_name":"SAVE30",
@ -102,35 +102,27 @@ class TestCouponCode(unittest.TestCase):
test_create_test_data()
def tearDown(self):
frappe.set_user("Administrator")
frappe.set_user("Administrator")
def test_1_check_coupon_code_used_before_so(self):
coupon_code = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
# reset used coupon code count
coupon_code.used=0
coupon_code.save()
# check no coupon code is used before sales order is made
self.assertEqual(coupon_code.get("used"),0)
def test_sales_order_with_coupon_code(self):
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
def test_2_sales_order_with_coupon_code(self):
so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1,
so = make_sales_order(company='_Test Company', warehouse='Stores - _TC',
customer="_Test Customer", selling_price_list="_Test Price List",
item_code="_Test Tesla Car", rate=5000, qty=1,
do_not_submit=True)
so = frappe.get_doc('Sales Order', so.name)
# check item price before coupon code is applied
self.assertEqual(so.items[0].rate, 5000)
so.coupon_code='SAVE30'
so.sales_partner='_Test Coupon Partner'
so.save()
# check item price after coupon code is applied
self.assertEqual(so.items[0].rate, 3500)
so.submit()
def test_3_check_coupon_code_used_after_so(self):
doc = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
# check no coupon code is used before sales order is made
self.assertEqual(doc.get("used"),1)
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)

View File

@ -9,11 +9,7 @@ frappe.ui.form.on('Fiscal Year', {
}
},
refresh: function (frm) {
let doc = frm.doc;
frm.toggle_enable('year_start_date', doc.__islocal);
frm.toggle_enable('year_end_date', doc.__islocal);
if (!doc.__islocal && (doc.name != frappe.sys_defaults.fiscal_year)) {
if (!frm.doc.__islocal && (frm.doc.name != frappe.sys_defaults.fiscal_year)) {
frm.add_custom_button(__("Set as Default"), () => frm.events.set_as_default(frm));
frm.set_intro(__("To set this Fiscal Year as Default, click on 'Set as Default'"));
} else {
@ -24,8 +20,10 @@ frappe.ui.form.on('Fiscal Year', {
return frm.call('set_as_default');
},
year_start_date: function(frm) {
let year_end_date =
frappe.datetime.add_days(frappe.datetime.add_months(frm.doc.year_start_date, 12), -1);
frm.set_value("year_end_date", year_end_date);
if (!frm.doc.is_short_year) {
let year_end_date =
frappe.datetime.add_days(frappe.datetime.add_months(frm.doc.year_start_date, 12), -1);
frm.set_value("year_end_date", year_end_date);
}
},
});

View File

@ -1,347 +1,126 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "field:year",
"beta": 0,
"creation": "2013-01-22 16:50:25",
"custom": 0,
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"actions": [],
"allow_import": 1,
"autoname": "field:year",
"creation": "2013-01-22 16:50:25",
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"year",
"disabled",
"is_short_year",
"year_start_date",
"year_end_date",
"companies",
"auto_created"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "For e.g. 2012, 2012-13",
"fieldname": "year",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Year Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "year",
"oldfieldtype": "Data",
"permlevel": 0,
"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
},
"description": "For e.g. 2012, 2012-13",
"fieldname": "year",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Year Name",
"oldfieldname": "year",
"oldfieldtype": "Data",
"reqd": 1,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"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": "Disabled",
"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
},
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "year_start_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_standard_filter": 0,
"label": "Year Start Date",
"length": 0,
"no_copy": 1,
"oldfieldname": "year_start_date",
"oldfieldtype": "Date",
"permlevel": 0,
"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
},
"fieldname": "year_start_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Year Start Date",
"no_copy": 1,
"oldfieldname": "year_start_date",
"oldfieldtype": "Date",
"reqd": 1,
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "year_end_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_standard_filter": 0,
"label": "Year End Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"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
},
"fieldname": "year_end_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Year End Date",
"no_copy": 1,
"reqd": 1,
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "companies",
"fieldtype": "Table",
"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": "Companies",
"length": 0,
"no_copy": 0,
"options": "Fiscal Year Company",
"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
},
"fieldname": "companies",
"fieldtype": "Table",
"label": "Companies",
"options": "Fiscal Year Company"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "auto_created",
"fieldtype": "Check",
"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": "Auto Created",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"default": "0",
"fieldname": "auto_created",
"fieldtype": "Check",
"hidden": 1,
"label": "Auto Created",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"description": "Less than 12 months.",
"fieldname": "is_short_year",
"fieldtype": "Check",
"label": "Is Short Year",
"set_only_once": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-calendar",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-25 14:21:41.273354",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year",
"owner": "Administrator",
],
"icon": "fa fa-calendar",
"idx": 1,
"links": [],
"modified": "2020-11-05 12:16:53.081573",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
"read": 1,
"role": "Sales User"
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Purchase User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
"read": 1,
"role": "Purchase User"
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
"read": 1,
"role": "Accounts User"
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Stock User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
"read": 1,
"role": "Stock User"
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Employee",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
"read": 1,
"role": "Employee"
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1,
"sort_field": "name",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
],
"show_name_in_global_search": 1,
"sort_field": "name",
"sort_order": "DESC"
}

View File

@ -36,6 +36,11 @@ class FiscalYear(Document):
frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."))
def validate_dates(self):
if self.is_short_year:
# Fiscal Year can be shorter than one year, in some jurisdictions
# under certain circumstances. For example, in the USA and Germany.
return
if getdate(self.year_start_date) > getdate(self.year_end_date):
frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
FiscalYearIncorrectDate)
@ -116,12 +121,8 @@ def auto_create_fiscal_year():
pass
def get_from_and_to_date(fiscal_year):
from_and_to_date_tuple = frappe.db.sql("""select year_start_date, year_end_date
from `tabFiscal Year` where name=%s""", (fiscal_year))[0]
from_and_to_date = {
"from_date": from_and_to_date_tuple[0],
"to_date": from_and_to_date_tuple[1]
}
return from_and_to_date
fields = [
"year_start_date as from_date",
"year_end_date as to_date"
]
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)

View File

@ -11,6 +11,7 @@ test_records = frappe.get_test_records('Fiscal Year')
test_ignore = ["Company"]
class TestFiscalYear(unittest.TestCase):
def test_extra_year(self):
if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"):
frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000")

View File

@ -1,4 +1,11 @@
[
{
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
"year_end_date": "2011-04-01",
"year_start_date": "2011-12-31"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2012",

View File

@ -30,20 +30,22 @@ class GLEntry(Document):
self.pl_must_have_cost_center()
self.validate_cost_center()
self.check_pl_account()
self.validate_party()
self.validate_currency()
if not self.flags.from_repost:
self.check_pl_account()
self.validate_party()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost:
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj)
# Update outstanding amt on against voucher
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
and self.against_voucher and update_outstanding == 'Yes':
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher)
@ -106,8 +108,8 @@ class GLEntry(Document):
from tabAccount where name=%s""", self.account, as_dict=1)[0]
if ret.is_group==1:
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
transactions''').format(self.voucher_type, self.voucher_no, self.account))
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''')
.format(self.voucher_type, self.voucher_no, self.account))
if ret.docstatus==2:
frappe.throw(_("{0} {1}: Account {2} is inactive")
@ -136,8 +138,8 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if self.cost_center and _check_is_group():
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party)

View File

@ -137,11 +137,12 @@ class InvoiceDiscounting(AccountsController):
"cost_center": erpnext.get_default_cost_center(self.company)
})
je.append("accounts", {
"account": self.bank_charges_account,
"debit_in_account_currency": flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company)
})
if self.bank_charges:
je.append("accounts", {
"account": self.bank_charges_account,
"debit_in_account_currency": flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company)
})
je.append("accounts", {
"account": self.short_term_loan,

View File

@ -80,6 +80,7 @@ class TestInvoiceDiscounting(unittest.TestCase):
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
bank_charges=100
)
je = inv_disc.create_disbursement_entry()
@ -289,6 +290,7 @@ def create_invoice_discounting(invoices, **args):
inv_disc.bank_account=args.bank_account
inv_disc.loan_start_date = args.start or nowdate()
inv_disc.loan_period = args.period or 30
inv_disc.bank_charges = flt(args.bank_charges)
for d in invoices:
inv_disc.append("invoices", {

View File

@ -1,5 +1,6 @@
{
"actions": [],
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-03-25 10:53:52",
@ -503,7 +504,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2020-06-02 18:15:46.955697",
"modified": "2020-10-30 13:56:01.121995",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@ -34,6 +34,7 @@ class JournalEntry(AccountsController):
self.validate_entries_for_advance()
self.validate_multi_currency()
self.set_amounts_in_company_currency()
self.validate_debit_credit_amount()
self.validate_total_debit_and_credit()
self.validate_against_jv()
self.validate_reference_doc()
@ -339,8 +340,7 @@ class JournalEntry(AccountsController):
currency=account_currency)
if flt(voucher_total) < (flt(order.advance_paid) + total):
frappe.throw(_("Advance paid against {0} {1} cannot be greater \
than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
frappe.throw(_("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
def validate_invoices(self):
"""Validate totals and docstatus for invoices"""
@ -369,6 +369,11 @@ class JournalEntry(AccountsController):
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
def validate_debit_credit_amount(self):
for d in self.get('accounts'):
if not flt(d.debit) and not flt(d.credit):
frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
def validate_total_debit_and_credit(self):
self.set_total_debit_credit()
if self.difference:

View File

@ -75,54 +75,40 @@ class TestJournalEntry(unittest.TestCase):
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", 0)
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
def test_jv_against_stock_account(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
set_perpetual_inventory()
company = "_Test Company with perpetual inventory"
stock_account = get_inventory_account(company)
jv = frappe.copy_doc({
"cheque_date": nowdate(),
"cheque_no": "33",
"company": "_Test Company with perpetual inventory",
"doctype": "Journal Entry",
"accounts": [
{
"account": "Debtors - TCP1",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 400.0,
"debit_in_account_currency": 0.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "Main - TCP1"
},
{
"account": "_Test Bank - TCP1",
"credit_in_account_currency": 0.0,
"debit_in_account_currency": 400.0,
"doctype": "Journal Entry Account",
"parentfield": "accounts",
"cost_center": "Main - TCP1"
}
],
"naming_series": "_T-Journal Entry-",
"posting_date": nowdate(),
"user_remark": "test",
"voucher_type": "Bank Entry"
})
jv.get("accounts")[0].update({
"account": get_inventory_account('_Test Company with perpetual inventory'),
"company": "_Test Company with perpetual inventory",
"party_type": None,
"party": None
jv = frappe.new_doc("Journal Entry")
jv.company = company
jv.posting_date = nowdate()
jv.append("accounts", {
"account": stock_account,
"cost_center": "Main - TCP1",
"debit_in_account_currency": 100
})
jv.append("accounts", {
"account": "Stock Adjustment - TCP1",
"credit_in_account_currency": 100,
"cost_center": "Main - TCP1",
})
jv.insert()
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
jv.cancel()
set_perpetual_inventory(0)
from erpnext.accounts.utils import get_stock_and_account_balance
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
if account_bal == stock_bal:
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
frappe.db.rollback()
else:
jv.submit()
jv.cancel()
def test_multi_currency(self):
jv = make_journal_entry("_Test Bank USD - _TC",

View File

@ -8,12 +8,10 @@ import unittest
from frappe.utils import today, cint, flt, getdate
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
from erpnext.accounts.party import get_dashboard_info
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
class TestLoyaltyProgram(unittest.TestCase):
@classmethod
def setUpClass(self):
set_perpetual_inventory(0)
# create relevant item, customer, loyalty program, etc
create_records()

View File

@ -1,13 +1,17 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
cur_frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
return{
filters: [
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
['Account', 'is_group', '=', 0],
['Account', 'company', '=', d.company]
]
}
});
frappe.ui.form.on('Mode of Payment', {
setup: function(frm) {
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
let d = locals[cdt][cdn];
return {
filters: [
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
['Account', 'is_group', '=', 0],
['Account', 'company', '=', d.company]
]
};
});
},
});

View File

@ -155,7 +155,8 @@ class OpeningInvoiceCreationTool(Document):
"posting_date": row.posting_date,
frappe.scrub(row.party_type): row.party,
"is_pos": 0,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
"update_stock": 0
})
accounting_dimension = get_accounting_dimensions()

View File

@ -7,17 +7,24 @@ import frappe
import unittest
test_dependencies = ["Customer", "Supplier"]
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
class TestOpeningInvoiceCreationTool(unittest.TestCase):
def make_invoices(self, invoice_type="Sales"):
def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company()
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
doc = frappe.get_single("Opening Invoice Creation Tool")
args = get_opening_invoice_creation_dict(invoice_type=invoice_type)
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
party_1=party_1, party_2=party_2)
doc.update(args)
return doc.make_invoices()
def test_opening_sales_invoice_creation(self):
invoices = self.make_invoices()
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
invoices = self.make_invoices(company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2)
expected_value = {
@ -27,6 +34,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
}
self.check_expected_values(invoices, expected_value)
si = frappe.get_doc("Sales Invoice", invoices[0])
# Check if update stock is not enabled
self.assertEqual(si.update_stock, 0)
property_setter.delete()
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@ -36,7 +50,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
def test_opening_purchase_invoice_creation(self):
invoices = self.make_invoices(invoice_type="Purchase")
invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2)
expected_value = {
@ -46,6 +60,32 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
}
self.check_expected_values(invoices, expected_value, "Purchase")
def test_opening_sales_invoice_creation_with_missing_debit_account(self):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
frappe.db.set_value("Company", company, "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
"is_group": 1, "company": "_Test Opening Invoice Company"})
cc.insert(ignore_mandatory=True)
cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
"company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
cc2.insert()
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
# Check if missing debit account error raised
error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]})
self.assertTrue(error_log)
# teardown
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company")
@ -57,7 +97,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 1.0,
"outstanding_amount": 300,
"party": "_Test {0}".format(party),
"party": args.get("party_1") or "_Test {0}".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@ -66,7 +106,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 2.0,
"outstanding_amount": 250,
"party": "_Test {0} 1".format(party),
"party": args.get("party_2") or "_Test {0} 1".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@ -76,4 +116,31 @@ def get_opening_invoice_creation_dict(**args):
})
invoice_dict.update(args)
return invoice_dict
return invoice_dict
def make_company():
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
return frappe.get_doc("Company", "_Test Opening Invoice Company")
company = frappe.new_doc("Company")
company.company_name = "_Test Opening Invoice Company"
company.abbr = "_TOIC"
company.default_currency = "INR"
company.country = "India"
company.insert()
return company
def make_customer(customer=None):
customer_name = customer or "Opening Customer"
customer = frappe.get_doc({
"doctype": "Customer",
"customer_name": customer_name,
"customer_group": "All Customer Groups",
"customer_type": "Company",
"territory": "All Territories"
})
if not frappe.db.exists("Customer", customer_name):
customer.insert(ignore_permissions=True)
return customer.name
else:
return frappe.db.exists("Customer", customer_name)

View File

@ -1,5 +1,6 @@
{
"actions": [],
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2016-06-01 14:38:51.012597",
@ -587,7 +588,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-09-02 13:39:43.383705",
"modified": "2020-10-30 13:56:20.007336",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@ -202,17 +202,32 @@ class PaymentEntry(AccountsController):
# if account_type not in account_types:
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
def set_exchange_rate(self):
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
self.set_target_exchange_rate(ref_doc)
def set_source_exchange_rate(self, ref_doc=None):
if self.paid_from and not self.source_exchange_rate:
if self.paid_from_account_currency == self.company_currency:
self.source_exchange_rate = 1
else:
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
self.company_currency, self.posting_date)
if ref_doc:
if self.paid_from_account_currency == ref_doc.currency:
self.source_exchange_rate = ref_doc.get("exchange_rate")
if not self.source_exchange_rate:
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
self.company_currency, self.posting_date)
def set_target_exchange_rate(self, ref_doc=None):
if self.paid_to and not self.target_exchange_rate:
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
self.company_currency, self.posting_date)
if ref_doc:
if self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate")
if not self.target_exchange_rate:
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
self.company_currency, self.posting_date)
def validate_mandatory(self):
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
@ -282,9 +297,10 @@ class PaymentEntry(AccountsController):
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items():
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
If this is undesirable please cancel the corresponding Payment Entry.")
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
frappe.msgprint(
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange")
@ -909,22 +925,24 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
elif reference_doctype != "Journal Entry":
if party_account_currency == company_currency:
if ref_doc.doctype == "Expense Claim":
if ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount = ref_doc.advance_amount
else:
elif ref_doc.doctype == "Employee Advance":
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
if not total_amount:
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
exchange_rate = 1
else:
total_amount = ref_doc.grand_total
exchange_rate = 1
else:
total_amount = ref_doc.grand_total
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc
# or get it based on the posting date of the ref doc.
exchange_rate = ref_doc.get("conversion_rate") or \
get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
@ -932,11 +950,15 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
elif reference_doctype == "Employee Advance":
outstanding_amount = ref_doc.advance_amount - flt(ref_doc.paid_amount)
outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
exchange_rate = 1
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
else:
# Get the exchange rate based on the posting date of the ref doc
# Get the exchange rate based on the posting date of the ref doc.
exchange_rate = get_exchange_rate(party_account_currency,
company_currency, ref_doc.posting_date)
@ -948,102 +970,104 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"bill_no": bill_no
})
def get_amounts_based_on_reference_doctype(reference_doctype, ref_doc, party_account_currency, company_currency, reference_name):
total_amount, outstanding_amount, exchange_rate = None
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
outstanding_amount = ref_doc.get("dunning_amount")
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
return total_amount, outstanding_amount, exchange_rate
def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_currency, company_currency):
total_amount, outstanding_amount, exchange_rate = None
if ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc)
if not total_amount:
total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
party_account_currency, company_currency, ref_doc)
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc
exchange_rate = ref_doc.get("conversion_rate") or \
get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency)
return total_amount, outstanding_amount, exchange_rate, bill_no
def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
return total_amount, exchange_rate
def get_total_amount_exchange_rate_base_on_currency(party_account_currency, company_currency, ref_doc):
exchange_rate = None
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
exchange_rate = 1
else:
total_amount = ref_doc.grand_total
return total_amount, exchange_rate
def get_bill_no_and_update_amounts(reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency):
outstanding_amount, bill_no = None
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
elif reference_doctype == "Employee Advance":
outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
exchange_rate = 1
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
return outstanding_amount, exchange_rate, bill_no
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
if dt in ("Sales Invoice", "Sales Order", "Dunning"):
party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier"
elif dt in ("Expense Claim", "Employee Advance"):
party_type = "Employee"
elif dt in ("Fees"):
party_type = "Student"
# party account
if dt == "Sales Invoice":
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
elif dt == "Purchase Invoice":
party_account = doc.credit_to
elif dt == "Fees":
party_account = doc.receivable_account
elif dt == "Employee Advance":
party_account = doc.advance_account
elif dt == "Expense Claim":
party_account = doc.payable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
if dt not in ("Sales Invoice", "Purchase Invoice"):
party_account_currency = get_account_currency(party_account)
else:
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
# payment type
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
payment_type = "Pay"
# amounts
grand_total = outstanding_amount = 0
if party_amount:
grand_total = outstanding_amount = party_amount
elif dt in ("Sales Invoice", "Purchase Invoice"):
if party_account_currency == doc.company_currency:
grand_total = doc.base_rounded_total or doc.base_grand_total
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
outstanding_amount = doc.grand_total \
- doc.total_amount_reimbursed
elif dt == "Employee Advance":
grand_total = doc.advance_amount
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
elif dt == "Fees":
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
else:
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
outstanding_amount = grand_total - flt(doc.advance_paid)
party_type = set_party_type(dt)
party_account = set_party_account(dt, dn, doc, party_type)
party_account_currency = set_party_account_currency(dt, party_account, doc)
payment_type = set_payment_type(dt, doc)
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc)
# bank or cash
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
account=bank_account)
bank = get_bank_cash_account(doc, bank_account)
if not bank:
bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
account=bank_account)
paid_amount = received_amount = 0
if party_account_currency == bank.account_currency:
paid_amount = received_amount = abs(outstanding_amount)
elif payment_type == "Receive":
paid_amount = abs(outstanding_amount)
if bank_amount:
received_amount = bank_amount
else:
received_amount = paid_amount * doc.get('conversion_rate', 1)
else:
received_amount = abs(outstanding_amount)
if bank_amount:
paid_amount = bank_amount
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.get('conversion_rate', 1)
paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
@ -1115,10 +1139,120 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.setup_party_account_field()
pe.set_missing_values()
if party_account and bank:
pe.set_exchange_rate()
if dt == "Employee Advance":
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
return pe
def get_bank_cash_account(doc, bank_account):
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
account=bank_account)
if not bank:
bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
account=bank_account)
return bank
def set_party_type(dt):
if dt in ("Sales Invoice", "Sales Order", "Dunning"):
party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier"
elif dt in ("Expense Claim", "Employee Advance"):
party_type = "Employee"
elif dt in ("Fees"):
party_type = "Student"
return party_type
def set_party_account(dt, dn, doc, party_type):
if dt == "Sales Invoice":
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
elif dt == "Purchase Invoice":
party_account = doc.credit_to
elif dt == "Fees":
party_account = doc.receivable_account
elif dt == "Employee Advance":
party_account = doc.advance_account
elif dt == "Expense Claim":
party_account = doc.payable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
def set_party_account_currency(dt, party_account, doc):
if dt not in ("Sales Invoice", "Purchase Invoice"):
party_account_currency = get_account_currency(party_account)
else:
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
return party_account_currency
def set_payment_type(dt, doc):
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
payment_type = "Pay"
return payment_type
def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc):
grand_total = outstanding_amount = 0
if party_amount:
grand_total = outstanding_amount = party_amount
elif dt in ("Sales Invoice", "Purchase Invoice"):
if party_account_currency == doc.company_currency:
grand_total = doc.base_rounded_total or doc.base_grand_total
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
outstanding_amount = doc.grand_total \
- doc.total_amount_reimbursed
elif dt == "Employee Advance":
grand_total = flt(doc.advance_amount)
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
if party_account_currency != doc.currency:
grand_total = flt(doc.advance_amount) * flt(doc.exchange_rate)
outstanding_amount = (flt(doc.advance_amount) - flt(doc.paid_amount)) * flt(doc.exchange_rate)
elif dt == "Fees":
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
else:
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
outstanding_amount = grand_total - flt(doc.advance_paid)
return grand_total, outstanding_amount
def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc):
paid_amount = received_amount = 0
if party_account_currency == bank.account_currency:
paid_amount = received_amount = abs(outstanding_amount)
elif payment_type == "Receive":
paid_amount = abs(outstanding_amount)
if bank_amount:
received_amount = bank_amount
else:
received_amount = paid_amount * doc.get('conversion_rate', 1)
if dt == "Employee Advance":
received_amount = paid_amount * doc.get('exchange_rate', 1)
else:
received_amount = abs(outstanding_amount)
if bank_amount:
paid_amount = bank_amount
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.get('conversion_rate', 1)
if dt == "Employee Advance":
paid_amount = received_amount * doc.get('exchange_rate', 1)
return paid_amount, received_amount
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:

View File

@ -1,5 +1,6 @@
{
"actions": [],
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2020-01-24 15:29:29.933693",
@ -1580,7 +1581,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2020-09-28 16:51:24.641755",
"modified": "2020-10-30 13:56:51.056083",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@ -39,6 +39,7 @@ class POSInvoice(SalesInvoice):
self.validate_serialised_or_batched_item()
self.validate_stock_availablility()
self.validate_return_items_qty()
self.validate_non_stock_items()
self.set_status()
self.set_account_for_mode_of_payment()
self.validate_pos()
@ -131,15 +132,19 @@ class POSInvoice(SalesInvoice):
msg = ""
item_code = frappe.bold(d.item_code)
serial_nos = get_serial_nos(d.serial_no)
if serialized and batched and (no_batch_selected or no_serial_selected):
msg = (_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.')
.format(d.idx, item_code))
if serialized and no_serial_selected:
elif serialized and no_serial_selected:
msg = (_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.')
.format(d.idx, item_code))
if batched and no_batch_selected:
elif batched and no_batch_selected:
msg = (_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.')
.format(d.idx, item_code))
elif serialized and not no_serial_selected and len(serial_nos) != d.qty:
msg = (_("Row #{}: You must select {} serial numbers for item {}.").format(d.idx, frappe.bold(cint(d.qty)), item_code))
if msg:
error_msg.append(msg)
@ -169,6 +174,14 @@ class POSInvoice(SalesInvoice):
_("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}")
.format(d.idx, bold_serial_no, bold_return_against)
)
def validate_non_stock_items(self):
for d in self.get("items"):
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
if not is_stock_item:
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format(
d.idx, frappe.bold(d.item_code)
), title=_("Invalid Item"))
def validate_mode_of_payment(self):
if len(self.payments) == 0:
@ -436,4 +449,19 @@ def make_merge_log(invoices):
})
if merge_log.get('pos_invoices'):
return merge_log.as_dict()
return merge_log.as_dict()
def add_return_modes(doc, pos_profile):
def append_payment(payment_mode):
payment = doc.append('payments', {})
payment.default = payment_mode.default
payment.mode_of_payment = payment_mode.parent
payment.account = payment_mode.default_account
payment.type = payment_mode.type
for pos_payment_method in pos_profile.get('payments'):
pos_payment_method = pos_payment_method.as_dict()
mode_of_payment = pos_payment_method.mode_of_payment
if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]:
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
append_payment(payment_mode[0])

View File

@ -35,6 +35,15 @@ frappe.ui.form.on('POS Profile', {
};
});
frm.set_query("taxes_and_charges", function() {
return {
filters: [
['Sales Taxes and Charges Template', 'company', '=', frm.doc.company],
['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
]
};
});
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));

View File

@ -14,7 +14,6 @@
"column_break_9",
"update_stock",
"ignore_pricing_rule",
"hide_unavailable_items",
"warehouse",
"campaign",
"company_address",
@ -23,6 +22,9 @@
"section_break_11",
"payments",
"section_break_14",
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
"item_groups",
"column_break_16",
"customer_groups",
@ -124,7 +126,8 @@
},
{
"fieldname": "section_break_14",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Configuration"
},
{
"description": "Only show Items from these Item Groups",
@ -314,13 +317,25 @@
"fieldname": "hide_unavailable_items",
"fieldtype": "Check",
"label": "Hide Unavailable Items"
},
{
"default": "0",
"fieldname": "hide_images",
"fieldtype": "Check",
"label": "Hide Images"
},
{
"default": "0",
"fieldname": "auto_add_item_to_cart",
"fieldtype": "Check",
"label": "Automatically Add Filtered Item To Cart"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-29 13:18:38.795925",
"modified": "2020-12-20 13:59:28.877572",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@ -70,6 +70,7 @@ def get_items_list(pos_profile, company):
""".format(cond=cond), tuple([company] + args_list), as_dict=1)
def make_pos_profile(**args):
frappe.db.sql("delete from `tabPOS Payment Method`")
frappe.db.sql("delete from `tabPOS Profile`")
args = frappe._dict(args)

View File

@ -42,56 +42,56 @@ frappe.ui.form.on('Pricing Rule', {
<tr><td>
<h4>
<i class="fa fa-hand-right"></i>
${__('Notes')}
{{__('Notes')}}
</h4>
<ul>
<li>
${__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}
{{__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}}
</li>
<li>
${__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}
{{__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}}
</li>
<li>
${__('Discount Percentage can be applied either against a Price List or for all Price List.')}
{{__('Discount Percentage can be applied either against a Price List or for all Price List.')}}
</li>
<li>
${__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}
{{__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}}
</li>
</ul>
</td></tr>
<tr><td>
<h4><i class="fa fa-question-sign"></i>
${__('How Pricing Rule is applied?')}
{{__('How Pricing Rule is applied?')}}
</h4>
<ol>
<li>
${__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}
{{__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}}
</li>
<li>
${__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}
{{__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}}
</li>
<li>
${__('Pricing Rules are further filtered based on quantity.')}
{{__('Pricing Rules are further filtered based on quantity.')}}
</li>
<li>
${__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}
{{__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}}
</li>
<li>
${__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}
{{__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}}
<ul>
<li>
${__('Item Code > Item Group > Brand')}
{{__('Item Code > Item Group > Brand')}}
</li>
<li>
${__('Customer > Customer Group > Territory')}
{{__('Customer > Customer Group > Territory')}}
</li>
<li>
${__('Supplier > Supplier Type')}
{{__('Supplier > Supplier Type')}}
</li>
</ul>
</li>
<li>
${__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}
{{__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}}
</li>
</ol>
</td></tr>

View File

@ -406,6 +406,7 @@
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
"fieldname": "rate",
"fieldtype": "Currency",
@ -469,6 +470,7 @@
"options": "UOM"
},
{
"description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate",
"fieldtype": "Currency",
"label": "Rate"
@ -504,10 +506,10 @@
},
{
"default": "0",
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
"depends_on": "eval:in_list(['Discount Percentage'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
"fieldname": "apply_discount_on_rate",
"fieldtype": "Check",
"label": "Apply Discount on Rate"
"label": "Apply Discount on Discounted Rate"
},
{
"default": "0",
@ -563,7 +565,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2020-08-26 12:24:44.740734",
"modified": "2020-12-04 00:36:24.698219",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@ -60,6 +60,15 @@ class PricingRule(Document):
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
if self.apply_discount_on_rate:
if not self.priority:
throw(_("As the field {0} is enabled, the field {1} is mandatory.")
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
if self.priority and cint(self.priority) == 1:
throw(_("As the field {0} is enabled, the value of the field {1} should be more than 1.")
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected"))
@ -226,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
item_details = frappe._dict({
"doctype": args.doctype,
"has_margin": False,
"name": args.name,
"parent": args.parent,
"parenttype": args.parenttype,
"child_docname": args.get('child_docname'),
"discount_percentage_on_rate": [],
"discount_amount_on_rate": []
"child_docname": args.get('child_docname')
})
if args.ignore_pricing_rule or not args.item_code:
@ -279,6 +287,10 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
else:
get_product_discount_rule(pricing_rule, item_details, args, doc)
if not item_details.get("has_margin"):
item_details.margin_type = None
item_details.margin_rate_or_amount = 0.0
item_details.has_pricing_rule = 1
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
@ -330,20 +342,24 @@ def get_pricing_rule_details(args, pricing_rule):
def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
or (pricing_rule.margin_type == 'Percentage')):
item_details.margin_type = pricing_rule.margin_type
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
else:
item_details.margin_type = None
item_details.margin_rate_or_amount = 0.0
item_details.has_margin = True
if pricing_rule.rate_or_discount == 'Rate':
pricing_rule_rate = 0.0
if pricing_rule.currency == args.currency:
pricing_rule_rate = pricing_rule.rate
if pricing_rule_rate:
# Override already set price list rate (from item price)
# if pricing_rule_rate > 0
item_details.update({
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
})
item_details.update({
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
"discount_percentage": 0.0
})
@ -351,9 +367,9 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.rate_or_discount != apply_on: continue
field = frappe.scrub(apply_on)
if pricing_rule.apply_discount_on_rate:
discount_field = "{0}_on_rate".format(field)
item_details[discount_field].append(pricing_rule.get(field, 0))
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
# Apply discount on discounted rate
item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
else:
if field not in item_details:
item_details.setdefault(field, 0)
@ -361,14 +377,6 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
item_details[field] += (pricing_rule.get(field, 0)
if pricing_rule else args.get(field, 0))
def set_discount_amount(rate, item_details):
for field in ['discount_percentage_on_rate', 'discount_amount_on_rate']:
for d in item_details.get(field):
dis_amount = (rate * d / 100
if field == 'discount_percentage_on_rate' else d)
rate -= dis_amount
item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
get_pricing_rule_items)

View File

@ -385,7 +385,7 @@ class TestPricingRule(unittest.TestCase):
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item 2")
def test_cumulative_pricing_rule(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
test_record = {
@ -429,34 +429,114 @@ class TestPricingRule(unittest.TestCase):
details = get_item_details(args)
self.assertTrue(details)
def test_pricing_rule_for_condition(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
make_pricing_rule(selling=1, margin_type="Percentage", \
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
# Incorrect Customer and Correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 100)
# Correct Customer and Incorrect is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 100)
# Correct Customer and correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 900)
def test_multiple_pricing_rules(self):
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
title="_Test Pricing Rule 1")
make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2,
apply_multiple_pricing_rules=1)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 30)
si.delete()
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
title="_Test Pricing Rule 1")
make_pricing_rule(discount_percentage=10, selling=1, priority=2,
apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 28)
si.delete()
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def test_item_price_with_pricing_rule(self):
item = make_item("Water Flask")
make_item_price("Water Flask", "_Test Price List", 100)
pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Water Flask Rule",
"apply_on": "Item Code",
"items": [{
"item_code": "Water Flask",
}],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 0,
"margin_type": "Percentage",
"margin_rate_or_amount": 2,
"company": "_Test Company"
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()
si = create_sales_invoice(do_not_save=True, item_code="Water Flask")
si.selling_price_list = "_Test Price List"
si.save()
# If rate in Rule is 0, give preference to Item Price if it exists
self.assertEqual(si.items[0].price_list_rate, 100)
self.assertEqual(si.items[0].margin_rate_or_amount, 2)
self.assertEqual(si.items[0].rate_with_margin, 102)
self.assertEqual(si.items[0].rate, 102)
si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
item.delete()
def test_pricing_rule_for_transaction(self):
make_item("Water Flask 1")
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
si = create_sales_invoice(qty=5, do_not_submit=True)
self.assertEquals(len(si.items), 2)
self.assertEquals(si.items[1].rate, 10)
si1 = create_sales_invoice(qty=2, do_not_submit=True)
self.assertEquals(len(si1.items), 1)
for doc in [si, si1]:
doc.delete()
def make_pricing_rule(**args):
args = frappe._dict(args)
@ -468,22 +548,30 @@ def make_pricing_rule(**args):
"applicable_for": args.applicable_for,
"selling": args.selling or 0,
"currency": "USD",
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
"buying": args.buying or 0,
"min_qty": args.min_qty or 0.0,
"max_qty": args.max_qty or 0.0,
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
"discount_percentage": args.discount_percentage or 0.0,
"rate": args.rate or 0.0,
"margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or ''
"condition": args.condition or '',
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
})
for field in ["free_item", "free_qty", "free_item_rate", "priority",
"margin_type", "price_or_product_discount"]:
if args.get(field):
doc.set(field, args.get(field))
apply_on = doc.apply_on.replace(' ', '_').lower()
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
doc.append(child_table.get(doc.apply_on), {
apply_on: args.get(apply_on) or "_Test Item"
})
if doc.apply_on != "Transaction":
doc.append(child_table.get(doc.apply_on), {
apply_on: args.get(apply_on) or "_Test Item"
})
doc.insert(ignore_permissions=True)
if args.get(apply_on) and apply_on != "item_code":

View File

@ -14,9 +14,8 @@ import frappe
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
from frappe import _, throw
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
from frappe import _, bold
from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
class MultiplePricingRuleConflict(frappe.ValidationError): pass
@ -42,6 +41,7 @@ def get_pricing_rules(args, doc=None):
if not pricing_rules: return []
if apply_multiple_pricing_rules(pricing_rules):
pricing_rules = sorted_by_priority(pricing_rules)
for pricing_rule in pricing_rules:
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
if pricing_rule:
@ -53,6 +53,20 @@ def get_pricing_rules(args, doc=None):
return rules
def sorted_by_priority(pricing_rules):
# If more than one pricing rules, then sort by priority
pricing_rules_list = []
pricing_rule_dict = {}
for pricing_rule in pricing_rules:
if not pricing_rule.get("priority"): continue
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
for key in sorted(pricing_rule_dict):
pricing_rules_list.append(pricing_rule_dict.get(key))
return pricing_rules_list or pricing_rules
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = []
if doc:
@ -284,12 +298,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
fieldname = field
if fieldname:
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b>
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
.format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
if fieldname in ['min_amt', 'max_amt']:
msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item.
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
.format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
bold(item_code), bold(args.rule_description)))
frappe.msgprint(msg)
@ -442,6 +457,9 @@ def apply_pricing_rule_on_transaction(doc):
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
doc.total, pricing_rules)
if not pricing_rules:
remove_free_item(doc)
for d in pricing_rules:
if d.price_or_product_discount == 'Price':
if d.apply_discount_on:
@ -465,6 +483,12 @@ def apply_pricing_rule_on_transaction(doc):
get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values()
doc.calculate_taxes_and_totals()
def remove_free_item(doc):
for d in doc.items:
if d.is_free_item:
doc.remove(d)
def get_applied_pricing_rules(pricing_rules):
if pricing_rules:
@ -477,7 +501,7 @@ def get_applied_pricing_rules(pricing_rules):
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = pricing_rule.free_item
if pricing_rule.same_item:
if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
free_item = item_details.item_code or args.item_code
if not free_item:

View File

@ -21,7 +21,7 @@ class TestProcessDeferredAccounting(unittest.TestCase):
item.no_of_months = 12
item.save()
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
si = create_sales_invoice(item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"

View File

@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
frm.refresh_field('customers');
}
else{
frappe.msgprint('No Customers found with selected options.');
frappe.throw('No Customers found with selected options.');
}
}
}

View File

@ -126,9 +126,11 @@ def get_customers_based_on_sales_person(sales_person):
sales_person_records = frappe._dict()
for d in records:
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
if sales_person_records.get('Customer'):
return frappe.get_list('Customer', fields=['name', 'email_id'], \
filters=[['name', 'in', list(sales_person_records['Customer'])]])
return customers
else:
return []
def get_recipients_and_cc(customer, doc):
recipients = []

View File

@ -15,6 +15,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
return (doc.qty<=doc.received_qty) ? "green" : "orange";
});
}
this.frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
},
onload: function() {
this._super();
@ -99,6 +109,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
target: me.frm,
setters: {
supplier: me.frm.doc.supplier || undefined,
schedule_date: undefined
},
get_query_filters: {
docstatus: 1,
@ -107,16 +118,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
company: me.frm.doc.company
}
})
}, __("Get items from"));
}, __("Get Items From"));
this.frm.add_custom_button(__('Purchase Receipt'), function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
source_doctype: "Purchase Receipt",
target: me.frm,
date_field: "posting_date",
setters: {
supplier: me.frm.doc.supplier || undefined,
posting_date: undefined
},
get_query_filters: {
docstatus: 1,
@ -125,7 +136,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
is_return: 0
}
})
}, __("Get items from"));
}, __("Get Items From"));
}
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");

View File

@ -126,6 +126,7 @@
"write_off_cost_center",
"advances_section",
"allocate_advances_automatically",
"adjust_advance_taxes",
"get_advances",
"advances",
"payment_schedule_section",
@ -151,9 +152,11 @@
"is_opening",
"against_expense_account",
"column_break_63",
"unrealized_profit_loss_account",
"status",
"inter_company_invoice_reference",
"is_internal_supplier",
"represents_company",
"remarks",
"subscription_section",
"from_date",
@ -1222,7 +1225,7 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1
},
{
@ -1329,12 +1332,37 @@
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"default": "0",
"description": "Taxes paid while advance payment will be adjusted against this invoice",
"fieldname": "adjust_advance_taxes",
"fieldtype": "Check",
"label": "Adjust Advance Taxes"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Company which internal supplier represents",
"fetch_from": "supplier.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"modified": "2020-09-21 12:22:09.164068",
"links": [],
"modified": "2020-12-11 12:46:12.796378",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@ -1396,4 +1424,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}

View File

@ -147,6 +147,11 @@ class PurchaseInvoice(BuyingController):
throw(_("Conversion rate cannot be 0 or 1"))
def validate_credit_to_acc(self):
if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
if not self.credit_to:
self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
account = frappe.db.get_value("Account", self.credit_to,
["account_type", "report_type", "account_currency"], as_dict=True)
@ -201,8 +206,8 @@ class PurchaseInvoice(BuyingController):
["Purchase Receipt", "purchase_receipt", "pr_detail"]
])
def validate_warehouse(self):
if self.update_stock:
def validate_warehouse(self, for_validate=True):
if self.update_stock and for_validate:
for d in self.get('items'):
if not d.warehouse:
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
@ -228,7 +233,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock:
self.validate_item_code()
self.validate_warehouse()
self.validate_warehouse(for_validate)
if auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
@ -405,10 +410,13 @@ class PurchaseInvoice(BuyingController):
# this sequence because outstanding may get -negative
self.make_gl_entries()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
def make_gl_entries(self, gl_entries=None):
def make_gl_entries(self, gl_entries=None, from_repost=False):
if not gl_entries:
gl_entries = self.get_gl_entries()
@ -416,7 +424,7 @@ class PurchaseInvoice(BuyingController):
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
if self.docstatus == 1:
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@ -431,9 +439,11 @@ class PurchaseInvoice(BuyingController):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if self.auto_accounting_for_stock:
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
else:
self.stock_received_but_not_billed = None
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
self.expenses_included_in_valuation = None
self.negative_expense_to_be_booked = 0.0
gl_entries = []
@ -444,15 +454,15 @@ class PurchaseInvoice(BuyingController):
self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries)
self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
return gl_entries
def check_asset_cwip_enabled(self):
@ -469,31 +479,30 @@ class PurchaseInvoice(BuyingController):
# because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total:
# Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
gl_entries.append(
self.get_gl_dict({
"account": self.credit_to,
"party_type": "Supplier",
"party": self.supplier,
"due_date": self.due_date,
"against": self.against_expense_account,
"credit": grand_total_in_company_currency,
"credit_in_account_currency": grand_total_in_company_currency \
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"project": self.project,
"cost_center": self.cost_center
}, self.party_account_currency, item=self)
)
if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
gl_entries.append(
self.get_gl_dict({
"account": self.credit_to,
"party_type": "Supplier",
"party": self.supplier,
"due_date": self.due_date,
"against": self.against_expense_account,
"credit": grand_total_in_company_currency,
"credit_in_account_currency": grand_total_in_company_currency \
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"project": self.project,
"cost_center": self.cost_center
}, self.party_account_currency, item=self)
)
def make_item_gl_entries(self, gl_entries):
# item gl entries
stock_items = self.get_stock_items()
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
@ -521,7 +530,6 @@ class PurchaseInvoice(BuyingController):
item, voucher_wise_stock_value, account_currency)
if item.from_warehouse:
gl_entries.append(self.get_gl_dict({
"account": warehouse_account[item.warehouse]['account'],
"against": warehouse_account[item.from_warehouse]["account"],
@ -541,16 +549,18 @@ class PurchaseInvoice(BuyingController):
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
# Do not book expense for transfer within same company transfer
if not self.is_internal_transfer():
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
else:
gl_entries.append(
@ -827,7 +837,8 @@ class PurchaseInvoice(BuyingController):
}, account_currency, item=tax)
)
# accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0)
@ -871,8 +882,19 @@ class PurchaseInvoice(BuyingController):
"against": self.supplier,
"credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock"
}, item=tax)
)
}, item=tax))
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.supplier,
"credit": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries
@ -977,11 +999,15 @@ class PurchaseInvoice(BuyingController):
self.delete_auto_created_batches()
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
self.update_project()
frappe.db.set(self, 'status', 'Cancelled')
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
def update_project(self):
project_list = []
@ -1032,7 +1058,9 @@ class PurchaseInvoice(BuyingController):
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
for pr in set(updated_pr):
frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
pr_doc = frappe.get_doc("Purchase Receipt", pr)
update_billing_percentage(pr_doc, update_modified=update_modified)
def on_recurring(self, reference_doc, auto_repeat_doc):
self.due_date = None
@ -1088,7 +1116,9 @@ class PurchaseInvoice(BuyingController):
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid"

View File

@ -4,23 +4,25 @@
// render
frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"],
"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
get_indicator: function(doc) {
if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"];
} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
return [__("Temporarily on Hold"), "darkgrey"];
} else if(frappe.datetime.get_diff(doc.due_date) < 0) {
} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
}
} else if(cint(doc.is_return)) {
} else if (cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"];
}
}

View File

@ -9,8 +9,7 @@ import frappe.model
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from frappe.utils import cint, flt, today, nowdate, add_days, getdate
import frappe.defaults
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
test_records as pr_test_records, make_purchase_receipt, get_taxes
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt, get_taxes
from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
@ -33,13 +32,10 @@ class TestPurchaseInvoice(unittest.TestCase):
def test_gl_entries_without_perpetual_inventory(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
wrapper = frappe.copy_doc(test_records[0])
set_perpetual_inventory(0, wrapper.company)
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(wrapper.company)))
wrapper.insert()
wrapper.submit()
wrapper.load_from_db()
dl = wrapper
pi = frappe.copy_doc(test_records[0])
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(pi.company)))
pi.insert()
pi.submit()
expected_gl_entries = {
"_Test Payable - _TC": [0, 1512.0],
@ -54,12 +50,16 @@ class TestPurchaseInvoice(unittest.TestCase):
"Round Off - _TC": [0, 0.3]
}
gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type = 'Purchase Invoice' and voucher_no = %s""", dl.name, as_dict=1)
where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name, as_dict=1)
for d in gl_entries:
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
def test_gl_entries_with_perpetual_inventory(self):
pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10)
pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
warehouse= "Stores - TCP1", cost_center = "Main - TCP1",
expense_account ="_Test Account Cost for Goods Sold - TCP1",
get_taxes_and_charges=True, qty=10)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
self.check_gle_for_pi(pi.name)
@ -198,8 +198,6 @@ class TestPurchaseInvoice(unittest.TestCase):
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
for d in pi.items:
@ -247,17 +245,11 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
def test_gl_entries_with_aia_for_non_stock_items(self):
pi = frappe.copy_doc(test_records[1])
set_perpetual_inventory(1, pi.company)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
pi.get("items")[0].item_code = "_Test Non Stock Item"
pi.get("items")[0].expense_account = "_Test Account Cost for Goods Sold - _TC"
pi.get("taxes").pop(0)
pi.get("taxes").pop(1)
pi.insert()
pi.submit()
pi.load_from_db()
def test_gl_entries_for_non_stock_items_with_perpetual_inventory(self):
pi = make_purchase_invoice(item_code = "_Test Non Stock Item",
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
self.assertTrue(pi.status, "Unpaid")
gl_entries = frappe.db.sql("""select account, debit, credit
@ -265,17 +257,15 @@ class TestPurchaseInvoice(unittest.TestCase):
order by account asc""", pi.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = sorted([
["_Test Payable - _TC", 0, 620],
["_Test Account Cost for Goods Sold - _TC", 500.0, 0],
["_Test Account VAT - _TC", 120.0, 0],
])
expected_values = [
["_Test Account Cost for Goods Sold - TCP1", 250.0, 0],
["Creditors - TCP1", 0, 250]
]
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[i][0], gle.account)
self.assertEqual(expected_values[i][1], gle.debit)
self.assertEqual(expected_values[i][2], gle.credit)
set_perpetual_inventory(0, pi.company)
def test_purchase_invoice_calculation(self):
pi = frappe.copy_doc(test_records[0])
@ -457,12 +447,13 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.cancel()
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
def test_return_purchase_invoice(self):
set_perpetual_inventory()
def test_return_purchase_invoice_with_perpetual_inventory(self):
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
pi = make_purchase_invoice()
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2)
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
# check gl entries for return
@ -473,19 +464,15 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertTrue(gl_entries)
expected_values = {
"Creditors - _TC": [100.0, 0.0],
"Stock Received But Not Billed - _TC": [0.0, 100.0],
"Creditors - TCP1": [100.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 100.0],
}
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
set_perpetual_inventory(0)
def test_multi_currency_gle(self):
set_perpetual_inventory(0)
pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
currency="USD", conversion_rate=50)
@ -640,10 +627,9 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(len(pi.get("supplied_items")), 2)
rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
def test_rejected_serial_no(self):
set_perpetual_inventory(0)
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
rejected_qty=1, rate=500, update_stock=1,
rejected_warehouse = "_Test Rejected Warehouse - _TC")
@ -998,7 +984,7 @@ def make_purchase_invoice(**args):
'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
"conversion_factor": 1.0,
"serial_no": args.serial_no,
"stock_uom": "_Test UOM",
"stock_uom": args.uom or "_Test UOM",
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
@ -1040,7 +1026,8 @@ def make_purchase_invoice_against_cost_center(**args):
pi.is_return = args.is_return
pi.credit_to = args.return_against or "Creditors - _TC"
pi.is_subcontracted = args.is_subcontracted or "No"
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
if args.supplier_warehouse:
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item",

View File

@ -1,92 +1,38 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-07-27 17:24:24.956896",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"actions": [],
"creation": "2016-07-27 17:24:24.956896",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"account"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
"fieldname": "default_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Default Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account",
"options": "Account"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-09-02 07:49:06.567389",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Salary Component Account",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
],
"istable": 1,
"links": [],
"modified": "2020-10-18 17:57:57.110257",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Salary Component Account",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -199,7 +199,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
company: me.frm.doc.company
}
})
}, __("Get items from"));
}, __("Get Items From"));
},
quotation_btn: function() {
@ -223,7 +223,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
company: me.frm.doc.company
}
})
}, __("Get items from"));
}, __("Get Items From"));
},
delivery_note_btn: function() {
@ -251,7 +251,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
};
}
});
}, __("Get items from"));
}, __("Get Items From"));
},
tc_name: function() {
@ -580,6 +580,16 @@ frappe.ui.form.on('Sales Invoice', {
};
});
frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return',
@ -812,10 +822,10 @@ frappe.ui.form.on('Sales Invoice', {
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
frm.add_custom_button(__('Healthcare Services'), function() {
get_healthcare_services_to_invoice(frm);
},"Get items from");
},"Get Items From");
frm.add_custom_button(__('Prescriptions'), function() {
get_drugs_to_invoice(frm);
},"Get items from");
},"Get Items From");
}
}
else {
@ -1080,7 +1090,7 @@ var get_drugs_to_invoice = function(frm) {
description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.',
get_query: function(doc) {
return {
filters: {
filters: {
patient: dialog.get_value("patient"),
company: frm.doc.company,
docstatus: 1

View File

@ -157,6 +157,7 @@
"more_information",
"inter_company_invoice_reference",
"is_internal_customer",
"represents_company",
"customer_group",
"campaign",
"is_discounted",
@ -170,6 +171,7 @@
"c_form_applicable",
"c_form_no",
"column_break8",
"unrealized_profit_loss_account",
"remarks",
"sales_team_section_break",
"sales_partner",
@ -1654,7 +1656,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1,
"read_only": 1
},
@ -1949,13 +1951,31 @@
"fieldtype": "Data",
"label": "Company Tax ID",
"read_only": 1
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Company which internal customer represents",
"fetch_from": "customer.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
"links": [],
"modified": "2020-10-09 15:59:57.544736",
"modified": "2020-12-11 12:48:31.769958",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -179,6 +179,9 @@ class SalesInvoice(SellingController):
# this sequence because outstanding may get -ve
self.make_gl_entries()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
@ -258,6 +261,10 @@ class SalesInvoice(SellingController):
self.update_stock_ledger()
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
frappe.db.set(self, 'status', 'Cancelled')
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
@ -279,7 +286,7 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_cancel")
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
def update_status_updater_args(self):
if cint(self.update_stock):
@ -405,6 +412,8 @@ class SalesInvoice(SellingController):
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
self.pos_profile = pos_profile.get('name')
pos = {}
@ -472,6 +481,11 @@ class SalesInvoice(SellingController):
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
def validate_debit_to_acc(self):
if not self.debit_to:
self.debit_to = get_party_account("Customer", self.customer, self.company)
if not self.debit_to:
self.raise_missing_debit_credit_account_error("Customer", self.customer)
account = frappe.get_cached_value("Account", self.debit_to,
["account_type", "report_type", "account_currency"], as_dict=True)
@ -715,22 +729,20 @@ class SalesInvoice(SellingController):
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def make_gl_entries(self, gl_entries=None):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
def make_gl_entries(self, gl_entries=None, from_repost=False):
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if not gl_entries:
gl_entries = self.get_gl_entries()
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
# if POS and amount is written off, updating outstanding amt after posting all gl entries
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
cint(self.redeem_loyalty_points)) else "Yes"
if self.docstatus == 1:
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@ -751,6 +763,7 @@ class SalesInvoice(SellingController):
self.make_customer_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries)
@ -770,7 +783,7 @@ class SalesInvoice(SellingController):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total:
if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
@ -809,6 +822,18 @@ class SalesInvoice(SellingController):
}, account_currency, item=tax)
)
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.customer,
"debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_item_gl_entries(self, gl_entries):
# income account gl entries
for item in self.get("items"):
@ -831,22 +856,24 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None)
else:
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
# Do not book income for transfer within same company
if not self.is_internal_transfer():
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict({
"account": income_account,
"against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict({
"account": income_account,
"against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
# expense account gl entries
if cint(self.update_stock) and \
@ -1258,7 +1285,9 @@ class SalesInvoice(SellingController):
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
@ -1401,6 +1430,7 @@ def make_delivery_note(source_name, target_doc=None):
def set_missing_values(source, target):
target.ignore_pricing_rule = 1
target.run_method("set_missing_values")
target.run_method("set_po_nos")
target.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent):
@ -1522,9 +1552,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
source_document_warehouse_field = 'target_warehouse'
target_document_warehouse_field = 'from_warehouse'
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
source_document_warehouse_field = 'from_warehouse'
target_document_warehouse_field = 'target_warehouse'
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
@ -1551,6 +1585,26 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if currency:
target_doc.currency = currency
item_field_map = {
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
if source_doc.get('update_stock'):
item_field_map.update({
'field_map': {
source_document_warehouse_field: target_document_warehouse_field,
'batch_no': 'batch_no',
'serial_no': 'serial_no'
}
})
doclist = get_mapped_doc(doctype, source_name, {
doctype: {
"doctype": target_doctype,
@ -1559,15 +1613,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"taxes_and_charges"
]
},
doctype +" Item": {
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
doctype +" Item": item_field_map
}, target_doc, set_missing_values)

View File

@ -14,8 +14,8 @@ frappe.listview_settings['Sales Invoice'] = {
"Credit Note Issued": "darkgrey",
"Unpaid and Discounted": "orange",
"Overdue and Discounted": "red",
"Overdue": "red"
"Overdue": "red",
"Internal Transfer": "darkgrey"
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
},

View File

@ -17,7 +17,8 @@
"description": "138-CMS Shoe",
"doctype": "Sales Invoice Item",
"income_account": "Sales - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"item_code": "138-CMS Shoe",
"item_name": "138-CMS Shoe",
"parentfield": "items",
"qty": 1.0,

View File

@ -10,7 +10,6 @@ from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname
@ -659,7 +658,6 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_invoice_gl_entry_without_perpetual_inventory(self):
si = frappe.copy_doc(test_records[1])
set_perpetual_inventory(0, si.company)
si.insert()
si.submit()
@ -690,7 +688,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
make_pos_profile()
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
@ -746,7 +745,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(pos_return.get('payments')[0].amount, -1000)
def test_pos_change_amount(self):
make_pos_profile()
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
@ -813,7 +813,6 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
def test_pos_si_without_payment(self):
set_perpetual_inventory()
make_pos_profile()
pos = copy.deepcopy(test_records[1])
@ -827,9 +826,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.ValidationError, si.submit)
def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self):
set_perpetual_inventory()
si = frappe.get_doc(test_records[1])
si = create_sales_invoice(company="_Test Company with perpetual inventory", debit_to = "Debtors - TCP1",
income_account="Sales - TCP1", cost_center = "Main - TCP1", do_not_save=True)
si.get("items")[0].item_code = None
si.insert()
si.submit()
@ -840,24 +838,16 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [
[si.debit_to, 630.0, 0.0],
[test_records[1]["items"][0]["income_account"], 0.0, 500.0],
[test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
[test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
["Debtors - TCP1", 100.0, 0.0],
["Sales - TCP1", 0.0, 100.0]
])
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
set_perpetual_inventory(0)
def test_sales_invoice_gl_entry_with_perpetual_inventory_non_stock_item(self):
set_perpetual_inventory()
si = frappe.get_doc(test_records[1])
si.get("items")[0].item_code = "_Test Non Stock Item"
si.insert()
si.submit()
si = create_sales_invoice(item="_Test Non Stock Item")
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
@ -865,17 +855,14 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [
[si.debit_to, 630.0, 0.0],
[test_records[1]["items"][0]["income_account"], 0.0, 500.0],
[test_records[1]["taxes"][0]["account_head"], 0.0, 80.0],
[test_records[1]["taxes"][1]["account_head"], 0.0, 50.0],
[si.debit_to, 100.0, 0.0],
[test_records[1]["items"][0]["income_account"], 0.0, 100.0]
])
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
set_perpetual_inventory(0)
def _insert_purchase_receipt(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import test_records \
@ -1104,7 +1091,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.grand_total, 859.43)
def test_multi_currency_gle(self):
set_perpetual_inventory(0)
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
@ -1571,7 +1557,7 @@ class TestSalesInvoice(unittest.TestCase):
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_sales_invoice_with_project_link(self):
from erpnext.projects.doctype.project.test_project import make_project
@ -1605,9 +1591,9 @@ class TestSalesInvoice(unittest.TestCase):
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", sales_invoice.name, as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
@ -1774,11 +1760,70 @@ class TestSalesInvoice(unittest.TestCase):
si.submit()
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
target_doc.items[0].update({
"expense_account": "Cost of Goods Sold - _TC1",
"cost_center": "Main - _TC1",
"warehouse": "Stores - _TC1"
})
target_doc.submit()
self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
# def test_internal_transfer_gl_entry(self):
# ## Create internal transfer account
# account = create_account(account_name="Unrealized Profit",
# parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
# frappe.db.set_value('Company', '_Test Company with perpetual inventory',
# 'unrealized_profit_loss_account', account)
# customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory")
# create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory")
# si = create_sales_invoice(
# company = "_Test Company with perpetual inventory",
# customer = customer,
# debit_to = "Debtors - TCP1",
# warehouse = "Stores - TCP1",
# income_account = "Sales - TCP1",
# expense_account = "Cost of Goods Sold - TCP1",
# cost_center = "Main - TCP1",
# currency = "INR",
# do_not_save = 1
# )
# si.selling_price_list = "_Test Price List Rest of the World"
# si.update_stock = 1
# si.items[0].target_warehouse = 'Work In Progress - TCP1'
# add_taxes(si)
# si.save()
# si.submit()
# target_doc = make_inter_company_transaction("Sales Invoice", si.name)
# target_doc.company = '_Test Company with perpetual inventory'
# target_doc.items[0].warehouse = 'Finished Goods - TCP1'
# add_taxes(target_doc)
# target_doc.save()
# target_doc.submit()
# si_gl_entries = [
# ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
# ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
# ]
# check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
# pi_gl_entries = [
# ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
# ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
# ]
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
def test_eway_bill_json(self):
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({
@ -1935,14 +1980,19 @@ def create_sales_invoice(**args):
si.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
"item_name": args.item_name or "_Test Item",
"description": args.description or "_Test Item",
"gst_hsn_code": "999800",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 1,
"uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos",
"rate": args.rate if args.get("rate") is not None else 100,
"income_account": args.income_account or "Sales - _TC",
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no
"serial_no": args.serial_no,
"conversion_factor": 1
})
if not args.do_not_save:
@ -2037,4 +2087,57 @@ def get_taxes_and_charges():
"parentfield": "taxes",
"rate": 2,
"row_id": 1
}]
}]
def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": customer_name,
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory",
"is_internal_customer": 1,
"represents_company": represents_company
})
customer.append("companies", {
"company": allowed_to_interact_with
})
customer.insert()
customer_name = customer.name
else:
customer_name = frappe.db.get_value("Customer", customer_name)
return customer_name
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc({
"supplier_group": "_Test Supplier Group",
"supplier_name": supplier_name,
"doctype": "Supplier",
"is_internal_supplier": 1,
"represents_company": represents_company
})
supplier.append("companies", {
"company": allowed_to_interact_with
})
supplier.insert()
supplier_name = supplier.name
else:
supplier_name = frappe.db.exists("Supplier", supplier_name)
return supplier_name
def add_taxes(doc):
doc.append('taxes', {
'account_head': '_Test Account Excise Duty - TCP1',
"charge_type": "On Net Total",
"cost_center": "Main - TCP1",
"description": "Excise Duty",
"rate": 12
})

View File

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "hash",
"creation": "2013-06-04 11:02:19",
"doctype": "DocType",
@ -51,6 +52,7 @@
"column_break_24",
"base_net_rate",
"base_net_amount",
"incoming_rate",
"drop_ship",
"delivered_by_supplier",
"accounting",
@ -792,20 +794,28 @@
"options": "Project"
},
{
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "incoming_rate",
"fieldtype": "Currency",
"label": "Incoming Rate",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-08-20 11:24:41.749986",
"modified": "2020-09-23 19:59:04.879322",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@ -140,9 +140,9 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai
else:
tds_amount = _get_tds(net_total, tax_details.rate)
else:
supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
fields = ['sum(net_amount)'],
filters = {'parent': ('in', vouchers), 'docstatus': 1}, as_list=1)
supplier_credit_amount = frappe.get_all('Purchase Invoice',
fields = ['sum(net_total)'],
filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1)
supplier_credit_amount = (supplier_credit_amount[0][0]
if supplier_credit_amount and supplier_credit_amount[0][0] else 0)

View File

@ -7,6 +7,7 @@ import frappe
import unittest
from frappe.utils import today
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
test_dependencies = ["Supplier Group"]
@ -101,6 +102,32 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in invoices:
d.cancel()
def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
invoices = []
doc = create_supplier(supplier_name = "Test TDS Supplier ABC",
tax_withholding_category="Single Threshold TDS")
supplier = doc.name
pi = create_purchase_invoice(supplier=supplier)
pi.submit()
invoices.append(pi)
# TDS not applied
pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True)
pi.submit()
invoices.append(pi)
pi = create_purchase_invoice(supplier=supplier)
pi.submit()
invoices.append(pi)
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
self.assertEqual(pi.grand_total, 8000)
# delete invoices to avoid clashing
for d in invoices:
d.cancel()
def create_purchase_invoice(**args):
# return sales invoice doc object
item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
@ -109,7 +136,7 @@ def create_purchase_invoice(**args):
pi = frappe.get_doc({
"doctype": "Purchase Invoice",
"posting_date": today(),
"apply_tds": 1,
"apply_tds": 0 if args.do_not_apply_tds else 1,
"supplier": args.supplier,
"company": '_Test Company',
"taxes_and_charges": "",

View File

@ -15,13 +15,13 @@ class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
if gl_map:
if not cancel:
validate_accounting_period(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding)
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
else:
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
else:
@ -119,8 +119,9 @@ def check_if_in_list(gle, gl_map, dimensions=None):
if same_head:
return e
def save_entries(gl_map, adv_adj, update_outstanding):
validate_cwip_accounts(gl_map)
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map)
@ -128,24 +129,24 @@ def save_entries(gl_map, adv_adj, update_outstanding):
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding)
make_entry(entry, adv_adj, update_outstanding, from_repost)
# check against budget
validate_expense_against_budget(entry)
validate_account_for_perpetual_inventory(gl_map)
if not from_repost:
validate_account_for_perpetual_inventory(gl_map)
def make_entry(args, adv_adj, update_outstanding):
def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle = frappe.new_doc("GL Entry")
gle.update(args)
gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost
gle.insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding)
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
gle.submit()
# check against budget
validate_expense_against_budget(args)
if not from_repost:
validate_expense_against_budget(args)
def validate_account_for_perpetual_inventory(gl_map):
if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
@ -161,7 +162,7 @@ def validate_account_for_perpetual_inventory(gl_map):
# Always use current date to get stock and account balance as there can future entries for
# other items
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
getdate(), gl_map[0].company)
gl_map[0].posting_date, gl_map[0].company)
if gl_map[0].voucher_type=="Journal Entry":
# In case of Journal Entry, there are no corresponding SL entries,
@ -176,8 +177,8 @@ def validate_account_for_perpetual_inventory(gl_map):
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
diff = flt(stock_bal - account_bal, precision)
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
stock_bal, account_bal, frappe.bold(account))
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses on {3}.").format(
stock_bal, account_bal, frappe.bold(account), gl_map[0].posting_date)
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
@ -185,9 +186,10 @@ def validate_account_for_perpetual_inventory(gl_map):
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
journal_entry_args = {
'accounts':[
{'account': account, db_or_cr_warehouse_account : abs(diff)},
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
'accounts':[
{'account': account, db_or_cr_warehouse_account : abs(diff)},
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff)}
]
}
frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),

View File

@ -156,7 +156,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
setup_transactions_dom() {
const me = this;
me.parent.$main_section.append(`<div class="transactions-table"></div>`)
me.parent.$main_section.append('<div class="transactions-table"></div>');
}
create_datatable() {
@ -167,9 +167,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
})
}
catch(err) {
let msg = __(`Your file could not be processed by ERPNext.
<br>It should be a standard CSV or XLSX file.
<br>The headers should be in the first row.`)
let msg = __("Your file could not be processed. It should be a standard CSV or XLSX file with headers in the first row.");
frappe.throw(msg)
}

View File

@ -59,7 +59,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
billing_address=party_address, shipping_address=shipping_address)
if fetch_payment_terms_template:
party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)
if not party_details.get("currency"):
party_details["currency"] = currency
@ -315,7 +315,7 @@ def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
due_date = None
if (bill_date or posting_date) and party:
due_date = bill_date or posting_date
template_name = get_pyt_term_template(party, party_type, company)
template_name = get_payment_terms_template(party, party_type, company)
if template_name:
due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")
@ -422,7 +422,7 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup
@frappe.whitelist()
def get_pyt_term_template(party_name, party_type, company=None):
def get_payment_terms_template(party_name, party_type, company=None):
if party_type not in ("Customer", "Supplier"):
return
template = None

View File

@ -19,7 +19,7 @@
</div>
<div class="col-xs-6">
<table>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
</table>
</div>
</div>

View File

@ -17,7 +17,7 @@
</div>
<div class="col-xs-6">
<table>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
</table>
</div>
</div>

View File

@ -5,7 +5,7 @@
{{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }}
{%- for label, value in (
(_("Received On"), frappe.utils.formatdate(doc.voucher_date)),
(_("Received On"), frappe.utils.format_date(doc.voucher_date)),
(_("Received From"), doc.pay_to_recd_from),
(_("Amount"), "<strong>" + doc.get_formatted("total_amount") + "</strong><br>" + (doc.total_amount_in_words or "") + "<br>"),
(_("Remarks"), doc.remark)

View File

@ -8,7 +8,7 @@
<div class="col-xs-6">
<table>
<tr><td><strong>Supplier Name: </strong></td><td>{{ doc.supplier }}</td></tr>
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.formatdate(doc.due_date) }}</td></tr>
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.format_date(doc.due_date) }}</td></tr>
<tr><td><strong>Address: </strong></td><td>{{doc.address_display}}</td></tr>
<tr><td><strong>Contact: </strong></td><td>{{doc.contact_display}}</td></tr>
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
@ -17,7 +17,7 @@
<div class="col-xs-6">
<table>
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
</table>
</div>
</div>

View File

@ -8,7 +8,7 @@
<div class="col-xs-6">
<table>
<tr><td><strong>Customer Name: </strong></td><td>{{ doc.customer }}</td></tr>
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.formatdate(doc.due_date) }}</td></tr>
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.format_date(doc.due_date) }}</td></tr>
<tr><td><strong>Address: </strong></td><td>{{doc.address_display}}</td></tr>
<tr><td><strong>Contact: </strong></td><td>{{doc.contact_display}}</td></tr>
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
@ -17,7 +17,7 @@
<div class="col-xs-6">
<table>
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
</table>
</div>
</div>

View File

@ -42,11 +42,13 @@
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label;
var range2 = report.columns[12].label;
var range3 = report.columns[13].label;
var range4 = report.columns[14].label;
var range5 = report.columns[15].label;
var start = filters.based_on_payment_terms ? 13 : 11;
var range1 = report.columns[start].label;
var range2 = report.columns[start+1].label;
var range3 = report.columns[start+2].label;
var range4 = report.columns[start+3].label;
var range5 = report.columns[start+4].label;
var range6 = report.columns[start+5].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
@ -70,20 +72,34 @@
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
<td class="text-right">
{%= format_number(balance_row["age"], null, 2) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td>
</td>
</tr>
<td>{%= __("Future Payments") %}</td>
<td></td>
@ -91,6 +107,7 @@
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td>
@ -101,6 +118,7 @@
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
@ -218,15 +236,15 @@
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td>
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
@ -234,8 +252,8 @@
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
@ -256,10 +274,10 @@
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
{% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
</tr>

View File

@ -160,6 +160,8 @@ class ReceivablePayableReport(object):
else:
# advance / unlinked payment or other adjustment
row.paid -= gle_balance
if gle.cost_center:
row.cost_center = str(gle.cost_center)
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
@ -210,7 +212,6 @@ class ReceivablePayableReport(object):
for key, row in self.voucher_balance.items():
row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision)
row.invoice_grand_total = row.invoiced
if abs(row.outstanding) > 1.0/10 ** self.currency_precision:
# non-zero oustanding, we must consider this row
@ -577,7 +578,7 @@ class ReceivablePayableReport(object):
self.gl_entries = frappe.db.sql("""
select
name, posting_date, account, party_type, party, voucher_type, voucher_no,
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
against_voucher_type, against_voucher, account_currency, remarks, {0}
from
`tabGL Entry`
@ -741,6 +742,7 @@ class ReceivablePayableReport(object):
self.add_column(_("Customer Contact"), fieldname='customer_primary_contact',
fieldtype='Link', options='Contact')
self.add_column(label=_('Cost Center'), fieldname='cost_center', fieldtype='Data')
self.add_column(label=_('Voucher Type'), fieldname='voucher_type', fieldtype='Data')
self.add_column(label=_('Voucher No'), fieldname='voucher_no', fieldtype='Dynamic Link',
options='voucher_type', width=180)

View File

@ -14,11 +14,93 @@ def execute(filters=None):
def get_column():
return [
_("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
{
"label": _("Delivery Note"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Delivery Note",
"width": 160
},
{
"label": _("Date"),
"fieldname": "date",
"fieldtype": "Date",
"width": 100
},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"width": 120
},
{
"label": _("Customer Name"),
"fieldname": "customer_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 120
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Billed Amount"),
"fieldname": "billed_amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Returned Amount"),
"fieldname": "returned_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Pending Amount"),
"fieldname": "pending_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Item Name"),
"fieldname": "item_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Description"),
"fieldname": "description",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
"width": 120
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120
}
]
def get_args():

View File

@ -8,6 +8,7 @@ from frappe.utils import flt
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions)
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
def execute(filters=None):
return _execute(filters)
@ -22,7 +23,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
aii_account_map = get_aii_accounts()
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges")
doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
@ -34,10 +35,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice')
item_details = get_item_details()
for d in item_list:
if not d.stock_qty:
continue
item_record = item_details.get(d.item_code)
purchase_receipt = None
if d.purchase_receipt:
purchase_receipt = d.purchase_receipt
@ -48,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'item_name': item_record.item_name,
'item_group': item_record.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
@ -81,10 +86,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
})
total_tax += flt(item_tax.get("tax_amount"))
total_tax += flt(item_tax.get('tax_amount'))
row.update({
'total_tax': total_tax,
@ -309,8 +314,8 @@ def get_items(filters, additional_query_columns):
select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@ -8,6 +8,7 @@ from frappe.utils import flt, cstr
from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
def execute(filters=None):
return _execute(filters)
@ -16,7 +17,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not filters: filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
item_list = get_items(filters, additional_query_columns)
if item_list:
@ -33,7 +34,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice')
customer_details = get_customer_details()
item_details = get_item_details()
for d in item_list:
customer_record = customer_details.get(d.customer)
item_record = item_details.get(d.item_code)
delivery_note = None
if d.delivery_note:
delivery_note = d.delivery_note
@ -45,14 +52,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'item_name': item_record.item_name,
'item_group': item_record.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
'customer': d.customer,
'customer_name': d.customer_name,
'customer_group': d.customer_group,
'customer_name': customer_record.customer_name,
'customer_group': customer_record.customer_group,
}
if additional_query_columns:
@ -90,10 +97,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
})
total_tax += flt(item_tax.get("tax_amount"))
total_tax += flt(item_tax.get('tax_amount'))
row.update({
'total_tax': total_tax,
@ -226,7 +233,7 @@ def get_columns(additional_table_columns, filters):
if filters.get('group_by') != 'Territory':
columns.extend([
{
'label': _("Territory"),
'label': _('Territory'),
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
@ -374,13 +381,12 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name,
`tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account,
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty,
`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate,
`tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name,
`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
@ -417,14 +423,14 @@ def get_deducted_taxes():
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
def get_tax_accounts(item_list, columns, company_currency,
doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
import json
item_row_map = {}
tax_columns = []
invoice_item_row = {}
itemised_tax = {}
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"),
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
currency=company_currency) or 2
for d in item_list:
@ -469,8 +475,8 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_rate = tax_data
tax_amount = 0
if charge_type == "Actual" and not tax_rate:
tax_rate = "NA"
if charge_type == 'Actual' and not tax_rate:
tax_rate = 'NA'
item_net_amount = sum([flt(d.base_net_amount)
for d in item_row_map.get(parent, {}).get(item_code, [])])
@ -484,17 +490,17 @@ def get_tax_accounts(item_list, columns, company_currency,
if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": tax_rate,
"tax_amount": tax_value
'tax_rate': tax_rate,
'tax_amount': tax_value
})
except ValueError:
continue
elif charge_type == "Actual" and tax_amount:
elif charge_type == 'Actual' and tax_amount:
for d in invoice_item_row.get(parent, []):
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": "NA",
"tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total,
'tax_rate': 'NA',
'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
tax_amount_precision)
})
@ -563,7 +569,7 @@ def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
})
total_row_map.setdefault('total_row', {
subtotal_display_field: "Total",
subtotal_display_field: 'Total',
'stock_qty': 0.0,
'amount': 0.0,
'bold': 1,

View File

@ -17,18 +17,26 @@ def get_ordered_to_be_billed_data(args):
return frappe.db.sql("""
Select
`{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
{project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
`{parent_tab}`.name, `{parent_tab}`.{date_field},
`{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
`{child_tab}`.item_code,
`{child_tab}`.base_amount,
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
(`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
`{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
(`{child_tab}`.base_amount -
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
`{child_tab}`.item_name, `{child_tab}`.description,
{project_field}, `{parent_tab}`.company
from
`{parent_tab}`, `{child_tab}`
where
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
and `{parent_tab}`.status not in ('Closed', 'Completed')
and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt *
ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount
and `{child_tab}`.amount > 0
and (`{child_tab}`.base_amount -
round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
order by
`{parent_tab}`.{order} {order_by}
""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,

View File

@ -14,11 +14,93 @@ def execute(filters=None):
def get_column():
return [
_("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
{
"label": _("Purchase Receipt"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Purchase Receipt",
"width": 160
},
{
"label": _("Date"),
"fieldname": "date",
"fieldtype": "Date",
"width": 100
},
{
"label": _("Supplier"),
"fieldname": "supplier",
"fieldtype": "Link",
"options": "Supplier",
"width": 120
},
{
"label": _("Supplier Name"),
"fieldname": "supplier_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 120
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Billed Amount"),
"fieldname": "billed_amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Returned Amount"),
"fieldname": "returned_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Pending Amount"),
"fieldname": "pending_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Item Name"),
"fieldname": "item_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Description"),
"fieldname": "description",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
"width": 120
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120
}
]
def get_args():

View File

@ -78,7 +78,10 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
else:
return ((fy.name, fy.year_start_date, fy.year_end_date),)
error_msg = _("""{0} {1} not in any active Fiscal Year.""").format(label, formatdate(transaction_date))
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
if company:
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
if verbose==1: frappe.msgprint(error_msg)
raise FiscalYearError(error_msg)
@ -925,7 +928,7 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
@ -944,7 +947,10 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
where
timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s)
and is_cancelled = 0
{condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
@ -961,3 +967,20 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries
def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = True
for entry in expected_gle:
account_existed = False
for e in existing_gle:
if entry.account == e.account:
account_existed = True
if entry.account == e.account and entry.against_account == e.against_account \
and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) \
and (entry.debit != e.debit or entry.credit != e.credit):
matched = False
break
if not account_existed:
matched = False
break
return matched

View File

@ -9,9 +9,9 @@
"filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}",
"group_by_type": "Count",
"idx": 0,
"is_public": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-23 13:53:33.211371",
"modified": "2020-10-28 23:15:58.432189",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Value Analytics",

View File

@ -8,9 +8,9 @@
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
"idx": 0,
"is_public": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-23 13:39:32.429240",
"modified": "2020-10-28 23:16:16.939070",
"modified_by": "Administrator",
"module": "Assets",
"name": "Category-wise Asset Value",

View File

@ -8,9 +8,9 @@
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
"idx": 0,
"is_public": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-23 13:42:44.912551",
"modified": "2020-10-28 23:16:07.883312",
"modified_by": "Administrator",
"module": "Assets",
"name": "Location-wise Asset Value",

View File

@ -373,8 +373,8 @@ frappe.ui.form.on('Asset', {
doctype_field = frappe.scrub(doctype)
frm.set_value(doctype_field, '');
frappe.msgprint({
title: __(`Invalid ${doctype}`),
message: __(`The selected ${doctype} doesn't contains selected Asset Item.`),
title: __('Invalid {0}', [__(doctype)]),
message: __('The selected {0} does not contain the selected Asset Item.', [__(doctype)]),
indicator: 'red'
});
}
@ -436,7 +436,7 @@ frappe.ui.form.on('Asset Finance Book', {
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.`));
frappe.msgprint(__("Depreciation Posting Date should not be equal to Available for Use Date."));
book.depreciation_start_date = "";
frm.refresh_field("finance_books");
}

View File

@ -50,13 +50,11 @@
"reqd": 1
},
{
"depends_on": "eval:parent.doctype == 'Asset'",
"fieldname": "depreciation_start_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Depreciation Posting Date",
"mandatory_depends_on": "eval:parent.doctype == 'Asset'",
"reqd": 1
"mandatory_depends_on": "eval:parent.doctype == 'Asset'"
},
{
"default": "0",
@ -87,7 +85,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-10-30 15:22:29.119868",
"modified": "2020-11-05 16:30:09.213479",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",

View File

@ -108,7 +108,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_team_members(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") })
return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") }, "team_member")
@frappe.whitelist()
def get_maintenance_log(asset_name):

View File

@ -25,7 +25,7 @@ class AssetValueAdjustment(Document):
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
self.reschedule_depreciations(self.current_asset_value)
def validate_date(self):
asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
if getdate(self.date) < getdate(asset_purchase_date):
@ -53,6 +53,7 @@ class AssetValueAdjustment(Document):
je.posting_date = self.date
je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
je.finance_book = self.finance_book
credit_entry = {
"account": accumulated_depreciation_account,
@ -78,7 +79,7 @@ class AssetValueAdjustment(Document):
debit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
})
je.append("accounts", credit_entry)
je.append("accounts", debit_entry)

View File

@ -75,24 +75,23 @@ def get_data(filters):
for asset in assets_record:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
- flt(depreciation_amount_map.get(asset.name))
if asset_value:
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
"cost_center": asset.cost_center,
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
"asset_value": asset_value
}
data.append(row)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
"cost_center": asset.cost_center,
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
"asset_value": asset_value
}
data.append(row)
return data

View File

@ -90,6 +90,11 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
this.frm.set_df_property("drop_ship", "hidden", !is_drop_ship);
if(doc.docstatus == 1) {
this.frm.fields_dict.items_section.wrapper.addClass("hide-border");
if(!this.frm.doc.set_warehouse) {
this.frm.fields_dict.items_section.wrapper.removeClass("hide-border");
}
if(!in_list(["Closed", "Delivered"], doc.status)) {
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
this.frm.add_custom_button(__('Update Items'), () => {
@ -126,16 +131,25 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if(doc.status != "Closed") {
if (doc.status != "On Hold") {
if(flt(doc.per_received) < 100 && allow_receipt) {
cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create'));
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) {
cur_frm.add_custom_button(__('Material to Supplier'),
function() { me.make_stock_entry(); }, __("Transfer"));
}
}
if(flt(doc.per_billed) < 100)
cur_frm.add_custom_button(__('Invoice'),
cur_frm.add_custom_button(__('Purchase Invoice'),
this.make_purchase_invoice, __('Create'));
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
}
if(flt(doc.per_billed)==0) {
this.frm.add_custom_button(__('Payment Request'),
function() { me.make_payment_request() }, __('Create'));
}
if(!doc.auto_repeat) {
cur_frm.add_custom_button(__('Subscription'), function() {
erpnext.utils.make_subscription(doc.doctype, doc.name)
@ -156,13 +170,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
});
}
}
if(flt(doc.per_billed)==0) {
this.frm.add_custom_button(__('Payment Request'),
function() { me.make_payment_request() }, __('Create'));
}
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
}
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
}
} else if(doc.docstatus===0) {
@ -299,7 +307,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if(me.values) {
me.values.sub_con_rm_items.map((row,i) => {
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
frappe.throw(__("Item Code, warehouse, quantity are required on row" + (i+1)));
frappe.throw(__("Item Code, warehouse, quantity are required on row {0}", [i+1]));
}
})
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
@ -358,15 +366,19 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order",
source_doctype: "Material Request",
target: me.frm,
setters: {},
setters: {
schedule_date: undefined,
status: undefined
},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
status: ["!=", "Stopped"],
per_ordered: ["<", 99.99],
company: me.frm.doc.company
}
})
}, __("Get items from"));
}, __("Get Items From"));
this.frm.add_custom_button(__('Supplier Quotation'),
function() {
@ -375,16 +387,17 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
source_doctype: "Supplier Quotation",
target: me.frm,
setters: {
supplier: me.frm.doc.supplier
supplier: me.frm.doc.supplier,
valid_till: undefined
},
get_query_filters: {
docstatus: 1,
status: ["!=", "Stopped"],
status: ["not in", ["Stopped", "Expired"]],
}
})
}, __("Get items from"));
}, __("Get Items From"));
this.frm.add_custom_button(__('Update rate as per last purchase'),
this.frm.add_custom_button(__('Update Rate as per Last Purchase'),
function() {
frappe.call({
"method": "get_last_purchase_rate",

View File

@ -1,5 +1,6 @@
{
"actions": [],
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
@ -30,8 +31,8 @@
"customer_contact_email",
"section_addresses",
"supplier_address",
"contact_person",
"address_display",
"contact_person",
"contact_display",
"contact_mobile",
"contact_email",
@ -49,12 +50,14 @@
"plc_conversion_rate",
"ignore_pricing_rule",
"sec_warehouse",
"set_warehouse",
"col_break_warehouse",
"is_subcontracted",
"col_break_warehouse",
"supplier_warehouse",
"items_section",
"before_items_section",
"scan_barcode",
"items_col_break",
"set_warehouse",
"items_section",
"items",
"sb_last_purchase",
"total_qty",
@ -108,18 +111,13 @@
"payment_terms_template",
"payment_schedule",
"tracking_section",
"per_billed",
"status",
"column_break_75",
"per_billed",
"per_received",
"terms_section_break",
"tc_name",
"terms",
"more_info",
"status",
"ref_sq",
"column_break_74",
"party_account_currency",
"inter_company_order_reference",
"column_break5",
"letter_head",
"select_print_heading",
@ -131,7 +129,12 @@
"to_date",
"column_break_97",
"auto_repeat",
"update_auto_repeat_reference"
"update_auto_repeat_reference",
"more_info",
"ref_sq",
"column_break_74",
"party_account_currency",
"inter_company_order_reference"
],
"fields": [
{
@ -165,6 +168,7 @@
"bold": 1,
"fieldname": "supplier",
"fieldtype": "Link",
"in_global_search": 1,
"in_standard_filter": 1,
"label": "Supplier",
"oldfieldname": "supplier",
@ -313,34 +317,34 @@
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"label": "Supplier Address",
"options": "Address",
"print_hide": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact Person",
"label": "Supplier Contact",
"options": "Contact",
"print_hide": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
"label": "Supplier Address Details",
"read_only": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"in_global_search": 1,
"label": "Contact",
"label": "Contact Name",
"read_only": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
"label": "Contact Mobile No",
"read_only": 1
},
{
@ -358,14 +362,14 @@
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"label": "Company Shipping Address",
"options": "Address",
"print_hide": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"label": "Shipping Address Details",
"print_hide": 1,
"read_only": 1
},
@ -433,7 +437,8 @@
},
{
"fieldname": "sec_warehouse",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Subcontracting"
},
{
"description": "Sets 'Warehouse' in each row of the Items table.",
@ -466,6 +471,7 @@
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"hide_border": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
@ -598,7 +604,8 @@
},
{
"fieldname": "section_break_52",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"hide_border": 1
},
{
"fieldname": "taxes",
@ -626,10 +633,12 @@
{
"fieldname": "totals",
"fieldtype": "Section Break",
"label": "Taxes and Charges",
"oldfieldtype": "Section Break",
"options": "fa fa-money"
},
{
"depends_on": "base_taxes_and_charges_added",
"fieldname": "base_taxes_and_charges_added",
"fieldtype": "Currency",
"label": "Taxes and Charges Added (Company Currency)",
@ -640,6 +649,7 @@
"read_only": 1
},
{
"depends_on": "base_taxes_and_charges_deducted",
"fieldname": "base_taxes_and_charges_deducted",
"fieldtype": "Currency",
"label": "Taxes and Charges Deducted (Company Currency)",
@ -650,6 +660,7 @@
"read_only": 1
},
{
"depends_on": "base_total_taxes_and_charges",
"fieldname": "base_total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges (Company Currency)",
@ -665,6 +676,7 @@
"fieldtype": "Column Break"
},
{
"depends_on": "taxes_and_charges_added",
"fieldname": "taxes_and_charges_added",
"fieldtype": "Currency",
"label": "Taxes and Charges Added",
@ -675,6 +687,7 @@
"read_only": 1
},
{
"depends_on": "taxes_and_charges_deducted",
"fieldname": "taxes_and_charges_deducted",
"fieldtype": "Currency",
"label": "Taxes and Charges Deducted",
@ -685,6 +698,7 @@
"read_only": 1
},
{
"depends_on": "total_taxes_and_charges",
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges",
@ -694,7 +708,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"collapsible_depends_on": "apply_discount_on",
"fieldname": "discount_section",
"fieldtype": "Section Break",
"label": "Additional Discount"
@ -734,7 +748,8 @@
},
{
"fieldname": "totals_section",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Totals"
},
{
"fieldname": "base_grand_total",
@ -902,12 +917,12 @@
},
{
"fieldname": "ref_sq",
"fieldtype": "Data",
"hidden": 1,
"label": "Ref SQ",
"fieldtype": "Link",
"label": "Supplier Quotation",
"no_copy": 1,
"oldfieldname": "ref_sq",
"oldfieldtype": "Data",
"options": "Supplier Quotation",
"print_hide": 1,
"read_only": 1
},
@ -1061,7 +1076,7 @@
"collapsible": 1,
"fieldname": "tracking_section",
"fieldtype": "Section Break",
"label": "Tracking"
"label": "Order Status"
},
{
"fieldname": "column_break_75",
@ -1070,21 +1085,29 @@
{
"fieldname": "billing_address",
"fieldtype": "Link",
"label": "Select Billing Address",
"label": "Company Billing Address",
"options": "Address"
},
{
"fieldname": "billing_address_display",
"fieldtype": "Small Text",
"label": "Billing Address",
"label": "Billing Address Details",
"read_only": 1
},
{
"fieldname": "before_items_section",
"fieldtype": "Section Break"
},
{
"fieldname": "items_col_break",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2020-10-07 14:31:57.661221",
"modified": "2020-12-03 16:46:44.229351",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@ -19,6 +19,8 @@ from erpnext.controllers.accounts_controller import update_child_qty_rate
from erpnext.controllers.status_updater import OverAllowanceError
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
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):
def test_make_purchase_receipt(self):
@ -686,7 +688,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_exploded_items_in_subcontracted(self):
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,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
@ -708,7 +710,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_backflush_based_on_stock_entry(self):
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', {
'is_stock_item': 1,
'is_sub_contracted_item': 1
@ -767,6 +769,133 @@ class TestPurchaseOrder(unittest.TestCase):
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_supplied_qty_against_subcontracted_po(self):
item_code = "_Test Subcontracted FG Item 5"
make_item('Sub Contracted Raw Material 4', {
'is_stock_item': 1,
'is_sub_contracted_item': 1
})
make_subcontracted_item(item_code=item_code, raw_materials=["Sub Contracted Raw Material 4"])
update_backflush_based_on("Material Transferred for Subcontract")
order_qty = 250
po = create_purchase_order(item_code=item_code, qty=order_qty,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", do_not_save=True)
# Add same subcontracted items multiple times
po.append("items", {
"item_code": item_code,
"qty": order_qty,
"schedule_date": add_days(nowdate(), 1),
"warehouse": "_Test Warehouse - _TC"
})
po.set_missing_values()
po.submit()
# Material receipt entry for the raw materials which will be send to supplier
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Sub Contracted Raw Material 4", qty=500, basic_rate=100)
rm_items = [
{
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name
},
{
"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item",
"qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"
},
]
# Raw Materials transfer entry from stores to supplier's warehouse
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.submit()
# Test po_detail field has value or not
for item_row in se.items:
self.assertEqual(item_row.po_detail, po.supplied_items[item_row.idx - 1].name)
po_doc = frappe.get_doc("Purchase Order", po.name)
for row in po_doc.supplied_items:
# Valid that whether transferred quantity is matching with supplied qty or not in the purchase order
self.assertEqual(row.supplied_qty, 250.0)
update_backflush_based_on("BOM")
def test_advance_payment_entry_unlink_against_purchase_order(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
frappe.db.set_value("Accounts Settings", "Accounts Settings",
@ -839,27 +968,33 @@ def make_pr_against_po(po, received_qty=0):
pr.submit()
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
if not frappe.db.exists('Item', item_code):
make_item(item_code, {
args = frappe._dict(args)
if not frappe.db.exists('Item', args.item_code):
make_item(args.item_code, {
'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"):
make_item("Test Extra Item 1", {
'is_stock_item': 1,
})
if not args.raw_materials:
if not frappe.db.exists('Item', "Test Extra Item 1"):
make_item("Test Extra Item 1", {
'is_stock_item': 1,
})
if not frappe.db.exists('Item', "Test Extra Item 2"):
make_item("Test Extra Item 2", {
'is_stock_item': 1,
})
if not frappe.db.exists('Item', "Test Extra Item 2"):
make_item("Test Extra Item 2", {
'is_stock_item': 1,
})
if not frappe.db.get_value('BOM', {'item': item_code}, 'name'):
make_bom(item = item_code, raw_materials = ['_Test FG Item', 'Test Extra Item 1'])
args.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):
doc = frappe.get_doc('Buying Settings')

View File

@ -24,6 +24,7 @@
"col_break2",
"uom",
"conversion_factor",
"stock_qty",
"sec_break1",
"price_list_rate",
"discount_percentage",
@ -46,11 +47,8 @@
"column_break_32",
"base_net_rate",
"base_net_amount",
"billed_amt",
"warehouse_and_reference",
"warehouse",
"delivered_by_supplier",
"project",
"material_request",
"material_request_item",
"sales_order",
@ -58,36 +56,37 @@
"supplier_quotation",
"supplier_quotation_item",
"col_break5",
"delivered_by_supplier",
"against_blanket_order",
"blanket_order",
"blanket_order_rate",
"item_group",
"brand",
"bom",
"include_exploded_items",
"section_break_56",
"stock_qty",
"column_break_60",
"received_qty",
"returned_qty",
"manufacture_details",
"manufacturer",
"column_break_14",
"manufacturer_part_no",
"more_info_section_break",
"is_fixed_asset",
"item_tax_rate",
"column_break_60",
"billed_amt",
"accounting_details",
"expense_account",
"column_break_68",
"manufacture_details",
"manufacturer",
"manufacturer_part_no",
"column_break_14",
"bom",
"include_exploded_items",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_40",
"weight_uom",
"accounting_dimensions_section",
"cost_center",
"project",
"dimension_col_break",
"cost_center",
"more_info_section_break",
"is_fixed_asset",
"item_tax_rate",
"section_break_72",
"page_break"
],
@ -346,6 +345,7 @@
},
{
"default": "0",
"depends_on": "is_free_item",
"fieldname": "is_free_item",
"fieldtype": "Check",
"label": "Is Free Item",
@ -508,9 +508,10 @@
},
{
"default": "0",
"depends_on": "delivered_by_supplier",
"fieldname": "delivered_by_supplier",
"fieldtype": "Check",
"label": "To be delivered to customer",
"label": "To be Delivered to Customer",
"print_hide": 1,
"read_only": 1
},
@ -558,6 +559,7 @@
"read_only": 1
},
{
"depends_on": "eval:parent.is_subcontracted == 'Yes'",
"fieldname": "bom",
"fieldtype": "Link",
"label": "BOM",
@ -574,21 +576,21 @@
},
{
"fieldname": "section_break_56",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Billed, Received & Returned"
},
{
"fieldname": "stock_qty",
"fieldtype": "Float",
"label": "Qty as per Stock UOM",
"label": "Qty in Stock UOM",
"no_copy": 1,
"oldfieldname": "stock_qty",
"oldfieldtype": "Currency",
"print_hide": 1,
"print_width": "100px",
"read_only": 1,
"width": "100px"
},
{
"depends_on": "received_qty",
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty",
@ -612,9 +614,10 @@
"fieldtype": "Column Break"
},
{
"depends_on": "billed_amt",
"fieldname": "billed_amt",
"fieldtype": "Currency",
"label": "Billed Amt",
"label": "Billed Amount",
"no_copy": 1,
"options": "currency",
"print_hide": 1,
@ -633,6 +636,7 @@
"report_hide": 1
},
{
"collapsible": 1,
"fieldname": "accounting_details",
"fieldtype": "Section Break",
"label": "Accounting Details"
@ -644,10 +648,6 @@
"options": "Account",
"print_hide": 1
},
{
"fieldname": "column_break_68",
"fieldtype": "Column Break"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
@ -715,6 +715,7 @@
},
{
"default": "0",
"depends_on": "is_fixed_asset",
"fetch_from": "item_code.is_fixed_asset",
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
@ -728,9 +729,10 @@
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-04-21 11:55:58.643393",
"modified": "2020-12-07 11:59:47.670951",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@ -217,13 +217,15 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
source_doctype: "Material Request",
target: me.frm,
setters: {
company: me.frm.doc.company
schedule_date: undefined,
status: undefined
},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
status: ["!=", "Stopped"],
per_ordered: ["<", 99.99]
per_ordered: ["<", 99.99],
company: me.frm.doc.company
}
})
}, __("Get Items From"));
@ -236,32 +238,40 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
source_doctype: "Opportunity",
target: me.frm,
setters: {
company: me.frm.doc.company
party_name: undefined,
opportunity_from: undefined,
status: undefined
},
get_query_filters: {
status: ["not in", ["Closed", "Lost"]],
company: me.frm.doc.company
}
})
}, __("Get Items From"));
// Get items from open Material Requests based on supplier
this.frm.add_custom_button(__('Possible Supplier'), function() {
// Create a dialog window for the user to pick their supplier
var d = new frappe.ui.Dialog({
var dialog = new frappe.ui.Dialog({
title: __('Select Possible Supplier'),
fields: [
{fieldname: 'supplier', fieldtype:'Link', options:'Supplier', label:'Supplier', reqd:1},
{fieldname: 'ok_button', fieldtype:'Button', label:'Get Items from Material Requests'},
]
});
// On the user clicking the ok button
d.fields_dict.ok_button.input.onclick = function() {
var btn = d.fields_dict.ok_button.input;
var v = d.get_values();
if(v) {
$(btn).set_working();
{
fieldname: 'supplier',
fieldtype:'Link',
options:'Supplier',
label:'Supplier',
reqd:1,
description: __("Get Items from Material Requests against this Supplier")
}
],
primary_action_label: __("Get Items"),
primary_action: (args) => {
if(!args) return;
dialog.hide();
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_item_from_material_requests_based_on_supplier",
source_name: v.supplier,
source_name: args.supplier,
target: me.frm,
setters: {
company: me.frm.doc.company
@ -273,18 +283,24 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
per_ordered: ["<", 99.99]
}
});
$(btn).done_working();
d.hide();
dialog.hide();
}
}
d.show();
});
dialog.show();
}, __("Get Items From"));
// Link Material Requests
this.frm.add_custom_button(__('Link to Material Requests'),
function() {
erpnext.buying.link_to_mrs(me.frm);
}, __("Tools"));
// Get Suppliers
this.frm.add_custom_button(__('Get Suppliers'),
function() {
me.get_suppliers_button(me.frm);
});
}, __("Tools"));
}
},
@ -304,7 +320,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
{
"fieldtype": "Select", "label": __("Get Suppliers By"),
"fieldname": "search_type",
"options": ["Tag","Supplier Group"],
"options": ["Supplier Group", "Tag"],
"reqd": 1,
onchange() {
if(dialog.get_value('search_type') == 'Tag'){

View File

@ -18,12 +18,11 @@
"suppliers",
"items_section",
"items",
"link_to_mrs",
"supplier_response_section",
"salutation",
"email_template",
"col_break_email_1",
"subject",
"col_break_email_1",
"email_template",
"preview",
"sec_break_email_2",
"message_for_supplier",
@ -118,13 +117,6 @@
"reqd": 1
},
{
"depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
"fieldname": "link_to_mrs",
"fieldtype": "Button",
"label": "Link to Material Requests"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "supplier_response_section",
"fieldtype": "Section Break",
"label": "Email Details"
@ -260,7 +252,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-10-16 17:49:09.561929",
"modified": "2020-11-05 22:04:29.017134",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",

View File

@ -5,14 +5,14 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"send_email",
"email_sent",
"supplier",
"contact",
"quote_status",
"column_break_3",
"supplier_name",
"email_id"
"email_id",
"send_email",
"email_sent"
],
"fields": [
{
@ -87,7 +87,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-10-16 12:23:41.769820",
"modified": "2020-11-04 22:01:43.832942",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Supplier",

View File

@ -49,6 +49,12 @@ class Supplier(TransactionBase):
msgprint(_("Series is mandatory"), raise_exception=1)
validate_party_accounts(self)
self.validate_internal_supplier()
def validate_internal_supplier(self):
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
frappe.throw(_("Internal Supplier for company {0} already exists").format(
frappe.bold(self.represents_company)))
def on_trash(self):
delete_contact_and_address('Supplier', self.name)

View File

@ -120,3 +120,20 @@ class TestSupplier(unittest.TestCase):
# Rollback
address.delete()
def create_supplier(**args):
args = frappe._dict(args)
try:
doc = frappe.get_doc({
"doctype": "Supplier",
"supplier_name": args.supplier_name,
"supplier_group": args.supplier_group or "Services",
"supplier_type": args.supplier_type or "Company",
"tax_withholding_category": args.tax_withholding_category
}).insert()
return doc
except frappe.DuplicateEntryError:
return frappe.get_doc("Supplier", args.supplier_name)

View File

@ -37,16 +37,24 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
source_doctype: "Material Request",
target: me.frm,
setters: {
company: me.frm.doc.company
schedule_date: undefined,
status: undefined
},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
status: ["!=", "Stopped"],
per_ordered: ["<", 99.99]
per_ordered: ["<", 99.99],
company: me.frm.doc.company
}
})
}, __("Get items from"));
}, __("Get Items From"));
// Link Material Requests
this.frm.add_custom_button(__('Link to Material Requests'),
function() {
erpnext.buying.link_to_mrs(me.frm);
}, __("Tools"));
this.frm.add_custom_button(__("Request for Quotation"),
function() {
@ -58,16 +66,16 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
source_doctype: "Request for Quotation",
target: me.frm,
setters: {
company: me.frm.doc.company,
transaction_date: null
},
get_query_filters: {
supplier: me.frm.doc.supplier
supplier: me.frm.doc.supplier,
company: me.frm.doc.company
},
get_query_method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_rfq_containing_supplier"
})
}, __("Get items from"));
}, __("Get Items From"));
}
},

View File

@ -1,5 +1,6 @@
{
"actions": [],
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:45",
@ -34,7 +35,6 @@
"ignore_pricing_rule",
"items_section",
"items",
"link_to_mrs",
"pricing_rule_details",
"pricing_rules",
"section_break_22",
@ -321,12 +321,6 @@
"options": "Supplier Quotation Item",
"reqd": 1
},
{
"depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
"fieldname": "link_to_mrs",
"fieldtype": "Button",
"label": "Link to material requests"
},
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
@ -805,9 +799,10 @@
],
"icon": "fa fa-shopping-cart",
"idx": 29,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-10-01 20:56:17.932007",
"modified": "2020-12-03 15:18:29.073368",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@ -0,0 +1,89 @@
### Version 13.0.0 Beta 5 Release Notes
#### Features and Enhancements
- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614))
- Process Statement Of Accounts ([#22901](https://github.com/frappe/erpnext/pull/22901))
- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014))
- Material Request and Stock Entry Enhancement ([#22671](https://github.com/frappe/erpnext/pull/22671))
- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641))
- Appraisal form Enhancements ([#23500](https://github.com/frappe/erpnext/pull/23500))
- Crm reports cleanup ([#22844](https://github.com/frappe/erpnext/pull/22844))
- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445))
- Quoted Item Comparison Report Enhancements v2 ([#23127](https://github.com/frappe/erpnext/pull/23127))
- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170))
- Added address template for luxembourg ([#23621](https://github.com/frappe/erpnext/pull/23621))
- Provision to draft quotation from portal ([#23416](https://github.com/frappe/erpnext/pull/23416))
- M-pesa integration ([#23439](https://github.com/frappe/erpnext/pull/23439))
- Education Desk, Dashboard, and Onboarding ([#22825](https://github.com/frappe/erpnext/pull/22825))
- Added search to support page ([#22447](https://github.com/frappe/erpnext/pull/22447))
- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675))
- Added POS Register report ([#23313](https://github.com/frappe/erpnext/pull/23313))
- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309))
- Enhancement in Loan Topups ([#23049](https://github.com/frappe/erpnext/pull/23049))
- Inpatient Medication Order and Entry ([#23473](https://github.com/frappe/erpnext/pull/23473))
- Option to print UOM after quantity ([#23263](https://github.com/frappe/erpnext/pull/23263))
- Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323))
- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557))
- Therapy Plan Template ([#23558](https://github.com/frappe/erpnext/pull/23558))
- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867))
- Laboratory Module ([#22853](https://github.com/frappe/erpnext/pull/22853))
- Shift management ([#22262](https://github.com/frappe/erpnext/pull/22262))
#### Fixes
- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725))
- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001))
- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217))
- Loan Security shortfall calculation fixes ([#22866](https://github.com/frappe/erpnext/pull/22866))
- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669))
- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256))
- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333))
- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580))
- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541))
- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755))
- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302))
- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165))
- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772))
- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880))
- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407))
- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072))
- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718))
- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288))
- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765))
- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548))
- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145))
- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918))
- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597))
- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245))
- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361))
- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704))
- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161))
- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369))
- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195))
- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419))
- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953))
- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110))
- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059))
- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007))
- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066))
- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814))
- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076))
- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434))
- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009))
- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958))
- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483))
- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539))
- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151))
- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476))
- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911))
- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950))
- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943))
- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282))
- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714))
- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205))
- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623))
- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264))
- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676))
- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988))
- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436))
- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357))
- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515))

View File

@ -1,12 +1,14 @@
{
"actions": [],
"autoname": "Prompt",
"creation": "2019-06-05 11:48:30.572795",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"communication_channel",
"communication_medium_type",
"catch_all",
"column_break_3",
"catch_all",
"provider",
"disabled",
"timeslots_section",
@ -54,9 +56,16 @@
"fieldtype": "Table",
"label": "Timeslots",
"options": "Communication Medium Timeslot"
},
{
"fieldname": "communication_channel",
"fieldtype": "Select",
"label": "Communication Channel",
"options": "\nExotel"
}
],
"modified": "2019-06-05 11:49:30.769006",
"links": [],
"modified": "2020-10-27 16:22:08.068542",
"modified_by": "Administrator",
"module": "Communication",
"name": "Communication Medium",

View File

@ -23,6 +23,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
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
class AccountMissingError(frappe.ValidationError): pass
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
class AccountsController(TransactionBase):
@ -105,6 +107,8 @@ class AccountsController(TransactionBase):
else:
self.validate_deferred_start_and_end_date()
self.set_inter_company_account()
validate_regional(self)
if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self)
@ -263,6 +267,7 @@ class AccountsController(TransactionBase):
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
parent_dict.update({"customer": parent_dict.get("party_name")})
self.pricing_rules = []
for item in self.get("items"):
if item.get("item_code"):
args = parent_dict.copy()
@ -301,6 +306,7 @@ class AccountsController(TransactionBase):
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
if self.doctype == "Purchase Invoice":
self.set_expense_account(for_validate)
@ -322,6 +328,9 @@ class AccountsController(TransactionBase):
if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount
if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
item.rate = pricing_rule_args.get("rate")
elif pricing_rule_args.get('free_item_data'):
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
@ -335,6 +344,18 @@ class AccountsController(TransactionBase):
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
def set_pricing_rule_details(self, item_row, args):
pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
if not pricing_rules: return
for pricing_rule in pricing_rules:
self.append("pricing_rules", {
"pricing_rule": pricing_rule,
"item_code": item_row.item_code,
"child_docname": item_row.name,
"rule_applied": True
})
def set_taxes(self):
if not self.meta.get_field("taxes"):
return
@ -718,6 +739,21 @@ class AccountsController(TransactionBase):
return self._abbr
def raise_missing_debit_credit_account_error(self, party_type, party):
"""Raise an error if debit to/credit to account does not exist."""
db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
link_to_party = frappe.utils.get_link_to_form(party_type, party)
link_to_company = frappe.utils.get_link_to_form("Company", self.company)
message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
message += "<br>" + _("Please set one of the following:") + "<br>"
message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
def validate_party(self):
party_type, party = self.get_party()
validate_party_frozen_disabled(party_type, party)
@ -898,6 +934,38 @@ class AccountsController(TransactionBase):
else:
return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
def set_inter_company_account(self):
"""
Set intercompany account for inter warehouse transactions
This account will be used in case billing company and internal customer's
representation company is same
"""
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
if not unrealized_profit_loss_account:
msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
frappe.bold(self.company))
frappe.throw(msg)
self.unrealized_profit_loss_account = unrealized_profit_loss_account
def is_internal_transfer(self):
"""
It will an internal transfer if its an internal customer and representation
company is same as billing company
"""
if self.doctype == 'Sales Invoice':
internal_party_field = 'is_internal_customer'
else:
internal_party_field = 'is_internal_supplier'
if self.get(internal_party_field) and (self.represents_company == self.company):
return True
return False
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _, msgprint
from frappe.utils import flt,cint, cstr, getdate
from six import iteritems
from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
@ -16,6 +16,8 @@ from frappe.contacts.doctype.address.address import get_address_display
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.controllers.stock_controller import StockController
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.stock.utils import get_incoming_rate
class BuyingController(StockController):
def __setup__(self):
@ -42,6 +44,7 @@ class BuyingController(StockController):
self.validate_items()
self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items()
self.update_tax_category_for_internal_transfer()
self.validate_warehouse()
self.validate_from_warehouse()
self.set_supplier_address()
@ -62,7 +65,7 @@ class BuyingController(StockController):
self.set_landed_cost_voucher_amount()
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
self.update_valuation_rate("items")
self.update_valuation_rate()
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@ -94,13 +97,23 @@ class BuyingController(StockController):
def validate_stock_or_nonstock_items(self):
if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items():
tax_for_valuation = [d for d in self.get("taxes")
msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
self.update_tax_category(msg)
def update_tax_category_for_internal_transfer(self):
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
self.update_tax_category(msg)
def update_tax_category(self, msg):
tax_for_valuation = [d for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]]
if tax_for_valuation:
for d in tax_for_valuation:
d.category = 'Total'
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
if tax_for_valuation:
for d in tax_for_valuation:
d.category = 'Total'
msgprint(msg)
def validate_asset_return(self):
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
@ -112,8 +125,8 @@ class BuyingController(StockController):
"docstatus": 1
})]
if self.is_return and len(not_cancelled_asset):
frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.".format(self.return_against)),
title=_("Not Allowed"))
frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.")
.format(self.return_against), title=_("Not Allowed"))
def get_asset_items(self):
if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
@ -166,7 +179,7 @@ class BuyingController(StockController):
self.in_words = money_in_words(amount, self.currency)
# update valuation rate
def update_valuation_rate(self, parentfield):
def update_valuation_rate(self, reset_outgoing_rate=True):
"""
item_tax_amount is the total tax amount applied on that item
stored for valuation
@ -177,7 +190,7 @@ class BuyingController(StockController):
stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
last_item_idx = 1
for d in self.get(parentfield):
for d in self.get("items"):
if d.item_code and d.item_code in stock_and_asset_items:
stock_and_asset_items_qty += flt(d.qty)
stock_and_asset_items_amount += flt(d.base_net_amount)
@ -187,7 +200,7 @@ class BuyingController(StockController):
if d.category in ["Valuation", "Valuation and Total"]])
valuation_amount_adjustment = total_valuation_amount
for i, item in enumerate(self.get(parentfield)):
for i, item in enumerate(self.get("items")):
if item.item_code and item.qty and item.item_code in stock_and_asset_items:
item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
else flt(item.qty) / stock_and_asset_items_qty
@ -205,16 +218,34 @@ class BuyingController(StockController):
item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
rm_supp_cost = flt(item.rm_supp_cost) if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0
landed_cost_voucher_amount = flt(item.landed_cost_voucher_amount) \
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] else 0.0
item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + rm_supp_cost
+ landed_cost_voucher_amount) / qty_in_stock_uom)
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + item.rm_supp_cost
+ flt(item.landed_cost_voucher_amount)) / qty_in_stock_uom)
else:
item.valuation_rate = 0.0
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
supplied_items_cost = 0.0
for d in self.get("supplied_items"):
if d.reference_name == item_row_id:
if reset_outgoing_rate and frappe.db.get_value('Item', d.rm_item_code, 'is_stock_item'):
rate = get_incoming_rate({
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * d.consumed_qty,
"serial_no": d.serial_no
})
if rate > 0:
d.rate = rate
d.amount = flt(d.consumed_qty) * flt(d.rate)
supplied_items_cost += flt(d.amount)
return supplied_items_cost
def validate_for_subcontracting(self):
if not self.is_subcontracted and self.sub_contracted_items:
frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No"))
@ -298,10 +329,10 @@ class BuyingController(StockController):
title=_("Limit Crossed"))
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:
rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order)
rm_item_key = (raw_material.rm_item_code, item.item_code, item.purchase_order)
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
consumed_qty = raw_material_data.get('qty', 0)
@ -330,8 +361,10 @@ class BuyingController(StockController):
set_serial_nos(raw_material, consumed_serial_nos, qty)
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,
qty, transferred_batch_qty_map, backflushed_batch_qty_map)
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
for batch_data in batches_qty:
qty = batch_data['qty']
raw_material.batch_no = batch_data['batch']
@ -339,31 +372,17 @@ class BuyingController(StockController):
else:
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
def append_raw_material_to_be_backflushed(self, fg_item_row, raw_material_data, qty):
rm = self.append('supplied_items', {})
rm.update(raw_material_data)
if not rm.main_item_code:
rm.main_item_code = fg_item_row.item_code
rm.reference_name = fg_item_row.name
rm.required_qty = qty
rm.consumed_qty = qty
if not raw_material_data.get('non_stock_item'):
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": raw_material_data.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * qty,
"serial_no": rm.serial_no
})
if not rm.rate:
rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company=self.company)
rm.amount = qty * flt(rm.rate)
fg_item_doc.rm_supp_cost += rm.amount
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
exploded_item = 1
if hasattr(item, 'include_exploded_items'):
@ -372,7 +391,7 @@ class BuyingController(StockController):
bom_items = get_items_from_bom(item.item_code, item.bom, exploded_item)
used_alternative_items = []
if self.doctype == 'Purchase Receipt' and item.purchase_order:
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order:
used_alternative_items = get_used_alternative_items(purchase_order = item.purchase_order)
raw_materials_cost = 0
@ -389,7 +408,7 @@ class BuyingController(StockController):
reserve_warehouse = None
conversion_factor = item.conversion_factor
if (self.doctype == 'Purchase Receipt' and item.purchase_order and
if (self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order and
bom_item.item_code in used_alternative_items):
alternative_item_data = used_alternative_items.get(bom_item.item_code)
bom_item.item_code = alternative_item_data.item_code
@ -417,9 +436,7 @@ class BuyingController(StockController):
rm.rm_item_code = bom_item.item_code
rm.stock_uom = bom_item.stock_uom
rm.required_qty = required_qty
if self.doctype == "Purchase Order" and not rm.reserve_warehouse:
rm.reserve_warehouse = reserve_warehouse
rm.rate = bom_item.rate
rm.conversion_factor = conversion_factor
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@ -427,29 +444,8 @@ class BuyingController(StockController):
rm.description = bom_item.description
if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no:
rm.batch_no = item.batch_no
# get raw materials rate
if self.doctype == "Purchase Receipt":
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": bom_item.item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * required_qty,
"serial_no": rm.serial_no
})
if not rm.rate:
rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company = self.company)
else:
rm.rate = bom_item.rate
rm.amount = required_qty * flt(rm.rate)
raw_materials_cost += flt(rm.amount)
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
item.rm_supp_cost = raw_materials_cost
elif not rm.reserve_warehouse:
rm.reserve_warehouse = reserve_warehouse
def cleanup_raw_materials_supplied(self, parent_items, raw_material_table):
"""Remove all those child items which are no longer present in main item table"""
@ -491,6 +487,10 @@ class BuyingController(StockController):
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
# Set Received Qty in Stock UOM
d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
def validate_purchase_return(self):
for d in self.get("items"):
if self.is_return and flt(d.rejected_qty) != 0:
@ -558,7 +558,8 @@ class BuyingController(StockController):
or (cint(self.is_return) and self.docstatus==2)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse
"warehouse": d.from_warehouse,
"dependant_sle_voucher_detail_no": d.name
})
sl_entries.append(from_warehouse_sle)
@ -568,28 +569,20 @@ class BuyingController(StockController):
"serial_no": cstr(d.serial_no).strip()
})
if self.is_return:
filters = {
"voucher_type": self.doctype,
"voucher_no": self.return_against,
"item_code": d.item_code
}
if (self.doctype == "Purchase Invoice" and self.update_stock
and d.get("purchase_invoice_item")):
filters["voucher_detail_no"] = d.purchase_invoice_item
elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"):
filters["voucher_detail_no"] = d.purchase_receipt_item
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate")
outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
sle.update({
"outgoing_rate": original_incoming_rate
"outgoing_rate": outgoing_rate,
"recalculate_rate": 1
})
if d.from_warehouse:
sle.dependant_sle_voucher_detail_no = d.name
else:
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
sle.update({
"incoming_rate": incoming_rate
"incoming_rate": incoming_rate,
"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0
})
sl_entries.append(sle)
@ -597,7 +590,8 @@ class BuyingController(StockController):
or (cint(self.is_return) and self.docstatus==1)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse
"warehouse": d.from_warehouse,
"recalculate_rate": 1
})
sl_entries.append(from_warehouse_sle)
@ -645,6 +639,7 @@ class BuyingController(StockController):
"item_code": d.rm_item_code,
"warehouse": self.supplier_warehouse,
"actual_qty": -1*flt(d.consumed_qty),
"dependant_sle_voucher_detail_no": d.reference_name
}))
def on_submit(self):
@ -792,8 +787,8 @@ class BuyingController(StockController):
asset.set(field, None)
asset.supplier = None
if asset.docstatus == 1 and delete_asset:
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\
Please cancel the it to continue.').format(frappe.utils.get_link_to_form('Asset', asset.name)))
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue.')
.format(frappe.utils.get_link_to_form('Asset', asset.name)))
asset.flags.ignore_validate_update_after_submit = True
asset.flags.ignore_mandatory = True
@ -836,6 +831,7 @@ class BuyingController(StockController):
else:
validate_item_type(self, "is_purchase_item", "purchase")
def get_items_from_bom(item_code, bom, exploded_item=1):
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
@ -873,7 +869,7 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
AND se.purpose='Send to Subcontractor'
AND se.purchase_order = %s
AND IFNULL(sed.t_warehouse, '') != ''
AND sed.subcontracted_item = %s
AND IFNULL(sed.subcontracted_item, '') in ('', %s)
GROUP BY sed.item_code, sed.subcontracted_item
"""
raw_materials = frappe.db.multisql({
@ -890,39 +886,49 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
return raw_materials
def get_backflushed_subcontracted_raw_materials(purchase_orders):
common_query = """
SELECT
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
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
"""
purchase_receipts = frappe.get_all("Purchase Receipt Item",
fields = ["purchase_order", "item_code", "name", "parent"],
filters={"docstatus": 1, "purchase_order": ("in", list(purchase_orders))})
backflushed_raw_materials = frappe.db.multisql({
'mariadb': common_query.format(
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
),
'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)
distinct_purchase_receipts = {}
for pr in purchase_receipts:
key = (pr.purchase_order, pr.item_code, pr.parent)
distinct_purchase_receipts.setdefault(key, []).append(pr.name)
backflushed_raw_materials_map = frappe._dict()
for item in backflushed_raw_materials:
backflushed_raw_materials_map.setdefault(item.item_key, item)
for args, references in iteritems(distinct_purchase_receipts):
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, data.main_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
def get_supplied_items(item_code, purchase_receipt, references):
return frappe.get_all("Purchase Receipt Item Supplied",
fields=["rm_item_code", "main_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):
asset_items_data = {}
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
@ -1004,14 +1010,15 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
SELECT
sed.batch_no,
SUM(sed.qty) AS qty,
sed.item_code
sed.item_code,
sed.subcontracted_item
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
WHERE
se.name = sed.parent
AND se.docstatus=1
AND se.purpose='Send to Subcontractor'
AND se.purchase_order = %s
AND sed.subcontracted_item = %s
AND ifnull(sed.subcontracted_item, '') in ('', %s)
AND sed.batch_no IS NOT NULL
GROUP BY
sed.batch_no,
@ -1019,8 +1026,10 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
""", (purchase_order, fg_item), as_dict=1)
for batch_data in transferred_batches:
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
key = ((batch_data.item_code, fg_item)
if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
transferred_batch_qty_map.setdefault(key, {})
transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
return transferred_batch_qty_map
@ -1057,10 +1066,11 @@ def get_backflushed_batch_qty_map(purchase_order, fg_item):
return backflushed_batch_qty_map
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
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
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
if not transferred_batches:
transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
available_batches = []

View File

@ -203,10 +203,37 @@ def get_already_returned_items(doc):
return items
def get_returned_qty_map_for_row(row_name, doctype):
child_doctype = doctype + " Item"
reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail"
fields = [
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
]
if doctype == "Purchase Receipt":
fields += [
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
"sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)
]
data = frappe.db.get_list(doctype,
fields = fields,
filters = [
[doctype, "docstatus", "=", 1],
[doctype, "is_return", "=", 1],
[child_doctype, reference_field, "=", row_name]
])
return data[0]
def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.is_return = 1
@ -261,20 +288,25 @@ def make_return_doc(doctype, source_name, target_doc=None):
doc.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = -1* source_doc.qty
target_doc.qty = -1 * source_doc.qty
if doctype == "Purchase Receipt":
target_doc.received_qty = -1* source_doc.received_qty
target_doc.rejected_qty = -1* source_doc.rejected_qty
target_doc.qty = -1* source_doc.qty
target_doc.stock_qty = -1 * source_doc.stock_qty
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_order_item = source_doc.purchase_order_item
target_doc.rejected_warehouse = source_doc.rejected_warehouse
target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice":
target_doc.received_qty = -1* source_doc.received_qty
target_doc.rejected_qty = -1* source_doc.rejected_qty
target_doc.received_qty = -1 * source_doc.received_qty
target_doc.rejected_qty = -1 * source_doc.rejected_qty
target_doc.qty = -1* source_doc.qty
target_doc.stock_qty = -1 * source_doc.stock_qty
target_doc.purchase_order = source_doc.purchase_order
@ -285,6 +317,10 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice
target_doc.so_detail = source_doc.so_detail
@ -329,3 +365,45 @@ def make_return_doc(doctype, source_name, target_doc=None):
}, target_doc, set_missing_values)
return doclist
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None):
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
return_against, item_code, return_against_item_field, item_row)
if voucher_type in ("Purchase Receipt", "Purchase Invoice"):
select_field = "incoming_rate"
else:
select_field = "abs(stock_value_difference / actual_qty)"
return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
def get_return_against_item_fields(voucher_type):
return_against_item_fields = {
"Purchase Receipt": "purchase_receipt_item",
"Purchase Invoice": "purchase_invoice_item",
"Delivery Note": "dn_detail",
"Sales Invoice": "sales_invoice_item"
}
return return_against_item_fields[voucher_type]
def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, item_code, return_against_item_field, item_row):
filters = {
"voucher_type": voucher_type,
"voucher_no": return_against,
"item_code": item_code
}
if item_row:
reference_voucher_detail_no = item_row.get(return_against_item_field)
else:
reference_voucher_detail_no = frappe.db.get_value(voucher_type + " Item", voucher_detail_no, return_against_item_field)
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
return filters

Some files were not shown because too many files have changed in this diff Show More