Merge branch 'develop' into issue_customer_filter
This commit is contained in:
commit
84166a931e
14
.editorconfig
Normal file
14
.editorconfig
Normal 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
|
20
.eslintrc
20
.eslintrc
@ -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
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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.
|
@ -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
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<p>ERP made simple</p>
|
||||
</p>
|
||||
|
||||
[](https://travis-ci.com/frappe/erpnext)
|
||||
[](https://travis-ci.com/frappe/erpnext)
|
||||
[](https://www.codetriage.com/frappe/erpnext)
|
||||
[](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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"))
|
||||
|
@ -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,6 +171,7 @@ class TestAccount(unittest.TestCase):
|
||||
for doc in to_delete:
|
||||
frappe.delete_doc("Account", doc)
|
||||
|
||||
|
||||
def _make_test_records(verbose):
|
||||
from frappe.test_runner import make_test_objects
|
||||
|
||||
|
@ -7,7 +7,7 @@ frappe.ui.form.on('Accounting Dimension', {
|
||||
frm.set_query('document_type', () => {
|
||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||
invalid_doctypes.push('Accounting Dimension', 'Project',
|
||||
'Cost Center', 'Accounting Dimension Detail');
|
||||
'Cost Center', 'Accounting Dimension Detail', 'Company');
|
||||
|
||||
return {
|
||||
filters: {
|
||||
|
@ -19,7 +19,7 @@ class AccountingDimension(Document):
|
||||
|
||||
def validate(self):
|
||||
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
||||
'Cost Center', 'Accounting Dimension Detail') :
|
||||
'Cost Center', 'Accounting Dimension Detail', 'Company') :
|
||||
|
||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||
frappe.throw(msg)
|
||||
|
@ -40,7 +40,7 @@
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, the system will post accounting entries for inventory automatically.",
|
||||
"description": "If enabled, the system will post accounting entries for inventory automatically",
|
||||
"fieldname": "auto_accounting_for_stock",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
@ -48,23 +48,23 @@
|
||||
"label": "Make Accounting Entry For Every Stock Movement"
|
||||
},
|
||||
{
|
||||
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
|
||||
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
|
||||
"fieldname": "acc_frozen_upto",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Accounts Frozen Upto"
|
||||
"label": "Accounts Frozen Till Date"
|
||||
},
|
||||
{
|
||||
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
|
||||
"fieldname": "frozen_accounts_modifier",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
|
||||
"label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"default": "Billing Address",
|
||||
"description": "Address used to determine Tax Category in transactions.",
|
||||
"description": "Address used to determine Tax Category in transactions",
|
||||
"fieldname": "determine_address_tax_category_from",
|
||||
"fieldtype": "Select",
|
||||
"label": "Determine Address Tax Category From",
|
||||
@ -75,7 +75,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Role that is allowed to submit transactions that exceed credit limits set.",
|
||||
"description": "This role is allowed to submit transactions that exceed credit limits",
|
||||
"fieldname": "credit_controller",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -127,7 +127,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "show_inclusive_tax_in_print",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Inclusive Tax In Print"
|
||||
"label": "Show Inclusive Tax in Print"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
@ -165,7 +165,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Only select if you have setup Cash Flow Mapper documents",
|
||||
"description": "Only select this if you have set up the Cash Flow Mapper documents",
|
||||
"fieldname": "use_custom_cash_flow",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Custom Cash Flow Format"
|
||||
@ -177,7 +177,7 @@
|
||||
"label": "Automatically Fetch Payment Terms"
|
||||
},
|
||||
{
|
||||
"description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
|
||||
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
||||
"fieldname": "over_billing_allowance",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Over Billing Allowance (%)"
|
||||
@ -199,7 +199,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If this is unchecked direct GL Entries will be created to book Deferred Revenue/Expense",
|
||||
"description": "If this is unchecked, direct GL entries will be created to book deferred revenue or expense",
|
||||
"fieldname": "book_deferred_entries_via_journal_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Book Deferred Entries Via Journal Entry"
|
||||
@ -214,7 +214,7 @@
|
||||
},
|
||||
{
|
||||
"default": "Days",
|
||||
"description": "If \"Months\" is selected then fixed amount will be booked as deferred revenue or expense for each month irrespective of number of days in a month. Will be prorated if deferred revenue or expense is not booked for an entire month.",
|
||||
"description": "If \"Months\" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month",
|
||||
"fieldname": "book_deferred_entries_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Book Deferred Entries Based On",
|
||||
@ -226,7 +226,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-07 14:58:50.325577",
|
||||
"modified": "2020-10-13 11:32:52.268826",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -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")
|
||||
|
||||
|
@ -29,7 +29,7 @@ class CashierClosing(Document):
|
||||
for i in self.payments:
|
||||
total += flt(i.amount)
|
||||
|
||||
self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.returns
|
||||
self.net_amount = total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
|
||||
|
||||
def validate_time(self):
|
||||
if self.from_time >= self.time:
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -1,347 +1,126 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"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,
|
||||
"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
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "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
|
||||
"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
|
||||
"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
|
||||
"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
|
||||
"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
|
||||
"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",
|
||||
"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,
|
||||
"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
|
||||
"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
|
||||
"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
|
||||
"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
|
||||
"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
|
||||
"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
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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", {
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
|
@ -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]
|
||||
]
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:mode_of_payment",
|
||||
@ -28,7 +29,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Type",
|
||||
"options": "Cash\nBank\nGeneral"
|
||||
"options": "Cash\nBank\nGeneral\nPhone"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts",
|
||||
@ -45,7 +46,9 @@
|
||||
],
|
||||
"icon": "fa fa-credit-card",
|
||||
"idx": 1,
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 17:57:23.835236",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Mode of Payment",
|
||||
|
@ -6,7 +6,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
||||
frm.set_query('party_type', 'invoices', function(doc, cdt, cdn) {
|
||||
return {
|
||||
filters: {
|
||||
'name': ['in', 'Customer,Supplier']
|
||||
'name': ['in', 'Customer, Supplier']
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -14,29 +14,46 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
||||
if (frm.doc.company) {
|
||||
frm.trigger('setup_company_filters');
|
||||
}
|
||||
|
||||
frappe.realtime.on('opening_invoice_creation_progress', data => {
|
||||
if (!frm.doc.import_in_progress) {
|
||||
frm.dashboard.reset();
|
||||
frm.doc.import_in_progress = true;
|
||||
}
|
||||
if (data.user != frappe.session.user) return;
|
||||
if (data.count == data.total) {
|
||||
setTimeout((title) => {
|
||||
frm.doc.import_in_progress = false;
|
||||
frm.clear_table("invoices");
|
||||
frm.refresh_fields();
|
||||
frm.page.clear_indicator();
|
||||
frm.dashboard.hide_progress(title);
|
||||
frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
|
||||
}, 1500, data.title);
|
||||
return;
|
||||
}
|
||||
|
||||
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
||||
frm.page.set_indicator(__('In Progress'), 'orange');
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.trigger("make_dashboard");
|
||||
!frm.doc.import_in_progress && frm.trigger("make_dashboard");
|
||||
frm.page.set_primary_action(__('Create Invoices'), () => {
|
||||
let btn_primary = frm.page.btn_primary.get(0);
|
||||
return frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
btn: $(btn_primary),
|
||||
method: "make_invoices",
|
||||
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
|
||||
callback: (r) => {
|
||||
if(!r.exc){
|
||||
frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
|
||||
frm.clear_table("invoices");
|
||||
frm.refresh_fields();
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
|
||||
});
|
||||
});
|
||||
|
||||
if (frm.doc.create_missing_party) {
|
||||
frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices");
|
||||
}
|
||||
},
|
||||
|
||||
setup_company_filters: function(frm) {
|
||||
|
@ -4,9 +4,12 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import traceback
|
||||
from json import dumps
|
||||
from frappe import _, scrub
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||
|
||||
|
||||
@ -62,66 +65,47 @@ class OpeningInvoiceCreationTool(Document):
|
||||
|
||||
return invoices_summary, max_count
|
||||
|
||||
def make_invoices(self):
|
||||
names = []
|
||||
mandatory_error_msg = _("Row {0}: {1} is required to create the Opening {2} Invoices")
|
||||
def validate_company(self):
|
||||
if not self.company:
|
||||
frappe.throw(_("Please select the Company"))
|
||||
|
||||
company_details = frappe.get_cached_value('Company', self.company,
|
||||
["default_currency", "default_letter_head"], as_dict=1) or {}
|
||||
def set_missing_values(self, row):
|
||||
row.qty = row.qty or 1.0
|
||||
row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
|
||||
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
|
||||
row.item_name = row.item_name or _("Opening Invoice Item")
|
||||
row.posting_date = row.posting_date or nowdate()
|
||||
row.due_date = row.due_date or nowdate()
|
||||
|
||||
def validate_mandatory_invoice_fields(self, row):
|
||||
if not frappe.db.exists(row.party_type, row.party):
|
||||
if self.create_missing_party:
|
||||
self.add_party(row.party_type, row.party)
|
||||
else:
|
||||
frappe.throw(_("Row #{}: {} {} does not exist.").format(row.idx, frappe.bold(row.party_type), frappe.bold(row.party)))
|
||||
|
||||
mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices")
|
||||
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
|
||||
if not row.get(scrub(d)):
|
||||
frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type))
|
||||
|
||||
def get_invoices(self):
|
||||
invoices = []
|
||||
for row in self.invoices:
|
||||
if not row.qty:
|
||||
row.qty = 1.0
|
||||
|
||||
# always mandatory fields for the invoices
|
||||
if not row.temporary_opening_account:
|
||||
row.temporary_opening_account = get_temporary_opening_account(self.company)
|
||||
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
|
||||
|
||||
# Allow to create invoice even if no party present in customer or supplier.
|
||||
if not frappe.db.exists(row.party_type, row.party):
|
||||
if self.create_missing_party:
|
||||
self.add_party(row.party_type, row.party)
|
||||
else:
|
||||
frappe.throw(_("{0} {1} does not exist.").format(frappe.bold(row.party_type), frappe.bold(row.party)))
|
||||
|
||||
if not row.item_name:
|
||||
row.item_name = _("Opening Invoice Item")
|
||||
if not row.posting_date:
|
||||
row.posting_date = nowdate()
|
||||
if not row.due_date:
|
||||
row.due_date = nowdate()
|
||||
|
||||
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
|
||||
if not row.get(scrub(d)):
|
||||
frappe.throw(mandatory_error_msg.format(row.idx, _(d), self.invoice_type))
|
||||
|
||||
args = self.get_invoice_dict(row=row)
|
||||
if not args:
|
||||
if not row:
|
||||
continue
|
||||
|
||||
self.set_missing_values(row)
|
||||
self.validate_mandatory_invoice_fields(row)
|
||||
invoice = self.get_invoice_dict(row)
|
||||
company_details = frappe.get_cached_value('Company', self.company, ["default_currency", "default_letter_head"], as_dict=1) or {}
|
||||
if company_details:
|
||||
args.update({
|
||||
invoice.update({
|
||||
"currency": company_details.get("default_currency"),
|
||||
"letter_head": company_details.get("default_letter_head")
|
||||
})
|
||||
invoices.append(invoice)
|
||||
|
||||
doc = frappe.get_doc(args).insert()
|
||||
doc.submit()
|
||||
names.append(doc.name)
|
||||
|
||||
if len(self.invoices) > 5:
|
||||
frappe.publish_realtime(
|
||||
"progress", dict(
|
||||
progress=[row.idx, len(self.invoices)],
|
||||
title=_('Creating {0}').format(doc.doctype)
|
||||
),
|
||||
user=frappe.session.user
|
||||
)
|
||||
|
||||
return names
|
||||
return invoices
|
||||
|
||||
def add_party(self, party_type, party):
|
||||
party_doc = frappe.new_doc(party_type)
|
||||
@ -140,14 +124,12 @@ class OpeningInvoiceCreationTool(Document):
|
||||
|
||||
def get_invoice_dict(self, row=None):
|
||||
def get_item_dict():
|
||||
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
||||
cost_center = row.get('cost_center') or frappe.get_cached_value('Company',
|
||||
self.company, "cost_center")
|
||||
|
||||
cost_center = row.get('cost_center') or frappe.get_cached_value('Company', self.company, "cost_center")
|
||||
if not cost_center:
|
||||
frappe.throw(
|
||||
_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
|
||||
)
|
||||
frappe.throw(_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company)))
|
||||
|
||||
income_expense_account_field = "income_account" if row.party_type == "Customer" else "expense_account"
|
||||
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
||||
rate = flt(row.outstanding_amount) / flt(row.qty)
|
||||
|
||||
return frappe._dict({
|
||||
@ -161,18 +143,9 @@ class OpeningInvoiceCreationTool(Document):
|
||||
"cost_center": cost_center
|
||||
})
|
||||
|
||||
if not row:
|
||||
return None
|
||||
|
||||
party_type = "Customer"
|
||||
income_expense_account_field = "income_account"
|
||||
if self.invoice_type == "Purchase":
|
||||
party_type = "Supplier"
|
||||
income_expense_account_field = "expense_account"
|
||||
|
||||
item = get_item_dict()
|
||||
|
||||
args = frappe._dict({
|
||||
invoice = frappe._dict({
|
||||
"items": [item],
|
||||
"is_opening": "Yes",
|
||||
"set_posting_time": 1,
|
||||
@ -180,21 +153,76 @@ class OpeningInvoiceCreationTool(Document):
|
||||
"cost_center": self.cost_center,
|
||||
"due_date": row.due_date,
|
||||
"posting_date": row.posting_date,
|
||||
frappe.scrub(party_type): row.party,
|
||||
frappe.scrub(row.party_type): row.party,
|
||||
"is_pos": 0,
|
||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
|
||||
})
|
||||
|
||||
accounting_dimension = get_accounting_dimensions()
|
||||
|
||||
for dimension in accounting_dimension:
|
||||
args.update({
|
||||
invoice.update({
|
||||
dimension: item.get(dimension)
|
||||
})
|
||||
|
||||
if self.invoice_type == "Sales":
|
||||
args["is_pos"] = 0
|
||||
return invoice
|
||||
|
||||
return args
|
||||
def make_invoices(self):
|
||||
self.validate_company()
|
||||
invoices = self.get_invoices()
|
||||
if len(invoices) < 50:
|
||||
return start_import(invoices)
|
||||
else:
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||
|
||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||
if self.name not in enqueued_jobs:
|
||||
enqueue(
|
||||
start_import,
|
||||
queue="default",
|
||||
timeout=6000,
|
||||
event="opening_invoice_creation",
|
||||
job_name=self.name,
|
||||
invoices=invoices,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||
)
|
||||
|
||||
def start_import(invoices):
|
||||
errors = 0
|
||||
names = []
|
||||
for idx, d in enumerate(invoices):
|
||||
try:
|
||||
publish(idx, len(invoices), d.doctype)
|
||||
doc = frappe.get_doc(d)
|
||||
doc.insert()
|
||||
doc.submit()
|
||||
frappe.db.commit()
|
||||
names.append(doc.name)
|
||||
except Exception:
|
||||
errors += 1
|
||||
frappe.db.rollback()
|
||||
message = "\n".join(["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
|
||||
frappe.log_error(title="Error while creating Opening Invoice", message=message)
|
||||
frappe.db.commit()
|
||||
if errors:
|
||||
frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
|
||||
.format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
|
||||
return names
|
||||
|
||||
def publish(index, total, doctype):
|
||||
if total < 5: return
|
||||
frappe.publish_realtime(
|
||||
"opening_invoice_creation_progress",
|
||||
dict(
|
||||
title=_("Opening Invoice Creation In Progress"),
|
||||
message=_('Creating {} out of {} {}').format(index + 1, total, doctype),
|
||||
user=frappe.session.user,
|
||||
count=index+1,
|
||||
total=total
|
||||
))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_temporary_opening_account(company=None):
|
||||
|
@ -44,7 +44,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
0: ["_Test Supplier", 300, "Overdue"],
|
||||
1: ["_Test Supplier 1", 250, "Overdue"],
|
||||
}
|
||||
self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
|
||||
self.check_expected_values(invoices, expected_value, "Purchase")
|
||||
|
||||
def get_opening_invoice_creation_dict(**args):
|
||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
|
@ -1,313 +1,98 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2015-12-23 21:31:52.699821",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"field_order": [
|
||||
"payment_gateway",
|
||||
"payment_channel",
|
||||
"is_default",
|
||||
"column_break_4",
|
||||
"payment_account",
|
||||
"currency",
|
||||
"payment_request_message",
|
||||
"message",
|
||||
"message_examples"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "payment_gateway",
|
||||
"fieldtype": "Link",
|
||||
"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": "Payment Gateway",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Payment Gateway",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "is_default",
|
||||
"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": "Is Default",
|
||||
"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
|
||||
"label": "Is Default"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "payment_account",
|
||||
"fieldtype": "Link",
|
||||
"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": "Payment Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "payment_account.account_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Read Only",
|
||||
"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": "Currency",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"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
|
||||
"label": "Currency"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: doc.payment_channel !== \"Phone\"",
|
||||
"fieldname": "payment_request_message",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Please click on the link below to make your payment",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Default Payment Request Message",
|
||||
"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
|
||||
"label": "Default Payment Request Message"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "message_examples",
|
||||
"fieldtype": "HTML",
|
||||
"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": "Message Examples",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "<pre><h5>Message Example</h5>\n\n<p> Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.</p>\n\n<p> Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.</p>\n\n<p> We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! </p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n</pre>\n",
|
||||
"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
|
||||
"options": "<pre><h5>Message Example</h5>\n\n<p> Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.</p>\n\n<p> Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.</p>\n\n<p> We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! </p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n</pre>\n"
|
||||
},
|
||||
{
|
||||
"default": "Email",
|
||||
"fieldname": "payment_channel",
|
||||
"fieldtype": "Select",
|
||||
"label": "Payment Channel",
|
||||
"options": "\nEmail\nPhone"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-05-16 22:43:34.970491",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-20 13:30:27.722852",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Gateway Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -25,7 +25,7 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
|
||||
})
|
||||
|
||||
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
||||
if(frm.doc.payment_request_type == 'Inward' &&
|
||||
if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
|
||||
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
|
||||
frm.add_custom_button(__('Resend Payment Email'), function(){
|
||||
frappe.call({
|
||||
|
@ -48,6 +48,7 @@
|
||||
"section_break_7",
|
||||
"payment_gateway",
|
||||
"payment_account",
|
||||
"payment_channel",
|
||||
"payment_order",
|
||||
"amended_from"
|
||||
],
|
||||
@ -230,6 +231,7 @@
|
||||
"label": "Recipient Message And Payment Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "print_format",
|
||||
"fieldtype": "Select",
|
||||
"label": "Print Format"
|
||||
@ -241,6 +243,7 @@
|
||||
"label": "To"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
@ -277,16 +280,18 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_request_type == 'Inward'",
|
||||
"depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Text",
|
||||
"label": "Message"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "message_examples",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Message Examples",
|
||||
@ -347,12 +352,21 @@
|
||||
"options": "Payment Request",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_gateway_account.payment_channel",
|
||||
"fieldname": "payment_channel",
|
||||
"fieldtype": "Select",
|
||||
"label": "Payment Channel",
|
||||
"options": "\nEmail\nPhone",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-17 14:06:42.185763",
|
||||
"modified": "2020-09-18 12:24:14.178853",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
|
@ -36,7 +36,7 @@ class PaymentRequest(Document):
|
||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||
if (hasattr(ref_doc, "order_type") \
|
||||
and getattr(ref_doc, "order_type") != "Shopping Cart"):
|
||||
ref_amount = get_amount(ref_doc)
|
||||
ref_amount = get_amount(ref_doc, self.payment_account)
|
||||
|
||||
if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
|
||||
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
|
||||
@ -76,11 +76,25 @@ class PaymentRequest(Document):
|
||||
or self.flags.mute_email:
|
||||
send_mail = False
|
||||
|
||||
if send_mail:
|
||||
if send_mail and self.payment_channel != "Phone":
|
||||
self.set_payment_request_url()
|
||||
self.send_email()
|
||||
self.make_communication_entry()
|
||||
|
||||
elif self.payment_channel == "Phone":
|
||||
controller = get_payment_gateway_controller(self.payment_gateway)
|
||||
payment_record = dict(
|
||||
reference_doctype="Payment Request",
|
||||
reference_docname=self.name,
|
||||
payment_reference=self.reference_name,
|
||||
grand_total=self.grand_total,
|
||||
sender=self.email_to,
|
||||
currency=self.currency,
|
||||
payment_gateway=self.payment_gateway
|
||||
)
|
||||
controller.validate_transaction_currency(self.currency)
|
||||
controller.request_for_payment(**payment_record)
|
||||
|
||||
def on_cancel(self):
|
||||
self.check_if_payment_entry_exists()
|
||||
self.set_as_cancelled()
|
||||
@ -105,13 +119,14 @@ class PaymentRequest(Document):
|
||||
return False
|
||||
|
||||
def set_payment_request_url(self):
|
||||
if self.payment_account:
|
||||
if self.payment_account and self.payment_channel != "Phone":
|
||||
self.payment_url = self.get_payment_url()
|
||||
|
||||
if self.payment_url:
|
||||
self.db_set('payment_url', self.payment_url)
|
||||
|
||||
if self.payment_url or not self.payment_gateway_account:
|
||||
if self.payment_url or not self.payment_gateway_account \
|
||||
or (self.payment_gateway_account and self.payment_channel == "Phone"):
|
||||
self.db_set('status', 'Initiated')
|
||||
|
||||
def get_payment_url(self):
|
||||
@ -140,10 +155,14 @@ class PaymentRequest(Document):
|
||||
})
|
||||
|
||||
def set_as_paid(self):
|
||||
payment_entry = self.create_payment_entry()
|
||||
self.make_invoice()
|
||||
if self.payment_channel == "Phone":
|
||||
self.db_set("status", "Paid")
|
||||
|
||||
return payment_entry
|
||||
else:
|
||||
payment_entry = self.create_payment_entry()
|
||||
self.make_invoice()
|
||||
|
||||
return payment_entry
|
||||
|
||||
def create_payment_entry(self, submit=True):
|
||||
"""create entry"""
|
||||
@ -151,7 +170,7 @@ class PaymentRequest(Document):
|
||||
|
||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||
|
||||
if self.reference_doctype == "Sales Invoice":
|
||||
if self.reference_doctype in ["Sales Invoice", "POS Invoice"]:
|
||||
party_account = ref_doc.debit_to
|
||||
elif self.reference_doctype == "Purchase Invoice":
|
||||
party_account = ref_doc.credit_to
|
||||
@ -166,8 +185,8 @@ class PaymentRequest(Document):
|
||||
else:
|
||||
party_amount = self.grand_total
|
||||
|
||||
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name,
|
||||
party_amount=party_amount, bank_account=self.payment_account, bank_amount=bank_amount)
|
||||
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name, party_amount=party_amount,
|
||||
bank_account=self.payment_account, bank_amount=bank_amount)
|
||||
|
||||
payment_entry.update({
|
||||
"reference_no": self.name,
|
||||
@ -255,7 +274,7 @@ class PaymentRequest(Document):
|
||||
|
||||
# if shopping cart enabled and in session
|
||||
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
||||
and frappe.local.session.user != "Guest"):
|
||||
and frappe.local.session.user != "Guest") and self.payment_channel != "Phone":
|
||||
|
||||
success_url = shopping_cart_settings.payment_success_url
|
||||
if success_url:
|
||||
@ -280,7 +299,9 @@ def make_payment_request(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
ref_doc = frappe.get_doc(args.dt, args.dn)
|
||||
grand_total = get_amount(ref_doc)
|
||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||
|
||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||
if args.loyalty_points and args.dt == "Sales Order":
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
|
||||
@ -288,8 +309,6 @@ def make_payment_request(**args):
|
||||
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
||||
grand_total = grand_total - loyalty_amount
|
||||
|
||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||
|
||||
bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
|
||||
if args.get('party_type') else '')
|
||||
|
||||
@ -314,9 +333,11 @@ def make_payment_request(**args):
|
||||
"payment_gateway_account": gateway_account.get("name"),
|
||||
"payment_gateway": gateway_account.get("payment_gateway"),
|
||||
"payment_account": gateway_account.get("payment_account"),
|
||||
"payment_channel": gateway_account.get("payment_channel"),
|
||||
"payment_request_type": args.get("payment_request_type"),
|
||||
"currency": ref_doc.currency,
|
||||
"grand_total": grand_total,
|
||||
"mode_of_payment": args.mode_of_payment,
|
||||
"email_to": args.recipient_id or ref_doc.owner,
|
||||
"subject": _("Payment Request for {0}").format(args.dn),
|
||||
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
||||
@ -344,7 +365,7 @@ def make_payment_request(**args):
|
||||
|
||||
return pr.as_dict()
|
||||
|
||||
def get_amount(ref_doc):
|
||||
def get_amount(ref_doc, payment_account=None):
|
||||
"""get amount based on doctype"""
|
||||
dt = ref_doc.doctype
|
||||
if dt in ["Sales Order", "Purchase Order"]:
|
||||
@ -356,6 +377,12 @@ def get_amount(ref_doc):
|
||||
else:
|
||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||
|
||||
elif dt == "POS Invoice":
|
||||
for pay in ref_doc.payments:
|
||||
if pay.type == "Phone" and pay.account == payment_account:
|
||||
grand_total = pay.amount
|
||||
break
|
||||
|
||||
elif dt == "Fees":
|
||||
grand_total = ref_doc.outstanding_amount
|
||||
|
||||
@ -366,6 +393,10 @@ def get_amount(ref_doc):
|
||||
frappe.throw(_("Payment Entry is already created"))
|
||||
|
||||
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||
"""
|
||||
Get the existing payment request which are unpaid or partially paid for payment channel other than Phone
|
||||
and get the summation of existing paid payment request for Phone payment channel.
|
||||
"""
|
||||
existing_payment_request_amount = frappe.db.sql("""
|
||||
select sum(grand_total)
|
||||
from `tabPayment Request`
|
||||
@ -373,7 +404,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||
reference_doctype = %s
|
||||
and reference_name = %s
|
||||
and docstatus = 1
|
||||
and status != 'Paid'
|
||||
and (status != 'Paid'
|
||||
or (payment_channel = 'Phone'
|
||||
and status = 'Paid'))
|
||||
""", (ref_dt, ref_dn))
|
||||
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
|
||||
|
||||
|
@ -51,6 +51,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
args: {
|
||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||
pos_profile: frm.doc.pos_profile,
|
||||
user: frm.doc.user
|
||||
},
|
||||
callback: (r) => {
|
||||
|
@ -14,19 +14,51 @@ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import
|
||||
|
||||
class POSClosingEntry(Document):
|
||||
def validate(self):
|
||||
user = frappe.get_all('POS Closing Entry',
|
||||
filters = { 'user': self.user, 'docstatus': 1 },
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
self.validate_pos_closing()
|
||||
self.validate_pos_invoices()
|
||||
|
||||
def validate_pos_closing(self):
|
||||
user = frappe.get_all("POS Closing Entry",
|
||||
filters = { "user": self.user, "docstatus": 1, "pos_profile": self.pos_profile },
|
||||
or_filters = {
|
||||
'period_start_date': ('between', [self.period_start_date, self.period_end_date]),
|
||||
'period_end_date': ('between', [self.period_start_date, self.period_end_date])
|
||||
"period_start_date": ("between", [self.period_start_date, self.period_end_date]),
|
||||
"period_end_date": ("between", [self.period_start_date, self.period_end_date])
|
||||
})
|
||||
|
||||
if user:
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period"
|
||||
.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period"))
|
||||
bold_already_exists = frappe.bold(_("already exists"))
|
||||
bold_user = frappe.bold(self.user)
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
def validate_pos_invoices(self):
|
||||
invalid_rows = []
|
||||
for d in self.pos_transactions:
|
||||
invalid_row = {'idx': d.idx}
|
||||
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
||||
if pos_invoice.consolidated_invoice:
|
||||
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
||||
invalid_rows.append(invalid_row)
|
||||
continue
|
||||
if pos_invoice.pos_profile != self.pos_profile:
|
||||
invalid_row.setdefault('msg', []).append(_("POS Profile doesn't matches {}").format(frappe.bold(self.pos_profile)))
|
||||
if pos_invoice.docstatus != 1:
|
||||
invalid_row.setdefault('msg', []).append(_('POS Invoice is not {}').format(frappe.bold("submitted")))
|
||||
if pos_invoice.owner != self.user:
|
||||
invalid_row.setdefault('msg', []).append(_("POS Invoice isn't created by user {}").format(frappe.bold(self.owner)))
|
||||
|
||||
if invalid_row.get('msg'):
|
||||
invalid_rows.append(invalid_row)
|
||||
|
||||
if not invalid_rows:
|
||||
return
|
||||
|
||||
error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
|
||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||
|
||||
def on_submit(self):
|
||||
merge_pos_invoices(self.pos_transactions)
|
||||
@ -47,16 +79,15 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
return [c['user'] for c in cashiers_list]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pos_invoices(start, end, user):
|
||||
def get_pos_invoices(start, end, pos_profile, user):
|
||||
data = frappe.db.sql("""
|
||||
select
|
||||
name, timestamp(posting_date, posting_time) as "timestamp"
|
||||
from
|
||||
`tabPOS Invoice`
|
||||
where
|
||||
owner = %s and docstatus = 1 and
|
||||
(consolidated_invoice is NULL or consolidated_invoice = '')
|
||||
""", (user), as_dict=1)
|
||||
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
|
||||
""", (user, pos_profile), as_dict=1)
|
||||
|
||||
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
||||
# need to get taxes and payments so can't avoid get_doc
|
||||
@ -76,7 +107,8 @@ def make_closing_entry_from_opening(opening_entry):
|
||||
closing_entry.net_total = 0
|
||||
closing_entry.total_quantity = 0
|
||||
|
||||
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date, closing_entry.user)
|
||||
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date,
|
||||
closing_entry.pos_profile, closing_entry.user)
|
||||
|
||||
pos_transactions = []
|
||||
taxes = []
|
||||
|
@ -7,8 +7,8 @@
|
||||
"field_order": [
|
||||
"mode_of_payment",
|
||||
"opening_amount",
|
||||
"closing_amount",
|
||||
"expected_amount",
|
||||
"closing_amount",
|
||||
"difference"
|
||||
],
|
||||
"fields": [
|
||||
@ -26,8 +26,7 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Expected Amount",
|
||||
"options": "company:company_currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
@ -55,9 +54,10 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:03:34.533607",
|
||||
"modified": "2020-10-23 16:45:43.662034",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry Detail",
|
||||
|
@ -9,80 +9,63 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
||||
this._super(doc);
|
||||
},
|
||||
|
||||
onload() {
|
||||
onload(doc) {
|
||||
this._super();
|
||||
if(this.frm.doc.__islocal && this.frm.doc.is_pos) {
|
||||
//Load pos profile data on the invoice if the default value of Is POS is 1
|
||||
|
||||
me.frm.script_manager.trigger("is_pos");
|
||||
me.frm.refresh_fields();
|
||||
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||
this.frm.script_manager.trigger("is_pos");
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
},
|
||||
|
||||
refresh(doc) {
|
||||
this._super();
|
||||
if (doc.docstatus == 1 && !doc.is_return) {
|
||||
if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
|
||||
cur_frm.add_custom_button(__('Return'),
|
||||
this.make_sales_return, __('Create'));
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
|
||||
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
if (this.frm.doc.is_return) {
|
||||
if (doc.is_return && doc.__islocal) {
|
||||
this.frm.return_print_format = "Sales Invoice Return";
|
||||
cur_frm.set_value('consolidated_invoice', '');
|
||||
this.frm.set_value('consolidated_invoice', '');
|
||||
}
|
||||
},
|
||||
|
||||
is_pos: function(frm){
|
||||
is_pos: function() {
|
||||
this.set_pos_data();
|
||||
},
|
||||
|
||||
set_pos_data: function() {
|
||||
set_pos_data: async function() {
|
||||
if(this.frm.doc.is_pos) {
|
||||
this.frm.set_value("allocate_advances_automatically", 0);
|
||||
if(!this.frm.doc.company) {
|
||||
this.frm.set_value("is_pos", 0);
|
||||
frappe.msgprint(__("Please specify Company to proceed"));
|
||||
} else {
|
||||
var me = this;
|
||||
return this.frm.call({
|
||||
doc: me.frm.doc,
|
||||
const r = await this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "set_missing_values",
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
if(r.message) {
|
||||
me.frm.pos_print_format = r.message.print_format || "";
|
||||
me.frm.meta.default_print_format = r.message.print_format || "";
|
||||
me.frm.allow_edit_rate = r.message.allow_edit_rate;
|
||||
me.frm.allow_edit_discount = r.message.allow_edit_discount;
|
||||
me.frm.doc.campaign = r.message.campaign;
|
||||
me.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
||||
}
|
||||
me.frm.script_manager.trigger("update_stock");
|
||||
me.calculate_taxes_and_totals();
|
||||
if(me.frm.doc.taxes_and_charges) {
|
||||
me.frm.script_manager.trigger("taxes_and_charges");
|
||||
}
|
||||
frappe.model.set_default_values(me.frm.doc);
|
||||
me.set_dynamic_labels();
|
||||
|
||||
}
|
||||
}
|
||||
freeze: true
|
||||
});
|
||||
if(!r.exc) {
|
||||
if(r.message) {
|
||||
this.frm.pos_print_format = r.message.print_format || "";
|
||||
this.frm.meta.default_print_format = r.message.print_format || "";
|
||||
this.frm.doc.campaign = r.message.campaign;
|
||||
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
||||
}
|
||||
this.frm.script_manager.trigger("update_stock");
|
||||
this.calculate_taxes_and_totals();
|
||||
this.frm.doc.taxes_and_charges && this.frm.script_manager.trigger("taxes_and_charges");
|
||||
frappe.model.set_default_values(this.frm.doc);
|
||||
this.set_dynamic_labels();
|
||||
}
|
||||
}
|
||||
}
|
||||
else this.frm.trigger("refresh");
|
||||
},
|
||||
|
||||
customer() {
|
||||
if (!this.frm.doc.customer) return
|
||||
|
||||
if (this.frm.doc.is_pos){
|
||||
var pos_profile = this.frm.doc.pos_profile;
|
||||
}
|
||||
var me = this;
|
||||
const pos_profile = this.frm.doc.pos_profile;
|
||||
if(this.frm.updating_party_details) return;
|
||||
erpnext.utils.get_party_details(this.frm,
|
||||
"erpnext.accounts.party.get_party_details", {
|
||||
@ -92,8 +75,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
||||
account: this.frm.doc.debit_to,
|
||||
price_list: this.frm.doc.selling_price_list,
|
||||
pos_profile: pos_profile
|
||||
}, function() {
|
||||
me.apply_pricing_rule();
|
||||
}, () => {
|
||||
this.apply_pricing_rule();
|
||||
});
|
||||
},
|
||||
|
||||
@ -201,5 +184,22 @@ frappe.ui.form.on('POS Invoice', {
|
||||
}
|
||||
frm.set_value("loyalty_amount", loyalty_amount);
|
||||
}
|
||||
},
|
||||
|
||||
request_for_payment: function (frm) {
|
||||
frm.save().then(() => {
|
||||
frappe.dom.freeze();
|
||||
frappe.call({
|
||||
method: 'create_payment_request',
|
||||
doc: frm.doc,
|
||||
})
|
||||
.fail(() => {
|
||||
frappe.dom.unfreeze();
|
||||
frappe.msgprint('Payment request failed');
|
||||
})
|
||||
.then(() => {
|
||||
frappe.msgprint('Payment request sent successfully');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2020-01-24 15:29:29.933693",
|
||||
@ -279,8 +280,7 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Return (Credit Note)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"set_only_once": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
@ -461,7 +461,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "contact_mobile",
|
||||
"fieldtype": "Small Text",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Mobile No",
|
||||
"read_only": 1
|
||||
@ -1579,10 +1579,9 @@
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-07 12:43:09.138720",
|
||||
"modified": "2020-10-30 13:56:51.056083",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
@ -10,11 +10,10 @@ from erpnext.controllers.selling_controller import SellingController
|
||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
||||
get_loyalty_program_details_with_points, validate_loyalty_points
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option, get_mode_of_payment_info
|
||||
|
||||
from six import iteritems
|
||||
|
||||
@ -29,8 +28,7 @@ class POSInvoice(SalesInvoice):
|
||||
# run on validate method of selling controller
|
||||
super(SalesInvoice, self).validate()
|
||||
self.validate_auto_set_posting_time()
|
||||
self.validate_pos_paid_amount()
|
||||
self.validate_pos_return()
|
||||
self.validate_mode_of_payment()
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_debit_to_acc()
|
||||
@ -40,11 +38,12 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_item_cost_centers()
|
||||
self.validate_serialised_or_batched_item()
|
||||
self.validate_stock_availablility()
|
||||
self.validate_return_items()
|
||||
self.validate_return_items_qty()
|
||||
self.validate_non_stock_items()
|
||||
self.set_status()
|
||||
self.set_account_for_mode_of_payment()
|
||||
self.validate_pos()
|
||||
self.verify_payment_amount()
|
||||
self.validate_payment_amount()
|
||||
self.validate_loyalty_transaction()
|
||||
|
||||
def on_submit(self):
|
||||
@ -57,6 +56,7 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
if self.redeem_loyalty_points and self.loyalty_points:
|
||||
self.apply_loyalty_points()
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
|
||||
def on_cancel(self):
|
||||
@ -69,71 +69,123 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc.delete_loyalty_point_entry()
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||
def check_phone_payments(self):
|
||||
for pay in self.payments:
|
||||
if pay.type == "Phone" and pay.amount >= 0:
|
||||
paid_amt = frappe.db.get_value("Payment Request",
|
||||
filters=dict(
|
||||
reference_doctype="POS Invoice", reference_name=self.name,
|
||||
mode_of_payment=pay.mode_of_payment, status="Paid"),
|
||||
fieldname="grand_total")
|
||||
|
||||
if pay.amount != paid_amt:
|
||||
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
if self.is_return:
|
||||
return
|
||||
|
||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||
error_msg = []
|
||||
for d in self.get('items'):
|
||||
msg = ""
|
||||
if d.serial_no:
|
||||
filters = {
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"delivery_document_no": "",
|
||||
"sales_invoice": ""
|
||||
}
|
||||
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||
if d.batch_no:
|
||||
filters["batch_no"] = d.batch_no
|
||||
reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
serial_nos = d.serial_no.split("\n")
|
||||
serial_nos = ' '.join(serial_nos).split() # remove whitespaces
|
||||
invalid_serial_nos = []
|
||||
for s in serial_nos:
|
||||
if s in reserved_serial_nos:
|
||||
invalid_serial_nos.append(s)
|
||||
|
||||
if len(invalid_serial_nos):
|
||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. Please select valid serial no.").format(
|
||||
d.idx, multiple_nos, frappe.bold(', '.join(invalid_serial_nos))), title=_("Not Available"))
|
||||
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||
|
||||
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
|
||||
if len(invalid_serial_nos) == 1:
|
||||
msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(d.idx, bold_invalid_serial_nos))
|
||||
elif invalid_serial_nos:
|
||||
msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(d.idx, bold_invalid_serial_nos))
|
||||
|
||||
else:
|
||||
if allow_negative_stock:
|
||||
return
|
||||
|
||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||
if not (flt(available_stock) > 0):
|
||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.').format(
|
||||
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse)), title=_("Not Available"))
|
||||
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||
if flt(available_stock) <= 0:
|
||||
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.').format(
|
||||
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)), title=_("Not Available"))
|
||||
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||
.format(d.idx, item_code, warehouse, qty))
|
||||
if msg:
|
||||
error_msg.append(msg)
|
||||
|
||||
if error_msg:
|
||||
frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
|
||||
|
||||
def validate_serialised_or_batched_item(self):
|
||||
error_msg = []
|
||||
for d in self.get("items"):
|
||||
serialized = d.get("has_serial_no")
|
||||
batched = d.get("has_batch_no")
|
||||
no_serial_selected = not d.get("serial_no")
|
||||
no_batch_selected = not d.get("batch_no")
|
||||
|
||||
|
||||
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):
|
||||
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.').format(
|
||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
if serialized and no_serial_selected:
|
||||
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.').format(
|
||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
if batched and no_batch_selected:
|
||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.').format(
|
||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
msg = (_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.')
|
||||
.format(d.idx, item_code))
|
||||
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))
|
||||
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))
|
||||
|
||||
def validate_return_items(self):
|
||||
if msg:
|
||||
error_msg.append(msg)
|
||||
|
||||
if error_msg:
|
||||
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||
|
||||
def validate_return_items_qty(self):
|
||||
if not self.get("is_return"): return
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.get("qty") > 0:
|
||||
frappe.throw(_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
frappe.throw(
|
||||
_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")
|
||||
)
|
||||
if d.get("serial_no"):
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
for sr in serial_nos:
|
||||
serial_no_exists = frappe.db.exists("POS Invoice Item", {
|
||||
"parent": self.return_against,
|
||||
"serial_no": ["like", d.get("serial_no")]
|
||||
})
|
||||
if not serial_no_exists:
|
||||
bold_return_against = frappe.bold(self.return_against)
|
||||
bold_serial_no = frappe.bold(sr)
|
||||
frappe.throw(
|
||||
_("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_pos_paid_amount(self):
|
||||
if len(self.payments) == 0 and self.is_pos:
|
||||
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:
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
|
||||
def validate_change_account(self):
|
||||
@ -151,20 +203,18 @@ class POSInvoice(SalesInvoice):
|
||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||
|
||||
def verify_payment_amount(self):
|
||||
def validate_payment_amount(self):
|
||||
total_amount_in_payments = 0
|
||||
for entry in self.payments:
|
||||
total_amount_in_payments += entry.amount
|
||||
if not self.is_return and entry.amount < 0:
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
||||
if self.is_return and entry.amount > 0:
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
||||
|
||||
def validate_pos_return(self):
|
||||
if self.is_pos and self.is_return:
|
||||
total_amount_in_payments = 0
|
||||
for payment in self.payments:
|
||||
total_amount_in_payments += payment.amount
|
||||
if self.is_return:
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if total_amount_in_payments < invoice_total:
|
||||
if total_amount_in_payments and total_amount_in_payments < invoice_total:
|
||||
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
|
||||
|
||||
def validate_loyalty_transaction(self):
|
||||
@ -219,55 +269,45 @@ class POSInvoice(SalesInvoice):
|
||||
pos_profile = get_pos_profile(self.company) or {}
|
||||
self.pos_profile = pos_profile.get('name')
|
||||
|
||||
pos = {}
|
||||
profile = {}
|
||||
if self.pos_profile:
|
||||
pos = frappe.get_doc('POS Profile', self.pos_profile)
|
||||
profile = frappe.get_doc('POS Profile', self.pos_profile)
|
||||
|
||||
if not self.get('payments') and not for_validate:
|
||||
update_multi_mode_option(self, pos)
|
||||
update_multi_mode_option(self, profile)
|
||||
|
||||
if not self.account_for_change_amount:
|
||||
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
||||
|
||||
if pos:
|
||||
if not for_validate:
|
||||
self.tax_category = pos.get("tax_category")
|
||||
if self.is_return and not for_validate:
|
||||
add_return_modes(self, profile)
|
||||
|
||||
if profile:
|
||||
if not for_validate and not self.customer:
|
||||
self.customer = pos.customer
|
||||
self.customer = profile.customer
|
||||
|
||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
||||
if pos.get('account_for_change_amount'):
|
||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||
if pos.get('warehouse'):
|
||||
self.set_warehouse = pos.get('warehouse')
|
||||
self.ignore_pricing_rule = profile.ignore_pricing_rule
|
||||
self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount
|
||||
self.set_warehouse = profile.get('warehouse') or self.set_warehouse
|
||||
|
||||
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
|
||||
for fieldname in ('currency', 'letter_head', 'tc_name',
|
||||
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||
self.set(fieldname, pos.get(fieldname))
|
||||
|
||||
if pos.get("company_address"):
|
||||
self.company_address = pos.get("company_address")
|
||||
'write_off_cost_center', 'apply_discount_on', 'cost_center', 'tax_category',
|
||||
'ignore_pricing_rule', 'company_address', 'update_stock'):
|
||||
if not for_validate:
|
||||
self.set(fieldname, profile.get(fieldname))
|
||||
|
||||
if self.customer:
|
||||
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
||||
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
||||
selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
|
||||
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
|
||||
else:
|
||||
selling_price_list = pos.get('selling_price_list')
|
||||
selling_price_list = profile.get('selling_price_list')
|
||||
|
||||
if selling_price_list:
|
||||
self.set('selling_price_list', selling_price_list)
|
||||
|
||||
if not for_validate:
|
||||
self.update_stock = cint(pos.get("update_stock"))
|
||||
|
||||
# set pos values in items
|
||||
for item in self.get("items"):
|
||||
if item.get('item_code'):
|
||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
|
||||
profile_details = get_pos_profile_item_details(profile.get("company"), frappe._dict(item.as_dict()), profile)
|
||||
for fname, val in iteritems(profile_details):
|
||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||
item.set(fname, val)
|
||||
@ -280,10 +320,13 @@ class POSInvoice(SalesInvoice):
|
||||
if self.taxes_and_charges and not len(self.get("taxes")):
|
||||
self.set_taxes()
|
||||
|
||||
return pos
|
||||
if not self.account_for_change_amount:
|
||||
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
||||
|
||||
return profile
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
profile = self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.debit_to:
|
||||
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
||||
@ -293,17 +336,15 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
super(SalesInvoice, self).set_missing_values(for_validate)
|
||||
|
||||
print_format = pos.get("print_format") if pos else None
|
||||
print_format = profile.get("print_format") if profile else None
|
||||
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
|
||||
print_format = 'POS Invoice'
|
||||
|
||||
if pos:
|
||||
if profile:
|
||||
return {
|
||||
"print_format": print_format,
|
||||
"allow_edit_rate": pos.get("allow_user_to_edit_rate"),
|
||||
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
|
||||
"campaign": pos.get("campaign"),
|
||||
"allow_print_before_pay": pos.get("allow_print_before_pay")
|
||||
"campaign": profile.get("campaign"),
|
||||
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||
}
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
@ -312,6 +353,32 @@ class POSInvoice(SalesInvoice):
|
||||
if not pay.account:
|
||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||
|
||||
def create_payment_request(self):
|
||||
for pay in self.payments:
|
||||
if pay.type == "Phone":
|
||||
if pay.amount <= 0:
|
||||
frappe.throw(_("Payment amount cannot be less than or equal to 0"))
|
||||
|
||||
if not self.contact_mobile:
|
||||
frappe.throw(_("Please enter the phone number first"))
|
||||
|
||||
payment_gateway = frappe.db.get_value("Payment Gateway Account", {
|
||||
"payment_account": pay.account,
|
||||
})
|
||||
record = {
|
||||
"payment_gateway": payment_gateway,
|
||||
"dt": "POS Invoice",
|
||||
"dn": self.name,
|
||||
"payment_request_type": "Inward",
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"mode_of_payment": pay.mode_of_payment,
|
||||
"recipient_id": self.contact_mobile,
|
||||
"submit_doc": True
|
||||
}
|
||||
|
||||
return make_payment_request(**record)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||
@ -333,11 +400,9 @@ def get_stock_availability(item_code, warehouse):
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||
|
||||
if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty:
|
||||
if sle_qty and pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
# when sle_qty is 0
|
||||
# when sle_qty > 0 and pos_sales_qty is 0
|
||||
return sle_qty
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -371,3 +436,18 @@ def make_merge_log(invoices):
|
||||
|
||||
if merge_log.get('pos_invoices'):
|
||||
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])
|
@ -27,17 +27,24 @@ class POSInvoiceMergeLog(Document):
|
||||
status, docstatus, is_return, return_against = frappe.db.get_value(
|
||||
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
||||
|
||||
bold_pos_invoice = frappe.bold(d.pos_invoice)
|
||||
bold_status = frappe.bold(status)
|
||||
if docstatus != 1:
|
||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
|
||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice))
|
||||
if status == "Consolidated":
|
||||
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
|
||||
if is_return and return_against not in [d.pos_invoice for d in self.pos_invoices] and status != "Consolidated":
|
||||
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||
frappe.throw(
|
||||
_("Row #{}: Return Invoice {} cannot be made against unconsolidated invoice. \
|
||||
You can add original invoice {} manually to proceed.")
|
||||
.format(d.idx, frappe.bold(d.pos_invoice), frappe.bold(return_against))
|
||||
)
|
||||
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, bold_pos_invoice, bold_status))
|
||||
if is_return and return_against and return_against not in [d.pos_invoice for d in self.pos_invoices]:
|
||||
bold_return_against = frappe.bold(return_against)
|
||||
return_against_status = frappe.db.get_value('POS Invoice', return_against, "status")
|
||||
if return_against_status != "Consolidated":
|
||||
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||
bold_unconsolidated = frappe.bold("not Consolidated")
|
||||
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ")
|
||||
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
|
||||
msg += _("Original invoice should be consolidated before or along with the return invoice.")
|
||||
msg += "<br><br>"
|
||||
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
|
||||
frappe.throw(msg)
|
||||
|
||||
def on_submit(self):
|
||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||
|
@ -17,18 +17,25 @@ class POSOpeningEntry(StatusUpdater):
|
||||
|
||||
def validate_pos_profile_and_cashier(self):
|
||||
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
|
||||
frappe.throw(_("POS Profile {} does not belongs to company {}".format(self.pos_profile, self.company)))
|
||||
frappe.throw(_("POS Profile {} does not belongs to company {}").format(self.pos_profile, self.company))
|
||||
|
||||
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
||||
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
|
||||
frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user))
|
||||
|
||||
def validate_payment_method_account(self):
|
||||
invalid_modes = []
|
||||
for d in self.balance_details:
|
||||
account = frappe.db.get_value("Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||
if not account:
|
||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||
|
||||
if invalid_modes:
|
||||
if invalid_modes == 1:
|
||||
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||
else:
|
||||
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||
|
||||
def on_submit(self):
|
||||
self.set_status(update=True)
|
@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"default",
|
||||
"allow_in_returns",
|
||||
"mode_of_payment"
|
||||
],
|
||||
"fields": [
|
||||
@ -24,11 +25,19 @@
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_returns",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Allow In Returns"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:08:41.704844",
|
||||
"modified": "2020-10-20 12:58:46.114456",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Payment Method",
|
||||
|
@ -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'));
|
||||
|
@ -14,6 +14,7 @@
|
||||
"column_break_9",
|
||||
"update_stock",
|
||||
"ignore_pricing_rule",
|
||||
"hide_unavailable_items",
|
||||
"warehouse",
|
||||
"campaign",
|
||||
"company_address",
|
||||
@ -290,28 +291,36 @@
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Warehouse",
|
||||
"mandatory_depends_on": "update_stock",
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "update_stock",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Stock"
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_pricing_rule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Pricing Rule"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "update_stock",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Stock",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide_unavailable_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Unavailable Items"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-01 17:29:27.759088",
|
||||
"modified": "2020-10-29 13:18:38.795925",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
@ -56,19 +56,29 @@ class POSProfile(Document):
|
||||
if not self.payments:
|
||||
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
||||
|
||||
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
||||
if not default_mode_of_payment:
|
||||
default_mode = [d.default for d in self.payments if d.default]
|
||||
if not default_mode:
|
||||
frappe.throw(_("Please select a default mode of payment"))
|
||||
|
||||
if len(default_mode_of_payment) > 1:
|
||||
if len(default_mode) > 1:
|
||||
frappe.throw(_("You can only select one mode of payment as default"))
|
||||
|
||||
invalid_modes = []
|
||||
for d in self.payments:
|
||||
account = frappe.db.get_value("Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||
account = frappe.db.get_value(
|
||||
"Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company},
|
||||
"default_account"
|
||||
)
|
||||
if not account:
|
||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||
|
||||
if invalid_modes:
|
||||
if invalid_modes == 1:
|
||||
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||
else:
|
||||
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||
|
||||
def on_update(self):
|
||||
self.set_defaults()
|
||||
|
@ -9,8 +9,7 @@ frappe.ui.form.on('POS Settings', {
|
||||
get_invoice_fields: function(frm) {
|
||||
frappe.model.with_doctype("POS Invoice", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
||||
['Table', 'Button'].includes(d.fieldtype)) {
|
||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || ['Button'].includes(d.fieldtype)) {
|
||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||
} else {
|
||||
return null;
|
||||
|
@ -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>
|
||||
|
@ -504,10 +504,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 +563,7 @@
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-26 12:24:44.740734",
|
||||
"modified": "2020-10-28 16:53:14.416172",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
@ -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)
|
||||
|
@ -457,6 +457,70 @@ class TestPricingRule(unittest.TestCase):
|
||||
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 make_pricing_rule(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@ -468,6 +532,7 @@ 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,
|
||||
@ -476,9 +541,13 @@ def make_pricing_rule(**args):
|
||||
"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
|
||||
})
|
||||
|
||||
if args.get("priority"):
|
||||
doc.priority = args.get("priority")
|
||||
|
||||
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), {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -99,6 +99,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 +108,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 +126,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");
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
@ -1334,7 +1335,8 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"modified": "2020-09-21 12:22:09.164068",
|
||||
"links": [],
|
||||
"modified": "2020-10-30 13:57:18.266978",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -151,14 +151,16 @@ class PurchaseInvoice(BuyingController):
|
||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
||||
frappe.throw(
|
||||
_("Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.")
|
||||
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||
)
|
||||
|
||||
if self.supplier and account.account_type != "Payable":
|
||||
frappe.throw(_("Please ensure {} account is a Payable account. \
|
||||
Change the account type to Payable or select a different account.")
|
||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
||||
frappe.throw(
|
||||
_("Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.")
|
||||
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||
)
|
||||
|
||||
self.party_account_currency = account.account_currency
|
||||
|
||||
@ -244,10 +246,10 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if self.update_stock and (not item.from_warehouse):
|
||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because account {2}
|
||||
is not linked to warehouse {3} or it is not the default inventory account'''.format(
|
||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]),
|
||||
frappe.bold(item.expense_account), frappe.bold(item.warehouse))))
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
||||
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||
msg += _("or it is not the default inventory account")
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||
else:
|
||||
@ -259,19 +261,19 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if negative_expense_booked_in_pr:
|
||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because
|
||||
expense is booked against this account in Purchase Receipt {2}'''.format(
|
||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))))
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
else:
|
||||
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
||||
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} as no Purchase
|
||||
Receipt is created against Item {2}. This is done to handle accounting for cases
|
||||
when Purchase Receipt is created after Purchase Invoice'''.format(
|
||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))))
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
||||
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
|
||||
@ -299,10 +301,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
for d in self.get('items'):
|
||||
if not d.purchase_order:
|
||||
throw(_("""Purchase Order Required for item {0}
|
||||
To submit the invoice without purchase order please set
|
||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Order Required')),
|
||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
||||
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||
msg += "<br><br>"
|
||||
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||
throw(msg, title=_("Mandatory Purchase Order"))
|
||||
|
||||
def pr_required(self):
|
||||
stock_items = self.get_stock_items()
|
||||
@ -313,10 +316,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
for d in self.get('items'):
|
||||
if not d.purchase_receipt and d.item_code in stock_items:
|
||||
throw(_("""Purchase Receipt Required for item {0}
|
||||
To submit the invoice without purchase receipt please set
|
||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Receipt Required')),
|
||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
||||
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||
msg += "<br><br>"
|
||||
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||
|
||||
def validate_write_off_account(self):
|
||||
if self.write_off_amount and not self.write_off_account:
|
||||
|
@ -998,7 +998,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 +1040,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",
|
||||
|
@ -1,92 +1,38 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2016-07-27 17:24:24.956896",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"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
|
||||
"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",
|
||||
"fieldname": "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
|
||||
"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",
|
||||
"links": [],
|
||||
"modified": "2020-10-18 17:57:57.110257",
|
||||
"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
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -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() {
|
||||
@ -812,10 +812,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 {
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-24 19:29:05",
|
||||
@ -1955,7 +1956,7 @@
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-09 15:59:57.544736",
|
||||
"modified": "2020-10-30 13:57:45.086303",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -479,14 +479,14 @@ class SalesInvoice(SellingController):
|
||||
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
||||
msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
|
||||
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
if self.customer and account.account_type != "Receivable":
|
||||
frappe.throw(_("Please ensure {} account is a Receivable account. \
|
||||
Change the account type to Receivable or select a different account.")
|
||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
||||
msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
|
||||
msg += _("Change the account type to Receivable or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
self.party_account_currency = account.account_currency
|
||||
|
||||
@ -1141,8 +1141,10 @@ class SalesInvoice(SellingController):
|
||||
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
||||
if against_lp_entry:
|
||||
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
|
||||
frappe.throw(_('''{} can't be cancelled since the Loyalty Points earned has been redeemed.
|
||||
First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list))
|
||||
frappe.throw(
|
||||
_('''{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}''')
|
||||
.format(self.doctype, self.doctype, invoice_list)
|
||||
)
|
||||
else:
|
||||
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
||||
# Set loyalty program
|
||||
@ -1399,6 +1401,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):
|
||||
@ -1613,17 +1616,25 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
payment.type = payment_mode.type
|
||||
|
||||
doc.set('payments', [])
|
||||
invalid_modes = []
|
||||
for pos_payment_method in pos_profile.get('payments'):
|
||||
pos_payment_method = pos_payment_method.as_dict()
|
||||
|
||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||
if not payment_mode:
|
||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||
.format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
|
||||
continue
|
||||
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
|
||||
if invalid_modes:
|
||||
if invalid_modes == 1:
|
||||
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||
else:
|
||||
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||
|
||||
def get_all_mode_of_payments(doc):
|
||||
return frappe.db.sql("""
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
|
@ -237,7 +237,7 @@ class TestSubscription(unittest.TestCase):
|
||||
subscription.party_type = 'Customer'
|
||||
subscription.party = '_Test Customer'
|
||||
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
|
||||
subscription.start_date = '2018-01-01'
|
||||
subscription.start_date = add_days(nowdate(), -1000)
|
||||
subscription.insert()
|
||||
subscription.process() # generate first invoice
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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": "",
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,11 @@ def check_amount_vs_description(amount_matching, description_matching):
|
||||
continue
|
||||
|
||||
if "reference_no" in am_match and "reference_no" in des_match:
|
||||
if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]).ratio() > 70:
|
||||
# Sequence Matcher does not handle None as input
|
||||
am_reference = am_match["reference_no"] or ""
|
||||
des_reference = des_match["reference_no"] or ""
|
||||
|
||||
if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70:
|
||||
if am_match not in result:
|
||||
result.append(am_match)
|
||||
if result:
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -307,7 +307,7 @@ def get_accounts(company, root_type):
|
||||
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
|
||||
|
||||
|
||||
def filter_accounts(accounts, depth=10):
|
||||
def filter_accounts(accounts, depth=20):
|
||||
parent_children_map = {}
|
||||
accounts_by_name = {}
|
||||
for d in accounts:
|
||||
|
@ -63,6 +63,7 @@ def get_pos_entries(filters, group_by_field):
|
||||
FROM
|
||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||
WHERE
|
||||
p.docstatus = 1 and
|
||||
{group_by_mop_condition}
|
||||
{conditions}
|
||||
ORDER BY
|
||||
|
@ -796,7 +796,7 @@ def get_children(doctype, parent, company, is_root=False):
|
||||
|
||||
return acc
|
||||
|
||||
def create_payment_gateway_account(gateway):
|
||||
def create_payment_gateway_account(gateway, payment_channel="Email"):
|
||||
from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account
|
||||
|
||||
company = frappe.db.get_value("Global Defaults", None, "default_company")
|
||||
@ -831,7 +831,8 @@ def create_payment_gateway_account(gateway):
|
||||
"is_default": 1,
|
||||
"payment_gateway": gateway,
|
||||
"payment_account": bank_account.name,
|
||||
"currency": bank_account.account_currency
|
||||
"currency": bank_account.account_currency,
|
||||
"payment_channel": payment_channel
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
except frappe.DuplicateEntryError:
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -50,12 +50,11 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||
"fieldname": "depreciation_start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Depreciation Posting Date",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval:parent.doctype == 'Asset'"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -86,7 +85,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-16 12:11:30.631788",
|
||||
"modified": "2020-11-05 16:30:09.213479",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
@ -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):
|
||||
|
@ -46,26 +46,26 @@
|
||||
{
|
||||
"fieldname": "po_required",
|
||||
"fieldtype": "Select",
|
||||
"label": "Purchase Order Required for Purchase Invoice & Receipt Creation",
|
||||
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
|
||||
"options": "No\nYes"
|
||||
},
|
||||
{
|
||||
"fieldname": "pr_required",
|
||||
"fieldtype": "Select",
|
||||
"label": "Purchase Receipt Required for Purchase Invoice Creation",
|
||||
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
|
||||
"options": "No\nYes"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "maintain_same_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Maintain same rate throughout purchase cycle"
|
||||
"label": "Maintain Same Rate Throughout the Purchase Cycle"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_multiple_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Item to be added multiple times in a transaction"
|
||||
"label": "Allow Item To Be Added Multiple Times in a Transaction"
|
||||
},
|
||||
{
|
||||
"fieldname": "subcontract",
|
||||
@ -93,9 +93,10 @@
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-15 14:49:32.513611",
|
||||
"modified": "2020-10-13 12:00:23.276329",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
@ -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",
|
||||
|
@ -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": [
|
||||
{
|
||||
@ -313,34 +316,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 +361,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 +436,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "sec_warehouse",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subcontracting"
|
||||
},
|
||||
{
|
||||
"description": "Sets 'Warehouse' in each row of the Items table.",
|
||||
@ -466,6 +470,7 @@
|
||||
{
|
||||
"fieldname": "items_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-shopping-cart"
|
||||
},
|
||||
@ -598,7 +603,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_52",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "taxes",
|
||||
@ -626,10 +632,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 +648,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 +659,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 +675,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "taxes_and_charges_added",
|
||||
"fieldname": "taxes_and_charges_added",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Taxes and Charges Added",
|
||||
@ -675,6 +686,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "taxes_and_charges_deducted",
|
||||
"fieldname": "taxes_and_charges_deducted",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Taxes and Charges Deducted",
|
||||
@ -685,6 +697,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "total_taxes_and_charges",
|
||||
"fieldname": "total_taxes_and_charges",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Taxes and Charges",
|
||||
@ -694,7 +707,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 +747,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "totals_section",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Totals"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_grand_total",
|
||||
@ -902,12 +916,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 +1075,7 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "tracking_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tracking"
|
||||
"label": "Order Status"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_75",
|
||||
@ -1070,21 +1084,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-10-30 13:58:14.697921",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -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')
|
||||
|
@ -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-10-30 11:59:47.670951",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
@ -29,14 +29,12 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
|
||||
refresh: function(frm, cdt, cdn) {
|
||||
if (frm.doc.docstatus === 1) {
|
||||
frm.add_custom_button(__('Create'),
|
||||
function(){ frm.trigger("make_suppplier_quotation") }, __("Supplier Quotation"));
|
||||
|
||||
frm.add_custom_button(__("View"),
|
||||
function(){ frappe.set_route('List', 'Supplier Quotation',
|
||||
{'request_for_quotation': frm.doc.name}) }, __("Supplier Quotation"));
|
||||
frm.add_custom_button(__('Supplier Quotation'),
|
||||
function(){ frm.trigger("make_suppplier_quotation") }, __("Create"));
|
||||
|
||||
frm.add_custom_button(__("Send Supplier Emails"), function() {
|
||||
|
||||
frm.add_custom_button(__("Send Emails to Suppliers"), function() {
|
||||
frappe.call({
|
||||
method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.send_supplier_emails',
|
||||
freeze: true,
|
||||
@ -47,150 +45,82 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, __("Tools"));
|
||||
|
||||
frm.add_custom_button(__('Download PDF'), () => {
|
||||
var suppliers = [];
|
||||
const fields = [{
|
||||
fieldtype: 'Link',
|
||||
label: __('Select a Supplier'),
|
||||
fieldname: 'supplier',
|
||||
options: 'Supplier',
|
||||
reqd: 1,
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: [
|
||||
["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})]
|
||||
]
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
frappe.prompt(fields, data => {
|
||||
var child = locals[cdt][cdn]
|
||||
|
||||
var w = window.open(
|
||||
frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
|
||||
+"doctype="+encodeURIComponent(frm.doc.doctype)
|
||||
+"&name="+encodeURIComponent(frm.doc.name)
|
||||
+"&supplier="+encodeURIComponent(data.supplier)
|
||||
+"&no_letterhead=0"));
|
||||
if(!w) {
|
||||
frappe.msgprint(__("Please enable pop-ups")); return;
|
||||
}
|
||||
},
|
||||
'Download PDF for Supplier',
|
||||
'Download');
|
||||
},
|
||||
__("Tools"));
|
||||
|
||||
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
get_suppliers_button: function (frm) {
|
||||
var doc = frm.doc;
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Get Suppliers"),
|
||||
fields: [
|
||||
{
|
||||
"fieldtype": "Select", "label": __("Get Suppliers By"),
|
||||
"fieldname": "search_type",
|
||||
"options": ["Tag","Supplier Group"],
|
||||
"reqd": 1,
|
||||
onchange() {
|
||||
if(dialog.get_value('search_type') == 'Tag'){
|
||||
frappe.call({
|
||||
method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_tag',
|
||||
}).then(r => {
|
||||
dialog.set_df_property("tag", "options", r.message)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldtype": "Link", "label": __("Supplier Group"),
|
||||
"fieldname": "supplier_group",
|
||||
"options": "Supplier Group",
|
||||
"reqd": 0,
|
||||
"depends_on": "eval:doc.search_type == 'Supplier Group'"
|
||||
},
|
||||
{
|
||||
"fieldtype": "Select", "label": __("Tag"),
|
||||
"fieldname": "tag",
|
||||
"reqd": 0,
|
||||
"depends_on": "eval:doc.search_type == 'Tag'",
|
||||
},
|
||||
{
|
||||
"fieldtype": "Button", "label": __("Add All Suppliers"),
|
||||
"fieldname": "add_suppliers"
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
dialog.fields_dict.add_suppliers.$input.click(function() {
|
||||
var args = dialog.get_values();
|
||||
if(!args) return;
|
||||
dialog.hide();
|
||||
|
||||
//Remove blanks
|
||||
for (var j = 0; j < frm.doc.suppliers.length; j++) {
|
||||
if(!frm.doc.suppliers[j].hasOwnProperty("supplier")) {
|
||||
frm.get_field("suppliers").grid.grid_rows[j].remove();
|
||||
}
|
||||
}
|
||||
|
||||
function load_suppliers(r) {
|
||||
if(r.message) {
|
||||
for (var i = 0; i < r.message.length; i++) {
|
||||
var exists = false;
|
||||
if (r.message[i].constructor === Array){
|
||||
var supplier = r.message[i][0];
|
||||
} else {
|
||||
var supplier = r.message[i].name;
|
||||
}
|
||||
|
||||
for (var j = 0; j < doc.suppliers.length;j++) {
|
||||
if (supplier === doc.suppliers[j].supplier) {
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
if(!exists) {
|
||||
var d = frm.add_child('suppliers');
|
||||
d.supplier = supplier;
|
||||
frm.script_manager.trigger("supplier", d.doctype, d.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
frm.refresh_field("suppliers");
|
||||
}
|
||||
|
||||
if (args.search_type === "Tag" && args.tag) {
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
|
||||
args: {
|
||||
"doctype": "Supplier",
|
||||
"tag": args.tag
|
||||
},
|
||||
callback: load_suppliers
|
||||
});
|
||||
} else if (args.supplier_group) {
|
||||
return frappe.call({
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "Supplier",
|
||||
order_by: "name",
|
||||
fields: ["name"],
|
||||
filters: [["Supplier", "supplier_group", "=", args.supplier_group]]
|
||||
|
||||
},
|
||||
callback: load_suppliers
|
||||
});
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
|
||||
},
|
||||
make_suppplier_quotation: function(frm) {
|
||||
var doc = frm.doc;
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("For Supplier"),
|
||||
title: __("Create Supplier Quotation"),
|
||||
fields: [
|
||||
{ "fieldtype": "Select", "label": __("Supplier"),
|
||||
"fieldname": "supplier",
|
||||
"options": doc.suppliers.map(d => d.supplier),
|
||||
"reqd": 1,
|
||||
"default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
|
||||
{ "fieldtype": "Button", "label": __('Create Supplier Quotation'),
|
||||
"fieldname": "make_supplier_quotation", "cssClass": "btn-primary" },
|
||||
]
|
||||
],
|
||||
primary_action_label: __("Create"),
|
||||
primary_action: (args) => {
|
||||
if(!args) return;
|
||||
dialog.hide();
|
||||
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
||||
args: {
|
||||
"source_name": doc.name,
|
||||
"for_supplier": args.supplier
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
var doc = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
dialog.fields_dict.make_supplier_quotation.$input.click(function() {
|
||||
var args = dialog.get_values();
|
||||
if(!args) return;
|
||||
dialog.hide();
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
||||
args: {
|
||||
"source_name": doc.name,
|
||||
"for_supplier": args.supplier
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
var doc = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
dialog.show()
|
||||
},
|
||||
|
||||
@ -273,42 +203,6 @@ frappe.ui.form.on("Request for Quotation Supplier",{
|
||||
})
|
||||
},
|
||||
|
||||
download_pdf: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn]
|
||||
|
||||
var w = window.open(
|
||||
frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
|
||||
+"doctype="+encodeURIComponent(frm.doc.doctype)
|
||||
+"&name="+encodeURIComponent(frm.doc.name)
|
||||
+"&supplier_idx="+encodeURIComponent(child.idx)
|
||||
+"&no_letterhead=0"));
|
||||
if(!w) {
|
||||
frappe.msgprint(__("Please enable pop-ups")); return;
|
||||
}
|
||||
},
|
||||
no_quote: function(frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
if (d.no_quote) {
|
||||
if (d.quote_status != __('Received')) {
|
||||
frappe.model.set_value(cdt, cdn, 'quote_status', 'No Quote');
|
||||
} else {
|
||||
frappe.msgprint(__("Cannot set a received RFQ to No Quote"));
|
||||
frappe.model.set_value(cdt, cdn, 'no_quote', 0);
|
||||
}
|
||||
} else {
|
||||
d.quote_status = __('Pending');
|
||||
frm.call({
|
||||
method:"update_rfq_supplier_status",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
sup_name: d.supplier
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.refresh_field("suppliers");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
|
||||
@ -323,16 +217,19 @@ 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"));
|
||||
}, __("Get Items From"));
|
||||
|
||||
// Get items from Opportunity
|
||||
this.frm.add_custom_button(__('Opportunity'),
|
||||
function() {
|
||||
@ -341,31 +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"));
|
||||
|
||||
// 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
|
||||
@ -377,13 +283,18 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
per_ordered: ["<", 99.99]
|
||||
}
|
||||
});
|
||||
$(btn).done_working();
|
||||
d.hide();
|
||||
dialog.hide();
|
||||
}
|
||||
}
|
||||
d.show();
|
||||
}, __("Get items from"));
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}, __("Get Items From"));
|
||||
|
||||
// Get Suppliers
|
||||
this.frm.add_custom_button(__('Get Suppliers'),
|
||||
function() {
|
||||
me.get_suppliers_button(me.frm);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -393,9 +304,108 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
|
||||
tc_name: function() {
|
||||
this.get_terms();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_suppliers_button: function (frm) {
|
||||
var doc = frm.doc;
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Get Suppliers"),
|
||||
fields: [
|
||||
{
|
||||
"fieldtype": "Select", "label": __("Get Suppliers By"),
|
||||
"fieldname": "search_type",
|
||||
"options": ["Supplier Group", "Tag"],
|
||||
"reqd": 1,
|
||||
onchange() {
|
||||
if(dialog.get_value('search_type') == 'Tag'){
|
||||
frappe.call({
|
||||
method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_tag',
|
||||
}).then(r => {
|
||||
dialog.set_df_property("tag", "options", r.message)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldtype": "Link", "label": __("Supplier Group"),
|
||||
"fieldname": "supplier_group",
|
||||
"options": "Supplier Group",
|
||||
"reqd": 0,
|
||||
"depends_on": "eval:doc.search_type == 'Supplier Group'"
|
||||
},
|
||||
{
|
||||
"fieldtype": "Select", "label": __("Tag"),
|
||||
"fieldname": "tag",
|
||||
"reqd": 0,
|
||||
"depends_on": "eval:doc.search_type == 'Tag'",
|
||||
}
|
||||
],
|
||||
primary_action_label: __("Add Suppliers"),
|
||||
primary_action : (args) => {
|
||||
if(!args) return;
|
||||
dialog.hide();
|
||||
|
||||
//Remove blanks
|
||||
for (var j = 0; j < frm.doc.suppliers.length; j++) {
|
||||
if(!frm.doc.suppliers[j].hasOwnProperty("supplier")) {
|
||||
frm.get_field("suppliers").grid.grid_rows[j].remove();
|
||||
}
|
||||
}
|
||||
|
||||
function load_suppliers(r) {
|
||||
if(r.message) {
|
||||
for (var i = 0; i < r.message.length; i++) {
|
||||
var exists = false;
|
||||
if (r.message[i].constructor === Array){
|
||||
var supplier = r.message[i][0];
|
||||
} else {
|
||||
var supplier = r.message[i].name;
|
||||
}
|
||||
|
||||
for (var j = 0; j < doc.suppliers.length;j++) {
|
||||
if (supplier === doc.suppliers[j].supplier) {
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
if(!exists) {
|
||||
var d = frm.add_child('suppliers');
|
||||
d.supplier = supplier;
|
||||
frm.script_manager.trigger("supplier", d.doctype, d.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
frm.refresh_field("suppliers");
|
||||
}
|
||||
|
||||
if (args.search_type === "Tag" && args.tag) {
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
|
||||
args: {
|
||||
"doctype": "Supplier",
|
||||
"tag": args.tag
|
||||
},
|
||||
callback: load_suppliers
|
||||
});
|
||||
} else if (args.supplier_group) {
|
||||
return frappe.call({
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "Supplier",
|
||||
order_by: "name",
|
||||
fields: ["name"],
|
||||
filters: [["Supplier", "supplier_group", "=", args.supplier_group]]
|
||||
|
||||
},
|
||||
callback: load_suppliers
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
},
|
||||
});
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
$.extend(cur_frm.cscript, new erpnext.buying.RequestforQuotationController({frm: cur_frm}));
|
||||
|
@ -12,17 +12,18 @@
|
||||
"vendor",
|
||||
"column_break1",
|
||||
"transaction_date",
|
||||
"status",
|
||||
"amended_from",
|
||||
"suppliers_section",
|
||||
"suppliers",
|
||||
"get_suppliers_button",
|
||||
"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",
|
||||
@ -31,11 +32,7 @@
|
||||
"terms",
|
||||
"printing_settings",
|
||||
"select_print_heading",
|
||||
"letter_head",
|
||||
"more_info",
|
||||
"status",
|
||||
"column_break3",
|
||||
"amended_from"
|
||||
"letter_head"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -83,6 +80,7 @@
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
@ -99,16 +97,11 @@
|
||||
{
|
||||
"fieldname": "suppliers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Supplier Detail",
|
||||
"label": "Suppliers",
|
||||
"options": "Request for Quotation Supplier",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "get_suppliers_button",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Suppliers"
|
||||
},
|
||||
{
|
||||
"fieldname": "items_section",
|
||||
"fieldtype": "Section Break",
|
||||
@ -144,6 +137,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fetch_from": "email_template.response",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "message_for_supplier",
|
||||
@ -206,14 +200,6 @@
|
||||
"options": "Letter Head",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "more_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-file-text"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
@ -227,10 +213,6 @@
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
@ -275,9 +257,10 @@
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-01 14:54:50.888729",
|
||||
"modified": "2020-11-04 22:04:29.017134",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
|
@ -28,6 +28,10 @@ class RequestforQuotation(BuyingController):
|
||||
super(RequestforQuotation, self).set_qty_as_per_stock_uom()
|
||||
self.update_email_id()
|
||||
|
||||
if self.docstatus < 1:
|
||||
# after amend and save, status still shows as cancelled, until submit
|
||||
frappe.db.set(self, 'status', 'Draft')
|
||||
|
||||
def validate_duplicate_supplier(self):
|
||||
supplier_list = [d.supplier for d in self.suppliers]
|
||||
if len(supplier_list) != len(set(supplier_list)):
|
||||
@ -82,7 +86,7 @@ class RequestforQuotation(BuyingController):
|
||||
# make new user if required
|
||||
update_password_link, contact = self.update_supplier_contact(rfq_supplier, self.get_link())
|
||||
|
||||
self.update_supplier_part_no(rfq_supplier)
|
||||
self.update_supplier_part_no(rfq_supplier.supplier)
|
||||
self.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link())
|
||||
rfq_supplier.email_sent = 1
|
||||
if not rfq_supplier.contact:
|
||||
@ -93,11 +97,11 @@ class RequestforQuotation(BuyingController):
|
||||
# RFQ link for supplier portal
|
||||
return get_url("/rfq/" + self.name)
|
||||
|
||||
def update_supplier_part_no(self, args):
|
||||
self.vendor = args.supplier
|
||||
def update_supplier_part_no(self, supplier):
|
||||
self.vendor = supplier
|
||||
for item in self.items:
|
||||
item.supplier_part_no = frappe.db.get_value('Item Supplier',
|
||||
{'parent': item.item_code, 'supplier': args.supplier}, 'supplier_part_no')
|
||||
{'parent': item.item_code, 'supplier': supplier}, 'supplier_part_no')
|
||||
|
||||
def update_supplier_contact(self, rfq_supplier, link):
|
||||
'''Create a new user for the supplier if not set in contact'''
|
||||
@ -197,23 +201,22 @@ class RequestforQuotation(BuyingController):
|
||||
def update_rfq_supplier_status(self, sup_name=None):
|
||||
for supplier in self.suppliers:
|
||||
if sup_name == None or supplier.supplier == sup_name:
|
||||
if supplier.quote_status != _('No Quote'):
|
||||
quote_status = _('Received')
|
||||
for item in self.items:
|
||||
sqi_count = frappe.db.sql("""
|
||||
SELECT
|
||||
COUNT(sqi.name) as count
|
||||
FROM
|
||||
`tabSupplier Quotation Item` as sqi,
|
||||
`tabSupplier Quotation` as sq
|
||||
WHERE sq.supplier = %(supplier)s
|
||||
AND sqi.docstatus = 1
|
||||
AND sqi.request_for_quotation_item = %(rqi)s
|
||||
AND sqi.parent = sq.name""",
|
||||
{"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0]
|
||||
if (sqi_count.count) == 0:
|
||||
quote_status = _('Pending')
|
||||
supplier.quote_status = quote_status
|
||||
quote_status = _('Received')
|
||||
for item in self.items:
|
||||
sqi_count = frappe.db.sql("""
|
||||
SELECT
|
||||
COUNT(sqi.name) as count
|
||||
FROM
|
||||
`tabSupplier Quotation Item` as sqi,
|
||||
`tabSupplier Quotation` as sq
|
||||
WHERE sq.supplier = %(supplier)s
|
||||
AND sqi.docstatus = 1
|
||||
AND sqi.request_for_quotation_item = %(rqi)s
|
||||
AND sqi.parent = sq.name""",
|
||||
{"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0]
|
||||
if (sqi_count.count) == 0:
|
||||
quote_status = _('Pending')
|
||||
supplier.quote_status = quote_status
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -322,16 +325,15 @@ def create_rfq_items(sq_doc, supplier, data):
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pdf(doctype, name, supplier_idx):
|
||||
doc = get_rfq_doc(doctype, name, supplier_idx)
|
||||
def get_pdf(doctype, name, supplier):
|
||||
doc = get_rfq_doc(doctype, name, supplier)
|
||||
if doc:
|
||||
download_pdf(doctype, name, doc=doc)
|
||||
|
||||
def get_rfq_doc(doctype, name, supplier_idx):
|
||||
if cint(supplier_idx):
|
||||
def get_rfq_doc(doctype, name, supplier):
|
||||
if supplier:
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
args = doc.get('suppliers')[cint(supplier_idx) - 1]
|
||||
doc.update_supplier_part_no(args)
|
||||
doc.update_supplier_part_no(supplier)
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -25,14 +25,10 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
|
||||
sq.submit()
|
||||
|
||||
# No Quote first supplier quotation
|
||||
rfq.get('suppliers')[1].no_quote = 1
|
||||
rfq.get('suppliers')[1].quote_status = 'No Quote'
|
||||
|
||||
rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier)
|
||||
|
||||
self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Received')
|
||||
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'No Quote')
|
||||
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
|
||||
|
||||
def test_make_supplier_quotation(self):
|
||||
rfq = make_request_for_quotation()
|
||||
|
@ -84,9 +84,6 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
||||
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
frappe.click_check('No Quote');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.cur_grid.toggle_view();
|
||||
@ -125,7 +122,6 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Received");
|
||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.no_quote == 1);
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
@ -27,10 +27,11 @@
|
||||
"stock_qty",
|
||||
"warehouse_and_reference",
|
||||
"warehouse",
|
||||
"project_name",
|
||||
"col_break4",
|
||||
"material_request",
|
||||
"material_request_item",
|
||||
"section_break_24",
|
||||
"project_name",
|
||||
"section_break_23",
|
||||
"page_break"
|
||||
],
|
||||
@ -161,7 +162,7 @@
|
||||
{
|
||||
"fieldname": "project_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project Name",
|
||||
"label": "Project",
|
||||
"options": "Project",
|
||||
"print_hide": 1
|
||||
},
|
||||
@ -249,11 +250,18 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_24",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-12 19:10:36.333441",
|
||||
"modified": "2020-09-24 17:26:46.276934",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation Item",
|
||||
|
@ -5,23 +5,23 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"send_email",
|
||||
"email_sent",
|
||||
"supplier",
|
||||
"contact",
|
||||
"no_quote",
|
||||
"quote_status",
|
||||
"column_break_3",
|
||||
"supplier_name",
|
||||
"email_id",
|
||||
"download_pdf"
|
||||
"send_email",
|
||||
"email_sent"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"columns": 2,
|
||||
"default": "1",
|
||||
"fieldname": "send_email",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Send Email"
|
||||
},
|
||||
{
|
||||
@ -35,7 +35,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 4,
|
||||
"columns": 2,
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -45,7 +45,7 @@
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"columns": 3,
|
||||
"columns": 2,
|
||||
"fieldname": "contact",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -55,19 +55,11 @@
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
|
||||
"fieldname": "no_quote",
|
||||
"fieldtype": "Check",
|
||||
"label": "No Quote"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",
|
||||
"depends_on": "eval:doc.docstatus >= 1",
|
||||
"fieldname": "quote_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Quote Status",
|
||||
"options": "Pending\nReceived\nNo Quote",
|
||||
"options": "Pending\nReceived",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -90,17 +82,12 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Email Id",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "download_pdf",
|
||||
"fieldtype": "Button",
|
||||
"label": "Download PDF"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-28 19:31:11.855588",
|
||||
"modified": "2020-11-04 22:01:43.832942",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation Supplier",
|
||||
|
@ -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)
|
@ -37,16 +37,18 @@ 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"));
|
||||
|
||||
this.frm.add_custom_button(__("Request for Quotation"),
|
||||
function() {
|
||||
@ -58,16 +60,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"));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:45",
|
||||
@ -807,7 +808,7 @@
|
||||
"idx": 29,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-01 20:56:17.932007",
|
||||
"modified": "2020-10-30 13:58:33.043971",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
@ -91,12 +91,7 @@ class SupplierQuotation(BuyingController):
|
||||
for my_item in self.items) if include_me else 0
|
||||
if (sqi_count.count + self_count) == 0:
|
||||
quote_status = _('Pending')
|
||||
if quote_status == _('Received') and doc_sup.quote_status == _('No Quote'):
|
||||
frappe.msgprint(_("{0} indicates that {1} will not provide a quotation, but all items \
|
||||
have been quoted. Updating the RFQ quote status.").format(doc.name, self.supplier))
|
||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
|
||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'no_quote', 0)
|
||||
elif doc_sup.quote_status != _('No Quote'):
|
||||
|
||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
|
||||
|
||||
def get_list_context(context=None):
|
||||
|
@ -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",
|
||||
|
@ -263,6 +263,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 +302,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 +324,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 +340,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
|
||||
@ -946,8 +963,10 @@ def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, c
|
||||
company_currency = frappe.get_cached_value('Company', company, "default_currency")
|
||||
|
||||
if not conversion_rate:
|
||||
throw(_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format(
|
||||
conversion_rate_label, currency, company_currency))
|
||||
throw(
|
||||
_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.")
|
||||
.format(conversion_rate_label, currency, company_currency)
|
||||
)
|
||||
|
||||
|
||||
def validate_taxes_and_charges(tax):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user