Merge branch 'develop' into skip_tcs
This commit is contained in:
commit
6b2dbdd394
38
.github/workflows/release_notes.yml
vendored
Normal file
38
.github/workflows/release_notes.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# This action:
|
||||||
|
#
|
||||||
|
# 1. Generates release notes using github API.
|
||||||
|
# 2. Strips unnecessary info like chore/style etc from notes.
|
||||||
|
# 3. Updates release info.
|
||||||
|
|
||||||
|
# This action needs to be maintained on all branches that do releases.
|
||||||
|
|
||||||
|
name: 'Release Notes'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag_name:
|
||||||
|
description: 'Tag of release like v13.0.0'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
regen-notes:
|
||||||
|
name: 'Regenerate release notes'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Update notes
|
||||||
|
run: |
|
||||||
|
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
|
||||||
|
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
|
||||||
|
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}
|
||||||
8
.github/workflows/server-tests-mariadb.yml
vendored
8
.github/workflows/server-tests-mariadb.yml
vendored
@ -7,11 +7,9 @@ on:
|
|||||||
- '**.css'
|
- '**.css'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
- '**.html'
|
||||||
push:
|
schedule:
|
||||||
branches: [ develop ]
|
# Run everday at midnight UTC / 5:30 IST
|
||||||
paths-ignore:
|
- cron: "0 0 * * *"
|
||||||
- '**.js'
|
|
||||||
- '**.md'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
user:
|
user:
|
||||||
|
|||||||
@ -4,18 +4,19 @@
|
|||||||
"creation": "2020-07-17 11:25:34.593061",
|
"creation": "2020-07-17 11:25:34.593061",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
|
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-22 12:24:49.144210",
|
"modified": "2023-07-19 13:13:13.307073",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Budget Variance",
|
"name": "Budget Variance",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Budget Variance Report",
|
"report_name": "Budget Variance Report",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Bar",
|
"type": "Bar",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
|||||||
@ -4,18 +4,19 @@
|
|||||||
"creation": "2020-07-17 11:25:34.448572",
|
"creation": "2020-07-17 11:25:34.448572",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
|
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-22 12:33:48.888943",
|
"modified": "2023-07-19 13:08:56.470390",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Profit and Loss",
|
"name": "Profit and Loss",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Profit and Loss Statement",
|
"report_name": "Profit and Loss Statement",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Bar",
|
"type": "Bar",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
|||||||
@ -14,10 +14,8 @@ class AccountClosingBalance(Document):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def make_closing_entries(closing_entries, voucher_name):
|
def make_closing_entries(closing_entries, voucher_name, company, closing_date):
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
company = closing_entries[0].get("company")
|
|
||||||
closing_date = closing_entries[0].get("closing_date")
|
|
||||||
|
|
||||||
previous_closing_entries = get_previous_closing_entries(
|
previous_closing_entries = get_previous_closing_entries(
|
||||||
company, closing_date, accounting_dimensions
|
company, closing_date, accounting_dimensions
|
||||||
|
|||||||
@ -271,6 +271,12 @@ def get_dimensions(with_cost_center_and_project=False):
|
|||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(with_cost_center_and_project, str):
|
||||||
|
if with_cost_center_and_project.lower().strip() == "true":
|
||||||
|
with_cost_center_and_project = True
|
||||||
|
else:
|
||||||
|
with_cost_center_and_project = False
|
||||||
|
|
||||||
if with_cost_center_and_project:
|
if with_cost_center_and_project:
|
||||||
dimension_filters.extend(
|
dimension_filters.extend(
|
||||||
[
|
[
|
||||||
|
|||||||
@ -20,5 +20,11 @@ frappe.ui.form.on('Accounting Period', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.set_query("document_type", "closed_documents", () => {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_doctypes_for_closing",
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,6 +11,10 @@ class OverlapError(frappe.ValidationError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ClosedAccountingPeriod(frappe.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AccountingPeriod(Document):
|
class AccountingPeriod(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_overlap()
|
self.validate_overlap()
|
||||||
@ -65,3 +69,42 @@ class AccountingPeriod(Document):
|
|||||||
"closed_documents",
|
"closed_documents",
|
||||||
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
|
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_accounting_period_on_doc_save(doc, method=None):
|
||||||
|
if doc.doctype == "Bank Clearance":
|
||||||
|
return
|
||||||
|
elif doc.doctype == "Asset":
|
||||||
|
if doc.is_existing_asset:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
date = doc.available_for_use_date
|
||||||
|
elif doc.doctype == "Asset Repair":
|
||||||
|
date = doc.completion_date
|
||||||
|
else:
|
||||||
|
date = doc.posting_date
|
||||||
|
|
||||||
|
ap = frappe.qb.DocType("Accounting Period")
|
||||||
|
cd = frappe.qb.DocType("Closed Document")
|
||||||
|
|
||||||
|
accounting_period = (
|
||||||
|
frappe.qb.from_(ap)
|
||||||
|
.from_(cd)
|
||||||
|
.select(ap.name)
|
||||||
|
.where(
|
||||||
|
(ap.name == cd.parent)
|
||||||
|
& (ap.company == doc.company)
|
||||||
|
& (cd.closed == 1)
|
||||||
|
& (cd.document_type == doc.doctype)
|
||||||
|
& (date >= ap.start_date)
|
||||||
|
& (date <= ap.end_date)
|
||||||
|
)
|
||||||
|
).run(as_dict=1)
|
||||||
|
|
||||||
|
if accounting_period:
|
||||||
|
frappe.throw(
|
||||||
|
_("You cannot create a {0} within the closed Accounting Period {1}").format(
|
||||||
|
doc.doctype, frappe.bold(accounting_period[0]["name"])
|
||||||
|
),
|
||||||
|
ClosedAccountingPeriod,
|
||||||
|
)
|
||||||
|
|||||||
@ -6,9 +6,11 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_months, nowdate
|
from frappe.utils import add_months, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
|
from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
||||||
|
ClosedAccountingPeriod,
|
||||||
|
OverlapError,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
|
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
test_dependencies = ["Item"]
|
||||||
|
|
||||||
@ -33,9 +35,9 @@ class TestAccountingPeriod(unittest.TestCase):
|
|||||||
ap1.save()
|
ap1.save()
|
||||||
|
|
||||||
doc = create_sales_invoice(
|
doc = create_sales_invoice(
|
||||||
do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
||||||
)
|
)
|
||||||
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
self.assertRaises(ClosedAccountingPeriod, doc.save)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for d in frappe.get_all("Accounting Period"):
|
for d in frappe.get_all("Accounting Period"):
|
||||||
|
|||||||
@ -8,9 +8,6 @@ frappe.ui.form.on('Bank', {
|
|||||||
},
|
},
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
add_fields_to_mapping_table(frm);
|
add_fields_to_mapping_table(frm);
|
||||||
|
|
||||||
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' };
|
|
||||||
|
|
||||||
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
|
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
|
||||||
|
|
||||||
if (frm.doc.__islocal) {
|
if (frm.doc.__islocal) {
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Dunning", {
|
frappe.ui.form.on("Dunning", {
|
||||||
setup: function (frm) {
|
setup: function (frm) {
|
||||||
frm.set_query("sales_invoice", () => {
|
frm.set_query("sales_invoice", "overdue_payments", () => {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
|
customer: frm.doc.customer,
|
||||||
outstanding_amount: [">", 0],
|
outstanding_amount: [">", 0],
|
||||||
status: "Overdue"
|
status: "Overdue"
|
||||||
},
|
},
|
||||||
@ -22,14 +23,24 @@ frappe.ui.form.on("Dunning", {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
frm.set_query("cost_center", () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
is_group: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("contact_person", erpnext.queries.contact_query);
|
||||||
|
frm.set_query("customer_address", erpnext.queries.address_query);
|
||||||
|
frm.set_query("company_address", erpnext.queries.company_address_query);
|
||||||
|
|
||||||
|
// cannot add rows manually, only via button "Fetch Overdue Payments"
|
||||||
|
frm.set_df_property("overdue_payments", "cannot_add_rows", true);
|
||||||
},
|
},
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
|
frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
|
||||||
frm.set_df_property(
|
|
||||||
"sales_invoice",
|
|
||||||
"read_only",
|
|
||||||
frm.doc.__islocal ? 0 : 1
|
|
||||||
);
|
|
||||||
if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
|
if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
|
||||||
frm.add_custom_button(__("Resolve"), () => {
|
frm.add_custom_button(__("Resolve"), () => {
|
||||||
frm.set_value("status", "Resolved");
|
frm.set_value("status", "Resolved");
|
||||||
@ -40,42 +51,111 @@ frappe.ui.form.on("Dunning", {
|
|||||||
__("Payment"),
|
__("Payment"),
|
||||||
function () {
|
function () {
|
||||||
frm.events.make_payment_entry(frm);
|
frm.events.make_payment_entry(frm);
|
||||||
},__("Create")
|
}, __("Create")
|
||||||
);
|
);
|
||||||
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.docstatus > 0) {
|
if (frm.doc.docstatus === 0) {
|
||||||
frm.add_custom_button(__('Ledger'), function() {
|
frm.add_custom_button(__("Fetch Overdue Payments"), () => {
|
||||||
frappe.route_options = {
|
erpnext.utils.map_current_doc({
|
||||||
"voucher_no": frm.doc.name,
|
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
|
||||||
"from_date": frm.doc.posting_date,
|
source_doctype: "Sales Invoice",
|
||||||
"to_date": frm.doc.posting_date,
|
date_field: "due_date",
|
||||||
"company": frm.doc.company,
|
target: frm,
|
||||||
"show_cancelled_entries": frm.doc.docstatus === 2
|
setters: {
|
||||||
};
|
customer: frm.doc.customer || undefined,
|
||||||
frappe.set_route("query-report", "General Ledger");
|
},
|
||||||
}, __('View'));
|
get_query_filters: {
|
||||||
|
docstatus: 1,
|
||||||
|
status: "Overdue",
|
||||||
|
company: frm.doc.company
|
||||||
|
},
|
||||||
|
allow_child_item_selection: true,
|
||||||
|
child_fieldname: "payment_schedule",
|
||||||
|
child_columns: ["due_date", "outstanding"],
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frappe.dynamic_link = { doc: frm.doc, fieldname: 'customer', doctype: 'Customer' };
|
||||||
|
|
||||||
|
frm.toggle_display("customer_name", (frm.doc.customer_name && frm.doc.customer_name !== frm.doc.customer));
|
||||||
},
|
},
|
||||||
overdue_days: function (frm) {
|
// When multiple companies are set up. in case company name is changed set default company address
|
||||||
frappe.db.get_value(
|
company: function (frm) {
|
||||||
"Dunning Type",
|
if (frm.doc.company) {
|
||||||
{
|
frappe.call({
|
||||||
start_day: ["<", frm.doc.overdue_days],
|
method: "erpnext.setup.doctype.company.company.get_default_company_address",
|
||||||
end_day: [">=", frm.doc.overdue_days],
|
args: { name: frm.doc.company, existing_address: frm.doc.company_address || "" },
|
||||||
},
|
debounce: 2000,
|
||||||
"dunning_type",
|
callback: function (r) {
|
||||||
(r) => {
|
frm.set_value("company_address", r && r.message || "");
|
||||||
if (r) {
|
}
|
||||||
frm.set_value("dunning_type", r.dunning_type);
|
});
|
||||||
} else {
|
|
||||||
frm.set_value("dunning_type", "");
|
if (frm.fields_dict.currency) {
|
||||||
frm.set_value("rate_of_interest", "");
|
const company_currency = erpnext.get_currency(frm.doc.company);
|
||||||
frm.set_value("dunning_fee", "");
|
|
||||||
|
if (!frm.doc.currency) {
|
||||||
|
frm.set_value("currency", company_currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.currency == company_currency) {
|
||||||
|
frm.set_value("conversion_rate", 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
const company_doc = frappe.get_doc(":Company", frm.doc.company);
|
||||||
|
if (company_doc.default_letter_head) {
|
||||||
|
if (frm.fields_dict.letter_head) {
|
||||||
|
frm.set_value("letter_head", company_doc.default_letter_head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currency: function (frm) {
|
||||||
|
// this.set_dynamic_labels();
|
||||||
|
const company_currency = erpnext.get_currency(frm.doc.company);
|
||||||
|
// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
|
||||||
|
if (frm.doc.currency && frm.doc.currency !== company_currency) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.setup.utils.get_exchange_rate",
|
||||||
|
args: {
|
||||||
|
transaction_date: frm.doc.posting_date,
|
||||||
|
from_currency: frm.doc.currency,
|
||||||
|
to_currency: company_currency,
|
||||||
|
args: "for_selling"
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Fetching exchange rates ..."),
|
||||||
|
callback: function(r) {
|
||||||
|
const exchange_rate = flt(r.message);
|
||||||
|
if (exchange_rate != frm.doc.conversion_rate) {
|
||||||
|
frm.set_value("conversion_rate", exchange_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
frm.trigger("conversion_rate");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customer: (frm) => {
|
||||||
|
erpnext.utils.get_party_details(frm);
|
||||||
|
},
|
||||||
|
conversion_rate: function (frm) {
|
||||||
|
if (frm.doc.currency === erpnext.get_currency(frm.doc.company)) {
|
||||||
|
frm.set_value("conversion_rate", 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make read only if Accounts Settings doesn't allow stale rates
|
||||||
|
frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
|
||||||
|
},
|
||||||
|
customer_address: function (frm) {
|
||||||
|
erpnext.utils.get_address_display(frm, "customer_address");
|
||||||
|
},
|
||||||
|
company_address: function (frm) {
|
||||||
|
erpnext.utils.get_address_display(frm, "company_address");
|
||||||
},
|
},
|
||||||
dunning_type: function (frm) {
|
dunning_type: function (frm) {
|
||||||
frm.trigger("get_dunning_letter_text");
|
frm.trigger("get_dunning_letter_text");
|
||||||
@ -87,7 +167,7 @@ frappe.ui.form.on("Dunning", {
|
|||||||
if (frm.doc.dunning_type) {
|
if (frm.doc.dunning_type) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method:
|
method:
|
||||||
"erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
|
"erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
|
||||||
args: {
|
args: {
|
||||||
dunning_type: frm.doc.dunning_type,
|
dunning_type: frm.doc.dunning_type,
|
||||||
language: frm.doc.language,
|
language: frm.doc.language,
|
||||||
@ -106,49 +186,62 @@ frappe.ui.form.on("Dunning", {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
due_date: function (frm) {
|
|
||||||
frm.trigger("calculate_overdue_days");
|
|
||||||
},
|
|
||||||
posting_date: function (frm) {
|
posting_date: function (frm) {
|
||||||
frm.trigger("calculate_overdue_days");
|
frm.trigger("calculate_overdue_days");
|
||||||
},
|
},
|
||||||
rate_of_interest: function (frm) {
|
rate_of_interest: function (frm) {
|
||||||
frm.trigger("calculate_interest_and_amount");
|
frm.trigger("calculate_interest");
|
||||||
},
|
|
||||||
outstanding_amount: function (frm) {
|
|
||||||
frm.trigger("calculate_interest_and_amount");
|
|
||||||
},
|
|
||||||
interest_amount: function (frm) {
|
|
||||||
frm.trigger("calculate_interest_and_amount");
|
|
||||||
},
|
},
|
||||||
dunning_fee: function (frm) {
|
dunning_fee: function (frm) {
|
||||||
frm.trigger("calculate_interest_and_amount");
|
frm.trigger("calculate_totals");
|
||||||
},
|
},
|
||||||
sales_invoice: function (frm) {
|
overdue_payments_add: function (frm) {
|
||||||
frm.trigger("calculate_overdue_days");
|
frm.trigger("calculate_totals");
|
||||||
|
},
|
||||||
|
overdue_payments_remove: function (frm) {
|
||||||
|
frm.trigger("calculate_totals");
|
||||||
},
|
},
|
||||||
calculate_overdue_days: function (frm) {
|
calculate_overdue_days: function (frm) {
|
||||||
if (frm.doc.posting_date && frm.doc.due_date) {
|
frm.doc.overdue_payments.forEach((row) => {
|
||||||
const overdue_days = moment(frm.doc.posting_date).diff(
|
if (frm.doc.posting_date && row.due_date) {
|
||||||
frm.doc.due_date,
|
const overdue_days = moment(frm.doc.posting_date).diff(
|
||||||
"days"
|
row.due_date,
|
||||||
);
|
"days"
|
||||||
frm.set_value("overdue_days", overdue_days);
|
);
|
||||||
}
|
frappe.model.set_value(row.doctype, row.name, "overdue_days", overdue_days);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
calculate_interest_and_amount: function (frm) {
|
calculate_interest: function (frm) {
|
||||||
const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100;
|
frm.doc.overdue_payments.forEach((row) => {
|
||||||
const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount'));
|
const interest_per_day = frm.doc.rate_of_interest / 100 / 365;
|
||||||
const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount'));
|
const interest = flt((interest_per_day * row.overdue_days * row.outstanding), precision("interest", row));
|
||||||
const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total'));
|
frappe.model.set_value(row.doctype, row.name, "interest", interest);
|
||||||
frm.set_value("interest_amount", interest_amount);
|
});
|
||||||
frm.set_value("dunning_amount", dunning_amount);
|
},
|
||||||
frm.set_value("grand_total", grand_total);
|
calculate_totals: function (frm) {
|
||||||
|
const total_interest = frm.doc.overdue_payments
|
||||||
|
.reduce((prev, cur) => prev + cur.interest, 0);
|
||||||
|
const total_outstanding = frm.doc.overdue_payments
|
||||||
|
.reduce((prev, cur) => prev + cur.outstanding, 0);
|
||||||
|
const dunning_amount = total_interest + frm.doc.dunning_fee;
|
||||||
|
const base_dunning_amount = dunning_amount * frm.doc.conversion_rate;
|
||||||
|
const grand_total = total_outstanding + dunning_amount;
|
||||||
|
|
||||||
|
function setWithPrecison(field, value) {
|
||||||
|
frm.set_value(field, flt(value, precision(field)));
|
||||||
|
}
|
||||||
|
|
||||||
|
setWithPrecison("total_outstanding", total_outstanding);
|
||||||
|
setWithPrecison("total_interest", total_interest);
|
||||||
|
setWithPrecison("dunning_amount", dunning_amount);
|
||||||
|
setWithPrecison("base_dunning_amount", base_dunning_amount);
|
||||||
|
setWithPrecison("grand_total", grand_total);
|
||||||
},
|
},
|
||||||
make_payment_entry: function (frm) {
|
make_payment_entry: function (frm) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method:
|
method:
|
||||||
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
|
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
|
||||||
args: {
|
args: {
|
||||||
dt: frm.doc.doctype,
|
dt: frm.doc.doctype,
|
||||||
dn: frm.doc.name,
|
dn: frm.doc.name,
|
||||||
@ -160,3 +253,9 @@ frappe.ui.form.on("Dunning", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Overdue Payment", {
|
||||||
|
interest: function (frm) {
|
||||||
|
frm.trigger("calculate_totals");
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -2,49 +2,60 @@
|
|||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_events_in_timeline": 1,
|
"allow_events_in_timeline": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
|
"beta": 1,
|
||||||
"creation": "2019-07-05 16:34:31.013238",
|
"creation": "2019-07-05 16:34:31.013238",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"sales_invoice",
|
|
||||||
"customer",
|
"customer",
|
||||||
"customer_name",
|
"customer_name",
|
||||||
"outstanding_amount",
|
|
||||||
"currency",
|
|
||||||
"conversion_rate",
|
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"posting_time",
|
"posting_time",
|
||||||
"due_date",
|
"status",
|
||||||
"overdue_days",
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
|
"column_break_11",
|
||||||
|
"conversion_rate",
|
||||||
"address_and_contact_section",
|
"address_and_contact_section",
|
||||||
|
"customer_address",
|
||||||
"address_display",
|
"address_display",
|
||||||
|
"contact_person",
|
||||||
"contact_display",
|
"contact_display",
|
||||||
|
"column_break_16",
|
||||||
|
"company_address",
|
||||||
|
"company_address_display",
|
||||||
"contact_mobile",
|
"contact_mobile",
|
||||||
"contact_email",
|
"contact_email",
|
||||||
"column_break_18",
|
|
||||||
"company_address_display",
|
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"dunning_type",
|
"dunning_type",
|
||||||
"dunning_fee",
|
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
"rate_of_interest",
|
"rate_of_interest",
|
||||||
"interest_amount",
|
|
||||||
"section_break_12",
|
"section_break_12",
|
||||||
"dunning_amount",
|
"overdue_payments",
|
||||||
"grand_total",
|
"section_break_28",
|
||||||
"income_account",
|
"total_interest",
|
||||||
|
"dunning_fee",
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"status",
|
"dunning_amount",
|
||||||
"printing_setting_section",
|
"base_dunning_amount",
|
||||||
|
"section_break_32",
|
||||||
|
"spacer",
|
||||||
|
"column_break_33",
|
||||||
|
"total_outstanding",
|
||||||
|
"grand_total",
|
||||||
|
"printing_settings_section",
|
||||||
"language",
|
"language",
|
||||||
"body_text",
|
"body_text",
|
||||||
"column_break_22",
|
"column_break_22",
|
||||||
"letter_head",
|
"letter_head",
|
||||||
"closing_text",
|
"closing_text",
|
||||||
|
"accounting_details_section",
|
||||||
|
"income_account",
|
||||||
|
"column_break_48",
|
||||||
|
"cost_center",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -60,32 +71,17 @@
|
|||||||
"fieldname": "naming_series",
|
"fieldname": "naming_series",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Series",
|
"label": "Series",
|
||||||
"options": "DUNN-.MM.-.YY.-"
|
"options": "DUNN-.MM.-.YY.-",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sales_invoice",
|
"fetch_from": "customer.customer_name",
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Sales Invoice",
|
|
||||||
"options": "Sales Invoice",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.customer_name",
|
|
||||||
"fieldname": "customer_name",
|
"fieldname": "customer_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Customer Name",
|
"label": "Customer Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.outstanding_amount",
|
|
||||||
"fieldname": "outstanding_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Outstanding Amount",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
@ -94,13 +90,8 @@
|
|||||||
"default": "Today",
|
"default": "Today",
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Date"
|
"label": "Date",
|
||||||
},
|
"reqd": 1
|
||||||
{
|
|
||||||
"fieldname": "overdue_days",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "Overdue Days",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_6",
|
"fieldname": "section_break_6",
|
||||||
@ -112,16 +103,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Dunning Type",
|
"label": "Dunning Type",
|
||||||
"options": "Dunning Type",
|
"options": "Dunning Type"
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "interest_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Interest Amount",
|
|
||||||
"precision": "2",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_8",
|
"fieldname": "column_break_8",
|
||||||
@ -134,6 +116,7 @@
|
|||||||
"fieldname": "dunning_fee",
|
"fieldname": "dunning_fee",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Dunning Fee",
|
"label": "Dunning Fee",
|
||||||
|
"options": "currency",
|
||||||
"precision": "2"
|
"precision": "2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -144,36 +127,24 @@
|
|||||||
"fieldname": "column_break_17",
|
"fieldname": "column_break_17",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "printing_setting_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Printing Setting"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "language",
|
"fieldname": "language",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Print Language",
|
"label": "Print Language",
|
||||||
"options": "Language"
|
"options": "Language",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "letter_head",
|
"fieldname": "letter_head",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Letter Head",
|
"label": "Letter Head",
|
||||||
"options": "Letter Head"
|
"options": "Letter Head",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_22",
|
"fieldname": "column_break_22",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.currency",
|
|
||||||
"fieldname": "currency",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Currency",
|
|
||||||
"options": "Currency",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -183,14 +154,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"default": "{customer_name}",
|
|
||||||
"fieldname": "title",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Title"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "body_text",
|
"fieldname": "body_text",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
@ -201,13 +164,6 @@
|
|||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Closing Text"
|
"label": "Closing Text"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.due_date",
|
|
||||||
"fieldname": "due_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "Due Date",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "posting_time",
|
"fieldname": "posting_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
@ -222,26 +178,24 @@
|
|||||||
"label": "Rate of Interest (%) Yearly"
|
"label": "Rate of Interest (%) Yearly"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"collapsible": 1,
|
||||||
"fieldname": "address_and_contact_section",
|
"fieldname": "address_and_contact_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Address and Contact"
|
"label": "Address and Contact"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.address_display",
|
|
||||||
"fieldname": "address_display",
|
"fieldname": "address_display",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Address",
|
"label": "Address",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.contact_display",
|
|
||||||
"fieldname": "contact_display",
|
"fieldname": "contact_display",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Contact",
|
"label": "Contact",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.contact_mobile",
|
|
||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
@ -249,18 +203,12 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_18",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "sales_invoice.company_address_display",
|
|
||||||
"fieldname": "company_address_display",
|
"fieldname": "company_address_display",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Company Address",
|
"label": "Company Address Display",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.contact_email",
|
|
||||||
"fieldname": "contact_email",
|
"fieldname": "contact_email",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
@ -268,18 +216,18 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.customer",
|
|
||||||
"fieldname": "customer",
|
"fieldname": "customer",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Customer",
|
"label": "Customer",
|
||||||
"options": "Customer",
|
"options": "Customer",
|
||||||
"read_only": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "grand_total",
|
"fieldname": "grand_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Grand Total",
|
"label": "Grand Total",
|
||||||
|
"options": "currency",
|
||||||
"precision": "2",
|
"precision": "2",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -290,33 +238,150 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "Draft\nResolved\nUnresolved\nCancelled"
|
"options": "Draft\nResolved\nUnresolved\nCancelled",
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "dunning_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Dunning Amount",
|
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "For dunning fee and interest",
|
||||||
|
"fetch_from": "dunning_type.income_account",
|
||||||
"fieldname": "income_account",
|
"fieldname": "income_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Income Account",
|
"label": "Income Account",
|
||||||
"options": "Account"
|
"options": "Account",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "overdue_payments",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Overdue Payments",
|
||||||
|
"options": "Overdue Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_28",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "total_interest",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Interest",
|
||||||
|
"options": "currency",
|
||||||
|
"precision": "2",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_outstanding",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Outstanding",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Customer Address",
|
||||||
|
"options": "Address",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "contact_person",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Contact Person",
|
||||||
|
"options": "Contact",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "dunning_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Dunning Amount",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "accounting_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "dunning_type.cost_center",
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "printing_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Printing Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_32",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_33",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "spacer",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Spacer",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"report_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_16",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company Address",
|
||||||
|
"options": "Address",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_9",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_11",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_invoice.conversion_rate",
|
|
||||||
"fieldname": "conversion_rate",
|
"fieldname": "conversion_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 1,
|
"label": "Conversion Rate"
|
||||||
"label": "Conversion Rate",
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "base_dunning_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Dunning Amount (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_48",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-03 16:24:01.677026",
|
"modified": "2023-06-15 15:46:53.865712",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning",
|
"name": "Dunning",
|
||||||
|
|||||||
@ -1,131 +1,150 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
"""
|
||||||
|
# Accounting
|
||||||
|
|
||||||
|
1. Payment of outstanding invoices with dunning amount
|
||||||
|
|
||||||
|
- Debit full amount to bank
|
||||||
|
- Credit invoiced amount to receivables
|
||||||
|
- Credit dunning amount to interest and similar revenue
|
||||||
|
|
||||||
|
-> Resolves dunning automatically
|
||||||
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint, flt, getdate
|
from frappe import _
|
||||||
|
from frappe.contacts.doctype.address.address import get_address_display
|
||||||
|
from frappe.utils import getdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|
||||||
get_accounting_dimensions,
|
|
||||||
)
|
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
|
|
||||||
|
|
||||||
class Dunning(AccountsController):
|
class Dunning(AccountsController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_overdue_days()
|
self.validate_same_currency()
|
||||||
self.validate_amount()
|
self.validate_overdue_payments()
|
||||||
if not self.income_account:
|
self.validate_totals()
|
||||||
self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account")
|
self.set_party_details()
|
||||||
|
self.set_dunning_level()
|
||||||
|
|
||||||
def validate_overdue_days(self):
|
def validate_same_currency(self):
|
||||||
self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
|
"""
|
||||||
|
Throw an error if invoice currency differs from dunning currency.
|
||||||
|
"""
|
||||||
|
for row in self.overdue_payments:
|
||||||
|
invoice_currency = frappe.get_value("Sales Invoice", row.sales_invoice, "currency")
|
||||||
|
if invoice_currency != self.currency:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"The currency of invoice {} ({}) is different from the currency of this dunning ({})."
|
||||||
|
).format(row.sales_invoice, invoice_currency, self.currency)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_amount(self):
|
def validate_overdue_payments(self):
|
||||||
amounts = calculate_interest_and_amount(
|
daily_interest = self.rate_of_interest / 100 / 365
|
||||||
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days
|
|
||||||
|
for row in self.overdue_payments:
|
||||||
|
row.overdue_days = (getdate(self.posting_date) - getdate(row.due_date)).days or 0
|
||||||
|
row.interest = row.outstanding * daily_interest * row.overdue_days
|
||||||
|
|
||||||
|
def validate_totals(self):
|
||||||
|
self.total_outstanding = sum(row.outstanding for row in self.overdue_payments)
|
||||||
|
self.total_interest = sum(row.interest for row in self.overdue_payments)
|
||||||
|
self.dunning_amount = self.total_interest + self.dunning_fee
|
||||||
|
self.base_dunning_amount = self.dunning_amount * self.conversion_rate
|
||||||
|
self.grand_total = self.total_outstanding + self.dunning_amount
|
||||||
|
|
||||||
|
def set_party_details(self):
|
||||||
|
from erpnext.accounts.party import _get_party_details
|
||||||
|
|
||||||
|
party_details = _get_party_details(
|
||||||
|
self.customer,
|
||||||
|
ignore_permissions=self.flags.ignore_permissions,
|
||||||
|
doctype=self.doctype,
|
||||||
|
company=self.company,
|
||||||
|
posting_date=self.get("posting_date"),
|
||||||
|
fetch_payment_terms_template=False,
|
||||||
|
party_address=self.customer_address,
|
||||||
|
company_address=self.get("company_address"),
|
||||||
)
|
)
|
||||||
if self.interest_amount != amounts.get("interest_amount"):
|
for field in [
|
||||||
self.interest_amount = flt(amounts.get("interest_amount"), self.precision("interest_amount"))
|
"customer_address",
|
||||||
if self.dunning_amount != amounts.get("dunning_amount"):
|
"address_display",
|
||||||
self.dunning_amount = flt(amounts.get("dunning_amount"), self.precision("dunning_amount"))
|
"company_address",
|
||||||
if self.grand_total != amounts.get("grand_total"):
|
"contact_person",
|
||||||
self.grand_total = flt(amounts.get("grand_total"), self.precision("grand_total"))
|
"contact_display",
|
||||||
|
"contact_mobile",
|
||||||
|
]:
|
||||||
|
self.set(field, party_details.get(field))
|
||||||
|
|
||||||
def on_submit(self):
|
self.set("company_address_display", get_address_display(self.company_address))
|
||||||
self.make_gl_entries()
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def set_dunning_level(self):
|
||||||
if self.dunning_amount:
|
for row in self.overdue_payments:
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
past_dunnings = frappe.get_all(
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
"Overdue Payment",
|
||||||
|
filters={
|
||||||
def make_gl_entries(self):
|
"payment_schedule": row.payment_schedule,
|
||||||
if not self.dunning_amount:
|
"parent": ("!=", row.parent),
|
||||||
return
|
"docstatus": 1,
|
||||||
gl_entries = []
|
|
||||||
invoice_fields = [
|
|
||||||
"project",
|
|
||||||
"cost_center",
|
|
||||||
"debit_to",
|
|
||||||
"party_account_currency",
|
|
||||||
"conversion_rate",
|
|
||||||
"cost_center",
|
|
||||||
]
|
|
||||||
inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
|
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
|
||||||
invoice_fields.extend(accounting_dimensions)
|
|
||||||
|
|
||||||
dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
|
|
||||||
default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
|
|
||||||
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": inv.debit_to,
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": self.customer,
|
|
||||||
"due_date": self.due_date,
|
|
||||||
"against": self.income_account,
|
|
||||||
"debit": dunning_in_company_currency,
|
|
||||||
"debit_in_account_currency": self.dunning_amount,
|
|
||||||
"against_voucher": self.name,
|
|
||||||
"against_voucher_type": "Dunning",
|
|
||||||
"cost_center": inv.cost_center or default_cost_center,
|
|
||||||
"project": inv.project,
|
|
||||||
},
|
},
|
||||||
inv.party_account_currency,
|
|
||||||
item=inv,
|
|
||||||
)
|
)
|
||||||
)
|
row.dunning_level = len(past_dunnings) + 1
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": self.income_account,
|
|
||||||
"against": self.customer,
|
|
||||||
"credit": dunning_in_company_currency,
|
|
||||||
"cost_center": inv.cost_center or default_cost_center,
|
|
||||||
"credit_in_account_currency": self.dunning_amount,
|
|
||||||
"project": inv.project,
|
|
||||||
},
|
|
||||||
item=inv,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
make_gl_entries(
|
|
||||||
gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_dunning(doc, state):
|
def resolve_dunning(doc, state):
|
||||||
|
"""
|
||||||
|
Check if all payments have been made and resolve dunning, if yes. Called
|
||||||
|
when a Payment Entry is submitted.
|
||||||
|
"""
|
||||||
for reference in doc.references:
|
for reference in doc.references:
|
||||||
if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0:
|
# Consider partial and full payments:
|
||||||
dunnings = frappe.get_list(
|
# Submitting full payment: outstanding_amount will be 0
|
||||||
"Dunning",
|
# Submitting 1st partial payment: outstanding_amount will be the pending installment
|
||||||
filters={"sales_invoice": reference.reference_name, "status": ("!=", "Resolved")},
|
# Cancelling full payment: outstanding_amount will revert to total amount
|
||||||
ignore_permissions=True,
|
# Cancelling last partial payment: outstanding_amount will revert to pending amount
|
||||||
)
|
submit_condition = reference.outstanding_amount < reference.total_amount
|
||||||
|
cancel_condition = reference.outstanding_amount <= reference.total_amount
|
||||||
|
|
||||||
|
if reference.reference_doctype == "Sales Invoice" and (
|
||||||
|
submit_condition if doc.docstatus == 1 else cancel_condition
|
||||||
|
):
|
||||||
|
state = "Resolved" if doc.docstatus == 2 else "Unresolved"
|
||||||
|
dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state)
|
||||||
|
|
||||||
for dunning in dunnings:
|
for dunning in dunnings:
|
||||||
frappe.db.set_value("Dunning", dunning.name, "status", "Resolved")
|
resolve = True
|
||||||
|
dunning = frappe.get_doc("Dunning", dunning.get("name"))
|
||||||
|
for overdue_payment in dunning.overdue_payments:
|
||||||
|
outstanding_inv = frappe.get_value(
|
||||||
|
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
|
||||||
|
)
|
||||||
|
outstanding_ps = frappe.get_value(
|
||||||
|
"Payment Schedule", overdue_payment.payment_schedule, "outstanding"
|
||||||
|
)
|
||||||
|
resolve = False if (outstanding_ps > 0 and outstanding_inv > 0) else True
|
||||||
|
|
||||||
|
dunning.status = "Resolved" if resolve else "Unresolved"
|
||||||
|
dunning.save()
|
||||||
|
|
||||||
|
|
||||||
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
def get_linked_dunnings_as_per_state(sales_invoice, state):
|
||||||
interest_amount = 0
|
dunning = frappe.qb.DocType("Dunning")
|
||||||
grand_total = flt(outstanding_amount) + flt(dunning_fee)
|
overdue_payment = frappe.qb.DocType("Overdue Payment")
|
||||||
if rate_of_interest:
|
|
||||||
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
return (
|
||||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
frappe.qb.from_(dunning)
|
||||||
grand_total += flt(interest_amount)
|
.join(overdue_payment)
|
||||||
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
.on(overdue_payment.parent == dunning.name)
|
||||||
return {
|
.select(dunning.name)
|
||||||
"interest_amount": interest_amount,
|
.where(
|
||||||
"grand_total": grand_total,
|
(dunning.status == state)
|
||||||
"dunning_amount": dunning_amount,
|
& (dunning.docstatus != 2)
|
||||||
}
|
& (overdue_payment.sales_invoice == sales_invoice)
|
||||||
|
)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def get_data():
|
|
||||||
return {
|
|
||||||
"fieldname": "dunning",
|
|
||||||
"non_standard_fieldnames": {
|
|
||||||
"Journal Entry": "reference_name",
|
|
||||||
"Payment Entry": "reference_name",
|
|
||||||
},
|
|
||||||
"transactions": [{"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]}],
|
|
||||||
}
|
|
||||||
@ -1,162 +1,197 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, nowdate, today
|
from frappe.utils import add_days, nowdate, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount
|
from erpnext import get_default_cost_center
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
||||||
unlink_payment_on_cancel_of_invoice,
|
unlink_payment_on_cancel_of_invoice,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||||
|
create_dunning as create_dunning_from_sales_invoice,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
||||||
create_sales_invoice_against_cost_center,
|
create_sales_invoice_against_cost_center,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test_dependencies = ["Company", "Cost Center"]
|
||||||
|
|
||||||
class TestDunning(unittest.TestCase):
|
|
||||||
|
class TestDunning(FrappeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(cls):
|
||||||
create_dunning_type()
|
super().setUpClass()
|
||||||
create_dunning_type_with_zero_interest_rate()
|
create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1)
|
||||||
|
create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0)
|
||||||
unlink_payment_on_cancel_of_invoice()
|
unlink_payment_on_cancel_of_invoice()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(self):
|
def tearDownClass(cls):
|
||||||
unlink_payment_on_cancel_of_invoice(0)
|
unlink_payment_on_cancel_of_invoice(0)
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
def test_dunning(self):
|
def test_dunning_without_fees(self):
|
||||||
dunning = create_dunning()
|
dunning = create_dunning(overdue_days=20)
|
||||||
amounts = calculate_interest_and_amount(
|
|
||||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
|
|
||||||
)
|
|
||||||
self.assertEqual(round(amounts.get("interest_amount"), 2), 0.44)
|
|
||||||
self.assertEqual(round(amounts.get("dunning_amount"), 2), 20.44)
|
|
||||||
self.assertEqual(round(amounts.get("grand_total"), 2), 120.44)
|
|
||||||
|
|
||||||
def test_dunning_with_zero_interest_rate(self):
|
self.assertEqual(round(dunning.total_outstanding, 2), 100.00)
|
||||||
dunning = create_dunning_with_zero_interest_rate()
|
self.assertEqual(round(dunning.total_interest, 2), 0.00)
|
||||||
amounts = calculate_interest_and_amount(
|
self.assertEqual(round(dunning.dunning_fee, 2), 0.00)
|
||||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
|
self.assertEqual(round(dunning.dunning_amount, 2), 0.00)
|
||||||
)
|
self.assertEqual(round(dunning.grand_total, 2), 100.00)
|
||||||
self.assertEqual(round(amounts.get("interest_amount"), 2), 0)
|
|
||||||
self.assertEqual(round(amounts.get("dunning_amount"), 2), 20)
|
|
||||||
self.assertEqual(round(amounts.get("grand_total"), 2), 120)
|
|
||||||
|
|
||||||
def test_gl_entries(self):
|
def test_dunning_with_fees_and_interest(self):
|
||||||
dunning = create_dunning()
|
dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC")
|
||||||
dunning.submit()
|
|
||||||
gl_entries = frappe.db.sql(
|
|
||||||
"""select account, debit, credit
|
|
||||||
from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s
|
|
||||||
order by account asc""",
|
|
||||||
dunning.name,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
self.assertTrue(gl_entries)
|
|
||||||
expected_values = dict(
|
|
||||||
(d[0], d) for d in [["Debtors - _TC", 20.44, 0.0], ["Sales - _TC", 0.0, 20.44]]
|
|
||||||
)
|
|
||||||
for gle in gl_entries:
|
|
||||||
self.assertEqual(expected_values[gle.account][0], gle.account)
|
|
||||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
|
||||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
|
||||||
|
|
||||||
def test_payment_entry(self):
|
self.assertEqual(round(dunning.total_outstanding, 2), 100.00)
|
||||||
dunning = create_dunning()
|
self.assertEqual(round(dunning.total_interest, 2), 0.41)
|
||||||
|
self.assertEqual(round(dunning.dunning_fee, 2), 10.00)
|
||||||
|
self.assertEqual(round(dunning.dunning_amount, 2), 10.41)
|
||||||
|
self.assertEqual(round(dunning.grand_total, 2), 110.41)
|
||||||
|
|
||||||
|
def test_dunning_with_payment_entry(self):
|
||||||
|
dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC")
|
||||||
dunning.submit()
|
dunning.submit()
|
||||||
pe = get_payment_entry("Dunning", dunning.name)
|
pe = get_payment_entry("Dunning", dunning.name)
|
||||||
pe.reference_no = "1"
|
pe.reference_no = "1"
|
||||||
pe.reference_date = nowdate()
|
pe.reference_date = nowdate()
|
||||||
pe.paid_from_account_currency = dunning.currency
|
|
||||||
pe.paid_to_account_currency = dunning.currency
|
|
||||||
pe.source_exchange_rate = 1
|
|
||||||
pe.target_exchange_rate = 1
|
|
||||||
pe.insert()
|
pe.insert()
|
||||||
pe.submit()
|
pe.submit()
|
||||||
si_doc = frappe.get_doc("Sales Invoice", dunning.sales_invoice)
|
|
||||||
self.assertEqual(si_doc.outstanding_amount, 0)
|
for overdue_payment in dunning.overdue_payments:
|
||||||
|
outstanding_amount = frappe.get_value(
|
||||||
|
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
|
||||||
|
)
|
||||||
|
self.assertEqual(outstanding_amount, 0)
|
||||||
|
|
||||||
|
dunning.reload()
|
||||||
|
self.assertEqual(dunning.status, "Resolved")
|
||||||
|
|
||||||
|
def test_dunning_and_payment_against_partially_due_invoice(self):
|
||||||
|
"""
|
||||||
|
Create SI with first installment overdue. Check impact of Dunning and Payment Entry.
|
||||||
|
"""
|
||||||
|
create_payment_terms_template_for_dunning()
|
||||||
|
sales_invoice = create_sales_invoice_against_cost_center(
|
||||||
|
posting_date=add_days(today(), -1 * 6),
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
sales_invoice.payment_terms_template = "_Test 50-50 for Dunning"
|
||||||
|
sales_invoice.submit()
|
||||||
|
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||||
|
|
||||||
|
self.assertEqual(len(dunning.overdue_payments), 1)
|
||||||
|
self.assertEqual(dunning.overdue_payments[0].payment_term, "_Test Payment Term 1 for Dunning")
|
||||||
|
|
||||||
|
dunning.submit()
|
||||||
|
pe = get_payment_entry("Dunning", dunning.name)
|
||||||
|
pe.reference_no, pe.reference_date = "2", nowdate()
|
||||||
|
pe.insert()
|
||||||
|
pe.submit()
|
||||||
|
sales_invoice.load_from_db()
|
||||||
|
dunning.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(sales_invoice.status, "Partly Paid")
|
||||||
|
self.assertEqual(sales_invoice.payment_schedule[0].outstanding, 0)
|
||||||
|
self.assertEqual(dunning.status, "Resolved")
|
||||||
|
|
||||||
|
# Test impact on cancellation of PE
|
||||||
|
pe.cancel()
|
||||||
|
sales_invoice.reload()
|
||||||
|
dunning.reload()
|
||||||
|
|
||||||
|
self.assertEqual(sales_invoice.status, "Overdue")
|
||||||
|
self.assertEqual(dunning.status, "Unresolved")
|
||||||
|
|
||||||
|
|
||||||
def create_dunning():
|
def create_dunning(overdue_days, dunning_type_name=None):
|
||||||
posting_date = add_days(today(), -20)
|
posting_date = add_days(today(), -1 * overdue_days)
|
||||||
due_date = add_days(today(), -15)
|
|
||||||
sales_invoice = create_sales_invoice_against_cost_center(
|
sales_invoice = create_sales_invoice_against_cost_center(
|
||||||
posting_date=posting_date, due_date=due_date, status="Overdue"
|
posting_date=posting_date, qty=1, rate=100
|
||||||
)
|
)
|
||||||
dunning_type = frappe.get_doc("Dunning Type", "First Notice")
|
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||||
dunning = frappe.new_doc("Dunning")
|
|
||||||
dunning.sales_invoice = sales_invoice.name
|
if dunning_type_name:
|
||||||
dunning.customer_name = sales_invoice.customer_name
|
dunning_type = frappe.get_doc("Dunning Type", dunning_type_name)
|
||||||
dunning.outstanding_amount = sales_invoice.outstanding_amount
|
dunning.dunning_type = dunning_type.name
|
||||||
dunning.debit_to = sales_invoice.debit_to
|
dunning.rate_of_interest = dunning_type.rate_of_interest
|
||||||
dunning.currency = sales_invoice.currency
|
dunning.dunning_fee = dunning_type.dunning_fee
|
||||||
dunning.company = sales_invoice.company
|
dunning.income_account = dunning_type.income_account
|
||||||
dunning.posting_date = nowdate()
|
dunning.cost_center = dunning_type.cost_center
|
||||||
dunning.due_date = sales_invoice.due_date
|
|
||||||
dunning.dunning_type = "First Notice"
|
return dunning.save()
|
||||||
dunning.rate_of_interest = dunning_type.rate_of_interest
|
|
||||||
dunning.dunning_fee = dunning_type.dunning_fee
|
|
||||||
dunning.save()
|
|
||||||
return dunning
|
|
||||||
|
|
||||||
|
|
||||||
def create_dunning_with_zero_interest_rate():
|
def create_dunning_type(title, fee, interest, is_default):
|
||||||
posting_date = add_days(today(), -20)
|
company = "_Test Company"
|
||||||
due_date = add_days(today(), -15)
|
if frappe.db.exists("Dunning Type", f"{title} - _TC"):
|
||||||
sales_invoice = create_sales_invoice_against_cost_center(
|
return
|
||||||
posting_date=posting_date, due_date=due_date, status="Overdue"
|
|
||||||
)
|
|
||||||
dunning_type = frappe.get_doc("Dunning Type", "First Notice with 0% Rate of Interest")
|
|
||||||
dunning = frappe.new_doc("Dunning")
|
|
||||||
dunning.sales_invoice = sales_invoice.name
|
|
||||||
dunning.customer_name = sales_invoice.customer_name
|
|
||||||
dunning.outstanding_amount = sales_invoice.outstanding_amount
|
|
||||||
dunning.debit_to = sales_invoice.debit_to
|
|
||||||
dunning.currency = sales_invoice.currency
|
|
||||||
dunning.company = sales_invoice.company
|
|
||||||
dunning.posting_date = nowdate()
|
|
||||||
dunning.due_date = sales_invoice.due_date
|
|
||||||
dunning.dunning_type = "First Notice with 0% Rate of Interest"
|
|
||||||
dunning.rate_of_interest = dunning_type.rate_of_interest
|
|
||||||
dunning.dunning_fee = dunning_type.dunning_fee
|
|
||||||
dunning.save()
|
|
||||||
return dunning
|
|
||||||
|
|
||||||
|
|
||||||
def create_dunning_type():
|
|
||||||
dunning_type = frappe.new_doc("Dunning Type")
|
dunning_type = frappe.new_doc("Dunning Type")
|
||||||
dunning_type.dunning_type = "First Notice"
|
dunning_type.dunning_type = title
|
||||||
dunning_type.start_day = 10
|
dunning_type.company = company
|
||||||
dunning_type.end_day = 20
|
dunning_type.is_default = is_default
|
||||||
dunning_type.dunning_fee = 20
|
dunning_type.dunning_fee = fee
|
||||||
dunning_type.rate_of_interest = 8
|
dunning_type.rate_of_interest = interest
|
||||||
|
dunning_type.income_account = get_income_account(company)
|
||||||
|
dunning_type.cost_center = get_default_cost_center(company)
|
||||||
dunning_type.append(
|
dunning_type.append(
|
||||||
"dunning_letter_text",
|
"dunning_letter_text",
|
||||||
{
|
{
|
||||||
"language": "en",
|
"language": "en",
|
||||||
"body_text": "We have still not received payment for our invoice ",
|
"body_text": "We have still not received payment for our invoice",
|
||||||
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.",
|
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
dunning_type.save()
|
dunning_type.insert()
|
||||||
|
|
||||||
|
|
||||||
def create_dunning_type_with_zero_interest_rate():
|
def get_income_account(company):
|
||||||
dunning_type = frappe.new_doc("Dunning Type")
|
return (
|
||||||
dunning_type.dunning_type = "First Notice with 0% Rate of Interest"
|
frappe.get_value("Company", company, "default_income_account")
|
||||||
dunning_type.start_day = 10
|
or frappe.get_all(
|
||||||
dunning_type.end_day = 20
|
"Account",
|
||||||
dunning_type.dunning_fee = 20
|
filters={"is_group": 0, "company": company},
|
||||||
dunning_type.rate_of_interest = 0
|
or_filters={
|
||||||
dunning_type.append(
|
"report_type": "Profit and Loss",
|
||||||
"dunning_letter_text",
|
"account_type": ("in", ("Income Account", "Temporary")),
|
||||||
{
|
},
|
||||||
"language": "en",
|
limit=1,
|
||||||
"body_text": "We have still not received payment for our invoice ",
|
pluck="name",
|
||||||
"closing_text": "We kindly request that you pay the outstanding amount immediately, and late fees.",
|
)[0]
|
||||||
},
|
|
||||||
)
|
)
|
||||||
dunning_type.save()
|
|
||||||
|
|
||||||
|
def create_payment_terms_template_for_dunning():
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_term
|
||||||
|
|
||||||
|
create_payment_term("_Test Payment Term 1 for Dunning")
|
||||||
|
create_payment_term("_Test Payment Term 2 for Dunning")
|
||||||
|
|
||||||
|
if not frappe.db.exists("Payment Terms Template", "_Test 50-50 for Dunning"):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template",
|
||||||
|
"template_name": "_Test 50-50 for Dunning",
|
||||||
|
"allocate_payment_based_on_payment_terms": 1,
|
||||||
|
"terms": [
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"payment_term": "_Test Payment Term 1 for Dunning",
|
||||||
|
"invoice_portion": 50.00,
|
||||||
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
|
"credit_days": 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Payment Terms Template Detail",
|
||||||
|
"payment_term": "_Test Payment Term 2 for Dunning",
|
||||||
|
"invoice_portion": 50.00,
|
||||||
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
|
"credit_days": 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|||||||
@ -1,8 +1,24 @@
|
|||||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Dunning Type', {
|
frappe.ui.form.on("Dunning Type", {
|
||||||
// refresh: function(frm) {
|
setup: function (frm) {
|
||||||
|
frm.set_query("income_account", () => {
|
||||||
// }
|
return {
|
||||||
|
filters: {
|
||||||
|
root_type: "Income",
|
||||||
|
is_group: 0,
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
frm.set_query("cost_center", () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_group: 0,
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,23 +1,26 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:dunning_type",
|
"beta": 1,
|
||||||
"creation": "2019-12-04 04:59:08.003664",
|
"creation": "2019-12-04 04:59:08.003664",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"dunning_type",
|
"dunning_type",
|
||||||
"overdue_interval_section",
|
"is_default",
|
||||||
"start_day",
|
"column_break_3",
|
||||||
"column_break_4",
|
"company",
|
||||||
"end_day",
|
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"dunning_fee",
|
"dunning_fee",
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
"rate_of_interest",
|
"rate_of_interest",
|
||||||
"text_block_section",
|
"text_block_section",
|
||||||
"dunning_letter_text"
|
"dunning_letter_text",
|
||||||
|
"section_break_9",
|
||||||
|
"income_account",
|
||||||
|
"column_break_13",
|
||||||
|
"cost_center"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -45,10 +48,6 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"options": "Dunning Letter Text"
|
"options": "Dunning Letter Text"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_6",
|
"fieldname": "section_break_6",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
@ -57,33 +56,62 @@
|
|||||||
"fieldname": "column_break_8",
|
"fieldname": "column_break_8",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "overdue_interval_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Overdue Interval"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "start_day",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "Start Day"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "end_day",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "End Day"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "rate_of_interest",
|
"fieldname": "rate_of_interest",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate of Interest (%) Yearly"
|
"label": "Rate of Interest (%) Yearly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_default",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_9",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "income_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Income Account",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_13",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2020-07-15 17:14:17.835074",
|
{
|
||||||
|
"link_doctype": "Dunning",
|
||||||
|
"link_fieldname": "dunning_type"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-11-13 00:25:35.659283",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning Type",
|
"name": "Dunning Type",
|
||||||
|
"naming_rule": "By script",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,9 +2,11 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class DunningType(Document):
|
class DunningType(Document):
|
||||||
pass
|
def autoname(self):
|
||||||
|
company_abbr = frappe.get_value("Company", self.company, "abbr")
|
||||||
|
self.name = f"{self.dunning_type} - {company_abbr}"
|
||||||
|
|||||||
36
erpnext/accounts/doctype/dunning_type/test_records.json
Normal file
36
erpnext/accounts/doctype/dunning_type/test_records.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"doctype": "Dunning Type",
|
||||||
|
"dunning_type": "_Test First Notice",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"is_default": 1,
|
||||||
|
"dunning_fee": 0.0,
|
||||||
|
"rate_of_interest": 0.0,
|
||||||
|
"dunning_letter_text": [
|
||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"body_text": "We have still not received payment for our invoice",
|
||||||
|
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Dunning Type",
|
||||||
|
"dunning_type": "_Test Second Notice",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"is_default": 0,
|
||||||
|
"dunning_fee": 10.0,
|
||||||
|
"rate_of_interest": 10.0,
|
||||||
|
"dunning_letter_text": [
|
||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"body_text": "We have still not received payment for our invoice",
|
||||||
|
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -258,8 +258,8 @@ class ExchangeRateRevaluation(Document):
|
|||||||
new_balance_in_base_currency = 0
|
new_balance_in_base_currency = 0
|
||||||
new_balance_in_account_currency = 0
|
new_balance_in_account_currency = 0
|
||||||
|
|
||||||
current_exchange_rate = calculate_exchange_rate_using_last_gle(
|
current_exchange_rate = (
|
||||||
company, d.account, d.party_type, d.party
|
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
gain_loss = new_balance_in_account_currency - (
|
gain_loss = new_balance_in_account_currency - (
|
||||||
|
|||||||
@ -8,17 +8,6 @@ frappe.ui.form.on('Fiscal Year', {
|
|||||||
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1));
|
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh: function (frm) {
|
|
||||||
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 {
|
|
||||||
frm.set_intro("");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set_as_default: function(frm) {
|
|
||||||
return frm.call('set_as_default');
|
|
||||||
},
|
|
||||||
year_start_date: function(frm) {
|
year_start_date: function(frm) {
|
||||||
if (!frm.doc.is_short_year) {
|
if (!frm.doc.is_short_year) {
|
||||||
let year_end_date =
|
let year_end_date =
|
||||||
|
|||||||
@ -4,28 +4,12 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from frappe import _, msgprint
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_days, add_years, cstr, getdate
|
from frappe.utils import add_days, add_years, cstr, getdate
|
||||||
|
|
||||||
|
|
||||||
class FiscalYear(Document):
|
class FiscalYear(Document):
|
||||||
@frappe.whitelist()
|
|
||||||
def set_as_default(self):
|
|
||||||
frappe.db.set_single_value("Global Defaults", "current_fiscal_year", self.name)
|
|
||||||
global_defaults = frappe.get_doc("Global Defaults")
|
|
||||||
global_defaults.check_permission("write")
|
|
||||||
global_defaults.on_update()
|
|
||||||
|
|
||||||
# clear cache
|
|
||||||
frappe.clear_cache()
|
|
||||||
|
|
||||||
msgprint(
|
|
||||||
_(
|
|
||||||
"{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect."
|
|
||||||
).format(self.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
self.validate_overlap()
|
self.validate_overlap()
|
||||||
@ -68,13 +52,6 @@ class FiscalYear(Document):
|
|||||||
frappe.cache().delete_value("fiscal_years")
|
frappe.cache().delete_value("fiscal_years")
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
global_defaults = frappe.get_doc("Global Defaults")
|
|
||||||
if global_defaults.current_fiscal_year == self.name:
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings"
|
|
||||||
).format(self.name)
|
|
||||||
)
|
|
||||||
frappe.cache().delete_value("fiscal_years")
|
frappe.cache().delete_value("fiscal_years")
|
||||||
|
|
||||||
def validate_overlap(self):
|
def validate_overlap(self):
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_filter": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
@ -56,7 +57,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-01-18 21:11:23.105589",
|
"modified": "2023-07-09 18:11:23.105589",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Item Tax Template",
|
"name": "Item Tax Template",
|
||||||
@ -102,4 +103,4 @@
|
|||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -408,6 +408,15 @@ class JournalEntry(AccountsController):
|
|||||||
d.idx, d.account
|
d.idx, d.account
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif (
|
||||||
|
d.party_type
|
||||||
|
and frappe.db.get_value("Party Type", d.party_type, "account_type") != account_type
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row {0}: Account {1} and Party Type {2} have different account types").format(
|
||||||
|
d.idx, d.account, d.party_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def check_credit_limit(self):
|
def check_credit_limit(self):
|
||||||
customers = list(
|
customers = list(
|
||||||
|
|||||||
170
erpnext/accounts/doctype/overdue_payment/overdue_payment.json
Normal file
170
erpnext/accounts/doctype/overdue_payment/overdue_payment.json
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-09-15 18:34:27.172906",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"sales_invoice",
|
||||||
|
"payment_schedule",
|
||||||
|
"dunning_level",
|
||||||
|
"payment_term",
|
||||||
|
"section_break_15",
|
||||||
|
"description",
|
||||||
|
"section_break_4",
|
||||||
|
"due_date",
|
||||||
|
"overdue_days",
|
||||||
|
"mode_of_payment",
|
||||||
|
"column_break_5",
|
||||||
|
"invoice_portion",
|
||||||
|
"section_break_16",
|
||||||
|
"payment_amount",
|
||||||
|
"outstanding",
|
||||||
|
"paid_amount",
|
||||||
|
"discounted_amount",
|
||||||
|
"interest"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "payment_term",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Term",
|
||||||
|
"options": "Payment Term",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_15",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fetch_from": "payment_term.description",
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "due_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Due Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Mode of Payment",
|
||||||
|
"options": "Mode of Payment",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "invoice_portion",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"label": "Invoice Portion",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "payment_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Payment Amount",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "payment_amount",
|
||||||
|
"fieldname": "outstanding",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Outstanding",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "paid_amount",
|
||||||
|
"fieldname": "paid_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "discounted_amount",
|
||||||
|
"fieldname": "discounted_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Discounted Amount",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sales_invoice",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Sales Invoice",
|
||||||
|
"options": "Sales Invoice",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_schedule",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Payment Schedule",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "overdue_days",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Overdue Days",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "dunning_level",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Dunning Level",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_16",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "interest",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Interest",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-09-23 13:48:27.898830",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Overdue Payment",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class OverduePayment(Document):
|
||||||
|
pass
|
||||||
@ -226,10 +226,12 @@ class PaymentEntry(AccountsController):
|
|||||||
latest_lookup = {}
|
latest_lookup = {}
|
||||||
for d in latest_references:
|
for d in latest_references:
|
||||||
d = frappe._dict(d)
|
d = frappe._dict(d)
|
||||||
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get(
|
||||||
|
d.payment_term
|
||||||
|
)
|
||||||
|
|
||||||
# The reference has already been fully paid
|
# The reference has already been fully paid
|
||||||
if not latest:
|
if not latest:
|
||||||
@ -237,10 +239,9 @@ class PaymentEntry(AccountsController):
|
|||||||
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
||||||
)
|
)
|
||||||
# The reference has already been partly paid
|
# The reference has already been partly paid
|
||||||
elif (
|
elif latest.outstanding_amount < latest.invoice_amount and flt(
|
||||||
latest.outstanding_amount < latest.invoice_amount
|
d.outstanding_amount, d.precision("outstanding_amount")
|
||||||
and flt(d.outstanding_amount, d.precision("outstanding_amount")) != latest.outstanding_amount
|
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
|
||||||
):
|
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
||||||
@ -252,6 +253,18 @@ class PaymentEntry(AccountsController):
|
|||||||
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
|
|
||||||
|
if d.payment_term and (
|
||||||
|
(flt(d.allocated_amount)) > 0
|
||||||
|
and flt(d.allocated_amount) > flt(latest.payment_term_outstanding)
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
|
||||||
|
).format(
|
||||||
|
d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Check for negative outstanding invoices as well
|
# Check for negative outstanding invoices as well
|
||||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
@ -1433,6 +1446,9 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
if args.get("party_type") == "Member":
|
if args.get("party_type") == "Member":
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not args.get("get_outstanding_invoices") and not args.get("get_orders_to_be_billed"):
|
||||||
|
args["get_outstanding_invoices"] = True
|
||||||
|
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
common_filter = []
|
common_filter = []
|
||||||
accounting_dimensions_filter = []
|
accounting_dimensions_filter = []
|
||||||
@ -1498,7 +1514,9 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
accounting_dimensions=accounting_dimensions_filter,
|
accounting_dimensions=accounting_dimensions_filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
outstanding_invoices = split_invoices_based_on_payment_terms(
|
||||||
|
outstanding_invoices, args.get("company")
|
||||||
|
)
|
||||||
|
|
||||||
for d in outstanding_invoices:
|
for d in outstanding_invoices:
|
||||||
d["exchange_rate"] = 1
|
d["exchange_rate"] = 1
|
||||||
@ -1558,8 +1576,27 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def split_invoices_based_on_payment_terms(outstanding_invoices):
|
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||||
invoice_ref_based_on_payment_terms = {}
|
invoice_ref_based_on_payment_terms = {}
|
||||||
|
|
||||||
|
company_currency = (
|
||||||
|
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||||
|
)
|
||||||
|
exc_rates = frappe._dict()
|
||||||
|
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
|
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||||
|
for x in frappe.db.get_all(
|
||||||
|
doctype,
|
||||||
|
filters={"name": ["in", invoices]},
|
||||||
|
fields=["name", "currency", "conversion_rate", "party_account_currency"],
|
||||||
|
):
|
||||||
|
exc_rates[x.name] = frappe._dict(
|
||||||
|
conversion_rate=x.conversion_rate,
|
||||||
|
currency=x.currency,
|
||||||
|
party_account_currency=x.party_account_currency,
|
||||||
|
company_currency=company_currency,
|
||||||
|
)
|
||||||
|
|
||||||
for idx, d in enumerate(outstanding_invoices):
|
for idx, d in enumerate(outstanding_invoices):
|
||||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
payment_term_template = frappe.db.get_value(
|
payment_term_template = frappe.db.get_value(
|
||||||
@ -1576,6 +1613,14 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
|
|||||||
|
|
||||||
for payment_term in payment_schedule:
|
for payment_term in payment_schedule:
|
||||||
if payment_term.outstanding > 0.1:
|
if payment_term.outstanding > 0.1:
|
||||||
|
doc_details = exc_rates.get(payment_term.parent, None)
|
||||||
|
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||||
|
doc_details.party_account_currency != doc_details.company_currency
|
||||||
|
)
|
||||||
|
payment_term_outstanding = flt(payment_term.outstanding)
|
||||||
|
if not is_multi_currency_acc:
|
||||||
|
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||||
|
|
||||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||||
invoice_ref_based_on_payment_terms[idx].append(
|
invoice_ref_based_on_payment_terms[idx].append(
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
@ -1587,6 +1632,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
|
|||||||
"posting_date": d.posting_date,
|
"posting_date": d.posting_date,
|
||||||
"invoice_amount": flt(d.invoice_amount),
|
"invoice_amount": flt(d.invoice_amount),
|
||||||
"outstanding_amount": flt(d.outstanding_amount),
|
"outstanding_amount": flt(d.outstanding_amount),
|
||||||
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
"payment_amount": payment_term.payment_amount,
|
"payment_amount": payment_term.payment_amount,
|
||||||
"payment_term": payment_term.payment_term,
|
"payment_term": payment_term.payment_term,
|
||||||
"account": d.account,
|
"account": d.account,
|
||||||
@ -1627,60 +1673,59 @@ def get_orders_to_be_billed(
|
|||||||
cost_center=None,
|
cost_center=None,
|
||||||
filters=None,
|
filters=None,
|
||||||
):
|
):
|
||||||
|
voucher_type = None
|
||||||
if party_type == "Customer":
|
if party_type == "Customer":
|
||||||
voucher_type = "Sales Order"
|
voucher_type = "Sales Order"
|
||||||
elif party_type == "Supplier":
|
elif party_type == "Supplier":
|
||||||
voucher_type = "Purchase Order"
|
voucher_type = "Purchase Order"
|
||||||
elif party_type == "Employee":
|
|
||||||
voucher_type = None
|
if not voucher_type:
|
||||||
|
return []
|
||||||
|
|
||||||
# Add cost center condition
|
# Add cost center condition
|
||||||
if voucher_type:
|
doc = frappe.get_doc({"doctype": voucher_type})
|
||||||
doc = frappe.get_doc({"doctype": voucher_type})
|
condition = ""
|
||||||
condition = ""
|
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||||
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
condition = " and cost_center='%s'" % cost_center
|
||||||
condition = " and cost_center='%s'" % cost_center
|
|
||||||
|
|
||||||
orders = []
|
if party_account_currency == company_currency:
|
||||||
if voucher_type:
|
grand_total_field = "base_grand_total"
|
||||||
if party_account_currency == company_currency:
|
rounded_total_field = "base_rounded_total"
|
||||||
grand_total_field = "base_grand_total"
|
else:
|
||||||
rounded_total_field = "base_rounded_total"
|
grand_total_field = "grand_total"
|
||||||
else:
|
rounded_total_field = "rounded_total"
|
||||||
grand_total_field = "grand_total"
|
|
||||||
rounded_total_field = "rounded_total"
|
|
||||||
|
|
||||||
orders = frappe.db.sql(
|
orders = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
name as voucher_no,
|
name as voucher_no,
|
||||||
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
|
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
|
||||||
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
|
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
|
||||||
transaction_date as posting_date
|
transaction_date as posting_date
|
||||||
from
|
from
|
||||||
`tab{voucher_type}`
|
`tab{voucher_type}`
|
||||||
where
|
where
|
||||||
{party_type} = %s
|
{party_type} = %s
|
||||||
and docstatus = 1
|
and docstatus = 1
|
||||||
and company = %s
|
and company = %s
|
||||||
and ifnull(status, "") != "Closed"
|
and ifnull(status, "") != "Closed"
|
||||||
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
||||||
and abs(100 - per_billed) > 0.01
|
and abs(100 - per_billed) > 0.01
|
||||||
{condition}
|
{condition}
|
||||||
order by
|
order by
|
||||||
transaction_date, name
|
transaction_date, name
|
||||||
""".format(
|
""".format(
|
||||||
**{
|
**{
|
||||||
"rounded_total_field": rounded_total_field,
|
"rounded_total_field": rounded_total_field,
|
||||||
"grand_total_field": grand_total_field,
|
"grand_total_field": grand_total_field,
|
||||||
"voucher_type": voucher_type,
|
"voucher_type": voucher_type,
|
||||||
"party_type": scrub(party_type),
|
"party_type": scrub(party_type),
|
||||||
"condition": condition,
|
"condition": condition,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
(party, company),
|
(party, company),
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
order_list = []
|
order_list = []
|
||||||
for d in orders:
|
for d in orders:
|
||||||
@ -1713,6 +1758,8 @@ def get_negative_outstanding_invoices(
|
|||||||
cost_center=None,
|
cost_center=None,
|
||||||
condition=None,
|
condition=None,
|
||||||
):
|
):
|
||||||
|
if party_type not in ["Customer", "Supplier"]:
|
||||||
|
return []
|
||||||
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
|
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
|
||||||
account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to"
|
account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to"
|
||||||
supplier_condition = ""
|
supplier_condition = ""
|
||||||
@ -2007,28 +2054,27 @@ def get_payment_entry(
|
|||||||
pe.append("references", reference)
|
pe.append("references", reference)
|
||||||
else:
|
else:
|
||||||
if dt == "Dunning":
|
if dt == "Dunning":
|
||||||
|
for overdue_payment in doc.overdue_payments:
|
||||||
|
pe.append(
|
||||||
|
"references",
|
||||||
|
{
|
||||||
|
"reference_doctype": "Sales Invoice",
|
||||||
|
"reference_name": overdue_payment.sales_invoice,
|
||||||
|
"payment_term": overdue_payment.payment_term,
|
||||||
|
"due_date": overdue_payment.due_date,
|
||||||
|
"total_amount": overdue_payment.outstanding,
|
||||||
|
"outstanding_amount": overdue_payment.outstanding,
|
||||||
|
"allocated_amount": overdue_payment.outstanding,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
pe.append(
|
pe.append(
|
||||||
"references",
|
"deductions",
|
||||||
{
|
{
|
||||||
"reference_doctype": "Sales Invoice",
|
"account": doc.income_account,
|
||||||
"reference_name": doc.get("sales_invoice"),
|
"cost_center": doc.cost_center,
|
||||||
"bill_no": doc.get("bill_no"),
|
"amount": -1 * doc.dunning_amount,
|
||||||
"due_date": doc.get("due_date"),
|
"description": _("Interest and/or dunning fee"),
|
||||||
"total_amount": doc.get("outstanding_amount"),
|
|
||||||
"outstanding_amount": doc.get("outstanding_amount"),
|
|
||||||
"allocated_amount": doc.get("outstanding_amount"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
pe.append(
|
|
||||||
"references",
|
|
||||||
{
|
|
||||||
"reference_doctype": dt,
|
|
||||||
"reference_name": dn,
|
|
||||||
"bill_no": doc.get("bill_no"),
|
|
||||||
"due_date": doc.get("due_date"),
|
|
||||||
"total_amount": doc.get("dunning_amount"),
|
|
||||||
"outstanding_amount": doc.get("dunning_amount"),
|
|
||||||
"allocated_amount": doc.get("dunning_amount"),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -2122,8 +2168,10 @@ def set_party_account_currency(dt, party_account, doc):
|
|||||||
|
|
||||||
def set_payment_type(dt, doc):
|
def set_payment_type(dt, doc):
|
||||||
if (
|
if (
|
||||||
dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
|
(dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0))
|
||||||
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
|
or (dt == "Purchase Invoice" and doc.outstanding_amount < 0)
|
||||||
|
or dt == "Dunning"
|
||||||
|
):
|
||||||
payment_type = "Receive"
|
payment_type = "Receive"
|
||||||
else:
|
else:
|
||||||
payment_type = "Pay"
|
payment_type = "Pay"
|
||||||
@ -2368,6 +2416,7 @@ def get_reference_as_per_payment_terms(
|
|||||||
"due_date": doc.get("due_date"),
|
"due_date": doc.get("due_date"),
|
||||||
"total_amount": grand_total,
|
"total_amount": grand_total,
|
||||||
"outstanding_amount": outstanding_amount,
|
"outstanding_amount": outstanding_amount,
|
||||||
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
"payment_term": payment_term.payment_term,
|
"payment_term": payment_term.payment_term,
|
||||||
"allocated_amount": payment_term_outstanding,
|
"allocated_amount": payment_term_outstanding,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1061,6 +1061,101 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
}
|
}
|
||||||
self.assertDictEqual(ref_details, expected_response)
|
self.assertDictEqual(ref_details, expected_response)
|
||||||
|
|
||||||
|
@change_settings(
|
||||||
|
"Accounts Settings",
|
||||||
|
{
|
||||||
|
"unlink_payment_on_cancellation_of_invoice": 1,
|
||||||
|
"delete_linked_ledger_entries": 1,
|
||||||
|
"allow_multi_currency_invoices_against_single_party_account": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_overallocation_validation_on_payment_terms(self):
|
||||||
|
"""
|
||||||
|
Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown.
|
||||||
|
|
||||||
|
"""
|
||||||
|
customer = create_customer()
|
||||||
|
create_payment_terms_template()
|
||||||
|
|
||||||
|
# Validate allocation on base/company currency
|
||||||
|
si1 = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||||
|
si1.payment_terms_template = "Test Receivable Template"
|
||||||
|
si1.save().submit()
|
||||||
|
|
||||||
|
si1.reload()
|
||||||
|
pe = get_payment_entry(si1.doctype, si1.name).save()
|
||||||
|
# Allocated amount should be according to the payment schedule
|
||||||
|
for idx, schedule in enumerate(si1.payment_schedule):
|
||||||
|
with self.subTest(idx=idx):
|
||||||
|
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
|
||||||
|
pe.save()
|
||||||
|
|
||||||
|
# Overallocation validation should trigger
|
||||||
|
pe.paid_amount = 400
|
||||||
|
pe.references[0].allocated_amount = 200
|
||||||
|
pe.references[1].allocated_amount = 200
|
||||||
|
self.assertRaises(frappe.ValidationError, pe.save)
|
||||||
|
pe.delete()
|
||||||
|
si1.cancel()
|
||||||
|
si1.delete()
|
||||||
|
|
||||||
|
# Validate allocation on foreign currency
|
||||||
|
si2 = create_sales_invoice(
|
||||||
|
customer="_Test Customer USD",
|
||||||
|
debit_to="_Test Receivable USD - _TC",
|
||||||
|
currency="USD",
|
||||||
|
conversion_rate=80,
|
||||||
|
do_not_save=1,
|
||||||
|
)
|
||||||
|
si2.payment_terms_template = "Test Receivable Template"
|
||||||
|
si2.save().submit()
|
||||||
|
|
||||||
|
si2.reload()
|
||||||
|
pe = get_payment_entry(si2.doctype, si2.name).save()
|
||||||
|
# Allocated amount should be according to the payment schedule
|
||||||
|
for idx, schedule in enumerate(si2.payment_schedule):
|
||||||
|
with self.subTest(idx=idx):
|
||||||
|
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
|
||||||
|
pe.save()
|
||||||
|
|
||||||
|
# Overallocation validation should trigger
|
||||||
|
pe.paid_amount = 200
|
||||||
|
pe.references[0].allocated_amount = 100
|
||||||
|
pe.references[1].allocated_amount = 100
|
||||||
|
self.assertRaises(frappe.ValidationError, pe.save)
|
||||||
|
pe.delete()
|
||||||
|
si2.cancel()
|
||||||
|
si2.delete()
|
||||||
|
|
||||||
|
# Validate allocation in base/company currency on a foreign currency document
|
||||||
|
# when invoice is made is foreign currency, but posted to base/company currency debtors account
|
||||||
|
si3 = create_sales_invoice(
|
||||||
|
customer=customer,
|
||||||
|
currency="USD",
|
||||||
|
conversion_rate=80,
|
||||||
|
do_not_save=1,
|
||||||
|
)
|
||||||
|
si3.payment_terms_template = "Test Receivable Template"
|
||||||
|
si3.save().submit()
|
||||||
|
|
||||||
|
si3.reload()
|
||||||
|
pe = get_payment_entry(si3.doctype, si3.name).save()
|
||||||
|
# Allocated amount should be equal to payment term outstanding
|
||||||
|
self.assertEqual(len(pe.references), 2)
|
||||||
|
for idx, ref in enumerate(pe.references):
|
||||||
|
with self.subTest(idx=idx):
|
||||||
|
self.assertEqual(ref.payment_term_outstanding, ref.allocated_amount)
|
||||||
|
pe.save()
|
||||||
|
|
||||||
|
# Overallocation validation should trigger
|
||||||
|
pe.paid_amount = 16000
|
||||||
|
pe.references[0].allocated_amount = 8000
|
||||||
|
pe.references[1].allocated_amount = 8000
|
||||||
|
self.assertRaises(frappe.ValidationError, pe.save)
|
||||||
|
pe.delete()
|
||||||
|
si3.cancel()
|
||||||
|
si3.delete()
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
@ -1150,3 +1245,17 @@ def create_payment_terms_template_with_discount(
|
|||||||
def create_payment_term(name):
|
def create_payment_term(name):
|
||||||
if not frappe.db.exists("Payment Term", name):
|
if not frappe.db.exists("Payment Term", name):
|
||||||
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()
|
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def create_customer(name="_Test Customer 2 USD", currency="USD"):
|
||||||
|
customer = None
|
||||||
|
if frappe.db.exists("Customer", name):
|
||||||
|
customer = name
|
||||||
|
else:
|
||||||
|
customer = frappe.new_doc("Customer")
|
||||||
|
customer.customer_name = name
|
||||||
|
customer.default_currency = currency
|
||||||
|
customer.type = "Individual"
|
||||||
|
customer.save()
|
||||||
|
customer = customer.name
|
||||||
|
return customer
|
||||||
|
|||||||
@ -126,21 +126,22 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
def make_gl_entries(self, get_opening_entries=False):
|
def make_gl_entries(self, get_opening_entries=False):
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||||
if gl_entries:
|
if len(gl_entries) > 5000:
|
||||||
if len(gl_entries) > 5000:
|
frappe.enqueue(
|
||||||
frappe.enqueue(
|
process_gl_entries,
|
||||||
process_gl_entries,
|
gl_entries=gl_entries,
|
||||||
gl_entries=gl_entries,
|
closing_entries=closing_entries,
|
||||||
closing_entries=closing_entries,
|
voucher_name=self.name,
|
||||||
voucher_name=self.name,
|
company=self.company,
|
||||||
queue="long",
|
closing_date=self.posting_date,
|
||||||
)
|
queue="long",
|
||||||
frappe.msgprint(
|
)
|
||||||
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
frappe.msgprint(
|
||||||
alert=True,
|
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
||||||
)
|
alert=True,
|
||||||
else:
|
)
|
||||||
process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
|
else:
|
||||||
|
process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
||||||
|
|
||||||
def get_grouped_gl_entries(self, get_opening_entries=False):
|
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||||
closing_entries = []
|
closing_entries = []
|
||||||
@ -321,24 +322,22 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
return query.run(as_dict=1)
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def process_gl_entries(gl_entries, closing_entries, voucher_name=None):
|
def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||||
make_closing_entries,
|
make_closing_entries,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
try:
|
try:
|
||||||
make_gl_entries(gl_entries, merge_entries=False)
|
if gl_entries:
|
||||||
make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
|
make_gl_entries(gl_entries, merge_entries=False)
|
||||||
frappe.db.set_value(
|
|
||||||
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
|
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||||
)
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
frappe.log_error(e)
|
frappe.log_error(e)
|
||||||
frappe.db.set_value(
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
||||||
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def make_reverse_gl_entries(voucher_type, voucher_no):
|
def make_reverse_gl_entries(voucher_type, voucher_no):
|
||||||
|
|||||||
@ -142,9 +142,15 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
cur_frm.events.create_invoice_discounting(cur_frm);
|
cur_frm.events.create_invoice_discounting(cur_frm);
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
|
|
||||||
if (doc.due_date < frappe.datetime.get_today()) {
|
const payment_is_overdue = doc.payment_schedule.map(
|
||||||
cur_frm.add_custom_button(__('Dunning'), function() {
|
row => Date.parse(row.due_date) < Date.now()
|
||||||
cur_frm.events.create_dunning(cur_frm);
|
).reduce(
|
||||||
|
(prev, current) => prev || current
|
||||||
|
);
|
||||||
|
|
||||||
|
if (payment_is_overdue) {
|
||||||
|
this.frm.add_custom_button(__('Dunning'), () => {
|
||||||
|
this.frm.events.create_dunning(this.frm);
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2516,55 +2516,49 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_dunning(source_name, target_doc=None):
|
def create_dunning(source_name, target_doc=None, ignore_permissions=False):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
from erpnext.accounts.doctype.dunning.dunning import (
|
def postprocess_dunning(source, target):
|
||||||
calculate_interest_and_amount,
|
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text
|
||||||
get_dunning_letter_text,
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_missing_values(source, target):
|
dunning_type = frappe.db.exists("Dunning Type", {"is_default": 1, "company": source.company})
|
||||||
target.sales_invoice = source_name
|
if dunning_type:
|
||||||
target.outstanding_amount = source.outstanding_amount
|
dunning_type = frappe.get_doc("Dunning Type", dunning_type)
|
||||||
overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days
|
|
||||||
target.overdue_days = overdue_days
|
|
||||||
if frappe.db.exists(
|
|
||||||
"Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
|
|
||||||
):
|
|
||||||
dunning_type = frappe.get_doc(
|
|
||||||
"Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
|
|
||||||
)
|
|
||||||
target.dunning_type = dunning_type.name
|
target.dunning_type = dunning_type.name
|
||||||
target.rate_of_interest = dunning_type.rate_of_interest
|
target.rate_of_interest = dunning_type.rate_of_interest
|
||||||
target.dunning_fee = dunning_type.dunning_fee
|
target.dunning_fee = dunning_type.dunning_fee
|
||||||
letter_text = get_dunning_letter_text(dunning_type=dunning_type.name, doc=target.as_dict())
|
target.income_account = dunning_type.income_account
|
||||||
|
target.cost_center = dunning_type.cost_center
|
||||||
|
letter_text = get_dunning_letter_text(
|
||||||
|
dunning_type=dunning_type.name, doc=target.as_dict(), language=source.language
|
||||||
|
)
|
||||||
|
|
||||||
if letter_text:
|
if letter_text:
|
||||||
target.body_text = letter_text.get("body_text")
|
target.body_text = letter_text.get("body_text")
|
||||||
target.closing_text = letter_text.get("closing_text")
|
target.closing_text = letter_text.get("closing_text")
|
||||||
target.language = letter_text.get("language")
|
target.language = letter_text.get("language")
|
||||||
amounts = calculate_interest_and_amount(
|
|
||||||
target.outstanding_amount,
|
|
||||||
target.rate_of_interest,
|
|
||||||
target.dunning_fee,
|
|
||||||
target.overdue_days,
|
|
||||||
)
|
|
||||||
target.interest_amount = amounts.get("interest_amount")
|
|
||||||
target.dunning_amount = amounts.get("dunning_amount")
|
|
||||||
target.grand_total = amounts.get("grand_total")
|
|
||||||
|
|
||||||
doclist = get_mapped_doc(
|
target.validate()
|
||||||
"Sales Invoice",
|
|
||||||
source_name,
|
return get_mapped_doc(
|
||||||
{
|
from_doctype="Sales Invoice",
|
||||||
|
from_docname=source_name,
|
||||||
|
target_doc=target_doc,
|
||||||
|
table_maps={
|
||||||
"Sales Invoice": {
|
"Sales Invoice": {
|
||||||
"doctype": "Dunning",
|
"doctype": "Dunning",
|
||||||
}
|
"field_map": {"customer_address": "customer_address", "parent": "sales_invoice"},
|
||||||
|
},
|
||||||
|
"Payment Schedule": {
|
||||||
|
"doctype": "Overdue Payment",
|
||||||
|
"field_map": {"name": "payment_schedule", "parent": "sales_invoice"},
|
||||||
|
"condition": lambda doc: doc.outstanding > 0 and getdate(doc.due_date) < getdate(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
target_doc,
|
postprocess=postprocess_dunning,
|
||||||
set_missing_values,
|
ignore_permissions=ignore_permissions,
|
||||||
)
|
)
|
||||||
return doclist
|
|
||||||
|
|
||||||
|
|
||||||
def check_if_return_invoice_linked_with_payment_entry(self):
|
def check_if_return_invoice_linked_with_payment_entry(self):
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Shareholder', {
|
frappe.ui.form.on('Shareholder', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Shareholder' };
|
|
||||||
|
|
||||||
frm.toggle_display(['contact_html'], !frm.doc.__islocal);
|
frm.toggle_display(['contact_html'], !frm.doc.__islocal);
|
||||||
|
|
||||||
if (frm.doc.__islocal) {
|
if (frm.doc.__islocal) {
|
||||||
|
|||||||
@ -13,14 +13,11 @@ import erpnext
|
|||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
|
||||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||||
from erpnext.accounts.utils import create_payment_ledger_entry
|
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||||
|
|
||||||
|
|
||||||
class ClosedAccountingPeriod(frappe.ValidationError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def make_gl_entries(
|
def make_gl_entries(
|
||||||
gl_map,
|
gl_map,
|
||||||
cancel=False,
|
cancel=False,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"absolute_value": 0,
|
||||||
"align_labels_right": 0,
|
"align_labels_right": 0,
|
||||||
"creation": "2019-12-11 04:37:14.012805",
|
"creation": "2019-12-11 04:37:14.012805",
|
||||||
"css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n",
|
"css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n",
|
||||||
@ -9,10 +10,10 @@
|
|||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Print Format",
|
"doctype": "Print Format",
|
||||||
"font": "Arial",
|
"font": "Arial",
|
||||||
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<b>{{doc.customer_name}}</b> <br />\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<div style=\\\"text-align:left;\\\">\\n<div style=\\\"font-size:24px; text-transform:uppercase;\\\">{{_(doc.dunning_type)}}</div>\\n<div style=\\\"font-size:16px;padding-bottom:5px;\\\">{{ doc.name }}</div>\\n</div>\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldname\": \"sales_invoice\", \"print_hide\": 0, \"label\": \"Sales Invoice\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"label\": \"Due Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table class=\\\"table table-borderless table-data\\\">\\n <tbody>\\n <tr>\\n <th>{{_(\\\"Description\\\")}}</th>\\n\\t <th style=\\\"text-align: right;\\\">{{_(\\\"Amount\\\")}}</th>\\n </tr>\\n <tr>\\n <td>\\n {{_(\\\"Outstanding Amount\\\")}}\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n </td>\\n </tr>\\n {%if doc.rate_of_interest > 0%}\\n <tr>\\n <td>\\n {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"interest_amount\\\")}}\\n </td>\\n </tr>\\n {% endif %}\\n {%if doc.dunning_fee > 0%}\\n <tr>\\n <td>\\n {{_(\\\"Dunning Fee\\\")}}\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n </td>\\n </tr>\\n {% endif %}\\n </tbody>\\n</table>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n<div class=\\\"row total\\\" style =\\\"margin-right: 0px;\\\">\\n\\t\\t<div class=\\\"col-xs-5\\\">\\n\\t\\t\\t<b>{{_(\\\"Grand Total\\\")}}</b></div>\\n\\t\\t<div class=\\\"col-xs-7 text-right\\\" style=\\\"padding-right: 4px;\\\">\\n\\t\\t\\t<b>{{doc.get_formatted(\\\"grand_total\\\")}}</b>\\n\\t\\t</div>\\n</div>\\n\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
|
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<b>{{doc.customer_name}}</b> <br />\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<div style=\\\"text-align:left;\\\">\\n<div style=\\\"font-size:24px; text-transform:uppercase;\\\">{{_(doc.dunning_type)}}</div>\\n<div style=\\\"font-size:16px;padding-bottom:5px;\\\">{{ doc.name }}</div>\\n</div>\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"overdue_payments\", \"print_hide\": 0, \"label\": \"Overdue Payments\", \"visible_columns\": [{\"fieldname\": \"sales_invoice\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"dunning_level\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"due_date\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"overdue_days\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"invoice_portion\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"outstanding\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"interest\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total_outstanding\", \"print_hide\": 0, \"label\": \"Total Outstanding\"}, {\"fieldname\": \"dunning_fee\", \"print_hide\": 0, \"label\": \"Dunning Fee\"}, {\"fieldname\": \"total_interest\", \"print_hide\": 0, \"label\": \"Total Interest\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"line_breaks": 0,
|
"line_breaks": 0,
|
||||||
"modified": "2020-07-14 18:25:44.348207",
|
"modified": "2021-09-30 10:22:02.603871",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning Letter",
|
"name": "Dunning Letter",
|
||||||
|
|||||||
@ -49,7 +49,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Start Year"),
|
"label": __("Start Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
on_change: () => {
|
on_change: () => {
|
||||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
|
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
|
||||||
@ -65,7 +65,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("End Year"),
|
"label": __("End Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
on_change: () => {
|
on_change: () => {
|
||||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
|
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
|
||||||
@ -139,7 +139,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
onload: function() {
|
onload: function() {
|
||||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||||
|
|
||||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
|
|||||||
@ -48,7 +48,7 @@ function get_filters() {
|
|||||||
"label": __("Start Year"),
|
"label": __("Start Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -56,7 +56,7 @@ function get_filters() {
|
|||||||
"label": __("End Year"),
|
"label": __("End Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -100,7 +100,7 @@ frappe.query_reports["Deferred Revenue and Expense"] = {
|
|||||||
return default_formatter(value, row, column, data);
|
return default_formatter(value, row, column, data);
|
||||||
},
|
},
|
||||||
onload: function(report){
|
onload: function(report){
|
||||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year");
|
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||||
|
|
||||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
|
|||||||
@ -4,9 +4,10 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb
|
from frappe import _, qb
|
||||||
from frappe.query_builder import Column, functions
|
from frappe.query_builder import Column, functions
|
||||||
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded
|
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, getdate, rounded
|
||||||
|
|
||||||
from erpnext.accounts.report.financial_statements import get_period_list
|
from erpnext.accounts.report.financial_statements import get_period_list
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
|
||||||
class Deferred_Item(object):
|
class Deferred_Item(object):
|
||||||
@ -226,7 +227,7 @@ class Deferred_Revenue_and_Expense_Report(object):
|
|||||||
|
|
||||||
# If no filters are provided, get user defaults
|
# If no filters are provided, get user defaults
|
||||||
if not filters:
|
if not filters:
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date=getdate()))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
||||||
Deferred_Revenue_and_Expense_Report,
|
Deferred_Revenue_and_Expense_Report,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
pda.submit()
|
pda.submit()
|
||||||
|
|
||||||
# execute report
|
# execute report
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
@ -209,7 +210,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
pda.submit()
|
pda.submit()
|
||||||
|
|
||||||
# execute report
|
# execute report
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
@ -297,7 +298,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
pda.submit()
|
pda.submit()
|
||||||
|
|
||||||
# execute report
|
# execute report
|
||||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
|||||||
@ -18,7 +18,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"on_change": function(query_report) {
|
"on_change": function(query_report) {
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
|
|||||||
@ -416,6 +416,7 @@ def set_gl_entries_by_account(
|
|||||||
filters,
|
filters,
|
||||||
gl_entries_by_account,
|
gl_entries_by_account,
|
||||||
ignore_closing_entries=False,
|
ignore_closing_entries=False,
|
||||||
|
ignore_opening_entries=False,
|
||||||
):
|
):
|
||||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
@ -426,7 +427,6 @@ def set_gl_entries_by_account(
|
|||||||
pluck="name",
|
pluck="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
ignore_opening_entries = False
|
|
||||||
if accounts_list:
|
if accounts_list:
|
||||||
# For balance sheet
|
# For balance sheet
|
||||||
if not from_date:
|
if not from_date:
|
||||||
|
|||||||
@ -12,14 +12,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
erpnext.financial_statements);
|
erpnext.financial_statements);
|
||||||
|
|
||||||
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
||||||
{
|
|
||||||
"fieldname": "project",
|
|
||||||
"label": __("Project"),
|
|
||||||
"fieldtype": "MultiSelectList",
|
|
||||||
get_data: function(txt) {
|
|
||||||
return frappe.db.get_link_options('Project', txt);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "accumulated_values",
|
"fieldname": "accumulated_values",
|
||||||
"label": __("Accumulated Values"),
|
"label": __("Accumulated Values"),
|
||||||
|
|||||||
@ -9,16 +9,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
|
erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
|
||||||
|
|
||||||
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
|
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
|
||||||
{
|
|
||||||
"fieldname": "project",
|
|
||||||
"label": __("Project"),
|
|
||||||
"fieldtype": "MultiSelectList",
|
|
||||||
get_data: function(txt) {
|
|
||||||
return frappe.db.get_link_options('Project', txt, {
|
|
||||||
company: frappe.query_report.get_filter_value("company")
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "include_default_book_entries",
|
"fieldname": "include_default_book_entries",
|
||||||
"label": __("Include Default Book Entries"),
|
"label": __("Include Default Book Entries"),
|
||||||
|
|||||||
@ -25,7 +25,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"on_change": function(query_report) {
|
"on_change": function(query_report) {
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"on_change": function(query_report) {
|
"on_change": function(query_report) {
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
|
|||||||
@ -117,6 +117,7 @@ def get_data(filters):
|
|||||||
filters,
|
filters,
|
||||||
gl_entries_by_account,
|
gl_entries_by_account,
|
||||||
ignore_closing_entries=not flt(filters.with_period_closing_entry),
|
ignore_closing_entries=not flt(filters.with_period_closing_entry),
|
||||||
|
ignore_opening_entries=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
calculate_values(accounts, gl_entries_by_account, opening_balances)
|
calculate_values(accounts, gl_entries_by_account, opening_balances)
|
||||||
@ -159,6 +160,8 @@ def get_rootwise_opening_balances(filters, report_type):
|
|||||||
accounting_dimensions,
|
accounting_dimensions,
|
||||||
period_closing_voucher=last_period_closing_voucher[0].name,
|
period_closing_voucher=last_period_closing_voucher[0].name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Report getting generate from the mid of a fiscal year
|
||||||
if getdate(last_period_closing_voucher[0].posting_date) < getdate(
|
if getdate(last_period_closing_voucher[0].posting_date) < getdate(
|
||||||
add_days(filters.from_date, -1)
|
add_days(filters.from_date, -1)
|
||||||
):
|
):
|
||||||
@ -218,9 +221,18 @@ def get_opening_balance(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if start_date:
|
if start_date:
|
||||||
opening_balance = opening_balance.where(closing_balance.posting_date >= start_date)
|
opening_balance = opening_balance.where(
|
||||||
|
(closing_balance.posting_date >= start_date)
|
||||||
|
& (closing_balance.posting_date < filters.from_date)
|
||||||
|
)
|
||||||
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
|
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
|
||||||
opening_balance = opening_balance.where(closing_balance.posting_date < filters.from_date)
|
else:
|
||||||
|
opening_balance = opening_balance.where(
|
||||||
|
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
|
||||||
|
)
|
||||||
|
|
||||||
|
if doctype == "GL Entry":
|
||||||
|
opening_balance = opening_balance.where(closing_balance.is_cancelled == 0)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not filters.show_unclosed_fy_pl_balances
|
not filters.show_unclosed_fy_pl_balances
|
||||||
|
|||||||
@ -16,7 +16,7 @@ frappe.query_reports["Trial Balance for Party"] = {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"on_change": function(query_report) {
|
"on_change": function(query_report) {
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
|
|||||||
@ -850,7 +850,7 @@ def get_held_invoices(party_type, party):
|
|||||||
|
|
||||||
if party_type == "Supplier":
|
if party_type == "Supplier":
|
||||||
held_invoices = frappe.db.sql(
|
held_invoices = frappe.db.sql(
|
||||||
"select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()",
|
"select name from `tabPurchase Invoice` where on_hold = 1 and release_date IS NOT NULL and release_date > CURDATE()",
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
held_invoices = set(d["name"] for d in held_invoices)
|
held_invoices = set(d["name"] for d in held_invoices)
|
||||||
@ -1110,6 +1110,12 @@ def get_autoname_with_number(number_value, doc_title, company):
|
|||||||
return " - ".join(parts)
|
return " - ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_naming_series_variable(doc, variable):
|
||||||
|
if variable == "FY":
|
||||||
|
date = doc.get("posting_date") or doc.get("transaction_date") or getdate()
|
||||||
|
return get_fiscal_year(date=date, company=doc.get("company"))[0]
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_coa(doctype, parent, is_root, chart=None):
|
def get_coa(doctype, parent, is_root, chart=None):
|
||||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
|
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
|
||||||
|
|||||||
@ -63,7 +63,7 @@ frappe.ui.form.on('Asset Movement', {
|
|||||||
fieldnames_to_be_altered = {
|
fieldnames_to_be_altered = {
|
||||||
target_location: { read_only: 0, reqd: 1 },
|
target_location: { read_only: 0, reqd: 1 },
|
||||||
source_location: { read_only: 1, reqd: 0 },
|
source_location: { read_only: 1, reqd: 0 },
|
||||||
from_employee: { read_only: 0, reqd: 1 },
|
from_employee: { read_only: 0, reqd: 0 },
|
||||||
to_employee: { read_only: 1, reqd: 0 }
|
to_employee: { read_only: 1, reqd: 0 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,29 +62,20 @@ class AssetMovement(Document):
|
|||||||
frappe.throw(_("Source and Target Location cannot be same"))
|
frappe.throw(_("Source and Target Location cannot be same"))
|
||||||
|
|
||||||
if self.purpose == "Receipt":
|
if self.purpose == "Receipt":
|
||||||
# only when asset is bought and first entry is made
|
if not (d.source_location or d.from_employee) and not (d.target_location or d.to_employee):
|
||||||
if not d.source_location and not (d.target_location or d.to_employee):
|
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
|
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
|
||||||
)
|
)
|
||||||
elif d.source_location:
|
elif d.from_employee and not d.target_location:
|
||||||
# when asset is received from an employee
|
frappe.throw(
|
||||||
if d.target_location and not d.from_employee:
|
_("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
|
||||||
frappe.throw(
|
)
|
||||||
_("From employee is required while receiving Asset {0} to a target location").format(
|
elif d.to_employee and d.target_location:
|
||||||
d.asset
|
frappe.throw(
|
||||||
)
|
_(
|
||||||
)
|
"Asset {0} cannot be received at a location and given to an employee in a single movement"
|
||||||
if d.from_employee and not d.target_location:
|
).format(d.asset)
|
||||||
frappe.throw(
|
)
|
||||||
_("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
|
|
||||||
)
|
|
||||||
if d.to_employee and d.target_location:
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"Asset {0} cannot be received at a location and given to employee in a single movement"
|
|
||||||
).format(d.asset)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_employee(self):
|
def validate_employee(self):
|
||||||
for d in self.assets:
|
for d in self.assets:
|
||||||
|
|||||||
@ -82,7 +82,7 @@ frappe.query_reports["Fixed Asset Register"] = {
|
|||||||
"label": __("Start Year"),
|
"label": __("Start Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -90,7 +90,7 @@ frappe.query_reports["Fixed Asset Register"] = {
|
|||||||
"label": __("End Year"),
|
"label": __("End Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,18 +5,19 @@
|
|||||||
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
|
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Item\"}",
|
"filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Item\"}",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-21 16:13:25.092287",
|
"modified": "2023-07-19 13:06:42.937941",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Trends",
|
"name": "Purchase Order Trends",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Purchase Order Trends",
|
"report_name": "Purchase Order Trends",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Line",
|
"type": "Line",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
|||||||
@ -4,18 +4,19 @@
|
|||||||
"creation": "2020-07-20 21:01:02.329519",
|
"creation": "2020-07-20 21:01:02.329519",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Supplier\"}",
|
"filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Supplier\"}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-22 12:43:40.829652",
|
"modified": "2023-07-19 13:07:41.753556",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Top Suppliers",
|
"name": "Top Suppliers",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Purchase Receipt Trends",
|
"report_name": "Purchase Receipt Trends",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Bar",
|
"type": "Bar",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
|||||||
@ -66,8 +66,6 @@ frappe.ui.form.on("Supplier", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
|
|
||||||
|
|
||||||
if (frappe.defaults.get_default("supp_master_name") != "Naming Series") {
|
if (frappe.defaults.get_default("supp_master_name") != "Naming Series") {
|
||||||
frm.toggle_display("naming_series", false);
|
frm.toggle_display("naming_series", false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -61,7 +61,7 @@
|
|||||||
"fieldname": "communication_channel",
|
"fieldname": "communication_channel",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Communication Channel",
|
"label": "Communication Channel",
|
||||||
"options": "\nExotel"
|
"options": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
|
|||||||
@ -822,6 +822,15 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
return frappe.db.sql(query, filters)
|
return frappe.db.sql(query, filters)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_doctypes_for_closing(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
doctypes = frappe.get_hooks("period_closing_doctypes")
|
||||||
|
if txt:
|
||||||
|
doctypes = [d for d in doctypes if txt.lower() in d.lower()]
|
||||||
|
return [(d,) for d in set(doctypes)]
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
|||||||
@ -669,7 +669,11 @@ def get_filters(
|
|||||||
if reference_voucher_detail_no:
|
if reference_voucher_detail_no:
|
||||||
filters["voucher_detail_no"] = reference_voucher_detail_no
|
filters["voucher_detail_no"] = reference_voucher_detail_no
|
||||||
|
|
||||||
if item_row and item_row.get("warehouse"):
|
if (
|
||||||
|
voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||||
|
and item_row
|
||||||
|
and item_row.get("warehouse")
|
||||||
|
):
|
||||||
filters["warehouse"] = item_row.get("warehouse")
|
filters["warehouse"] = item_row.get("warehouse")
|
||||||
|
|
||||||
return filters
|
return filters
|
||||||
|
|||||||
@ -201,6 +201,12 @@ class StockController(AccountsController):
|
|||||||
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
|
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
|
||||||
|
|
||||||
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
|
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
|
||||||
|
if not expense_account:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer"
|
||||||
|
).format(frappe.bold(self.company))
|
||||||
|
)
|
||||||
|
|
||||||
gl_list.append(
|
gl_list.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
|
|||||||
@ -30,11 +30,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
|
|||||||
var me = this;
|
var me = this;
|
||||||
let doc = this.frm.doc;
|
let doc = this.frm.doc;
|
||||||
erpnext.toggle_naming_series();
|
erpnext.toggle_naming_series();
|
||||||
frappe.dynamic_link = {
|
|
||||||
doc: doc,
|
|
||||||
fieldname: 'name',
|
|
||||||
doctype: 'Lead'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
|
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
|
||||||
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
|
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Prospect', {
|
frappe.ui.form.on('Prospect', {
|
||||||
refresh (frm) {
|
refresh (frm) {
|
||||||
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
|
|
||||||
|
|
||||||
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
|
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
|
||||||
frm.add_custom_button(__("Customer"), function() {
|
frm.add_custom_button(__("Customer"), function() {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"creation": "2019-05-21 07:41:53.536536",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"enabled",
|
|
||||||
"section_break_2",
|
|
||||||
"account_sid",
|
|
||||||
"api_key",
|
|
||||||
"api_token",
|
|
||||||
"section_break_6",
|
|
||||||
"map_custom_field_to_doctype",
|
|
||||||
"target_doctype"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "enabled",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Enabled"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "enabled",
|
|
||||||
"fieldname": "section_break_2",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Credentials"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "account_sid",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Account SID"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "api_token",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "API Token"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "api_key",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "API Key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "enabled",
|
|
||||||
"fieldname": "section_break_6",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Custom Field"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "map_custom_field_to_doctype",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Map Custom Field to DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "map_custom_field_to_doctype",
|
|
||||||
"fieldname": "target_doctype",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Target DocType",
|
|
||||||
"mandatory_depends_on": "map_custom_field_to_doctype",
|
|
||||||
"options": "DocType"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"issingle": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2022-12-14 17:24:50.176107",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "ERPNext Integrations",
|
|
||||||
"name": "Exotel Settings",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "ASC",
|
|
||||||
"states": [],
|
|
||||||
"track_changes": 1
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
import requests
|
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class ExotelSettings(Document):
|
|
||||||
def validate(self):
|
|
||||||
self.verify_credentials()
|
|
||||||
|
|
||||||
def verify_credentials(self):
|
|
||||||
if self.enabled:
|
|
||||||
response = requests.get(
|
|
||||||
"https://api.exotel.com/v1/Accounts/{sid}".format(sid=self.account_sid),
|
|
||||||
auth=(self.api_key, self.api_token),
|
|
||||||
)
|
|
||||||
if response.status_code != 200:
|
|
||||||
frappe.throw(_("Invalid credentials"))
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
import frappe
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# api/method/erpnext.erpnext_integrations.exotel_integration.handle_incoming_call
|
|
||||||
# api/method/erpnext.erpnext_integrations.exotel_integration.handle_end_call
|
|
||||||
# api/method/erpnext.erpnext_integrations.exotel_integration.handle_missed_call
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def handle_incoming_call(**kwargs):
|
|
||||||
try:
|
|
||||||
exotel_settings = get_exotel_settings()
|
|
||||||
if not exotel_settings.enabled:
|
|
||||||
return
|
|
||||||
|
|
||||||
call_payload = kwargs
|
|
||||||
status = call_payload.get("Status")
|
|
||||||
if status == "free":
|
|
||||||
return
|
|
||||||
|
|
||||||
call_log = get_call_log(call_payload)
|
|
||||||
if not call_log:
|
|
||||||
create_call_log(call_payload)
|
|
||||||
else:
|
|
||||||
update_call_log(call_payload, call_log=call_log)
|
|
||||||
except Exception as e:
|
|
||||||
frappe.db.rollback()
|
|
||||||
exotel_settings.log_error("Error in Exotel incoming call")
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def handle_end_call(**kwargs):
|
|
||||||
update_call_log(kwargs, "Completed")
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def handle_missed_call(**kwargs):
|
|
||||||
status = ""
|
|
||||||
call_type = kwargs.get("CallType")
|
|
||||||
dial_call_status = kwargs.get("DialCallStatus")
|
|
||||||
|
|
||||||
if call_type == "incomplete" and dial_call_status == "no-answer":
|
|
||||||
status = "No Answer"
|
|
||||||
elif call_type == "client-hangup" and dial_call_status == "canceled":
|
|
||||||
status = "Canceled"
|
|
||||||
elif call_type == "incomplete" and dial_call_status == "failed":
|
|
||||||
status = "Failed"
|
|
||||||
|
|
||||||
update_call_log(kwargs, status)
|
|
||||||
|
|
||||||
|
|
||||||
def update_call_log(call_payload, status="Ringing", call_log=None):
|
|
||||||
call_log = call_log or get_call_log(call_payload)
|
|
||||||
|
|
||||||
# for a new sid, call_log and get_call_log will be empty so create a new log
|
|
||||||
if not call_log:
|
|
||||||
call_log = create_call_log(call_payload)
|
|
||||||
if call_log:
|
|
||||||
call_log.status = status
|
|
||||||
call_log.to = call_payload.get("DialWhomNumber")
|
|
||||||
call_log.duration = call_payload.get("DialCallDuration") or 0
|
|
||||||
call_log.recording_url = call_payload.get("RecordingUrl")
|
|
||||||
call_log.save(ignore_permissions=True)
|
|
||||||
frappe.db.commit()
|
|
||||||
return call_log
|
|
||||||
|
|
||||||
|
|
||||||
def get_call_log(call_payload):
|
|
||||||
call_log_id = call_payload.get("CallSid")
|
|
||||||
if frappe.db.exists("Call Log", call_log_id):
|
|
||||||
return frappe.get_doc("Call Log", call_log_id)
|
|
||||||
|
|
||||||
|
|
||||||
def map_custom_field(call_payload, call_log):
|
|
||||||
field_value = call_payload.get("CustomField")
|
|
||||||
|
|
||||||
if not field_value:
|
|
||||||
return call_log
|
|
||||||
|
|
||||||
settings = get_exotel_settings()
|
|
||||||
target_doctype = settings.target_doctype
|
|
||||||
mapping_enabled = settings.map_custom_field_to_doctype
|
|
||||||
|
|
||||||
if not mapping_enabled or not target_doctype:
|
|
||||||
return call_log
|
|
||||||
|
|
||||||
call_log.append("links", {"link_doctype": target_doctype, "link_name": field_value})
|
|
||||||
|
|
||||||
return call_log
|
|
||||||
|
|
||||||
|
|
||||||
def create_call_log(call_payload):
|
|
||||||
call_log = frappe.new_doc("Call Log")
|
|
||||||
call_log.id = call_payload.get("CallSid")
|
|
||||||
call_log.to = call_payload.get("DialWhomNumber")
|
|
||||||
call_log.medium = call_payload.get("To")
|
|
||||||
call_log.status = "Ringing"
|
|
||||||
setattr(call_log, "from", call_payload.get("CallFrom"))
|
|
||||||
map_custom_field(call_payload, call_log)
|
|
||||||
call_log.save(ignore_permissions=True)
|
|
||||||
frappe.db.commit()
|
|
||||||
return call_log
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_call_status(call_id):
|
|
||||||
endpoint = get_exotel_endpoint("Calls/{call_id}.json".format(call_id=call_id))
|
|
||||||
response = requests.get(endpoint)
|
|
||||||
status = response.json().get("Call", {}).get("Status")
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def make_a_call(from_number, to_number, caller_id, **kwargs):
|
|
||||||
endpoint = get_exotel_endpoint("Calls/connect.json?details=true")
|
|
||||||
response = requests.post(
|
|
||||||
endpoint, data={"From": from_number, "To": to_number, "CallerId": caller_id, **kwargs}
|
|
||||||
)
|
|
||||||
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
|
|
||||||
def get_exotel_settings():
|
|
||||||
return frappe.get_single("Exotel Settings")
|
|
||||||
|
|
||||||
|
|
||||||
def whitelist_numbers(numbers, caller_id):
|
|
||||||
endpoint = get_exotel_endpoint("CustomerWhitelist")
|
|
||||||
response = requests.post(
|
|
||||||
endpoint,
|
|
||||||
data={
|
|
||||||
"VirtualNumber": caller_id,
|
|
||||||
"Number": numbers,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_exophones():
|
|
||||||
endpoint = get_exotel_endpoint("IncomingPhoneNumbers")
|
|
||||||
response = requests.post(endpoint)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def get_exotel_endpoint(action):
|
|
||||||
settings = get_exotel_settings()
|
|
||||||
return "https://{api_key}:{api_token}@api.exotel.com/v1/Accounts/{sid}/{action}".format(
|
|
||||||
api_key=settings.api_key, api_token=settings.api_token, sid=settings.account_sid, action=action
|
|
||||||
)
|
|
||||||
@ -230,17 +230,6 @@
|
|||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Card Break"
|
"type": "Card Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"dependencies": "",
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Exotel Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Exotel Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"is_query_report": 0,
|
"is_query_report": 0,
|
||||||
@ -252,7 +241,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-05-24 14:47:25.984717",
|
"modified": "2023-05-24 14:47:26.984717",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "ERPNext Integrations",
|
"module": "ERPNext Integrations",
|
||||||
"name": "ERPNext Integrations",
|
"name": "ERPNext Integrations",
|
||||||
|
|||||||
@ -83,7 +83,7 @@ update_website_context = [
|
|||||||
my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context"
|
my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context"
|
||||||
webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context"
|
webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context"
|
||||||
|
|
||||||
calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "ToDo"]
|
calendars = ["Task", "Work Order", "Sales Order", "Holiday List", "ToDo"]
|
||||||
|
|
||||||
website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"]
|
website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"]
|
||||||
|
|
||||||
@ -285,10 +285,34 @@ standard_queries = {
|
|||||||
"Customer": "erpnext.controllers.queries.customer_query",
|
"Customer": "erpnext.controllers.queries.customer_query",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
period_closing_doctypes = [
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Journal Entry",
|
||||||
|
"Bank Clearance",
|
||||||
|
"Stock Entry",
|
||||||
|
"Dunning",
|
||||||
|
"Invoice Discounting",
|
||||||
|
"Payment Entry",
|
||||||
|
"Period Closing Voucher",
|
||||||
|
"Process Deferred Accounting",
|
||||||
|
"Asset",
|
||||||
|
"Asset Capitalization",
|
||||||
|
"Asset Repair",
|
||||||
|
"Delivery Note",
|
||||||
|
"Landed Cost Voucher",
|
||||||
|
"Purchase Receipt",
|
||||||
|
"Stock Reconciliation",
|
||||||
|
"Subcontracting Receipt",
|
||||||
|
]
|
||||||
|
|
||||||
doc_events = {
|
doc_events = {
|
||||||
"*": {
|
"*": {
|
||||||
"validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
|
"validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
|
||||||
},
|
},
|
||||||
|
tuple(period_closing_doctypes): {
|
||||||
|
"validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save",
|
||||||
|
},
|
||||||
"Stock Entry": {
|
"Stock Entry": {
|
||||||
"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
|
"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
|
||||||
"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
|
"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
|
||||||
@ -334,6 +358,7 @@ doc_events = {
|
|||||||
"erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status",
|
"erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status",
|
||||||
"erpnext.accounts.doctype.dunning.dunning.resolve_dunning",
|
"erpnext.accounts.doctype.dunning.dunning.resolve_dunning",
|
||||||
],
|
],
|
||||||
|
"on_cancel": ["erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
|
||||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||||
},
|
},
|
||||||
"Address": {
|
"Address": {
|
||||||
@ -354,6 +379,11 @@ doc_events = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# function should expect the variable and doc as arguments
|
||||||
|
naming_series_variables = {
|
||||||
|
"FY": "erpnext.accounts.utils.parse_naming_series_variable",
|
||||||
|
}
|
||||||
|
|
||||||
# On cancel event Payment Entry will be exempted and all linked submittable doctype will get cancelled.
|
# On cancel event Payment Entry will be exempted and all linked submittable doctype will get cancelled.
|
||||||
# to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled.
|
# to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled.
|
||||||
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
|
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
|
||||||
@ -459,15 +489,6 @@ advance_payment_doctypes = ["Sales Order", "Purchase Order"]
|
|||||||
|
|
||||||
invoice_doctypes = ["Sales Invoice", "Purchase Invoice"]
|
invoice_doctypes = ["Sales Invoice", "Purchase Invoice"]
|
||||||
|
|
||||||
period_closing_doctypes = [
|
|
||||||
"Sales Invoice",
|
|
||||||
"Purchase Invoice",
|
|
||||||
"Journal Entry",
|
|
||||||
"Bank Clearance",
|
|
||||||
"Asset",
|
|
||||||
"Stock Entry",
|
|
||||||
]
|
|
||||||
|
|
||||||
bank_reconciliation_doctypes = [
|
bank_reconciliation_doctypes = [
|
||||||
"Payment Entry",
|
"Payment Entry",
|
||||||
"Journal Entry",
|
"Journal Entry",
|
||||||
@ -611,3 +632,8 @@ global_search_doctypes = {
|
|||||||
additional_timeline_content = {
|
additional_timeline_content = {
|
||||||
"*": ["erpnext.telephony.doctype.call_log.call_log.get_linked_call_logs"]
|
"*": ["erpnext.telephony.doctype.call_log.call_log.get_linked_call_logs"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extend_bootinfo = [
|
||||||
|
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
|
||||||
|
]
|
||||||
|
|||||||
@ -621,7 +621,7 @@ class ProductionPlan(Document):
|
|||||||
def create_work_order(self, item):
|
def create_work_order(self, item):
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
|
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
|
||||||
|
|
||||||
if item.get("qty") <= 0:
|
if flt(item.get("qty")) <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
wo = frappe.new_doc("Work Order")
|
wo = frappe.new_doc("Work Order")
|
||||||
@ -697,10 +697,9 @@ class ProductionPlan(Document):
|
|||||||
material_request.flags.ignore_permissions = 1
|
material_request.flags.ignore_permissions = 1
|
||||||
material_request.run_method("set_missing_values")
|
material_request.run_method("set_missing_values")
|
||||||
|
|
||||||
|
material_request.save()
|
||||||
if self.get("submit_material_request"):
|
if self.get("submit_material_request"):
|
||||||
material_request.submit()
|
material_request.submit()
|
||||||
else:
|
|
||||||
material_request.save()
|
|
||||||
|
|
||||||
frappe.flags.mute_messages = False
|
frappe.flags.mute_messages = False
|
||||||
|
|
||||||
@ -1540,7 +1539,7 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
|
|||||||
frappe.qb.from_(table)
|
frappe.qb.from_(table)
|
||||||
.inner_join(child)
|
.inner_join(child)
|
||||||
.on(table.name == child.parent)
|
.on(table.name == child.parent)
|
||||||
.select(Sum(child.required_bom_qty * IfNull(child.conversion_factor, 1.0)))
|
.select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0)))
|
||||||
.where(
|
.where(
|
||||||
(table.docstatus == 1)
|
(table.docstatus == 1)
|
||||||
& (child.item_code == item_code)
|
& (child.item_code == item_code)
|
||||||
|
|||||||
@ -933,6 +933,54 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(after_qty, before_qty)
|
self.assertEqual(after_qty, before_qty)
|
||||||
|
|
||||||
|
def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self):
|
||||||
|
from erpnext.stock.utils import get_or_make_bin
|
||||||
|
|
||||||
|
fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
|
||||||
|
bom_item = make_item(
|
||||||
|
properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
|
||||||
|
).name
|
||||||
|
|
||||||
|
if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
|
||||||
|
doc = frappe.get_doc("Item", bom_item)
|
||||||
|
doc.append("uoms", {"uom": "Nos", "conversion_factor": 25})
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
|
||||||
|
|
||||||
|
bin_name = get_or_make_bin(bom_item, "_Test Warehouse - _TC")
|
||||||
|
before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
|
||||||
|
|
||||||
|
pln = create_production_plan(
|
||||||
|
item_code=fg_item, planned_qty=100, ignore_existing_ordered_qty=1, stock_uom="_Test UOM 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in pln.mr_items:
|
||||||
|
self.assertEqual(row.uom, "Nos")
|
||||||
|
self.assertEqual(row.quantity, 4)
|
||||||
|
|
||||||
|
reserved_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
|
||||||
|
self.assertEqual(reserved_qty - before_qty, 100.0)
|
||||||
|
|
||||||
|
pln.submit_material_request = 1
|
||||||
|
pln.make_work_order()
|
||||||
|
|
||||||
|
for work_order in frappe.get_all(
|
||||||
|
"Work Order",
|
||||||
|
fields=["name"],
|
||||||
|
filters={"production_plan": pln.name},
|
||||||
|
):
|
||||||
|
wo_doc = frappe.get_doc("Work Order", work_order.name)
|
||||||
|
wo_doc.source_warehouse = "_Test Warehouse - _TC"
|
||||||
|
wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
wo_doc.fg_warehouse = "_Test Warehouse - _TC"
|
||||||
|
wo_doc.submit()
|
||||||
|
|
||||||
|
reserved_qty_after_mr = flt(
|
||||||
|
frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")
|
||||||
|
)
|
||||||
|
self.assertEqual(reserved_qty_after_mr, before_qty)
|
||||||
|
|
||||||
def test_skip_available_qty_for_sub_assembly_items(self):
|
def test_skip_available_qty_for_sub_assembly_items(self):
|
||||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||||
|
|
||||||
|
|||||||
@ -1026,7 +1026,7 @@ class WorkOrder(Document):
|
|||||||
consumed_qty = frappe.db.sql(
|
consumed_qty = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
SUM(qty)
|
SUM(detail.qty)
|
||||||
FROM
|
FROM
|
||||||
`tabStock Entry` entry,
|
`tabStock Entry` entry,
|
||||||
`tabStock Entry Detail` detail
|
`tabStock Entry Detail` detail
|
||||||
|
|||||||
@ -17,7 +17,7 @@ frappe.query_reports["Job Card Summary"] = {
|
|||||||
label: __("Fiscal Year"),
|
label: __("Fiscal Year"),
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Fiscal Year",
|
options: "Fiscal Year",
|
||||||
default: frappe.defaults.get_user_default("fiscal_year"),
|
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
on_change: function(query_report) {
|
on_change: function(query_report) {
|
||||||
var fiscal_year = query_report.get_values().fiscal_year;
|
var fiscal_year = query_report.get_values().fiscal_year;
|
||||||
|
|||||||
@ -317,7 +317,7 @@ erpnext.patches.v13_0.update_docs_link
|
|||||||
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
||||||
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
|
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
||||||
erpnext.patches.v14_0.update_closing_balances #17-05-2023
|
erpnext.patches.v14_0.update_closing_balances #14-07-2023
|
||||||
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||||
# below migration patches should always run last
|
# below migration patches should always run last
|
||||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||||
@ -333,4 +333,7 @@ execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missin
|
|||||||
erpnext.patches.v14_0.cleanup_workspaces
|
erpnext.patches.v14_0.cleanup_workspaces
|
||||||
erpnext.patches.v15_0.remove_loan_management_module #2023-07-03
|
erpnext.patches.v15_0.remove_loan_management_module #2023-07-03
|
||||||
erpnext.patches.v14_0.set_report_in_process_SOA
|
erpnext.patches.v14_0.set_report_in_process_SOA
|
||||||
erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
|
erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
|
||||||
|
execute:frappe.defaults.clear_default("fiscal_year")
|
||||||
|
erpnext.patches.v15_0.remove_exotel_integration
|
||||||
|
erpnext.patches.v14_0.single_to_multi_dunning
|
||||||
|
|||||||
49
erpnext/patches/v14_0/single_to_multi_dunning.py
Normal file
49
erpnext/patches/v14_0/single_to_multi_dunning.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("accounts", "doctype", "overdue_payment")
|
||||||
|
frappe.reload_doc("accounts", "doctype", "dunning")
|
||||||
|
|
||||||
|
all_dunnings = frappe.get_all("Dunning", filters={"docstatus": ("!=", 2)}, pluck="name")
|
||||||
|
for dunning_name in all_dunnings:
|
||||||
|
dunning = frappe.get_doc("Dunning", dunning_name)
|
||||||
|
if not dunning.sales_invoice:
|
||||||
|
# nothing we can do
|
||||||
|
continue
|
||||||
|
|
||||||
|
if dunning.overdue_payments:
|
||||||
|
# something's already here, doesn't need patching
|
||||||
|
continue
|
||||||
|
|
||||||
|
payment_schedules = frappe.get_all(
|
||||||
|
"Payment Schedule",
|
||||||
|
filters={"parent": dunning.sales_invoice},
|
||||||
|
fields=[
|
||||||
|
"parent as sales_invoice",
|
||||||
|
"name as payment_schedule",
|
||||||
|
"payment_term",
|
||||||
|
"due_date",
|
||||||
|
"invoice_portion",
|
||||||
|
"payment_amount",
|
||||||
|
# at the time of creating this dunning, the full amount was outstanding
|
||||||
|
"payment_amount as outstanding",
|
||||||
|
"'0' as paid_amount",
|
||||||
|
"discounted_amount",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
dunning.extend("overdue_payments", payment_schedules)
|
||||||
|
dunning.validate()
|
||||||
|
|
||||||
|
dunning.flags.ignore_validate_update_after_submit = True
|
||||||
|
dunning.save()
|
||||||
|
|
||||||
|
if dunning.status != "Resolved":
|
||||||
|
# With the new logic, dunning amount gets recorded as additional income
|
||||||
|
# at time of payment. We don't want to record the dunning amount twice,
|
||||||
|
# so we reverse previous GL Entries that recorded the dunning amount at
|
||||||
|
# time of submission of the Dunning.
|
||||||
|
make_reverse_gl_entries(voucher_type="Dunning", voucher_no=dunning.name)
|
||||||
@ -13,56 +13,62 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
def execute():
|
def execute():
|
||||||
frappe.db.truncate("Account Closing Balance")
|
frappe.db.truncate("Account Closing Balance")
|
||||||
|
|
||||||
i = 0
|
for company in frappe.get_all("Company", pluck="name"):
|
||||||
company_wise_order = {}
|
i = 0
|
||||||
for pcv in frappe.db.get_all(
|
company_wise_order = {}
|
||||||
"Period Closing Voucher",
|
for pcv in frappe.db.get_all(
|
||||||
fields=["company", "posting_date", "name"],
|
"Period Closing Voucher",
|
||||||
filters={"docstatus": 1},
|
fields=["company", "posting_date", "name"],
|
||||||
order_by="posting_date",
|
filters={"docstatus": 1, "company": company},
|
||||||
):
|
order_by="posting_date",
|
||||||
|
):
|
||||||
|
|
||||||
company_wise_order.setdefault(pcv.company, [])
|
company_wise_order.setdefault(pcv.company, [])
|
||||||
if pcv.posting_date not in company_wise_order[pcv.company]:
|
if pcv.posting_date not in company_wise_order[pcv.company]:
|
||||||
pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name)
|
pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name)
|
||||||
pcv_doc.year_start_date = get_fiscal_year(
|
pcv_doc.year_start_date = get_fiscal_year(
|
||||||
pcv.posting_date, pcv.fiscal_year, company=pcv.company
|
pcv.posting_date, pcv.fiscal_year, company=pcv.company
|
||||||
)[1]
|
)[1]
|
||||||
|
|
||||||
# get gl entries against pcv
|
# get gl entries against pcv
|
||||||
gl_entries = frappe.db.get_all(
|
gl_entries = frappe.db.get_all(
|
||||||
"GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"]
|
"GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"]
|
||||||
)
|
|
||||||
for entry in gl_entries:
|
|
||||||
entry["is_period_closing_voucher_entry"] = 1
|
|
||||||
entry["closing_date"] = pcv_doc.posting_date
|
|
||||||
entry["period_closing_voucher"] = pcv_doc.name
|
|
||||||
|
|
||||||
# get all gl entries for the year
|
|
||||||
closing_entries = frappe.db.get_all(
|
|
||||||
"GL Entry",
|
|
||||||
filters={
|
|
||||||
"is_cancelled": 0,
|
|
||||||
"voucher_no": ["!=", pcv.name],
|
|
||||||
"posting_date": ["between", [pcv_doc.year_start_date, pcv.posting_date]],
|
|
||||||
"is_opening": "No",
|
|
||||||
},
|
|
||||||
fields=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
if i == 0:
|
|
||||||
# add opening entries only for the first pcv
|
|
||||||
closing_entries += frappe.db.get_all(
|
|
||||||
"GL Entry",
|
|
||||||
filters={"is_cancelled": 0, "is_opening": "Yes"},
|
|
||||||
fields=["*"],
|
|
||||||
)
|
)
|
||||||
|
for entry in gl_entries:
|
||||||
|
entry["is_period_closing_voucher_entry"] = 1
|
||||||
|
entry["closing_date"] = pcv_doc.posting_date
|
||||||
|
entry["period_closing_voucher"] = pcv_doc.name
|
||||||
|
|
||||||
for entry in closing_entries:
|
closing_entries = []
|
||||||
entry["closing_date"] = pcv_doc.posting_date
|
|
||||||
entry["period_closing_voucher"] = pcv_doc.name
|
|
||||||
|
|
||||||
make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
|
if pcv.posting_date not in company_wise_order[pcv.company]:
|
||||||
company_wise_order[pcv.company].append(pcv.posting_date)
|
# get all gl entries for the year
|
||||||
|
closing_entries = frappe.db.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters={
|
||||||
|
"is_cancelled": 0,
|
||||||
|
"voucher_no": ["!=", pcv.name],
|
||||||
|
"posting_date": ["between", [pcv_doc.year_start_date, pcv.posting_date]],
|
||||||
|
"is_opening": "No",
|
||||||
|
"company": company,
|
||||||
|
},
|
||||||
|
fields=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
i += 1
|
if i == 0:
|
||||||
|
# add opening entries only for the first pcv
|
||||||
|
closing_entries += frappe.db.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters={"is_cancelled": 0, "is_opening": "Yes", "company": company},
|
||||||
|
fields=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry in closing_entries:
|
||||||
|
entry["closing_date"] = pcv_doc.posting_date
|
||||||
|
entry["period_closing_voucher"] = pcv_doc.name
|
||||||
|
|
||||||
|
entries = gl_entries + closing_entries
|
||||||
|
|
||||||
|
make_closing_entries(entries, pcv.name, pcv.company, pcv.posting_date)
|
||||||
|
company_wise_order[pcv.company].append(pcv.posting_date)
|
||||||
|
i += 1
|
||||||
|
|||||||
37
erpnext/patches/v15_0/remove_exotel_integration.py
Normal file
37
erpnext/patches/v15_0/remove_exotel_integration.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import click
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||||
|
from frappe.utils.user import get_system_managers
|
||||||
|
|
||||||
|
SETTINGS_DOCTYPE = "Exotel Settings"
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if "exotel_integration" in frappe.get_installed_apps():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
exotel = frappe.get_doc(SETTINGS_DOCTYPE)
|
||||||
|
if exotel.enabled:
|
||||||
|
notify_existing_users()
|
||||||
|
|
||||||
|
frappe.delete_doc("DocType", SETTINGS_DOCTYPE)
|
||||||
|
except Exception:
|
||||||
|
frappe.log_error("Failed to remove Exotel Integration.")
|
||||||
|
|
||||||
|
|
||||||
|
def notify_existing_users():
|
||||||
|
click.secho(
|
||||||
|
"Exotel integration is moved to a separate app and will be removed from ERPNext in version-15.\n"
|
||||||
|
"Please install the app to continue using the integration: https://github.com/frappe/exotel_integration",
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
|
|
||||||
|
notification = {
|
||||||
|
"subject": _(
|
||||||
|
"WARNING: Exotel app has been separated from ERPNext, please install the app to continue using Exotel integration."
|
||||||
|
),
|
||||||
|
"type": "Alert",
|
||||||
|
}
|
||||||
|
make_notification_logs(notification, get_system_managers(only_name=True))
|
||||||
@ -358,12 +358,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
|
|
||||||
erpnext.toggle_naming_series();
|
erpnext.toggle_naming_series();
|
||||||
erpnext.hide_company();
|
erpnext.hide_company();
|
||||||
this.set_dynamic_labels();
|
this.set_dynamic_labels();
|
||||||
this.setup_sms();
|
this.setup_sms();
|
||||||
this.setup_quality_inspection();
|
this.setup_quality_inspection();
|
||||||
this.validate_has_items();
|
this.validate_has_items();
|
||||||
|
erpnext.utils.view_serial_batch_nos(this.frm);
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_barcode() {
|
scan_barcode() {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ erpnext.financial_statements = {
|
|||||||
// dropdown for links to other financial statements
|
// dropdown for links to other financial statements
|
||||||
erpnext.financial_statements.filters = get_filters()
|
erpnext.financial_statements.filters = get_filters()
|
||||||
|
|
||||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||||
|
|
||||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
@ -137,7 +137,7 @@ function get_filters() {
|
|||||||
"label": __("Start Year"),
|
"label": __("Start Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
|
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
|
||||||
},
|
},
|
||||||
@ -146,7 +146,7 @@ function get_filters() {
|
|||||||
"label": __("End Year"),
|
"label": __("End Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
|
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
|
||||||
},
|
},
|
||||||
@ -182,6 +182,16 @@ function get_filters() {
|
|||||||
company: frappe.query_report.get_filter_value("company")
|
company: frappe.query_report.get_filter_value("company")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"label": __("Project"),
|
||||||
|
"fieldtype": "MultiSelectList",
|
||||||
|
get_data: function(txt) {
|
||||||
|
return frappe.db.get_link_options('Project', txt, {
|
||||||
|
company: frappe.query_report.get_filter_value("company")
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -113,6 +113,23 @@ $.extend(erpnext.utils, {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
view_serial_batch_nos: function(frm) {
|
||||||
|
let bundle_ids = frm.doc.items.filter(d => d.serial_and_batch_bundle);
|
||||||
|
|
||||||
|
if (bundle_ids?.length) {
|
||||||
|
frm.add_custom_button(__('Serial / Batch Nos'), () => {
|
||||||
|
frappe.route_options = {
|
||||||
|
"voucher_no": frm.doc.name,
|
||||||
|
"voucher_type": frm.doc.doctype,
|
||||||
|
"from_date": frm.doc.posting_date || frm.doc.transaction_date,
|
||||||
|
"to_date": frm.doc.posting_date || frm.doc.transaction_date,
|
||||||
|
"company": frm.doc.company,
|
||||||
|
};
|
||||||
|
frappe.set_route("query-report", "Serial and Batch Summary");
|
||||||
|
}, __('View'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
add_indicator_for_multicompany: function(frm, info) {
|
add_indicator_for_multicompany: function(frm, info) {
|
||||||
frm.dashboard.stats_area.show();
|
frm.dashboard.stats_area.show();
|
||||||
frm.dashboard.stats_area_row.addClass('flex');
|
frm.dashboard.stats_area_row.addClass('flex');
|
||||||
@ -381,6 +398,27 @@ $.extend(erpnext.utils, {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_fiscal_year: function(date) {
|
||||||
|
if(!date) {
|
||||||
|
date = frappe.datetime.get_today();
|
||||||
|
}
|
||||||
|
|
||||||
|
let fiscal_year = '';
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.accounts.utils.get_fiscal_year",
|
||||||
|
args: {
|
||||||
|
date: date
|
||||||
|
},
|
||||||
|
async: false,
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
fiscal_year = r.message[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return fiscal_year;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -632,7 +670,6 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
fields.splice(3, 0, {
|
fields.splice(3, 0, {
|
||||||
fieldtype: 'Float',
|
fieldtype: 'Float',
|
||||||
fieldname: "conversion_factor",
|
fieldname: "conversion_factor",
|
||||||
in_list_view: 1,
|
|
||||||
label: __("Conversion Factor"),
|
label: __("Conversion Factor"),
|
||||||
precision: get_precision('conversion_factor')
|
precision: get_precision('conversion_factor')
|
||||||
})
|
})
|
||||||
@ -640,6 +677,7 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
|
|
||||||
new frappe.ui.Dialog({
|
new frappe.ui.Dialog({
|
||||||
title: __("Update Items"),
|
title: __("Update Items"),
|
||||||
|
size: "extra-large",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: "trans_items",
|
fieldname: "trans_items",
|
||||||
@ -854,95 +892,87 @@ $(document).on('app_ready', function() {
|
|||||||
|
|
||||||
// Show SLA dashboard
|
// Show SLA dashboard
|
||||||
$(document).on('app_ready', function() {
|
$(document).on('app_ready', function() {
|
||||||
frappe.call({
|
$.each(frappe.boot.service_level_agreement_doctypes, function(_i, d) {
|
||||||
method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_sla_doctypes',
|
frappe.ui.form.on(d, {
|
||||||
callback: function(r) {
|
onload: function(frm) {
|
||||||
if (!r.message)
|
if (!frm.doc.service_level_agreement)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
$.each(r.message, function(_i, d) {
|
frappe.call({
|
||||||
frappe.ui.form.on(d, {
|
method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
|
||||||
onload: function(frm) {
|
args: {
|
||||||
if (!frm.doc.service_level_agreement)
|
doctype: frm.doc.doctype,
|
||||||
return;
|
name: frm.doc.service_level_agreement,
|
||||||
|
customer: frm.doc.customer
|
||||||
frappe.call({
|
|
||||||
method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
|
|
||||||
args: {
|
|
||||||
doctype: frm.doc.doctype,
|
|
||||||
name: frm.doc.service_level_agreement,
|
|
||||||
customer: frm.doc.customer
|
|
||||||
},
|
|
||||||
callback: function (r) {
|
|
||||||
if (r && r.message) {
|
|
||||||
frm.set_query('priority', function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'name': ['in', r.message.priority],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
frm.set_query('service_level_agreement', function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'name': ['in', r.message.service_level_agreements],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
callback: function (r) {
|
||||||
refresh: function(frm) {
|
if (r && r.message) {
|
||||||
if (frm.doc.status !== 'Closed' && frm.doc.service_level_agreement
|
frm.set_query('priority', function() {
|
||||||
&& ['First Response Due', 'Resolution Due'].includes(frm.doc.agreement_status)) {
|
return {
|
||||||
frappe.call({
|
filters: {
|
||||||
'method': 'frappe.client.get',
|
'name': ['in', r.message.priority],
|
||||||
args: {
|
|
||||||
doctype: 'Service Level Agreement',
|
|
||||||
name: frm.doc.service_level_agreement
|
|
||||||
},
|
|
||||||
callback: function(data) {
|
|
||||||
let statuses = data.message.pause_sla_on;
|
|
||||||
const hold_statuses = [];
|
|
||||||
$.each(statuses, (_i, entry) => {
|
|
||||||
hold_statuses.push(entry.status);
|
|
||||||
});
|
|
||||||
if (hold_statuses.includes(frm.doc.status)) {
|
|
||||||
frm.dashboard.clear_headline();
|
|
||||||
let message = {'indicator': 'orange', 'msg': __('SLA is on hold since {0}', [moment(frm.doc.on_hold_since).fromNow(true)])};
|
|
||||||
frm.dashboard.set_headline_alert(
|
|
||||||
'<div class="row">' +
|
|
||||||
'<div class="col-xs-12">' +
|
|
||||||
'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
set_time_to_resolve_and_response(frm, data.message.apply_sla_for_resolution);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
});
|
||||||
|
frm.set_query('service_level_agreement', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'name': ['in', r.message.service_level_agreements],
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
} else if (frm.doc.service_level_agreement) {
|
|
||||||
frm.dashboard.clear_headline();
|
|
||||||
|
|
||||||
let agreement_status = (frm.doc.agreement_status == 'Fulfilled') ?
|
|
||||||
{'indicator': 'green', 'msg': 'Service Level Agreement has been fulfilled'} :
|
|
||||||
{'indicator': 'red', 'msg': 'Service Level Agreement Failed'};
|
|
||||||
|
|
||||||
frm.dashboard.set_headline_alert(
|
|
||||||
'<div class="row">' +
|
|
||||||
'<div class="col-xs-12">' +
|
|
||||||
'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
}
|
|
||||||
|
refresh: function(frm) {
|
||||||
|
if (frm.doc.status !== 'Closed' && frm.doc.service_level_agreement
|
||||||
|
&& ['First Response Due', 'Resolution Due'].includes(frm.doc.agreement_status)) {
|
||||||
|
frappe.call({
|
||||||
|
'method': 'frappe.client.get',
|
||||||
|
args: {
|
||||||
|
doctype: 'Service Level Agreement',
|
||||||
|
name: frm.doc.service_level_agreement
|
||||||
|
},
|
||||||
|
callback: function(data) {
|
||||||
|
let statuses = data.message.pause_sla_on;
|
||||||
|
const hold_statuses = [];
|
||||||
|
$.each(statuses, (_i, entry) => {
|
||||||
|
hold_statuses.push(entry.status);
|
||||||
|
});
|
||||||
|
if (hold_statuses.includes(frm.doc.status)) {
|
||||||
|
frm.dashboard.clear_headline();
|
||||||
|
let message = {'indicator': 'orange', 'msg': __('SLA is on hold since {0}', [moment(frm.doc.on_hold_since).fromNow(true)])};
|
||||||
|
frm.dashboard.set_headline_alert(
|
||||||
|
'<div class="row">' +
|
||||||
|
'<div class="col-xs-12">' +
|
||||||
|
'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
set_time_to_resolve_and_response(frm, data.message.apply_sla_for_resolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (frm.doc.service_level_agreement) {
|
||||||
|
frm.dashboard.clear_headline();
|
||||||
|
|
||||||
|
let agreement_status = (frm.doc.agreement_status == 'Fulfilled') ?
|
||||||
|
{'indicator': 'green', 'msg': 'Service Level Agreement has been fulfilled'} :
|
||||||
|
{'indicator': 'red', 'msg': 'Service Level Agreement Failed'};
|
||||||
|
|
||||||
|
frm.dashboard.set_headline_alert(
|
||||||
|
'<div class="row">' +
|
||||||
|
'<div class="col-xs-12">' +
|
||||||
|
'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1011,4 +1041,4 @@ function attach_selector_button(inner_text, append_loction, context, grid_row) {
|
|||||||
$btn.on("click", function() {
|
$btn.on("click", function() {
|
||||||
context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
|
context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -16,7 +16,7 @@ frappe.query_reports["Fichier des Ecritures Comptables [FEC]"] = {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@ -17,7 +17,7 @@ frappe.query_reports["IRS 1099"] = {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"width": 80,
|
"width": 80,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,18 +5,19 @@
|
|||||||
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
|
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"period\":\"Monthly\",\"based_on\":\"Item\"}",
|
"filters_json": "{\"period\":\"Monthly\",\"based_on\":\"Item\"}",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-22 16:24:45.726270",
|
"modified": "2023-07-19 13:09:45.341791",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Trends",
|
"name": "Sales Order Trends",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Sales Order Trends",
|
"report_name": "Sales Order Trends",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Line",
|
"type": "Line",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
|||||||
@ -5,18 +5,19 @@
|
|||||||
"custom_options": "",
|
"custom_options": "",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||||
"filters_json": "{\"period\":\"Yearly\",\"based_on\":\"Customer\"}",
|
"filters_json": "{\"period\":\"Yearly\",\"based_on\":\"Customer\"}",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"modified": "2020-07-22 17:03:10.320147",
|
"modified": "2023-07-19 13:14:20.151502",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Top Customers",
|
"name": "Top Customers",
|
||||||
"number_of_groups": 0,
|
"number_of_groups": 0,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"report_name": "Delivery Note Trends",
|
"report_name": "Delivery Note Trends",
|
||||||
|
"roles": [],
|
||||||
"timeseries": 0,
|
"timeseries": 0,
|
||||||
"type": "Bar",
|
"type": "Bar",
|
||||||
"use_report_chart": 1,
|
"use_report_chart": 1,
|
||||||
|
|||||||
@ -131,8 +131,6 @@ frappe.ui.form.on("Customer", {
|
|||||||
erpnext.toggle_naming_series();
|
erpnext.toggle_naming_series();
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'}
|
|
||||||
|
|
||||||
if(!frm.doc.__islocal) {
|
if(!frm.doc.__islocal) {
|
||||||
frappe.contacts.render_address_and_contact(frm);
|
frappe.contacts.render_address_and_contact(frm);
|
||||||
|
|
||||||
|
|||||||
@ -1904,12 +1904,11 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
"voucher_no": so.name,
|
"voucher_no": so.name,
|
||||||
"voucher_detail_no": item.name,
|
"voucher_detail_no": item.name,
|
||||||
},
|
},
|
||||||
fields=["status", "reserved_qty", "delivered_qty"],
|
fields=["reserved_qty", "delivered_qty"],
|
||||||
)
|
)
|
||||||
|
|
||||||
for sre_detail in sre_details:
|
for sre_detail in sre_details:
|
||||||
self.assertEqual(sre_detail.reserved_qty, sre_detail.delivered_qty)
|
self.assertEqual(sre_detail.reserved_qty, sre_detail.delivered_qty)
|
||||||
self.assertEqual(sre_detail.status, "Delivered")
|
|
||||||
|
|
||||||
def test_delivered_item_material_request(self):
|
def test_delivered_item_material_request(self):
|
||||||
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
||||||
|
|||||||
@ -13,7 +13,7 @@ frappe.query_reports["Address And Contacts"] = {
|
|||||||
"get_query": function() {
|
"get_query": function() {
|
||||||
return {
|
return {
|
||||||
"filters": {
|
"filters": {
|
||||||
"name": ["in","Customer,Supplier,Sales Partner"],
|
"name": ["in","Customer,Supplier,Sales Partner,Lead"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,6 +130,7 @@ def get_party_group(party_type):
|
|||||||
"Customer": "customer_group",
|
"Customer": "customer_group",
|
||||||
"Supplier": "supplier_group",
|
"Supplier": "supplier_group",
|
||||||
"Sales Partner": "partner_type",
|
"Sales Partner": "partner_type",
|
||||||
|
"Lead": "status",
|
||||||
}
|
}
|
||||||
|
|
||||||
return group[party_type]
|
return group[party_type]
|
||||||
|
|||||||
@ -81,8 +81,6 @@ frappe.ui.form.on("Company", {
|
|||||||
disbale_coa_fields(frm);
|
disbale_coa_fields(frm);
|
||||||
frappe.contacts.render_address_and_contact(frm);
|
frappe.contacts.render_address_and_contact(frm);
|
||||||
|
|
||||||
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'}
|
|
||||||
|
|
||||||
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
|
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
|
||||||
frm.add_custom_button(__('Cost Centers'), function() {
|
frm.add_custom_button(__('Cost Centers'), function() {
|
||||||
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
|
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
|
||||||
|
|||||||
@ -1,352 +1,99 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 1,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"allow_copy": 1,
|
||||||
"allow_import": 0,
|
"creation": "2013-05-02 17:53:24",
|
||||||
"allow_rename": 0,
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"engine": "InnoDB",
|
||||||
"creation": "2013-05-02 17:53:24",
|
"field_order": [
|
||||||
"custom": 0,
|
"default_company",
|
||||||
"docstatus": 0,
|
"country",
|
||||||
"doctype": "DocType",
|
"default_distance_unit",
|
||||||
"editable_grid": 0,
|
"column_break_8",
|
||||||
|
"default_currency",
|
||||||
|
"hide_currency_symbol",
|
||||||
|
"disable_rounded_total",
|
||||||
|
"disable_in_words"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "default_company",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"ignore_user_permissions": 1,
|
||||||
"bold": 0,
|
"label": "Default Company",
|
||||||
"collapsible": 0,
|
"options": "Company"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "default_company",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 1,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Company",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "country",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Country",
|
||||||
"bold": 0,
|
"options": "Country"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "current_fiscal_year",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Current Fiscal Year",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Fiscal Year",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "default_distance_unit",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Default Distance Unit",
|
||||||
"bold": 0,
|
"options": "UOM"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "country",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Country",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Country",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_8",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "",
|
|
||||||
"fieldname": "default_distance_unit",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"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 Distance Unit",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "UOM",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "INR",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "default_currency",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"ignore_user_permissions": 1,
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Default Currency",
|
||||||
"fieldname": "column_break_8",
|
"options": "Currency",
|
||||||
"fieldtype": "Column Break",
|
"reqd": 1
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"description": "Do not show any symbol like $ etc next to currencies.",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "hide_currency_symbol",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Select",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Hide Currency Symbol",
|
||||||
"columns": 0,
|
"options": "\nNo\nYes"
|
||||||
"default": "INR",
|
},
|
||||||
"fieldname": "default_currency",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 1,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Currency",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Currency",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"description": "If disable, 'Rounded Total' field will not be visible in any transaction",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "disable_rounded_total",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Disable Rounded Total"
|
||||||
"description": "Do not show any symbol like $ etc next to currencies.",
|
},
|
||||||
"fieldname": "hide_currency_symbol",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"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": "Hide Currency Symbol",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "\nNo\nYes",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"description": "If disable, 'In Words' field will not be visible in any transaction",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "disable_in_words",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Disable In Words"
|
||||||
"description": "If disable, 'Rounded Total' field will not be visible in any transaction",
|
|
||||||
"fieldname": "disable_rounded_total",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"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": "Disable Rounded Total",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "If disable, 'In Words' field will not be visible in any transaction",
|
|
||||||
"fieldname": "disable_in_words",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"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": "Disable In Words",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"icon": "fa fa-cog",
|
||||||
"hide_heading": 0,
|
"idx": 1,
|
||||||
"hide_toolbar": 0,
|
"in_create": 1,
|
||||||
"icon": "fa fa-cog",
|
"issingle": 1,
|
||||||
"idx": 1,
|
"links": [],
|
||||||
"image_view": 0,
|
"modified": "2023-07-01 19:45:00.323953",
|
||||||
"in_create": 1,
|
"modified_by": "Administrator",
|
||||||
"is_submittable": 0,
|
"module": "Setup",
|
||||||
"issingle": 1,
|
"name": "Global Defaults",
|
||||||
"istable": 0,
|
"owner": "Administrator",
|
||||||
"max_attachments": 0,
|
|
||||||
"menu_index": 0,
|
|
||||||
"modified": "2018-10-15 03:08:19.886212",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Setup",
|
|
||||||
"name": "Global Defaults",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"read": 1,
|
||||||
"create": 1,
|
"role": "System Manager",
|
||||||
"delete": 0,
|
"share": 1,
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "System Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"read_only": 1,
|
||||||
"read_only": 1,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC",
|
||||||
"show_name_in_global_search": 0,
|
"states": []
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@ -10,7 +10,6 @@ from frappe.utils import cint
|
|||||||
|
|
||||||
keydict = {
|
keydict = {
|
||||||
# "key in defaults": "key in Global Defaults"
|
# "key in defaults": "key in Global Defaults"
|
||||||
"fiscal_year": "current_fiscal_year",
|
|
||||||
"company": "default_company",
|
"company": "default_company",
|
||||||
"currency": "default_currency",
|
"currency": "default_currency",
|
||||||
"country": "country",
|
"country": "country",
|
||||||
@ -29,22 +28,6 @@ class GlobalDefaults(Document):
|
|||||||
for key in keydict:
|
for key in keydict:
|
||||||
frappe.db.set_default(key, self.get(keydict[key], ""))
|
frappe.db.set_default(key, self.get(keydict[key], ""))
|
||||||
|
|
||||||
# update year start date and year end date from fiscal_year
|
|
||||||
if self.current_fiscal_year:
|
|
||||||
if fiscal_year := frappe.get_all(
|
|
||||||
"Fiscal Year",
|
|
||||||
filters={"name": self.current_fiscal_year},
|
|
||||||
fields=["year_start_date", "year_end_date"],
|
|
||||||
limit=1,
|
|
||||||
order_by=None,
|
|
||||||
):
|
|
||||||
ysd = fiscal_year[0].year_start_date or ""
|
|
||||||
yed = fiscal_year[0].year_end_date or ""
|
|
||||||
|
|
||||||
if ysd and yed:
|
|
||||||
frappe.db.set_default("year_start_date", ysd.strftime("%Y-%m-%d"))
|
|
||||||
frappe.db.set_default("year_end_date", yed.strftime("%Y-%m-%d"))
|
|
||||||
|
|
||||||
# enable default currency
|
# enable default currency
|
||||||
if self.default_currency:
|
if self.default_currency:
|
||||||
frappe.db.set_value("Currency", self.default_currency, "enabled", 1)
|
frappe.db.set_value("Currency", self.default_currency, "enabled", 1)
|
||||||
|
|||||||
@ -6,13 +6,41 @@ frappe.ui.form.on("Holiday List", {
|
|||||||
if (frm.doc.holidays) {
|
if (frm.doc.holidays) {
|
||||||
frm.set_value("total_holidays", frm.doc.holidays.length);
|
frm.set_value("total_holidays", frm.doc.holidays.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.call("get_supported_countries").then(r => {
|
||||||
|
frm.subdivisions_by_country = r.message.subdivisions_by_country;
|
||||||
|
frm.fields_dict.country.set_data(
|
||||||
|
r.message.countries.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (frm.doc.country) {
|
||||||
|
frm.trigger("set_subdivisions");
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
from_date: function(frm) {
|
from_date: function(frm) {
|
||||||
if (frm.doc.from_date && !frm.doc.to_date) {
|
if (frm.doc.from_date && !frm.doc.to_date) {
|
||||||
var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
|
var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
|
||||||
frm.set_value("to_date", frappe.datetime.add_days(a_year_from_start, -1));
|
frm.set_value("to_date", frappe.datetime.add_days(a_year_from_start, -1));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
country: function(frm) {
|
||||||
|
frm.set_value("subdivision", "");
|
||||||
|
|
||||||
|
if (frm.doc.country) {
|
||||||
|
frm.trigger("set_subdivisions");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set_subdivisions: function(frm) {
|
||||||
|
const subdivisions = [...frm.subdivisions_by_country[frm.doc.country]];
|
||||||
|
if (subdivisions && subdivisions.length > 0) {
|
||||||
|
frm.fields_dict.subdivision.set_data(subdivisions);
|
||||||
|
frm.set_df_property("subdivision", "hidden", 0);
|
||||||
|
} else {
|
||||||
|
frm.fields_dict.subdivision.set_data([]);
|
||||||
|
frm.set_df_property("subdivision", "hidden", 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.tour["Holiday List"] = [
|
frappe.tour["Holiday List"] = [
|
||||||
|
|||||||
@ -1,480 +1,166 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:holiday_list_name",
|
"autoname": "field:holiday_list_name",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-01-10 16:34:14",
|
"creation": "2013-01-10 16:34:14",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 0,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"holiday_list_name",
|
||||||
|
"from_date",
|
||||||
|
"to_date",
|
||||||
|
"column_break_4",
|
||||||
|
"total_holidays",
|
||||||
|
"add_weekly_holidays",
|
||||||
|
"weekly_off",
|
||||||
|
"get_weekly_off_dates",
|
||||||
|
"add_local_holidays",
|
||||||
|
"country",
|
||||||
|
"subdivision",
|
||||||
|
"get_local_holidays",
|
||||||
|
"holidays_section",
|
||||||
|
"holidays",
|
||||||
|
"clear_table",
|
||||||
|
"section_break_9",
|
||||||
|
"color"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "holiday_list_name",
|
"fieldname": "holiday_list_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"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": "Holiday List Name",
|
"label": "Holiday List Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "holiday_list_name",
|
"oldfieldname": "holiday_list_name",
|
||||||
"oldfieldtype": "Data",
|
"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,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "From Date",
|
"label": "From Date",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "To Date",
|
"label": "To Date",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "total_holidays",
|
"fieldname": "total_holidays",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Total Holidays",
|
"label": "Total Holidays",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"columns": 0,
|
"depends_on": "eval: doc.from_date && doc.to_date",
|
||||||
"fieldname": "add_weekly_holidays",
|
"fieldname": "add_weekly_holidays",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Add Weekly Holidays"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Add Weekly Holidays",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "weekly_off",
|
"fieldname": "weekly_off",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Weekly Off",
|
"label": "Weekly Off",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "\nSunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
|
"options": "\nSunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"report_hide": 1
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 1,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "get_weekly_off_dates",
|
"fieldname": "get_weekly_off_dates",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"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": "Add to Holidays",
|
"label": "Add to Holidays",
|
||||||
"length": 0,
|
"options": "get_weekly_off_dates"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "get_weekly_off_dates",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "holidays_section",
|
"fieldname": "holidays_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"label": "Holidays"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Holidays",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "holidays",
|
"fieldname": "holidays",
|
||||||
"fieldtype": "Table",
|
"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": "Holidays",
|
"label": "Holidays",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "holiday_list_details",
|
"oldfieldname": "holiday_list_details",
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"options": "Holiday",
|
"options": "Holiday"
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "clear_table",
|
"fieldname": "clear_table",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"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": "Clear Table",
|
"label": "Clear Table",
|
||||||
"length": 0,
|
"options": "clear_table"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "clear_table",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_9",
|
"fieldname": "section_break_9",
|
||||||
"fieldtype": "Section Break",
|
"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,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "color",
|
"fieldname": "color",
|
||||||
"fieldtype": "Color",
|
"fieldtype": "Color",
|
||||||
"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": "Color",
|
"label": "Color",
|
||||||
"length": 0,
|
"print_hide": 1
|
||||||
"no_copy": 0,
|
},
|
||||||
"permlevel": 0,
|
{
|
||||||
"precision": "",
|
"fieldname": "country",
|
||||||
"print_hide": 1,
|
"fieldtype": "Autocomplete",
|
||||||
"print_hide_if_no_value": 0,
|
"label": "Country"
|
||||||
"read_only": 0,
|
},
|
||||||
"remember_last_selected_value": 0,
|
{
|
||||||
"report_hide": 0,
|
"depends_on": "country",
|
||||||
"reqd": 0,
|
"fieldname": "subdivision",
|
||||||
"search_index": 0,
|
"fieldtype": "Autocomplete",
|
||||||
"set_only_once": 0,
|
"label": "Subdivision"
|
||||||
"translatable": 0,
|
},
|
||||||
"unique": 0
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"depends_on": "eval: doc.from_date && doc.to_date",
|
||||||
|
"fieldname": "add_local_holidays",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Add Local Holidays"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "get_local_holidays",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Add to Holidays",
|
||||||
|
"options": "get_local_holidays"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-calendar",
|
"icon": "fa fa-calendar",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"links": [],
|
||||||
"in_create": 0,
|
"modified": "2023-07-14 13:28:53.156421",
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-07-03 07:22:46.474096",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Holiday List",
|
"name": "Holiday List",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "HR Manager",
|
"role": "HR Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 0,
|
"states": []
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@ -3,11 +3,15 @@
|
|||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from babel import Locale
|
||||||
from frappe import _, throw
|
from frappe import _, throw
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint, formatdate, getdate, today
|
from frappe.utils import formatdate, getdate, today
|
||||||
|
from holidays import country_holidays
|
||||||
|
from holidays.utils import list_supported_countries
|
||||||
|
|
||||||
|
|
||||||
class OverlapError(frappe.ValidationError):
|
class OverlapError(frappe.ValidationError):
|
||||||
@ -21,25 +25,66 @@ class HolidayList(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_weekly_off_dates(self):
|
def get_weekly_off_dates(self):
|
||||||
self.validate_values()
|
|
||||||
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
|
|
||||||
last_idx = max(
|
|
||||||
[cint(d.idx) for d in self.get("holidays")]
|
|
||||||
or [
|
|
||||||
0,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
for i, d in enumerate(date_list):
|
|
||||||
ch = self.append("holidays", {})
|
|
||||||
ch.description = _(self.weekly_off)
|
|
||||||
ch.holiday_date = d
|
|
||||||
ch.weekly_off = 1
|
|
||||||
ch.idx = last_idx + i + 1
|
|
||||||
|
|
||||||
def validate_values(self):
|
|
||||||
if not self.weekly_off:
|
if not self.weekly_off:
|
||||||
throw(_("Please select weekly off day"))
|
throw(_("Please select weekly off day"))
|
||||||
|
|
||||||
|
existing_holidays = self.get_holidays()
|
||||||
|
|
||||||
|
for d in self.get_weekly_off_date_list(self.from_date, self.to_date):
|
||||||
|
if d in existing_holidays:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.append("holidays", {"description": _(self.weekly_off), "holiday_date": d, "weekly_off": 1})
|
||||||
|
|
||||||
|
self.sort_holidays()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_supported_countries(self):
|
||||||
|
subdivisions_by_country = list_supported_countries()
|
||||||
|
countries = [
|
||||||
|
{"value": country, "label": local_country_name(country)}
|
||||||
|
for country in subdivisions_by_country.keys()
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
"countries": countries,
|
||||||
|
"subdivisions_by_country": subdivisions_by_country,
|
||||||
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_local_holidays(self):
|
||||||
|
if not self.country:
|
||||||
|
throw(_("Please select a country"))
|
||||||
|
|
||||||
|
existing_holidays = self.get_holidays()
|
||||||
|
from_date = getdate(self.from_date)
|
||||||
|
to_date = getdate(self.to_date)
|
||||||
|
|
||||||
|
for holiday_date, holiday_name in country_holidays(
|
||||||
|
self.country,
|
||||||
|
subdiv=self.subdivision,
|
||||||
|
years=[from_date.year, to_date.year],
|
||||||
|
language=frappe.local.lang,
|
||||||
|
).items():
|
||||||
|
if holiday_date in existing_holidays:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if holiday_date < from_date or holiday_date > to_date:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.append(
|
||||||
|
"holidays", {"description": holiday_name, "holiday_date": holiday_date, "weekly_off": 0}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.sort_holidays()
|
||||||
|
|
||||||
|
def sort_holidays(self):
|
||||||
|
self.holidays.sort(key=lambda x: getdate(x.holiday_date))
|
||||||
|
for i in range(len(self.holidays)):
|
||||||
|
self.holidays[i].idx = i + 1
|
||||||
|
|
||||||
|
def get_holidays(self) -> list[date]:
|
||||||
|
return [getdate(holiday.holiday_date) for holiday in self.holidays]
|
||||||
|
|
||||||
def validate_days(self):
|
def validate_days(self):
|
||||||
if getdate(self.from_date) > getdate(self.to_date):
|
if getdate(self.from_date) > getdate(self.to_date):
|
||||||
throw(_("To Date cannot be before From Date"))
|
throw(_("To Date cannot be before From Date"))
|
||||||
@ -120,3 +165,8 @@ def is_holiday(holiday_list, date=None):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def local_country_name(country_code: str) -> str:
|
||||||
|
"""Return the localized country name for the given country code."""
|
||||||
|
return Locale.parse(frappe.local.lang).territories.get(country_code, country_code)
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
@ -23,6 +23,41 @@ class TestHolidayList(unittest.TestCase):
|
|||||||
fetched_holiday_list = frappe.get_value("Holiday List", holiday_list.name)
|
fetched_holiday_list = frappe.get_value("Holiday List", holiday_list.name)
|
||||||
self.assertEqual(holiday_list.name, fetched_holiday_list)
|
self.assertEqual(holiday_list.name, fetched_holiday_list)
|
||||||
|
|
||||||
|
def test_weekly_off(self):
|
||||||
|
holiday_list = frappe.new_doc("Holiday List")
|
||||||
|
holiday_list.from_date = "2023-01-01"
|
||||||
|
holiday_list.to_date = "2023-02-28"
|
||||||
|
holiday_list.weekly_off = "Sunday"
|
||||||
|
holiday_list.get_weekly_off_dates()
|
||||||
|
|
||||||
|
holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
|
||||||
|
|
||||||
|
self.assertNotIn(date(2022, 12, 25), holidays)
|
||||||
|
self.assertIn(date(2023, 1, 1), holidays)
|
||||||
|
self.assertIn(date(2023, 1, 8), holidays)
|
||||||
|
self.assertIn(date(2023, 1, 15), holidays)
|
||||||
|
self.assertIn(date(2023, 1, 22), holidays)
|
||||||
|
self.assertIn(date(2023, 1, 29), holidays)
|
||||||
|
self.assertIn(date(2023, 2, 5), holidays)
|
||||||
|
self.assertIn(date(2023, 2, 12), holidays)
|
||||||
|
self.assertIn(date(2023, 2, 19), holidays)
|
||||||
|
self.assertIn(date(2023, 2, 26), holidays)
|
||||||
|
self.assertNotIn(date(2023, 3, 5), holidays)
|
||||||
|
|
||||||
|
def test_local_holidays(self):
|
||||||
|
holiday_list = frappe.new_doc("Holiday List")
|
||||||
|
holiday_list.from_date = "2023-04-01"
|
||||||
|
holiday_list.to_date = "2023-04-30"
|
||||||
|
holiday_list.country = "DE"
|
||||||
|
holiday_list.subdivision = "SN"
|
||||||
|
holiday_list.get_local_holidays()
|
||||||
|
|
||||||
|
holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
|
||||||
|
self.assertNotIn(date(2023, 1, 1), holidays)
|
||||||
|
self.assertIn(date(2023, 4, 7), holidays)
|
||||||
|
self.assertIn(date(2023, 4, 10), holidays)
|
||||||
|
self.assertNotIn(date(2023, 5, 1), holidays)
|
||||||
|
|
||||||
|
|
||||||
def make_holiday_list(
|
def make_holiday_list(
|
||||||
name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None
|
name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Sales Partner', {
|
frappe.ui.form.on('Sales Partner', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Sales Partner'}
|
|
||||||
|
|
||||||
if(frm.doc.__islocal){
|
if(frm.doc.__islocal){
|
||||||
hide_field(['address_html', 'contact_html', 'address_contacts']);
|
hide_field(['address_html', 'contact_html', 'address_contacts']);
|
||||||
frappe.contacts.clear_address_and_contact(frm);
|
frappe.contacts.clear_address_and_contact(frm);
|
||||||
|
|||||||
@ -462,11 +462,9 @@ def install_defaults(args=None): # nosemgrep
|
|||||||
|
|
||||||
def set_global_defaults(args):
|
def set_global_defaults(args):
|
||||||
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
|
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
|
||||||
current_fiscal_year = frappe.get_all("Fiscal Year")[0]
|
|
||||||
|
|
||||||
global_defaults.update(
|
global_defaults.update(
|
||||||
{
|
{
|
||||||
"current_fiscal_year": current_fiscal_year.name,
|
|
||||||
"default_currency": args.get("currency"),
|
"default_currency": args.get("currency"),
|
||||||
"default_company": args.get("company_name"),
|
"default_company": args.get("company_name"),
|
||||||
"country": args.get("country"),
|
"country": args.get("country"),
|
||||||
|
|||||||
@ -318,6 +318,37 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
self.assertEqual(dn.per_returned, 100)
|
self.assertEqual(dn.per_returned, 100)
|
||||||
self.assertEqual(dn.status, "Return Issued")
|
self.assertEqual(dn.status, "Return Issued")
|
||||||
|
|
||||||
|
def test_delivery_note_return_valuation_on_different_warehuose(self):
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
|
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||||
|
item_code = "Test Return Valuation For DN"
|
||||||
|
make_item("Test Return Valuation For DN", {"is_stock_item": 1})
|
||||||
|
return_warehouse = create_warehouse("Returned Test Warehouse", company=company)
|
||||||
|
|
||||||
|
make_stock_entry(item_code=item_code, target="Stores - TCP1", qty=5, basic_rate=150)
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
company=company,
|
||||||
|
expense_account="Cost of Goods Sold - TCP1",
|
||||||
|
cost_center="Main - TCP1",
|
||||||
|
)
|
||||||
|
|
||||||
|
dn.submit()
|
||||||
|
self.assertEqual(dn.items[0].incoming_rate, 150)
|
||||||
|
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
return_dn = make_return_doc(dn.doctype, dn.name)
|
||||||
|
return_dn.items[0].warehouse = return_warehouse
|
||||||
|
return_dn.save().submit()
|
||||||
|
|
||||||
|
self.assertEqual(return_dn.items[0].incoming_rate, 150)
|
||||||
|
|
||||||
def test_return_single_item_from_bundled_items(self):
|
def test_return_single_item_from_bundled_items(self):
|
||||||
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||||
|
|
||||||
|
|||||||
@ -194,7 +194,8 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "disabled",
|
"fieldname": "disabled",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Disabled"
|
"label": "Disabled",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@ -911,7 +912,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2023-02-14 04:48:26.343620",
|
"modified": "2023-07-14 17:18:18.658942",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
|
|||||||
@ -773,7 +773,7 @@ class Item(Document):
|
|||||||
|
|
||||||
rows = ""
|
rows = ""
|
||||||
for docname, attr_list in not_included.items():
|
for docname, attr_list in not_included.items():
|
||||||
link = "<a href='/app/Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
|
link = f"<a href='/app/item/{docname}'>{frappe.bold(docname)}</a>"
|
||||||
rows += table_row(link, body(attr_list))
|
rows += table_row(link, body(attr_list))
|
||||||
|
|
||||||
error_description = _(
|
error_description = _(
|
||||||
|
|||||||
@ -1,370 +1,90 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
"creation": "2015-05-19 05:12:30.344797",
|
||||||
"allow_guest_to_view": 0,
|
"doctype": "DocType",
|
||||||
"allow_import": 0,
|
"document_type": "Other",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"autoname": "",
|
"engine": "InnoDB",
|
||||||
"beta": 0,
|
"field_order": [
|
||||||
"creation": "2015-05-19 05:12:30.344797",
|
"variant_of",
|
||||||
"custom": 0,
|
"attribute",
|
||||||
"docstatus": 0,
|
"column_break_2",
|
||||||
"doctype": "DocType",
|
"attribute_value",
|
||||||
"document_type": "Other",
|
"numeric_values",
|
||||||
"editable_grid": 1,
|
"section_break_4",
|
||||||
|
"from_range",
|
||||||
|
"increment",
|
||||||
|
"column_break_8",
|
||||||
|
"to_range"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "variant_of",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Variant Of",
|
||||||
"bold": 0,
|
"options": "Item",
|
||||||
"collapsible": 0,
|
"search_index": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "variant_of",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Variant Of",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Item",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "attribute",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Attribute",
|
||||||
"collapsible": 0,
|
"options": "Item Attribute",
|
||||||
"columns": 0,
|
"reqd": 1,
|
||||||
"fieldname": "attribute",
|
"search_index": 1
|
||||||
"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": "Attribute",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Item Attribute",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_2",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_2",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "attribute_value",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Attribute Value"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"fieldname": "attribute_value",
|
|
||||||
"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": "Attribute Value",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"depends_on": "has_variants",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "numeric_values",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"label": "Numeric Values"
|
||||||
"columns": 0,
|
},
|
||||||
"depends_on": "has_variants",
|
|
||||||
"fieldname": "numeric_values",
|
|
||||||
"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": "Numeric Values",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "numeric_values",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "section_break_4",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Section Break"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "numeric_values",
|
|
||||||
"fieldname": "section_break_4",
|
|
||||||
"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,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "from_range",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"label": "From Range"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"fieldname": "from_range",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"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": "From Range",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "increment",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"label": "Increment"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"fieldname": "increment",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"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": "Increment",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_8",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_8",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "to_range",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"label": "To Range"
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "",
|
|
||||||
"fieldname": "to_range",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"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": "To Range",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"istable": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2023-07-14 17:15:19.112119",
|
||||||
"icon": "",
|
"modified_by": "Administrator",
|
||||||
"idx": 0,
|
"module": "Stock",
|
||||||
"image_view": 0,
|
"name": "Item Variant Attribute",
|
||||||
"in_create": 0,
|
"owner": "Administrator",
|
||||||
"is_submittable": 0,
|
"permissions": [],
|
||||||
"issingle": 0,
|
"sort_field": "modified",
|
||||||
"istable": 1,
|
"sort_order": "DESC",
|
||||||
"max_attachments": 0,
|
"states": []
|
||||||
"modified": "2019-01-03 15:36:59.129006",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Stock",
|
|
||||||
"name": "Item Variant Attribute",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"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,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Manufacturer', {
|
frappe.ui.form.on('Manufacturer', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Manufacturer' };
|
|
||||||
if (frm.doc.__islocal) {
|
if (frm.doc.__islocal) {
|
||||||
hide_field(['address_html','contact_html']);
|
hide_field(['address_html','contact_html']);
|
||||||
frappe.contacts.clear_address_and_contact(frm);
|
frappe.contacts.clear_address_and_contact(frm);
|
||||||
|
|||||||
@ -118,8 +118,8 @@ class MaterialRequest(BuyingController):
|
|||||||
self.title = _("{0} Request for {1}").format(_(self.material_request_type), items)[:100]
|
self.title = _("{0} Request for {1}").format(_(self.material_request_type), items)[:100]
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_requested_qty()
|
|
||||||
self.update_requested_qty_in_production_plan()
|
self.update_requested_qty_in_production_plan()
|
||||||
|
self.update_requested_qty()
|
||||||
if self.material_request_type == "Purchase":
|
if self.material_request_type == "Purchase":
|
||||||
self.validate_budget()
|
self.validate_budget()
|
||||||
|
|
||||||
@ -178,8 +178,8 @@ class MaterialRequest(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_requested_qty()
|
|
||||||
self.update_requested_qty_in_production_plan()
|
self.update_requested_qty_in_production_plan()
|
||||||
|
self.update_requested_qty()
|
||||||
|
|
||||||
def get_mr_items_ordered_qty(self, mr_items):
|
def get_mr_items_ordered_qty(self, mr_items):
|
||||||
mr_items_ordered_qty = {}
|
mr_items_ordered_qty = {}
|
||||||
@ -270,7 +270,13 @@ class MaterialRequest(BuyingController):
|
|||||||
item_wh_list.append([d.item_code, d.warehouse])
|
item_wh_list.append([d.item_code, d.warehouse])
|
||||||
|
|
||||||
for item_code, warehouse in item_wh_list:
|
for item_code, warehouse in item_wh_list:
|
||||||
update_bin_qty(item_code, warehouse, {"indented_qty": get_indented_qty(item_code, warehouse)})
|
update_bin_qty(
|
||||||
|
item_code,
|
||||||
|
warehouse,
|
||||||
|
{
|
||||||
|
"indented_qty": get_indented_qty(item_code, warehouse),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def update_requested_qty_in_production_plan(self):
|
def update_requested_qty_in_production_plan(self):
|
||||||
production_plans = []
|
production_plans = []
|
||||||
|
|||||||
@ -1965,6 +1965,32 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
ste5.reload()
|
ste5.reload()
|
||||||
self.assertEqual(ste5.items[0].valuation_rate, 275.00)
|
self.assertEqual(ste5.items[0].valuation_rate, 275.00)
|
||||||
|
|
||||||
|
ste6 = make_stock_entry(
|
||||||
|
purpose="Material Transfer",
|
||||||
|
posting_date=add_days(today(), -3),
|
||||||
|
source=warehouse1,
|
||||||
|
target=warehouse,
|
||||||
|
item_code=item_code,
|
||||||
|
qty=20,
|
||||||
|
company=pr.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
ste6.reload()
|
||||||
|
self.assertEqual(ste6.items[0].valuation_rate, 275.00)
|
||||||
|
|
||||||
|
ste7 = make_stock_entry(
|
||||||
|
purpose="Material Transfer",
|
||||||
|
posting_date=add_days(today(), -3),
|
||||||
|
source=warehouse,
|
||||||
|
target=warehouse1,
|
||||||
|
item_code=item_code,
|
||||||
|
qty=20,
|
||||||
|
company=pr.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
ste7.reload()
|
||||||
|
self.assertEqual(ste7.items[0].valuation_rate, 275.00)
|
||||||
|
|
||||||
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1)
|
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1)
|
||||||
|
|
||||||
pr.reload()
|
pr.reload()
|
||||||
@ -1985,6 +2011,12 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
ste5.reload()
|
ste5.reload()
|
||||||
self.assertEqual(ste5.items[0].valuation_rate, valuation_rate)
|
self.assertEqual(ste5.items[0].valuation_rate, valuation_rate)
|
||||||
|
|
||||||
|
ste6.reload()
|
||||||
|
self.assertEqual(ste6.items[0].valuation_rate, valuation_rate)
|
||||||
|
|
||||||
|
ste7.reload()
|
||||||
|
self.assertEqual(ste7.items[0].valuation_rate, valuation_rate)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
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