Merge branch 'develop' into translate-accounts-settings-de
This commit is contained in:
commit
7f39318340
16
.github/helper/install.sh
vendored
16
.github/helper/install.sh
vendored
@ -4,7 +4,9 @@ set -e
|
||||
|
||||
cd ~ || exit
|
||||
|
||||
sudo apt update && sudo apt install redis-server libcups2-dev
|
||||
sudo apt update
|
||||
sudo apt remove mysql-server mysql-client
|
||||
sudo apt install libcups2-dev redis-server mariadb-client-10.6
|
||||
|
||||
pip install frappe-bench
|
||||
|
||||
@ -25,14 +27,14 @@ fi
|
||||
|
||||
|
||||
if [ "$DB" == "mariadb" ];then
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
|
||||
fi
|
||||
|
||||
if [ "$DB" == "postgres" ];then
|
||||
|
20
.github/workflows/initiate_release.yml
vendored
20
.github/workflows/initiate_release.yml
vendored
@ -30,23 +30,3 @@ jobs:
|
||||
head: version-${{ matrix.version }}-hotfix
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
beta-release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
with:
|
||||
route: POST /repos/{owner}/{repo}/pulls
|
||||
owner: frappe
|
||||
repo: erpnext
|
||||
title: |-
|
||||
"chore: release v15 beta"
|
||||
body: "Automated beta release."
|
||||
base: version-15-beta
|
||||
head: develop
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
3
.github/workflows/patch.yml
vendored
3
.github/workflows/patch.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
MARIADB_ROOT_PASSWORD: 'root'
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
@ -134,6 +134,7 @@ jobs:
|
||||
}
|
||||
|
||||
update_to_version 14
|
||||
update_to_version 15
|
||||
|
||||
echo "Updating to latest version"
|
||||
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||
|
2
.github/workflows/server-tests-mariadb.yml
vendored
2
.github/workflows/server-tests-mariadb.yml
vendored
@ -47,7 +47,7 @@ jobs:
|
||||
MARIADB_ROOT_PASSWORD: 'root'
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
|
46
.mergify.yml
46
.mergify.yml
@ -17,6 +17,7 @@ pull_request_rules:
|
||||
- base=version-12
|
||||
- base=version-14
|
||||
- base=version-15
|
||||
- base=version-16
|
||||
actions:
|
||||
close:
|
||||
comment:
|
||||
@ -24,16 +25,6 @@ pull_request_rules:
|
||||
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
|
||||
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
|
||||
|
||||
- name: Auto-close PRs on pre-release branch
|
||||
conditions:
|
||||
- base=version-13-pre-release
|
||||
actions:
|
||||
close:
|
||||
comment:
|
||||
message: |
|
||||
@{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
|
||||
|
||||
|
||||
- name: backport to develop
|
||||
conditions:
|
||||
- label="backport develop"
|
||||
@ -54,13 +45,13 @@ pull_request_rules:
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-14-pre-release
|
||||
- name: backport to version-15-hotfix
|
||||
conditions:
|
||||
- label="backport version-14-pre-release"
|
||||
- label="backport version-15-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-14-pre-release
|
||||
- version-15-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
@ -74,35 +65,6 @@ pull_request_rules:
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-13-pre-release
|
||||
conditions:
|
||||
- label="backport version-13-pre-release"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-13-pre-release
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-12-hotfix
|
||||
conditions:
|
||||
- label="backport version-12-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-12-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-12-pre-release
|
||||
conditions:
|
||||
- label="backport version-12-pre-release"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-12-pre-release
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: Automatic merge on CI success and review
|
||||
conditions:
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"country_code": "ni",
|
||||
"name": "Nicaragua - Catalogo de Cuentas",
|
||||
"name": "Nicaragua - Catálogo de Cuentas",
|
||||
"tree": {
|
||||
"Activo": {
|
||||
"Activo Corriente": {
|
||||
@ -491,4 +491,4 @@
|
||||
"root_type": "Liability"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
|
||||
}
|
||||
)
|
||||
cle.flags.ignore_permissions = True
|
||||
cle.flags.ignore_links = True
|
||||
cle.submit()
|
||||
|
||||
|
||||
|
@ -302,3 +302,30 @@ def get_dimensions(with_cost_center_and_project=False):
|
||||
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
|
||||
|
||||
return dimension_filters, default_dimensions_map
|
||||
|
||||
|
||||
def create_accounting_dimensions_for_doctype(doctype):
|
||||
accounting_dimensions = frappe.db.get_all(
|
||||
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
|
||||
)
|
||||
|
||||
if not accounting_dimensions:
|
||||
return
|
||||
|
||||
for d in accounting_dimensions:
|
||||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
|
||||
|
||||
if field:
|
||||
continue
|
||||
|
||||
df = {
|
||||
"fieldname": d.fieldname,
|
||||
"label": d.label,
|
||||
"fieldtype": "Link",
|
||||
"options": d.document_type,
|
||||
"insert_after": "accounting_dimensions_section",
|
||||
}
|
||||
|
||||
create_custom_field(doctype, df, ignore_validate=True)
|
||||
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
|
@ -32,6 +32,7 @@
|
||||
"column_break_19",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"book_tax_discount_loss",
|
||||
"round_row_wise_tax",
|
||||
"print_settings",
|
||||
"show_inclusive_tax_in_print",
|
||||
"show_taxes_as_table_in_print",
|
||||
@ -414,6 +415,13 @@
|
||||
"fieldname": "ignore_account_closing_balance",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Account Closing Balance"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Tax Amount will be rounded on a row(items) level",
|
||||
"fieldname": "round_row_wise_tax",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Tax Amount Row-wise"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -421,7 +429,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-27 15:05:34.000264",
|
||||
"modified": "2023-08-28 00:12:02.740633",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -35,13 +35,14 @@ class TestBankClearance(unittest.TestCase):
|
||||
from lending.loan_management.doctype.loan.test_loan import (
|
||||
create_loan,
|
||||
create_loan_accounts,
|
||||
create_loan_type,
|
||||
create_loan_product,
|
||||
create_repayment_entry,
|
||||
make_loan_disbursement_entry,
|
||||
)
|
||||
|
||||
def create_loan_masters():
|
||||
create_loan_type(
|
||||
create_loan_product(
|
||||
"Clearance Loan",
|
||||
"Clearance Loan",
|
||||
2000000,
|
||||
13.5,
|
||||
|
@ -2,6 +2,16 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Bank Statement Import", {
|
||||
onload(frm) {
|
||||
frm.set_query("bank_account", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setup(frm) {
|
||||
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
|
||||
frm.import_in_progress = false;
|
||||
@ -352,10 +362,11 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
|
||||
export_errored_rows(frm) {
|
||||
open_url_post(
|
||||
"/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
|
||||
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_errored_template",
|
||||
{
|
||||
data_import_name: frm.doc.name,
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -410,7 +410,7 @@ def add_vouchers():
|
||||
def create_loan_and_repayment():
|
||||
from lending.loan_management.doctype.loan.test_loan import (
|
||||
create_loan,
|
||||
create_loan_type,
|
||||
create_loan_product,
|
||||
create_repayment_entry,
|
||||
make_loan_disbursement_entry,
|
||||
)
|
||||
@ -420,7 +420,8 @@ def create_loan_and_repayment():
|
||||
|
||||
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||
|
||||
create_loan_type(
|
||||
create_loan_product(
|
||||
"Personal Loan",
|
||||
"Personal Loan",
|
||||
500000,
|
||||
8.4,
|
||||
@ -441,7 +442,7 @@ def create_loan_and_repayment():
|
||||
"applicant_type": "Employee",
|
||||
"company": "_Test Company",
|
||||
"applicant": applicant,
|
||||
"loan_type": "Personal Loan",
|
||||
"loan_product": "Personal Loan",
|
||||
"loan_amount": 5000,
|
||||
"repayment_method": "Repay Fixed Amount per Period",
|
||||
"monthly_repayment_amount": 500,
|
||||
|
@ -9,6 +9,7 @@
|
||||
"disabled",
|
||||
"service_provider",
|
||||
"api_endpoint",
|
||||
"access_key",
|
||||
"url",
|
||||
"column_break_3",
|
||||
"help",
|
||||
@ -84,12 +85,18 @@
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.service_provider == 'exchangerate.host';",
|
||||
"fieldname": "access_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "Access Key"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-01-09 12:19:03.955906",
|
||||
"modified": "2023-10-04 15:30:25.333860",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
@ -18,11 +18,21 @@ class CurrencyExchangeSettings(Document):
|
||||
|
||||
def set_parameters_and_result(self):
|
||||
if self.service_provider == "exchangerate.host":
|
||||
|
||||
if not self.access_key:
|
||||
frappe.throw(
|
||||
_("Access Key is required for Service Provider: {0}").format(
|
||||
frappe.bold(self.service_provider)
|
||||
)
|
||||
)
|
||||
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
self.api_endpoint = "https://api.exchangerate.host/convert"
|
||||
self.append("result_key", {"key": "result"})
|
||||
self.append("req_params", {"key": "access_key", "value": self.access_key})
|
||||
self.append("req_params", {"key": "amount", "value": "1"})
|
||||
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
|
||||
self.append("req_params", {"key": "from", "value": "{from_currency}"})
|
||||
self.append("req_params", {"key": "to", "value": "{to_currency}"})
|
||||
|
@ -53,7 +53,15 @@ frappe.ui.form.on("Journal Entry", {
|
||||
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
},
|
||||
|
||||
before_save: function(frm) {
|
||||
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
|
||||
let payment_entry_references = frm.doc.accounts.filter(elem => (elem.reference_type == "Payment Entry"));
|
||||
if (payment_entry_references.length > 0) {
|
||||
let rows = payment_entry_references.map(x => "#"+x.idx);
|
||||
frappe.throw(__("Rows: {0} have 'Payment Entry' as reference_type. This should not be set manually.", [frappe.utils.comma_and(rows)]));
|
||||
}
|
||||
}
|
||||
},
|
||||
make_inter_company_journal_entry: function(frm) {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __("Select Company"),
|
||||
|
@ -218,6 +218,7 @@ def make_customer(customer=None):
|
||||
"territory": "All Territories",
|
||||
}
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
customer.insert(ignore_permissions=True)
|
||||
return customer.name
|
||||
|
@ -271,16 +271,18 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
|
||||
latest = latest.get(d.payment_term) or latest.get(None)
|
||||
|
||||
# The reference has already been fully paid
|
||||
if not latest:
|
||||
frappe.throw(
|
||||
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
||||
)
|
||||
# The reference has already been partly paid
|
||||
elif latest.outstanding_amount < latest.invoice_amount and flt(
|
||||
d.outstanding_amount, d.precision("outstanding_amount")
|
||||
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
|
||||
elif (
|
||||
latest.outstanding_amount < latest.invoice_amount
|
||||
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
|
||||
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
|
||||
and d.payment_term == ""
|
||||
):
|
||||
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."
|
||||
@ -1751,11 +1753,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||
"voucher_type": d.voucher_type,
|
||||
"posting_date": d.posting_date,
|
||||
"invoice_amount": flt(d.invoice_amount),
|
||||
"outstanding_amount": flt(d.outstanding_amount),
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"allocated_amount": payment_term_outstanding
|
||||
"outstanding_amount": payment_term_outstanding
|
||||
if payment_term_outstanding
|
||||
else d.outstanding_amount,
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"payment_amount": payment_term.payment_amount,
|
||||
"payment_term": payment_term.payment_term,
|
||||
"account": d.account,
|
||||
|
@ -1,13 +1,9 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, get_url, nowdate
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
@ -20,7 +16,6 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||
from erpnext.accounts.party import get_party_account, get_party_bank_account
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription
|
||||
from erpnext.utilities import payment_app_import_guard
|
||||
|
||||
|
||||
@ -249,7 +244,7 @@ class PaymentRequest(Document):
|
||||
if (
|
||||
party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
|
||||
):
|
||||
party_amount = ref_doc.base_grand_total
|
||||
party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
|
||||
else:
|
||||
party_amount = self.grand_total
|
||||
|
||||
@ -364,35 +359,11 @@ class PaymentRequest(Document):
|
||||
def get_payment_success_url(self):
|
||||
return self.payment_success_url
|
||||
|
||||
def on_payment_authorized(self, status=None):
|
||||
if not status:
|
||||
return
|
||||
|
||||
shopping_cart_settings = frappe.get_doc("E Commerce Settings")
|
||||
|
||||
if status in ["Authorized", "Completed"]:
|
||||
redirect_to = None
|
||||
self.set_as_paid()
|
||||
|
||||
# if shopping cart enabled and in session
|
||||
if (
|
||||
shopping_cart_settings.enabled
|
||||
and hasattr(frappe.local, "session")
|
||||
and frappe.local.session.user != "Guest"
|
||||
) and self.payment_channel != "Phone":
|
||||
|
||||
success_url = shopping_cart_settings.payment_success_url
|
||||
if success_url:
|
||||
redirect_to = ({"Orders": "/orders", "Invoices": "/invoices", "My Account": "/me"}).get(
|
||||
success_url, "/me"
|
||||
)
|
||||
else:
|
||||
redirect_to = get_url("/orders/{0}".format(self.reference_name))
|
||||
|
||||
return redirect_to
|
||||
|
||||
def create_subscription(self, payment_provider, gateway_controller, data):
|
||||
if payment_provider == "stripe":
|
||||
with payment_app_import_guard():
|
||||
from payments.payment_gateways.stripe_integration import create_stripe_subscription
|
||||
|
||||
return create_stripe_subscription(gateway_controller, data)
|
||||
|
||||
|
||||
@ -544,13 +515,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||
|
||||
|
||||
def get_gateway_details(args): # nosemgrep
|
||||
"""return gateway and payment account of default payment gateway"""
|
||||
if args.get("payment_gateway_account"):
|
||||
return get_payment_gateway_account(args.get("payment_gateway_account"))
|
||||
|
||||
if args.order_type == "Shopping Cart":
|
||||
payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account
|
||||
return get_payment_gateway_account(payment_gateway_account)
|
||||
"""
|
||||
Return gateway and payment account of default payment gateway
|
||||
"""
|
||||
gateway_account = args.get("payment_gateway_account", {"is_default": 1})
|
||||
if gateway_account:
|
||||
return get_payment_gateway_account(gateway_account)
|
||||
|
||||
gateway_account = get_payment_gateway_account({"is_default": 1})
|
||||
|
||||
|
@ -6,6 +6,7 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_days, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
@ -125,70 +126,64 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
self.assertEqual(inv.grand_total, 5474.0)
|
||||
|
||||
def test_tax_calculation_with_item_tax_template(self):
|
||||
inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1)
|
||||
item_row = inv.get("items")[0]
|
||||
import json
|
||||
|
||||
add_items = [
|
||||
(54, "_Test Account Excise Duty @ 12 - _TC"),
|
||||
(288, "_Test Account Excise Duty @ 15 - _TC"),
|
||||
(144, "_Test Account Excise Duty @ 20 - _TC"),
|
||||
(430, "_Test Item Tax Template 1 - _TC"),
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
|
||||
# set tax template in item
|
||||
item = frappe.get_cached_doc("Item", "_Test Item")
|
||||
item.set(
|
||||
"taxes",
|
||||
[
|
||||
{
|
||||
"item_tax_template": "_Test Account Excise Duty @ 15 - _TC",
|
||||
"valid_from": add_days(nowdate(), -5),
|
||||
}
|
||||
],
|
||||
)
|
||||
item.save()
|
||||
|
||||
# create POS invoice with item
|
||||
pos_inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True)
|
||||
item_details = get_item_details(
|
||||
doc=pos_inv,
|
||||
args={
|
||||
"item_code": item.item_code,
|
||||
"company": pos_inv.company,
|
||||
"doctype": "POS Invoice",
|
||||
"conversion_rate": 1.0,
|
||||
},
|
||||
)
|
||||
tax_map = json.loads(item_details.item_tax_rate)
|
||||
for tax in tax_map:
|
||||
pos_inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": tax,
|
||||
"rate": tax_map[tax],
|
||||
"description": "Test",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
},
|
||||
)
|
||||
pos_inv.submit()
|
||||
pos_inv.load_from_db()
|
||||
|
||||
# check if correct tax values are applied from tax template
|
||||
self.assertEqual(pos_inv.net_total, 386.4)
|
||||
|
||||
expected_taxes = [
|
||||
{
|
||||
"tax_amount": 57.96,
|
||||
"total": 444.36,
|
||||
},
|
||||
]
|
||||
for qty, item_tax_template in add_items:
|
||||
item_row_copy = copy.deepcopy(item_row)
|
||||
item_row_copy.qty = qty
|
||||
item_row_copy.item_tax_template = item_tax_template
|
||||
inv.append("items", item_row_copy)
|
||||
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account Excise Duty - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Excise Duty",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 11,
|
||||
},
|
||||
)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account Education Cess - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Education Cess",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 0,
|
||||
},
|
||||
)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account S&H Education Cess - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "S&H Education Cess",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 3,
|
||||
},
|
||||
)
|
||||
inv.insert()
|
||||
for i in range(len(expected_taxes)):
|
||||
for key in expected_taxes[i]:
|
||||
self.assertEqual(expected_taxes[i][key], pos_inv.get("taxes")[i].get(key))
|
||||
|
||||
self.assertEqual(inv.net_total, 4600)
|
||||
|
||||
self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41)
|
||||
self.assertEqual(inv.get("taxes")[0].total, 5102.41)
|
||||
|
||||
self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80)
|
||||
self.assertEqual(inv.get("taxes")[1].total, 5300.21)
|
||||
|
||||
self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36)
|
||||
self.assertEqual(inv.get("taxes")[2].total, 5675.57)
|
||||
|
||||
self.assertEqual(inv.grand_total, 5675.57)
|
||||
self.assertEqual(inv.rounding_adjustment, 0.43)
|
||||
self.assertEqual(inv.rounded_total, 5676.0)
|
||||
self.assertEqual(pos_inv.get("base_total_taxes_and_charges"), 57.96)
|
||||
|
||||
def test_tax_calculation_with_multiple_items_and_discount(self):
|
||||
inv = create_pos_invoice(qty=1, rate=75, do_not_save=True)
|
||||
|
@ -454,7 +454,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||
error_message = safe_load_json(message_log)
|
||||
error_message = get_error_message(message_log)
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status="Failed")
|
||||
@ -483,7 +483,7 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||
error_message = safe_load_json(message_log)
|
||||
error_message = get_error_message(message_log)
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status="Submitted")
|
||||
@ -525,10 +525,8 @@ def check_scheduler_status():
|
||||
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
||||
|
||||
|
||||
def safe_load_json(message):
|
||||
def get_error_message(message) -> str:
|
||||
try:
|
||||
json_message = json.loads(message).get("message")
|
||||
return message["message"]
|
||||
except Exception:
|
||||
json_message = message
|
||||
|
||||
return json_message
|
||||
return str(message)
|
||||
|
@ -48,6 +48,20 @@ class ProcessStatementOfAccounts(Document):
|
||||
|
||||
|
||||
def get_report_pdf(doc, consolidated=True):
|
||||
statement_dict = get_statement_dict(doc)
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
elif consolidated:
|
||||
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
|
||||
result = delimiter.join(list(statement_dict.values()))
|
||||
return get_pdf(result, {"orientation": doc.orientation})
|
||||
else:
|
||||
for customer, statement_html in statement_dict.items():
|
||||
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
|
||||
return statement_dict
|
||||
|
||||
|
||||
def get_statement_dict(doc, get_statement_dict=False):
|
||||
statement_dict = {}
|
||||
ageing = ""
|
||||
|
||||
@ -78,18 +92,11 @@ def get_report_pdf(doc, consolidated=True):
|
||||
if not res:
|
||||
continue
|
||||
|
||||
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
||||
statement_dict[entry.customer] = (
|
||||
[res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing)
|
||||
)
|
||||
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
elif consolidated:
|
||||
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
|
||||
result = delimiter.join(list(statement_dict.values()))
|
||||
return get_pdf(result, {"orientation": doc.orientation})
|
||||
else:
|
||||
for customer, statement_html in statement_dict.items():
|
||||
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
|
||||
return statement_dict
|
||||
return statement_dict
|
||||
|
||||
|
||||
def set_ageing(doc, entry):
|
||||
@ -102,7 +109,8 @@ def set_ageing(doc, entry):
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"customer": entry.customer,
|
||||
"party_type": "Customer",
|
||||
"party": [entry.customer],
|
||||
}
|
||||
)
|
||||
col1, ageing = get_ageing(ageing_filters)
|
||||
@ -146,7 +154,7 @@ def get_ar_filters(doc, entry):
|
||||
return {
|
||||
"report_date": doc.posting_date if doc.posting_date else None,
|
||||
"party_type": "Customer",
|
||||
"party": entry.customer,
|
||||
"party": [entry.customer],
|
||||
"customer_name": entry.customer_name if entry.customer_name else None,
|
||||
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||
|
@ -4,39 +4,107 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import (
|
||||
get_statement_dict,
|
||||
send_emails,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
|
||||
|
||||
class TestProcessStatementOfAccounts(unittest.TestCase):
|
||||
class TestProcessStatementOfAccounts(AccountsTestMixin, FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.create_customer(customer_name="Other Customer")
|
||||
self.clear_old_entries()
|
||||
self.si = create_sales_invoice()
|
||||
self.process_soa = create_process_soa()
|
||||
create_sales_invoice(customer="Other Customer")
|
||||
|
||||
def test_process_soa_for_gl(self):
|
||||
"""Tests the utils for Statement of Accounts(General Ledger)"""
|
||||
process_soa = create_process_soa(
|
||||
name="_Test Process SOA for GL",
|
||||
customers=[{"customer": "_Test Customer"}, {"customer": "Other Customer"}],
|
||||
)
|
||||
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
|
||||
|
||||
# Checks if the statements are filtered based on the Customer
|
||||
self.assertIn("Other Customer", statement_dict)
|
||||
self.assertIn("_Test Customer", statement_dict)
|
||||
|
||||
# Checks if the correct number of receivable entries exist
|
||||
# 3 rows for opening and closing and 1 row for SI
|
||||
receivable_entries = statement_dict["_Test Customer"][0]
|
||||
self.assertEqual(len(receivable_entries), 4)
|
||||
|
||||
# Checks the amount for the receivable entry
|
||||
self.assertEqual(receivable_entries[1].voucher_no, self.si.name)
|
||||
self.assertEqual(receivable_entries[1].balance, 100)
|
||||
|
||||
def test_process_soa_for_ar(self):
|
||||
"""Tests the utils for Statement of Accounts(Accounts Receivable)"""
|
||||
process_soa = create_process_soa(name="_Test Process SOA for AR", report="Accounts Receivable")
|
||||
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
|
||||
|
||||
# Checks if the statements are filtered based on the Customer
|
||||
self.assertNotIn("Other Customer", statement_dict)
|
||||
self.assertIn("_Test Customer", statement_dict)
|
||||
|
||||
# Checks if the correct number of receivable entries exist
|
||||
receivable_entries = statement_dict["_Test Customer"][0]
|
||||
self.assertEqual(len(receivable_entries), 1)
|
||||
|
||||
# Checks the amount for the receivable entry
|
||||
self.assertEqual(receivable_entries[0].voucher_no, self.si.name)
|
||||
self.assertEqual(receivable_entries[0].total_due, 100)
|
||||
|
||||
# Checks the ageing summary for AR
|
||||
ageing_summary = statement_dict["_Test Customer"][1][0]
|
||||
expected_summary = frappe._dict(
|
||||
range1=100,
|
||||
range2=0,
|
||||
range3=0,
|
||||
range4=0,
|
||||
range5=0,
|
||||
)
|
||||
self.check_ageing_summary(ageing_summary, expected_summary)
|
||||
|
||||
def test_auto_email_for_process_soa_ar(self):
|
||||
send_emails(self.process_soa.name, from_scheduler=True)
|
||||
self.process_soa.load_from_db()
|
||||
self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7)))
|
||||
process_soa = create_process_soa(
|
||||
name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable"
|
||||
)
|
||||
send_emails(process_soa.name, from_scheduler=True)
|
||||
process_soa.load_from_db()
|
||||
self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7)))
|
||||
|
||||
def check_ageing_summary(self, ageing, expected_ageing):
|
||||
for age_range in expected_ageing:
|
||||
self.assertEqual(expected_ageing[age_range], ageing.get(age_range))
|
||||
|
||||
def tearDown(self):
|
||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
||||
frappe.db.rollback()
|
||||
|
||||
|
||||
def create_process_soa():
|
||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
||||
def create_process_soa(**args):
|
||||
args = frappe._dict(args)
|
||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", args.name)
|
||||
process_soa = frappe.new_doc("Process Statement Of Accounts")
|
||||
soa_dict = {
|
||||
"name": "Test Process SOA",
|
||||
"company": "_Test Company",
|
||||
}
|
||||
soa_dict = frappe._dict(
|
||||
name=args.name,
|
||||
company=args.company or "_Test Company",
|
||||
customers=args.customers or [{"customer": "_Test Customer"}],
|
||||
enable_auto_email=1 if args.enable_auto_email else 0,
|
||||
frequency=args.frequency or "Weekly",
|
||||
report=args.report or "General Ledger",
|
||||
from_date=args.from_date or getdate(today()),
|
||||
to_date=args.to_date or getdate(today()),
|
||||
posting_date=args.posting_date or getdate(today()),
|
||||
include_ageing=1,
|
||||
)
|
||||
process_soa.update(soa_dict)
|
||||
process_soa.set("customers", [{"customer": "_Test Customer"}])
|
||||
process_soa.enable_auto_email = 1
|
||||
process_soa.frequency = "Weekly"
|
||||
process_soa.report = "Accounts Receivable"
|
||||
process_soa.save()
|
||||
return process_soa
|
||||
|
@ -479,6 +479,12 @@ cur_frm.set_query("expense_account", "items", function(doc) {
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.set_query("wip_composite_asset", "items", function() {
|
||||
return {
|
||||
filters: {'is_composite_asset': 1, 'docstatus': 0 }
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.cscript.expense_account = function(doc, cdt, cdn){
|
||||
var d = locals[cdt][cdn];
|
||||
if(d.idx == 1 && d.expense_account){
|
||||
|
@ -36,6 +36,7 @@
|
||||
"currency_and_price_list",
|
||||
"currency",
|
||||
"conversion_rate",
|
||||
"use_transaction_date_exchange_rate",
|
||||
"column_break2",
|
||||
"buying_price_list",
|
||||
"price_list_currency",
|
||||
@ -1385,6 +1386,7 @@
|
||||
"depends_on": "eval:doc.is_subcontracted",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Supplier Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
@ -1587,13 +1589,20 @@
|
||||
"label": "Repost Required",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_transaction_date_exchange_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Transaction Date Exchange Rate",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-21 12:22:04.545106",
|
||||
"modified": "2023-10-16 16:24:51.886231",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -33,7 +33,7 @@ from erpnext.accounts.general_ledger import (
|
||||
)
|
||||
from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
@ -281,9 +281,6 @@ class PurchaseInvoice(BuyingController):
|
||||
# in case of auto inventory accounting,
|
||||
# expense account is always "Stock Received But Not Billed" for a stock item
|
||||
# except opening entry, drop-ship entry and fixed asset items
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if (
|
||||
auto_accounting_for_stock
|
||||
and item.item_code in stock_items
|
||||
@ -350,22 +347,26 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif item.is_fixed_asset:
|
||||
account_type = (
|
||||
"capital_work_in_progress_account"
|
||||
if is_cwip_accounting_enabled(item.asset_category)
|
||||
else "fixed_asset_account"
|
||||
)
|
||||
asset_category_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=item.item_code, company=self.company
|
||||
account_type, item=item.item_code, company=self.company
|
||||
)
|
||||
if not asset_category_account:
|
||||
form_link = get_link_to_form("Asset Category", asset_category)
|
||||
form_link = get_link_to_form("Asset Category", item.asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||
title=_("Missing Account"),
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||
|
||||
@ -539,8 +540,9 @@ class PurchaseInvoice(BuyingController):
|
||||
]
|
||||
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
if not gl_entries:
|
||||
@ -586,6 +588,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.auto_accounting_for_stock:
|
||||
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
||||
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
self.asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
else:
|
||||
self.stock_received_but_not_billed = None
|
||||
self.expenses_included_in_valuation = None
|
||||
@ -597,9 +600,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_precision_loss_gl_entry(gl_entries)
|
||||
|
||||
if self.check_asset_cwip_enabled():
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
@ -701,7 +701,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
|
||||
if (
|
||||
self.update_stock
|
||||
and self.auto_accounting_for_stock
|
||||
and (item.item_code in stock_items or item.is_fixed_asset)
|
||||
):
|
||||
# warehouse account
|
||||
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
||||
gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@ -816,9 +820,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
elif not item.is_fixed_asset or (
|
||||
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
|
||||
):
|
||||
else:
|
||||
expense_account = (
|
||||
item.expense_account
|
||||
if (not item.enable_deferred_expense or self.is_return)
|
||||
@ -969,11 +971,16 @@ class PurchaseInvoice(BuyingController):
|
||||
(item.purchase_receipt, valuation_tax_accounts),
|
||||
)
|
||||
|
||||
stock_rbnb = (
|
||||
self.asset_received_but_not_billed
|
||||
if item.is_fixed_asset
|
||||
else self.stock_received_but_not_billed
|
||||
)
|
||||
if not negative_expense_booked_in_pr:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.stock_received_but_not_billed,
|
||||
"account": stock_rbnb,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
@ -988,156 +995,12 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount, item.precision("item_tax_amount")
|
||||
)
|
||||
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
arbnb_account = None
|
||||
eiiav_account = None
|
||||
asset_eiiav_currency = None
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
|
||||
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
||||
|
||||
item_exp_acc_type = frappe.get_cached_value("Account", item.expense_account, "account_type")
|
||||
if not item.expense_account or item_exp_acc_type not in [
|
||||
"Asset Received But Not Billed",
|
||||
"Fixed Asset",
|
||||
]:
|
||||
if not arbnb_account:
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = arbnb_account
|
||||
|
||||
if not self.update_stock:
|
||||
arbnb_currency = get_account_currency(item.expense_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if arbnb_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount:
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"credit": item.item_tax_amount,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
else:
|
||||
cwip_account = get_asset_account(
|
||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
||||
)
|
||||
|
||||
cwip_account_currency = get_account_currency(cwip_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": self.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"credit": item.item_tax_amount,
|
||||
"project": item.project or self.project,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# Assets are bought through this document then it will be linked to this document
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
if not eiiav_account:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
return gl_entries
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
|
@ -5,7 +5,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
@ -38,7 +38,7 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
|
||||
test_ignore = ["Serial No"]
|
||||
|
||||
|
||||
class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
@ -48,6 +48,9 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
def tearDownClass(self):
|
||||
unlink_payment_on_cancel_of_invoice(0)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_purchase_invoice_received_qty(self):
|
||||
"""
|
||||
1. Test if received qty is validated against accepted + rejected
|
||||
@ -422,6 +425,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
self.assertEqual(tax.tax_amount, expected_values[i][1])
|
||||
self.assertEqual(tax.total, expected_values[i][2])
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_purchase_invoice_with_advance(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
@ -476,6 +480,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
)
|
||||
)
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_invoice_with_advance_and_multi_payment_terms(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
@ -1220,6 +1225,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_gain_loss_with_advance_entry(self):
|
||||
unlink_enabled = frappe.db.get_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
|
||||
@ -1420,6 +1426,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
)
|
||||
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_purchase_invoice_advance_taxes(self):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
|
||||
@ -1916,6 +1923,56 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
pi.load_from_db()
|
||||
self.assertFalse(pi.repost_required)
|
||||
|
||||
@change_settings("Buying Settings", {"supplier_group": None})
|
||||
def test_purchase_invoice_without_supplier_group(self):
|
||||
# Create a Supplier
|
||||
test_supplier_name = "_Test Supplier Without Supplier Group"
|
||||
if not frappe.db.exists("Supplier", test_supplier_name):
|
||||
supplier = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": test_supplier_name,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
self.assertEqual(supplier.supplier_group, None)
|
||||
|
||||
po = create_purchase_order(
|
||||
supplier=test_supplier_name,
|
||||
rate=3000,
|
||||
item="_Test Non Stock Item",
|
||||
posting_date="2021-09-15",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(supplier=test_supplier_name)
|
||||
|
||||
self.assertEqual(po.docstatus, 1)
|
||||
self.assertEqual(pi.docstatus, 1)
|
||||
|
||||
def test_default_cost_center_for_purchase(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
|
||||
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
|
||||
create_cost_center(cost_center_name=c_center)
|
||||
|
||||
item = create_item(
|
||||
"_Test Cost Center Item For Purchase",
|
||||
is_stock_item=1,
|
||||
buying_cost_center="_Test Cost Center Buying - _TC",
|
||||
selling_cost_center="_Test Cost Center Selling - _TC",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
|
||||
)
|
||||
|
||||
pi.items[0].cost_center = ""
|
||||
pi.set_missing_values()
|
||||
pi.calculate_taxes_and_totals()
|
||||
pi.save()
|
||||
|
||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
|
@ -77,6 +77,7 @@
|
||||
"manufacturer_part_no",
|
||||
"accounting",
|
||||
"expense_account",
|
||||
"wip_composite_asset",
|
||||
"col_break5",
|
||||
"is_fixed_asset",
|
||||
"asset_location",
|
||||
@ -903,12 +904,18 @@
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_composite_asset",
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-26 12:54:53.178156",
|
||||
"modified": "2023-10-03 21:01:01.824892",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -536,8 +536,9 @@ class SalesInvoice(SellingController):
|
||||
"taxes": ("account_head",),
|
||||
}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
|
||||
def set_paid_amount(self):
|
||||
paid_amount = 0.0
|
||||
|
@ -6,7 +6,7 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
@ -26,6 +26,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
from erpnext.controllers.accounts_controller import update_invoice_status
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
@ -44,13 +45,17 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
|
||||
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
|
||||
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
class TestSalesInvoice(FrappeTestCase):
|
||||
def setUp(self):
|
||||
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
|
||||
|
||||
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
|
||||
create_internal_parties()
|
||||
setup_accounts()
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def make(self):
|
||||
w = frappe.copy_doc(test_records[0])
|
||||
@ -178,6 +183,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertRaises(frappe.LinkExistsError, si.cancel)
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_payment_entry_unlink_against_standalone_credit_note(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
@ -510,70 +516,72 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(si.grand_total, 5474.0)
|
||||
|
||||
def test_tax_calculation_with_item_tax_template(self):
|
||||
import json
|
||||
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
|
||||
# set tax template in item
|
||||
item = frappe.get_cached_doc("Item", "_Test Item")
|
||||
item.set(
|
||||
"taxes",
|
||||
[
|
||||
{
|
||||
"item_tax_template": "_Test Item Tax Template 1 - _TC",
|
||||
"valid_from": add_days(nowdate(), -5),
|
||||
}
|
||||
],
|
||||
)
|
||||
item.save()
|
||||
|
||||
# create sales invoice with item
|
||||
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
|
||||
item_row = si.get("items")[0]
|
||||
item_details = get_item_details(
|
||||
doc=si,
|
||||
args={
|
||||
"item_code": item.item_code,
|
||||
"company": si.company,
|
||||
"doctype": "Sales Invoice",
|
||||
"conversion_rate": 1.0,
|
||||
},
|
||||
)
|
||||
tax_map = json.loads(item_details.item_tax_rate)
|
||||
for tax in tax_map:
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": tax,
|
||||
"rate": tax_map[tax],
|
||||
"description": "Test",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
},
|
||||
)
|
||||
si.submit()
|
||||
si.load_from_db()
|
||||
|
||||
add_items = [
|
||||
(54, "_Test Account Excise Duty @ 12 - _TC"),
|
||||
(288, "_Test Account Excise Duty @ 15 - _TC"),
|
||||
(144, "_Test Account Excise Duty @ 20 - _TC"),
|
||||
(430, "_Test Item Tax Template 1 - _TC"),
|
||||
# check if correct tax values are applied from tax template
|
||||
self.assertEqual(si.net_total, 386.4)
|
||||
|
||||
expected_taxes = [
|
||||
{
|
||||
"tax_amount": 19.32,
|
||||
"total": 405.72,
|
||||
},
|
||||
{
|
||||
"tax_amount": 38.64,
|
||||
"total": 444.36,
|
||||
},
|
||||
{
|
||||
"tax_amount": 57.96,
|
||||
"total": 502.32,
|
||||
},
|
||||
]
|
||||
for qty, item_tax_template in add_items:
|
||||
item_row_copy = copy.deepcopy(item_row)
|
||||
item_row_copy.qty = qty
|
||||
item_row_copy.item_tax_template = item_tax_template
|
||||
si.append("items", item_row_copy)
|
||||
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account Excise Duty - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Excise Duty",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 11,
|
||||
},
|
||||
)
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account Education Cess - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Education Cess",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 0,
|
||||
},
|
||||
)
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account S&H Education Cess - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "S&H Education Cess",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 3,
|
||||
},
|
||||
)
|
||||
si.insert()
|
||||
for i in range(len(expected_taxes)):
|
||||
for key in expected_taxes[i]:
|
||||
self.assertEqual(expected_taxes[i][key], si.get("taxes")[i].get(key))
|
||||
|
||||
self.assertEqual(si.net_total, 4600)
|
||||
|
||||
self.assertEqual(si.get("taxes")[0].tax_amount, 502.41)
|
||||
self.assertEqual(si.get("taxes")[0].total, 5102.41)
|
||||
|
||||
self.assertEqual(si.get("taxes")[1].tax_amount, 197.80)
|
||||
self.assertEqual(si.get("taxes")[1].total, 5300.21)
|
||||
|
||||
self.assertEqual(si.get("taxes")[2].tax_amount, 375.36)
|
||||
self.assertEqual(si.get("taxes")[2].total, 5675.57)
|
||||
|
||||
self.assertEqual(si.grand_total, 5675.57)
|
||||
self.assertEqual(si.rounding_adjustment, 0.43)
|
||||
self.assertEqual(si.rounded_total, 5676.0)
|
||||
self.assertEqual(si.get("base_total_taxes_and_charges"), 115.92)
|
||||
|
||||
def test_tax_calculation_with_multiple_items_and_discount(self):
|
||||
si = create_sales_invoice(qty=1, rate=75, do_not_save=True)
|
||||
@ -1299,6 +1307,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
dn.submit()
|
||||
return dn
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_sales_invoice_with_advance(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
@ -2774,6 +2783,13 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
tds_payable_account = create_account(
|
||||
account_name="TDS Payable",
|
||||
account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.additional_discount_account = additional_discount_account
|
||||
@ -3072,8 +3088,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.commission_rate = commission_rate
|
||||
self.assertRaises(frappe.ValidationError, si.save)
|
||||
|
||||
@change_settings("Accounts Settings", {"acc_frozen_upto": add_days(getdate(), 1)})
|
||||
def test_sales_invoice_submission_post_account_freezing_date(self):
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", add_days(getdate(), 1))
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.posting_date = add_days(getdate(), 1)
|
||||
si.save()
|
||||
@ -3082,8 +3098,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.posting_date = getdate()
|
||||
si.submit()
|
||||
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def test_over_billing_case_against_delivery_note(self):
|
||||
"""
|
||||
Test a case where duplicating the item with qty = 1 in the invoice
|
||||
@ -3112,6 +3126,13 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", over_billing_allowance)
|
||||
|
||||
@change_settings(
|
||||
"Accounts Settings",
|
||||
{
|
||||
"book_deferred_entries_via_journal_entry": 1,
|
||||
"submit_journal_entries": 1,
|
||||
},
|
||||
)
|
||||
def test_multi_currency_deferred_revenue_via_journal_entry(self):
|
||||
deferred_account = create_account(
|
||||
account_name="Deferred Revenue",
|
||||
@ -3119,11 +3140,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
acc_settings = frappe.get_single("Accounts Settings")
|
||||
acc_settings.book_deferred_entries_via_journal_entry = 1
|
||||
acc_settings.submit_journal_entries = 1
|
||||
acc_settings.save()
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item.enable_deferred_expense = 1
|
||||
item.item_defaults[0].deferred_revenue_account = deferred_account
|
||||
@ -3189,13 +3205,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(expected_gle[i][2], gle.debit)
|
||||
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||
|
||||
acc_settings = frappe.get_single("Accounts Settings")
|
||||
acc_settings.book_deferred_entries_via_journal_entry = 0
|
||||
acc_settings.submit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def test_standalone_serial_no_return(self):
|
||||
si = create_sales_invoice(
|
||||
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
|
||||
@ -3400,6 +3409,24 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||
|
||||
@change_settings("Selling Settings", {"customer_group": None, "territory": None})
|
||||
def test_sales_invoice_without_customer_group_and_territory(self):
|
||||
# create a customer
|
||||
if not frappe.db.exists("Customer", "_Test Simple Customer"):
|
||||
customer_dict = get_customer_dict("_Test Simple Customer")
|
||||
customer_dict.pop("customer_group")
|
||||
customer_dict.pop("territory")
|
||||
customer = frappe.get_doc(customer_dict).insert(ignore_permissions=True)
|
||||
|
||||
self.assertEqual(customer.customer_group, None)
|
||||
self.assertEqual(customer.territory, None)
|
||||
|
||||
# create a sales invoice
|
||||
si = create_sales_invoice(customer="_Test Simple Customer")
|
||||
self.assertEqual(si.docstatus, 1)
|
||||
self.assertEqual(si.customer_group, None)
|
||||
self.assertEqual(si.territory, None)
|
||||
|
||||
@change_settings("Selling Settings", {"allow_negative_rates_for_items": 0})
|
||||
def test_sales_return_negative_rate(self):
|
||||
si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True)
|
||||
|
@ -157,7 +157,6 @@
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "200px",
|
||||
"reqd": 1,
|
||||
"width": "200px"
|
||||
},
|
||||
{
|
||||
@ -912,4 +911,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,14 @@ frappe.ui.form.on('Subscription', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('sales_tax_template', function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils.data import (
|
||||
add_days,
|
||||
add_months,
|
||||
@ -21,11 +22,15 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto
|
||||
test_dependencies = ("UOM", "Item Group", "Item")
|
||||
|
||||
|
||||
class TestSubscription(unittest.TestCase):
|
||||
class TestSubscription(FrappeTestCase):
|
||||
def setUp(self):
|
||||
make_plans()
|
||||
create_parties()
|
||||
reset_settings()
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_create_subscription_with_trial_with_correct_period(self):
|
||||
subscription = create_subscription(
|
||||
|
@ -22,7 +22,7 @@ class SubscriptionPlan(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_plan_rate(
|
||||
plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1
|
||||
plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1, party=None
|
||||
):
|
||||
plan = frappe.get_doc("Subscription Plan", plan)
|
||||
if plan.price_determination == "Fixed Rate":
|
||||
@ -40,6 +40,7 @@ def get_plan_rate(
|
||||
customer_group=customer_group,
|
||||
company=None,
|
||||
qty=quantity,
|
||||
party=party,
|
||||
)
|
||||
if not price:
|
||||
return 0
|
||||
|
@ -8,7 +8,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.address.address import get_default_address
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, cstr
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
|
||||
from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups
|
||||
@ -34,7 +34,6 @@ class TaxRule(Document):
|
||||
self.validate_tax_template()
|
||||
self.validate_from_to_dates("from_date", "to_date")
|
||||
self.validate_filters()
|
||||
self.validate_use_for_shopping_cart()
|
||||
|
||||
def validate_tax_template(self):
|
||||
if self.tax_type == "Sales":
|
||||
@ -106,21 +105,6 @@ class TaxRule(Document):
|
||||
if tax_rule[0].priority == self.priority:
|
||||
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
|
||||
|
||||
def validate_use_for_shopping_cart(self):
|
||||
"""If shopping cart is enabled and no tax rule exists for shopping cart, enable this one"""
|
||||
if (
|
||||
not self.use_for_shopping_cart
|
||||
and cint(frappe.db.get_single_value("E Commerce Settings", "enabled"))
|
||||
and not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1, "name": ["!=", self.name]})
|
||||
):
|
||||
|
||||
self.use_for_shopping_cart = 1
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Enabling 'Use for Shopping Cart', as Shopping Cart is enabled and there should be at least one Tax Rule for Shopping Cart"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(party, party_type, args=None):
|
||||
|
@ -41,7 +41,7 @@ def make_gl_entries(
|
||||
from_repost=from_repost,
|
||||
)
|
||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||
# Post GL Map proccess there may no be any GL Entries
|
||||
# Post GL Map process there may no be any GL Entries
|
||||
elif gl_map:
|
||||
frappe.throw(
|
||||
_(
|
||||
|
@ -6,12 +6,7 @@ from typing import Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.contacts.doctype.address.address import (
|
||||
get_address_display,
|
||||
get_company_address,
|
||||
get_default_address,
|
||||
)
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_details
|
||||
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
|
||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.query_builder.functions import Abs, Date, Sum
|
||||
@ -133,6 +128,7 @@ def _get_party_details(
|
||||
party_address,
|
||||
company_address,
|
||||
shipping_address,
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
set_contact_details(party_details, party, party_type)
|
||||
set_other_values(party_details, party, party_type)
|
||||
@ -193,6 +189,8 @@ def set_address_details(
|
||||
party_address=None,
|
||||
company_address=None,
|
||||
shipping_address=None,
|
||||
*,
|
||||
ignore_permissions=False
|
||||
):
|
||||
billing_address_field = (
|
||||
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
|
||||
@ -205,13 +203,17 @@ def set_address_details(
|
||||
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
|
||||
)
|
||||
# address display
|
||||
party_details.address_display = get_address_display(party_details[billing_address_field])
|
||||
party_details.address_display = render_address(
|
||||
party_details[billing_address_field], check_permissions=not ignore_permissions
|
||||
)
|
||||
# shipping address
|
||||
if party_type in ["Customer", "Lead"]:
|
||||
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
|
||||
party_type, party.name
|
||||
)
|
||||
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
|
||||
party_details.shipping_address = render_address(
|
||||
party_details["shipping_address_name"], check_permissions=not ignore_permissions
|
||||
)
|
||||
if doctype:
|
||||
party_details.update(
|
||||
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
|
||||
@ -229,7 +231,7 @@ def set_address_details(
|
||||
if shipping_address:
|
||||
party_details.update(
|
||||
shipping_address=shipping_address,
|
||||
shipping_address_display=get_address_display(shipping_address),
|
||||
shipping_address_display=render_address(shipping_address),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||
)
|
||||
|
||||
@ -238,7 +240,8 @@ def set_address_details(
|
||||
party_details.update(
|
||||
billing_address=party_details.company_address,
|
||||
billing_address_display=(
|
||||
party_details.company_address_display or get_address_display(party_details.company_address)
|
||||
party_details.company_address_display
|
||||
or render_address(party_details.company_address, check_permissions=False)
|
||||
),
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
||||
)
|
||||
@ -290,7 +293,21 @@ def set_contact_details(party_details, party, party_type):
|
||||
}
|
||||
)
|
||||
else:
|
||||
party_details.update(get_contact_details(party_details.contact_person))
|
||||
fields = [
|
||||
"name as contact_person",
|
||||
"full_name as contact_display",
|
||||
"email_id as contact_email",
|
||||
"mobile_no as contact_mobile",
|
||||
"phone as contact_phone",
|
||||
"designation as contact_designation",
|
||||
"department as contact_department",
|
||||
]
|
||||
|
||||
contact_details = frappe.db.get_value(
|
||||
"Contact", party_details.contact_person, fields, as_dict=True
|
||||
)
|
||||
|
||||
party_details.update(contact_details)
|
||||
|
||||
|
||||
def set_other_values(party_details, party, party_type):
|
||||
@ -995,3 +1012,13 @@ def add_party_account(party_type, party, company, account):
|
||||
doc.append("accounts", accounts)
|
||||
|
||||
doc.save()
|
||||
|
||||
|
||||
def render_address(address, check_permissions=True):
|
||||
try:
|
||||
from frappe.contacts.doctype.address.address import render_address as _render
|
||||
except ImportError:
|
||||
# Older frappe versions where this function is not available
|
||||
from frappe.contacts.doctype.address.address import get_address_display as _render
|
||||
|
||||
return frappe.call(_render, address, check_permissions=check_permissions)
|
||||
|
@ -95,18 +95,11 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Party Type",
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
'account_type': 'Payable'
|
||||
}
|
||||
};
|
||||
},
|
||||
on_change: () => {
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||
}
|
||||
@ -114,8 +107,15 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_group",
|
||||
@ -164,3 +164,15 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Payable', 9);
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
@ -34,7 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"party": [self.supplier],
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
|
@ -72,18 +72,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Party Type",
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
'account_type': 'Payable'
|
||||
}
|
||||
};
|
||||
},
|
||||
on_change: () => {
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||
}
|
||||
@ -91,8 +84,15 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname":"payment_terms_template",
|
||||
@ -122,3 +122,15 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide("erpnext.utils");
|
||||
|
||||
frappe.query_reports["Accounts Receivable"] = {
|
||||
"filters": [
|
||||
{
|
||||
@ -38,19 +40,11 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Party Type",
|
||||
"Default": "Customer",
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
'account_type': 'Receivable'
|
||||
}
|
||||
};
|
||||
},
|
||||
on_change: () => {
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||
}
|
||||
@ -58,8 +52,15 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "party_account",
|
||||
@ -192,3 +193,16 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Receivable', 9);
|
||||
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
@ -116,7 +116,7 @@ class ReceivablePayableReport(object):
|
||||
# build all keys, since we want to exclude vouchers beyond the report date
|
||||
for ple in self.ple_entries:
|
||||
# get the balance object for voucher_type
|
||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||
if not key in self.voucher_balance:
|
||||
self.voucher_balance[key] = frappe._dict(
|
||||
voucher_type=ple.voucher_type,
|
||||
@ -183,7 +183,7 @@ class ReceivablePayableReport(object):
|
||||
):
|
||||
return
|
||||
|
||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
|
||||
# If payment is made against credit note
|
||||
# and credit note is made against a Sales Invoice
|
||||
@ -192,13 +192,13 @@ class ReceivablePayableReport(object):
|
||||
if ple.against_voucher_no in self.return_entries:
|
||||
return_against = self.return_entries.get(ple.against_voucher_no)
|
||||
if return_against:
|
||||
key = (ple.against_voucher_type, return_against, ple.party)
|
||||
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
|
||||
|
||||
row = self.voucher_balance.get(key)
|
||||
|
||||
if not row:
|
||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
|
||||
|
||||
row.party_type = ple.party_type
|
||||
return row
|
||||
@ -801,7 +801,7 @@ class ReceivablePayableReport(object):
|
||||
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
|
||||
|
||||
if self.filters.get("party"):
|
||||
self.qb_selection_filter.append(self.filters.party == self.ple.party)
|
||||
self.qb_selection_filter.append(self.ple.party.isin(self.filters.party))
|
||||
|
||||
if self.filters.party_account:
|
||||
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, today
|
||||
|
||||
@ -23,29 +24,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def create_usd_account(self):
|
||||
name = "Debtors USD"
|
||||
exists = frappe.db.get_list(
|
||||
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
|
||||
)
|
||||
if exists:
|
||||
self.debtors_usd = exists[0].name
|
||||
else:
|
||||
debtors = frappe.get_doc(
|
||||
"Account",
|
||||
frappe.db.get_list(
|
||||
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
|
||||
)[0].name,
|
||||
)
|
||||
|
||||
debtors_usd = frappe.new_doc("Account")
|
||||
debtors_usd.company = debtors.company
|
||||
debtors_usd.account_name = "Debtors USD"
|
||||
debtors_usd.account_currency = "USD"
|
||||
debtors_usd.parent_account = debtors.parent_account
|
||||
debtors_usd.account_type = debtors.account_type
|
||||
self.debtors_usd = debtors_usd.save().name
|
||||
|
||||
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
|
||||
frappe.set_user("Administrator")
|
||||
si = create_sales_invoice(
|
||||
@ -573,7 +551,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"party": [self.customer],
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
@ -605,3 +583,132 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
for field in expected:
|
||||
with self.subTest(field=field):
|
||||
self.assertEqual(report_output.get(field), expected.get(field))
|
||||
|
||||
def test_multi_select_party_filter(self):
|
||||
self.customer1 = self.customer
|
||||
self.create_customer("_Test Customer 2")
|
||||
self.customer2 = self.customer
|
||||
self.create_customer("_Test Customer 3")
|
||||
self.customer3 = self.customer
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Customer",
|
||||
"party": [self.customer1, self.customer3],
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si1.customer = self.customer1
|
||||
si1.save().submit()
|
||||
|
||||
si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si2.customer = self.customer2
|
||||
si2.save().submit()
|
||||
|
||||
si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si3.customer = self.customer3
|
||||
si3.save().submit()
|
||||
|
||||
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||
report = execute(filters)
|
||||
|
||||
expected_output = {self.customer1, self.customer3}
|
||||
self.assertEqual(len(report[1]), 2)
|
||||
output_for = set([x.party for x in report[1]])
|
||||
self.assertEqual(output_for, expected_output)
|
||||
|
||||
def test_report_output_if_party_is_missing(self):
|
||||
acc_name = "Additional Debtors"
|
||||
if not frappe.db.get_value(
|
||||
"Account", filters={"account_name": acc_name, "company": self.company}
|
||||
):
|
||||
additional_receivable_acc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": acc_name,
|
||||
"parent_account": "Accounts Receivable - " + self.company_abbr,
|
||||
"company": self.company,
|
||||
"account_type": "Receivable",
|
||||
}
|
||||
).save()
|
||||
self.debtors2 = additional_receivable_acc.name
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.company = self.company
|
||||
je.posting_date = today()
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"debit_in_account_currency": 150,
|
||||
"credit_in_account_currency": 0,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.debtors2,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"debit_in_account_currency": 200,
|
||||
"credit_in_account_currency": 0,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.cash,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit_in_account_currency": 350,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
je.save().submit()
|
||||
|
||||
# manually remove party from Payment Ledger
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
qb.update(ple).set(ple.party, None).where(ple.voucher_no == je.name).run()
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
report_ouput = execute(filters)[1]
|
||||
expected_data = [
|
||||
[self.debtors2, je.doctype, je.name, "Customer", self.customer, 200.0, 0.0, 0.0, 200.0],
|
||||
[self.debit_to, je.doctype, je.name, "Customer", self.customer, 150.0, 0.0, 0.0, 150.0],
|
||||
]
|
||||
self.assertEqual(len(report_ouput), 2)
|
||||
# fetch only required fields
|
||||
report_output = [
|
||||
[
|
||||
x.party_account,
|
||||
x.voucher_type,
|
||||
x.voucher_no,
|
||||
"Customer",
|
||||
self.customer,
|
||||
x.invoiced,
|
||||
x.paid,
|
||||
x.credit_note,
|
||||
x.outstanding,
|
||||
]
|
||||
for x in report_ouput
|
||||
]
|
||||
# use account name to sort
|
||||
# post sorting output should be [[Additional Debtors, ...], [Debtors, ...]]
|
||||
report_output = sorted(report_output, key=lambda x: x[0])
|
||||
self.assertEqual(expected_data, report_output)
|
||||
|
@ -72,19 +72,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Party Type",
|
||||
"Default": "Customer",
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
'account_type': 'Receivable'
|
||||
}
|
||||
};
|
||||
},
|
||||
on_change: () => {
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||
}
|
||||
@ -92,8 +84,15 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname":"customer_group",
|
||||
@ -151,3 +150,15 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
@ -99,13 +99,11 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
|
||||
# Add all amount columns
|
||||
for k in list(self.party_total[d.party]):
|
||||
if k not in ["currency", "sales_person"]:
|
||||
|
||||
self.party_total[d.party][k] += d.get(k, 0.0)
|
||||
if isinstance(self.party_total[d.party][k], float):
|
||||
self.party_total[d.party][k] += d.get(k) or 0.0
|
||||
|
||||
# set territory, customer_group, sales person etc
|
||||
self.set_party_details(d)
|
||||
self.party_total[d.party].update({"party_type": d.party_type})
|
||||
|
||||
def init_party_total(self, row):
|
||||
self.party_total.setdefault(
|
||||
@ -124,6 +122,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
"total_due": 0.0,
|
||||
"future_amount": 0.0,
|
||||
"sales_person": [],
|
||||
"party_type": row.party_type,
|
||||
}
|
||||
),
|
||||
)
|
||||
@ -133,13 +132,12 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
|
||||
for key in ("territory", "customer_group", "supplier_group"):
|
||||
if row.get(key):
|
||||
self.party_total[row.party][key] = row.get(key)
|
||||
|
||||
self.party_total[row.party][key] = row.get(key, "")
|
||||
if row.sales_person:
|
||||
self.party_total[row.party].sales_person.append(row.sales_person)
|
||||
self.party_total[row.party].sales_person.append(row.get("sales_person", ""))
|
||||
|
||||
if self.filters.sales_partner:
|
||||
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner")
|
||||
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "")
|
||||
|
||||
def get_columns(self):
|
||||
self.columns = []
|
||||
|
@ -1,25 +1,23 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
||||
frappe.query_reports["Balance Sheet"] = $.extend(
|
||||
{},
|
||||
erpnext.financial_statements
|
||||
);
|
||||
frappe.query_reports["Balance Sheet"] = $.extend(
|
||||
{},
|
||||
erpnext.financial_statements
|
||||
);
|
||||
|
||||
erpnext.utils.add_dimensions("Balance Sheet", 10);
|
||||
erpnext.utils.add_dimensions("Balance Sheet", 10);
|
||||
|
||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||
fieldname: "accumulated_values",
|
||||
label: __("Accumulated Values"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
|
||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||
fieldname: "include_default_book_entries",
|
||||
label: __("Include Default Book Entries"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||
fieldname: "accumulated_values",
|
||||
label: __("Accumulated Values"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
|
||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||
fieldname: "include_default_book_entries",
|
||||
label: __("Include Default Book Entries"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
|
@ -1,24 +1,24 @@
|
||||
// Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Cash Flow"] = $.extend({},
|
||||
erpnext.financial_statements);
|
||||
frappe.query_reports["Cash Flow"] = $.extend(
|
||||
{},
|
||||
erpnext.financial_statements
|
||||
);
|
||||
|
||||
erpnext.utils.add_dimensions('Cash Flow', 10);
|
||||
erpnext.utils.add_dimensions('Cash Flow', 10);
|
||||
|
||||
// The last item in the array is the definition for Presentation Currency
|
||||
// filter. It won't be used in cash flow for now so we pop it. Please take
|
||||
// of this if you are working here.
|
||||
// The last item in the array is the definition for Presentation Currency
|
||||
// filter. It won't be used in cash flow for now so we pop it. Please take
|
||||
// of this if you are working here.
|
||||
|
||||
frappe.query_reports["Cash Flow"]["filters"].splice(8, 1);
|
||||
frappe.query_reports["Cash Flow"]["filters"].splice(8, 1);
|
||||
|
||||
frappe.query_reports["Cash Flow"]["filters"].push(
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
);
|
||||
});
|
||||
frappe.query_reports["Cash Flow"]["filters"].push(
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
);
|
||||
|
@ -2,152 +2,150 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Filter Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Fiscal Year", "Date Range"],
|
||||
"default": ["Fiscal Year"],
|
||||
"reqd": 1,
|
||||
on_change: function() {
|
||||
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"period_start_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"period_end_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
on_change: () => {
|
||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
|
||||
let year_start_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), "year_start_date");
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: year_start_date
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
on_change: () => {
|
||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
|
||||
let year_end_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), "year_end_date");
|
||||
frappe.query_report.set_filter_value({
|
||||
period_end_date: year_end_date
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"report",
|
||||
"label": __("Report"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
|
||||
"default": "Balance Sheet",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list(),
|
||||
"default": frappe.defaults.get_user_default("Currency")
|
||||
},
|
||||
{
|
||||
"fieldname":"accumulated_in_group_company",
|
||||
"label": __("Accumulated Values in Group Company"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
"label": __("Show zero values"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (data && column.fieldname=="account") {
|
||||
value = data.account_name || value;
|
||||
|
||||
column.link_onclick =
|
||||
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
||||
column.is_tree = true;
|
||||
}
|
||||
|
||||
if (data && data.account && column.apply_currency_formatter) {
|
||||
data.currency = erpnext.get_currency(column.company_name);
|
||||
}
|
||||
|
||||
value = default_formatter(value, row, column, data);
|
||||
if (!data.parent_account) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
}
|
||||
return value;
|
||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
onload: function() {
|
||||
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Filter Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Fiscal Year", "Date Range"],
|
||||
"default": ["Fiscal Year"],
|
||||
"reqd": 1,
|
||||
on_change: function() {
|
||||
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"period_start_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"period_end_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
on_change: () => {
|
||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
|
||||
let year_start_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), "year_start_date");
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: year_start_date
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
on_change: () => {
|
||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
|
||||
let year_end_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), "year_end_date");
|
||||
frappe.query_report.set_filter_value({
|
||||
period_end_date: year_end_date
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"report",
|
||||
"label": __("Report"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
|
||||
"default": "Balance Sheet",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list(),
|
||||
"default": frappe.defaults.get_user_default("Currency")
|
||||
},
|
||||
{
|
||||
"fieldname":"accumulated_in_group_company",
|
||||
"label": __("Accumulated Values in Group Company"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
"label": __("Show zero values"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (data && column.fieldname=="account") {
|
||||
value = data.account_name || value;
|
||||
|
||||
column.link_onclick =
|
||||
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
||||
column.is_tree = true;
|
||||
}
|
||||
|
||||
if (data && data.account && column.apply_currency_formatter) {
|
||||
data.currency = erpnext.get_currency(column.company_name);
|
||||
}
|
||||
|
||||
value = default_formatter(value, row, column, data);
|
||||
if (!data.parent_account) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
onload: function() {
|
||||
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2,83 +2,81 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
from_date: fy.year_start_date,
|
||||
to_date: fy.year_end_date
|
||||
});
|
||||
});
|
||||
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book",
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension",
|
||||
"label": __("Select Dimension"),
|
||||
"fieldtype": "Select",
|
||||
"default": "Cost Center",
|
||||
"options": get_accounting_dimension_options(),
|
||||
"reqd": 1,
|
||||
},
|
||||
],
|
||||
"formatter": erpnext.financial_statements.formatter,
|
||||
"tree": true,
|
||||
"name_field": "account",
|
||||
"parent_field": "parent_account",
|
||||
"initial_depth": 3
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
from_date: fy.year_start_date,
|
||||
to_date: fy.year_end_date
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book",
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension",
|
||||
"label": __("Select Dimension"),
|
||||
"fieldtype": "Select",
|
||||
"default": "Cost Center",
|
||||
"options": get_accounting_dimension_options(),
|
||||
"reqd": 1,
|
||||
},
|
||||
],
|
||||
"formatter": erpnext.financial_statements.formatter,
|
||||
"tree": true,
|
||||
"name_field": "account",
|
||||
"parent_field": "parent_account",
|
||||
"initial_depth": 3
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function get_accounting_dimension_options() {
|
||||
let options =["Cost Center", "Project"];
|
||||
frappe.db.get_list('Accounting Dimension',
|
||||
{fields:['document_type']}).then((res) => {
|
||||
res.forEach((dimension) => {
|
||||
options.push(dimension.document_type);
|
||||
});
|
||||
});
|
||||
{fields:['document_type']}).then((res) => {
|
||||
res.forEach((dimension) => {
|
||||
options.push(dimension.document_type);
|
||||
});
|
||||
});
|
||||
return options
|
||||
}
|
||||
|
@ -177,8 +177,8 @@ def add_solvency_ratios(
|
||||
return_on_equity_ratio = {"ratio": "Return on Equity Ratio"}
|
||||
|
||||
for year in years:
|
||||
profit_after_tax = total_income[year] + total_expense[year]
|
||||
share_holder_fund = total_asset[year] - total_liability[year]
|
||||
profit_after_tax = flt(total_income.get(year)) + flt(total_expense.get(year))
|
||||
share_holder_fund = flt(total_asset.get(year)) - flt(total_liability.get(year))
|
||||
|
||||
debt_equity_ratio[year] = calculate_ratio(
|
||||
total_liability.get(year), share_holder_fund, precision
|
||||
|
@ -133,15 +133,17 @@ class General_Payment_Ledger_Comparison(object):
|
||||
self.gle_balances = set(val.gle) | self.gle_balances
|
||||
self.ple_balances = set(val.ple) | self.ple_balances
|
||||
|
||||
self.diff1 = self.gle_balances.difference(self.ple_balances)
|
||||
self.diff2 = self.ple_balances.difference(self.gle_balances)
|
||||
self.variation_in_payment_ledger = self.gle_balances.difference(self.ple_balances)
|
||||
self.variation_in_general_ledger = self.ple_balances.difference(self.gle_balances)
|
||||
self.diff = frappe._dict({})
|
||||
|
||||
for x in self.diff1:
|
||||
for x in self.variation_in_payment_ledger:
|
||||
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
|
||||
|
||||
for x in self.diff2:
|
||||
self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]}))
|
||||
for x in self.variation_in_general_ledger:
|
||||
self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update(
|
||||
frappe._dict({"pl_balance": x[4]})
|
||||
)
|
||||
|
||||
def generate_data(self):
|
||||
self.data = []
|
||||
|
@ -2,20 +2,15 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
|
||||
frappe.query_reports["Gross and Net Profit Report"] = {
|
||||
"filters": [
|
||||
frappe.query_reports["Gross and Net Profit Report"] = $.extend(
|
||||
{},
|
||||
erpnext.financial_statements
|
||||
);
|
||||
|
||||
]
|
||||
}
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Gross and Net Profit Report"] = $.extend({},
|
||||
erpnext.financial_statements);
|
||||
|
||||
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
||||
{
|
||||
"fieldname": "accumulated_values",
|
||||
"label": __("Accumulated Values"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
);
|
||||
});
|
||||
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
||||
{
|
||||
"fieldname": "accumulated_values",
|
||||
"label": __("Accumulated Values"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
);
|
||||
|
@ -544,6 +544,8 @@ class GrossProfitGenerator(object):
|
||||
new_row.qty += flt(row.qty)
|
||||
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
|
||||
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
||||
if self.filters.get("group_by") == "Sales Person":
|
||||
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
|
||||
new_row = self.set_average_rate(new_row)
|
||||
self.grouped_data.append(new_row)
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
||||
frappe.query_reports["Profit and Loss Statement"] = $.extend(
|
||||
{},
|
||||
erpnext.financial_statements
|
||||
);
|
||||
frappe.query_reports["Profit and Loss Statement"] = $.extend(
|
||||
{},
|
||||
erpnext.financial_statements
|
||||
);
|
||||
|
||||
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({
|
||||
fieldname: "accumulated_values",
|
||||
label: __("Accumulated Values"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
|
||||
fieldname: "accumulated_values",
|
||||
label: __("Accumulated Values"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
|
@ -1,133 +1,131 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Profitability Analysis"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "based_on",
|
||||
"label": __("Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Cost Center", "Project", "Accounting Dimension"],
|
||||
"default": "Cost Center",
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report){
|
||||
let based_on = query_report.get_values().based_on;
|
||||
if(based_on!='Accounting Dimension'){
|
||||
frappe.query_report.set_filter_value({
|
||||
accounting_dimension: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimension",
|
||||
"label": __("Accounting Dimension"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Accounting Dimension",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
from_date: fy.year_start_date,
|
||||
to_date: fy.year_end_date
|
||||
});
|
||||
frappe.query_reports["Profitability Analysis"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "based_on",
|
||||
"label": __("Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Cost Center", "Project", "Accounting Dimension"],
|
||||
"default": "Cost Center",
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report){
|
||||
let based_on = query_report.get_values().based_on;
|
||||
if(based_on!='Accounting Dimension'){
|
||||
frappe.query_report.set_filter_value({
|
||||
accounting_dimension: ''
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
"label": __("Show zero values"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (column.fieldname=="account") {
|
||||
value = data.account_name;
|
||||
|
||||
column.link_onclick =
|
||||
"frappe.query_reports['Profitability Analysis'].open_profit_and_loss_statement(" + JSON.stringify(data) + ")";
|
||||
column.is_tree = true;
|
||||
}
|
||||
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (!data.parent_account && data.based_on != 'project') {
|
||||
value = $(`<span>${value}</span>`);
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
if (data.warn_if_negative && data[column.fieldname] < 0) {
|
||||
$value.addClass("text-danger");
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimension",
|
||||
"label": __("Accounting Dimension"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Accounting Dimension",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
from_date: fy.year_start_date,
|
||||
to_date: fy.year_end_date
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
"label": __("Show zero values"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (column.fieldname=="account") {
|
||||
value = data.account_name;
|
||||
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
column.link_onclick =
|
||||
"frappe.query_reports['Profitability Analysis'].open_profit_and_loss_statement(" + JSON.stringify(data) + ")";
|
||||
column.is_tree = true;
|
||||
}
|
||||
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (!data.parent_account && data.based_on != 'project') {
|
||||
value = $(`<span>${value}</span>`);
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
if (data.warn_if_negative && data[column.fieldname] < 0) {
|
||||
$value.addClass("text-danger");
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
"open_profit_and_loss_statement": function(data) {
|
||||
if (!data.account) return;
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
}
|
||||
|
||||
frappe.route_options = {
|
||||
"company": frappe.query_report.get_filter_value('company'),
|
||||
"from_fiscal_year": data.fiscal_year,
|
||||
"to_fiscal_year": data.fiscal_year
|
||||
};
|
||||
return value;
|
||||
},
|
||||
"open_profit_and_loss_statement": function(data) {
|
||||
if (!data.account) return;
|
||||
|
||||
if(data.based_on == 'cost_center'){
|
||||
frappe.route_options["cost_center"] = data.account
|
||||
} else {
|
||||
frappe.route_options["project"] = data.account
|
||||
}
|
||||
frappe.route_options = {
|
||||
"company": frappe.query_report.get_filter_value('company'),
|
||||
"from_fiscal_year": data.fiscal_year,
|
||||
"to_fiscal_year": data.fiscal_year
|
||||
};
|
||||
|
||||
frappe.set_route("query-report", "Profit and Loss Statement");
|
||||
},
|
||||
"tree": true,
|
||||
"name_field": "account",
|
||||
"parent_field": "parent_account",
|
||||
"initial_depth": 3
|
||||
}
|
||||
if(data.based_on == 'Cost Center'){
|
||||
frappe.route_options["cost_center"] = data.account
|
||||
} else {
|
||||
frappe.route_options["project"] = data.account
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
||||
});
|
||||
frappe.set_route("query-report", "Profit and Loss Statement");
|
||||
},
|
||||
"tree": true,
|
||||
"name_field": "account",
|
||||
"parent_field": "parent_account",
|
||||
"initial_depth": 3
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
||||
});
|
||||
|
||||
|
@ -68,7 +68,11 @@ def get_result(
|
||||
tax_amount += entry.credit - entry.debit
|
||||
|
||||
if net_total_map.get(name):
|
||||
total_amount, grand_total, base_total = net_total_map.get(name)
|
||||
if voucher_type == "Journal Entry":
|
||||
# back calcalute total amount from rate and tax_amount
|
||||
total_amount = grand_total = base_total = tax_amount / (rate / 100)
|
||||
else:
|
||||
total_amount, grand_total, base_total = net_total_map.get(name)
|
||||
else:
|
||||
total_amount += entry.credit
|
||||
|
||||
|
@ -1,118 +1,116 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Trial Balance"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
from_date: fy.year_start_date,
|
||||
to_date: fy.year_end_date
|
||||
});
|
||||
frappe.query_reports["Trial Balance"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
from_date: fy.year_start_date,
|
||||
to_date: fy.year_end_date
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"label": __("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"get_query": function() {
|
||||
var company = frappe.query_report.get_filter_value('company');
|
||||
return {
|
||||
"doctype": "Cost Center",
|
||||
"filters": {
|
||||
"company": company,
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"label": __("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"get_query": function() {
|
||||
var company = frappe.query_report.get_filter_value('company');
|
||||
return {
|
||||
"doctype": "Cost Center",
|
||||
"filters": {
|
||||
"company": company,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"label": __("Project"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book",
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list()
|
||||
},
|
||||
{
|
||||
"fieldname": "with_period_closing_entry",
|
||||
"label": __("Period Closing Entry"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
"label": __("Show zero values"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "show_unclosed_fy_pl_balances",
|
||||
"label": __("Show unclosed fiscal year's P&L balances"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_net_values",
|
||||
"label": __("Show net values in opening and closing columns"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
],
|
||||
"formatter": erpnext.financial_statements.formatter,
|
||||
"tree": true,
|
||||
"name_field": "account",
|
||||
"parent_field": "parent_account",
|
||||
"initial_depth": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"label": __("Project"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book",
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list()
|
||||
},
|
||||
{
|
||||
"fieldname": "with_period_closing_entry",
|
||||
"label": __("Period Closing Entry"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
"label": __("Show zero values"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "show_unclosed_fy_pl_balances",
|
||||
"label": __("Show unclosed fiscal year's P&L balances"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_net_values",
|
||||
"label": __("Show net values in opening and closing columns"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
],
|
||||
"formatter": erpnext.financial_statements.formatter,
|
||||
"tree": true,
|
||||
"name_field": "account",
|
||||
"parent_field": "parent_account",
|
||||
"initial_depth": 3
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Trial Balance', 6);
|
||||
});
|
||||
erpnext.utils.add_dimensions('Trial Balance', 6);
|
||||
|
@ -148,6 +148,15 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
if (frm.doc.docstatus == 0) {
|
||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||
|
||||
if (frm.doc.is_composite_asset && !frm.doc.capitalized_in) {
|
||||
$('.primary-action').prop('hidden', true);
|
||||
$('.form-message').text('Capitalize this asset to confirm');
|
||||
|
||||
frm.add_custom_button(__("Capitalize Asset"), function() {
|
||||
frm.trigger("create_asset_capitalization");
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -169,7 +178,7 @@ frappe.ui.form.on('Asset', {
|
||||
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
||||
frm.set_df_property('purchase_receipt', 'read_only', 1);
|
||||
}
|
||||
else if (frm.doc.is_existing_asset) {
|
||||
else if (frm.doc.is_existing_asset || frm.doc.is_composite_asset) {
|
||||
frm.toggle_reqd('purchase_receipt', 0);
|
||||
frm.toggle_reqd('purchase_invoice', 0);
|
||||
}
|
||||
@ -239,7 +248,7 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
datatable.style.setStyle(`.dt-scrollable`, {'font-size': '0.75rem', 'margin-bottom': '1rem', 'margin-left': '0.35rem', 'margin-right': '0.35rem'});
|
||||
datatable.style.setStyle(`.dt-header`, {'margin-left': '0.35rem', 'margin-right': '0.35rem'});
|
||||
datatable.style.setStyle(`.dt-cell--header`, {'color': 'var(--text-muted)'});
|
||||
datatable.style.setStyle(`.dt-cell--header .dt-cell__content`, {'color': 'var(--gray-600)', 'font-size': 'var(--text-sm)'});
|
||||
datatable.style.setStyle(`.dt-cell`, {'color': 'var(--text-color)'});
|
||||
datatable.style.setStyle(`.dt-cell--col-1`, {'text-align': 'center'});
|
||||
datatable.style.setStyle(`.dt-cell--col-2`, {'font-weight': 600});
|
||||
@ -328,7 +337,7 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
|
||||
item_code: function(frm) {
|
||||
if(frm.doc.item_code && frm.doc.calculate_depreciation) {
|
||||
if(frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
|
||||
frm.trigger('set_finance_book');
|
||||
} else {
|
||||
frm.set_value('finance_books', []);
|
||||
@ -340,7 +349,8 @@ frappe.ui.form.on('Asset', {
|
||||
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
||||
args: {
|
||||
item_code: frm.doc.item_code,
|
||||
asset_category: frm.doc.asset_category
|
||||
asset_category: frm.doc.asset_category,
|
||||
gross_purchase_amount: frm.doc.gross_purchase_amount
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message) {
|
||||
@ -352,7 +362,17 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
is_existing_asset: function(frm) {
|
||||
frm.trigger("toggle_reference_doc");
|
||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||
},
|
||||
|
||||
is_composite_asset: function(frm) {
|
||||
if(frm.doc.is_composite_asset) {
|
||||
frm.set_value('gross_purchase_amount', 0);
|
||||
frm.set_df_property('gross_purchase_amount', 'read_only', 1);
|
||||
} else {
|
||||
frm.set_df_property('gross_purchase_amount', 'read_only', 0);
|
||||
}
|
||||
|
||||
frm.trigger("toggle_reference_doc");
|
||||
},
|
||||
|
||||
make_sales_invoice: function(frm) {
|
||||
@ -402,6 +422,19 @@ frappe.ui.form.on('Asset', {
|
||||
});
|
||||
},
|
||||
|
||||
create_asset_capitalization: function(frm) {
|
||||
frappe.call({
|
||||
args: {
|
||||
"asset": frm.doc.name,
|
||||
},
|
||||
method: "erpnext.assets.doctype.asset.asset.create_asset_capitalization",
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
split_asset: function(frm) {
|
||||
const title = __('Split Asset');
|
||||
|
||||
@ -457,7 +490,7 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
calculate_depreciation: function(frm) {
|
||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||
if (frm.doc.item_code && frm.doc.calculate_depreciation ) {
|
||||
if (frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
|
||||
frm.trigger("set_finance_book");
|
||||
} else {
|
||||
frm.set_value("finance_books", []);
|
||||
@ -465,9 +498,11 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
gross_purchase_amount: function(frm) {
|
||||
frm.doc.finance_books.forEach(d => {
|
||||
frm.events.set_depreciation_rate(frm, d);
|
||||
})
|
||||
if (frm.doc.finance_books) {
|
||||
frm.doc.finance_books.forEach(d => {
|
||||
frm.events.set_depreciation_rate(frm, d);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
purchase_receipt: (frm) => {
|
||||
@ -546,7 +581,21 @@ frappe.ui.form.on('Asset', {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
set_salvage_value_percentage_or_expected_value_after_useful_life: function(frm, row, salvage_value_percentage_changed, expected_value_after_useful_life_changed) {
|
||||
if (expected_value_after_useful_life_changed) {
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||
const new_salvage_value_percentage = flt((row.expected_value_after_useful_life * 100) / frm.doc.gross_purchase_amount, precision("salvage_value_percentage", row));
|
||||
frappe.model.set_value(row.doctype, row.name, "salvage_value_percentage", new_salvage_value_percentage);
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||
} else if (salvage_value_percentage_changed) {
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||
const new_expected_value_after_useful_life = flt(frm.doc.gross_purchase_amount * (row.salvage_value_percentage / 100), precision('gross_purchase_amount'));
|
||||
frappe.model.set_value(row.doctype, row.name, "expected_value_after_useful_life", new_expected_value_after_useful_life);
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Asset Finance Book', {
|
||||
@ -557,9 +606,19 @@ frappe.ui.form.on('Asset Finance Book', {
|
||||
|
||||
expected_value_after_useful_life: function(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, false, true);
|
||||
}
|
||||
frm.events.set_depreciation_rate(frm, row);
|
||||
},
|
||||
|
||||
salvage_value_percentage: function(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, true, false);
|
||||
}
|
||||
},
|
||||
|
||||
frequency_of_depreciation: function(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
frm.events.set_depreciation_rate(frm, row);
|
||||
|
@ -14,6 +14,7 @@
|
||||
"asset_owner",
|
||||
"asset_owner_company",
|
||||
"is_existing_asset",
|
||||
"is_composite_asset",
|
||||
"supplier",
|
||||
"customer",
|
||||
"image",
|
||||
@ -72,7 +73,8 @@
|
||||
"purchase_receipt_amount",
|
||||
"default_finance_book",
|
||||
"depr_entry_posting_status",
|
||||
"amended_from"
|
||||
"amended_from",
|
||||
"capitalized_in"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -199,7 +201,7 @@
|
||||
"fieldtype": "Date",
|
||||
"label": "Purchase Date",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -237,10 +239,12 @@
|
||||
"default": "0",
|
||||
"fieldname": "calculate_depreciation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Calculate Depreciation"
|
||||
"label": "Calculate Depreciation",
|
||||
"read_only_depends_on": "eval:doc.is_composite_asset && !doc.gross_purchase_amount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.is_composite_asset",
|
||||
"fieldname": "is_existing_asset",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Existing Asset"
|
||||
@ -478,7 +482,7 @@
|
||||
"fieldname": "asset_quantity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Asset Quantity",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset"
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "depr_entry_posting_status",
|
||||
@ -507,6 +511,21 @@
|
||||
"fieldname": "is_fully_depreciated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Fully Depreciated"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.is_existing_asset",
|
||||
"fieldname": "is_composite_asset",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Composite Asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "capitalized_in",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Capitalized In",
|
||||
"options": "Asset Capitalization",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@ -545,7 +564,7 @@
|
||||
"table_fieldname": "accounts"
|
||||
}
|
||||
],
|
||||
"modified": "2023-07-28 20:12:44.819616",
|
||||
"modified": "2023-10-03 23:28:26.732269",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
@ -198,7 +198,9 @@ class Asset(AccountsController):
|
||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||
|
||||
if self.item_code and not self.get("finance_books"):
|
||||
finance_books = get_item_details(self.item_code, self.asset_category)
|
||||
finance_books = get_item_details(
|
||||
self.item_code, self.asset_category, self.gross_purchase_amount
|
||||
)
|
||||
self.set("finance_books", finance_books)
|
||||
|
||||
def validate_finance_books(self):
|
||||
@ -226,7 +228,7 @@ class Asset(AccountsController):
|
||||
if not self.asset_category:
|
||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||
|
||||
if not flt(self.gross_purchase_amount):
|
||||
if not flt(self.gross_purchase_amount) and not self.is_composite_asset:
|
||||
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
|
||||
|
||||
if is_cwip_accounting_enabled(self.asset_category):
|
||||
@ -766,6 +768,15 @@ def create_asset_repair(asset, asset_name):
|
||||
return asset_repair
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_asset_capitalization(asset):
|
||||
asset_capitalization = frappe.new_doc("Asset Capitalization")
|
||||
asset_capitalization.update(
|
||||
{"target_asset": asset, "capitalization_method": "Choose a WIP composite asset"}
|
||||
)
|
||||
return asset_capitalization
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_asset_value_adjustment(asset, asset_category, company):
|
||||
asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
|
||||
@ -797,7 +808,7 @@ def transfer_asset(args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item_code, asset_category):
|
||||
def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
|
||||
books = []
|
||||
for d in asset_category_doc.finance_books:
|
||||
@ -807,7 +818,11 @@ def get_item_details(item_code, asset_category):
|
||||
"depreciation_method": d.depreciation_method,
|
||||
"total_number_of_depreciations": d.total_number_of_depreciations,
|
||||
"frequency_of_depreciation": d.frequency_of_depreciation,
|
||||
"start_date": nowdate(),
|
||||
"daily_depreciation": d.daily_depreciation,
|
||||
"salvage_value_percentage": d.salvage_value_percentage,
|
||||
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
||||
* flt(d.salvage_value_percentage / 100),
|
||||
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -19,7 +19,6 @@ from frappe.utils import (
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.assets.doctype.asset.asset import (
|
||||
get_asset_value_after_depreciation,
|
||||
make_sales_invoice,
|
||||
split_asset,
|
||||
update_maintenance_status,
|
||||
@ -194,6 +193,7 @@ class TestAsset(AssetSetup):
|
||||
def test_is_fixed_asset_set(self):
|
||||
asset = create_asset(is_existing_asset=1)
|
||||
doc = frappe.new_doc("Purchase Invoice")
|
||||
doc.company = "_Test Company"
|
||||
doc.supplier = "_Test Supplier"
|
||||
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
|
||||
|
||||
@ -534,7 +534,7 @@ class TestAsset(AssetSetup):
|
||||
|
||||
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
|
||||
|
||||
# CWIP: Capital Work In Progress
|
||||
# Capital Work In Progress
|
||||
def test_cwip_accounting(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
|
||||
@ -567,7 +567,8 @@ class TestAsset(AssetSetup):
|
||||
pr.submit()
|
||||
|
||||
expected_gle = (
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||
("_Test Account Shipping Charges - _TC", 0.0, 250.0),
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
|
||||
("CWIP Account - _TC", 5250.0, 0.0),
|
||||
)
|
||||
|
||||
@ -586,9 +587,8 @@ class TestAsset(AssetSetup):
|
||||
expected_gle = (
|
||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5000.0, 0.0),
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||
)
|
||||
|
||||
pi_gle = frappe.db.sql(
|
||||
@ -1744,6 +1744,7 @@ def create_asset(**args):
|
||||
"location": args.location or "Test Location",
|
||||
"asset_owner": args.asset_owner or "Company",
|
||||
"is_existing_asset": args.is_existing_asset or 1,
|
||||
"is_composite_asset": args.is_composite_asset or 0,
|
||||
"asset_quantity": args.get("asset_quantity") or 1,
|
||||
"depr_entry_posting_status": args.depr_entry_posting_status or "",
|
||||
}
|
||||
|
@ -75,13 +75,14 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-01 11:09:52.584482",
|
||||
"modified": "2023-09-29 15:56:17.608643",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Activity",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
@ -89,6 +90,7 @@
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
@ -96,6 +98,7 @@
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
|
@ -16,9 +16,15 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
|
||||
refresh() {
|
||||
this.show_general_ledger();
|
||||
|
||||
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
||||
this.show_stock_ledger();
|
||||
}
|
||||
|
||||
if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
|
||||
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
|
||||
this.get_target_asset_details();
|
||||
}
|
||||
}
|
||||
|
||||
setup_queries() {
|
||||
@ -35,18 +41,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
});
|
||||
|
||||
me.frm.set_query("target_asset", function() {
|
||||
var filters = {};
|
||||
|
||||
if (me.frm.doc.target_item_code) {
|
||||
filters['item_code'] = me.frm.doc.target_item_code;
|
||||
}
|
||||
|
||||
filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]];
|
||||
filters['docstatus'] = 1;
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
};
|
||||
filters: {'is_composite_asset': 1, 'docstatus': 0 }
|
||||
}
|
||||
});
|
||||
|
||||
me.frm.set_query("asset", "asset_items", function() {
|
||||
@ -128,6 +125,39 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
return this.get_target_item_details();
|
||||
}
|
||||
|
||||
target_asset() {
|
||||
if (this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
|
||||
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
|
||||
this.get_target_asset_details();
|
||||
}
|
||||
}
|
||||
|
||||
set_consumed_stock_items_tagged_to_wip_composite_asset(asset) {
|
||||
var me = this;
|
||||
|
||||
if (asset) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset",
|
||||
args: {
|
||||
asset: asset,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc && r.message) {
|
||||
me.frm.clear_table("stock_items");
|
||||
|
||||
for (let item of r.message) {
|
||||
me.frm.add_child("stock_items", item);
|
||||
}
|
||||
|
||||
refresh_field("stock_items");
|
||||
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
item_code(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
if (cdt === "Asset Capitalization Stock Item") {
|
||||
@ -242,6 +272,26 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
}
|
||||
}
|
||||
|
||||
get_target_asset_details() {
|
||||
var me = this;
|
||||
|
||||
if (me.frm.doc.target_asset) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
|
||||
child: me.frm.doc,
|
||||
args: {
|
||||
asset: me.frm.doc.target_asset,
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_consumed_stock_item_details(row) {
|
||||
var me = this;
|
||||
|
||||
|
@ -8,24 +8,25 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"company",
|
||||
"naming_series",
|
||||
"entry_type",
|
||||
"target_item_code",
|
||||
"target_asset",
|
||||
"target_item_name",
|
||||
"target_is_fixed_asset",
|
||||
"target_has_batch_no",
|
||||
"target_has_serial_no",
|
||||
"column_break_9",
|
||||
"target_asset_name",
|
||||
"capitalization_method",
|
||||
"target_item_code",
|
||||
"target_asset_location",
|
||||
"target_asset",
|
||||
"target_asset_name",
|
||||
"target_warehouse",
|
||||
"target_qty",
|
||||
"target_stock_uom",
|
||||
"target_batch_no",
|
||||
"target_serial_no",
|
||||
"column_break_5",
|
||||
"company",
|
||||
"finance_book",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
@ -57,12 +58,13 @@
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || ((doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization')",
|
||||
"fieldname": "target_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval:(doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization'",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code",
|
||||
@ -86,16 +88,18 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.target_asset && !doc.__islocal) || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
|
||||
"fieldname": "target_asset",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Asset",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset'",
|
||||
"no_copy": 1,
|
||||
"options": "Asset",
|
||||
"read_only": 1
|
||||
"read_only_depends_on": "eval:(doc.entry_type=='Decapitalization') || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset')"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
|
||||
"fetch_from": "target_asset.asset_name",
|
||||
"fieldname": "target_asset_name",
|
||||
"fieldtype": "Data",
|
||||
@ -186,12 +190,14 @@
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||
"fieldname": "target_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Target Qty",
|
||||
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||
"fetch_from": "target_item_code.stock_uom",
|
||||
"fieldname": "target_stock_uom",
|
||||
"fieldtype": "Link",
|
||||
@ -331,18 +337,26 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
|
||||
"fieldname": "target_asset_location",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Asset Location",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
|
||||
"options": "Location"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fieldname": "capitalization_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Capitalization Method",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"options": "\nCreate a new composite asset\nChoose a WIP composite asset"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-22 14:17:07.995120",
|
||||
"modified": "2023-10-03 22:55:59.461456",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization",
|
||||
|
@ -53,6 +53,7 @@ class AssetCapitalization(StockController):
|
||||
self.validate_posting_time()
|
||||
self.set_missing_values(for_validate=True)
|
||||
self.validate_target_item()
|
||||
self.validate_target_asset()
|
||||
self.validate_consumed_stock_item()
|
||||
self.validate_consumed_asset_item()
|
||||
self.validate_service_item()
|
||||
@ -67,12 +68,12 @@ class AssetCapitalization(StockController):
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_source_mandatory()
|
||||
if self.entry_type == "Capitalization":
|
||||
self.create_target_asset()
|
||||
self.create_target_asset()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = (
|
||||
@ -94,6 +95,11 @@ class AssetCapitalization(StockController):
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
target_asset_details = get_target_asset_details(self.target_asset, self.company)
|
||||
for k, v in target_asset_details.items():
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
for d in self.stock_items:
|
||||
args = self.as_dict()
|
||||
args.update(d.as_dict())
|
||||
@ -155,6 +161,33 @@ class AssetCapitalization(StockController):
|
||||
|
||||
self.validate_item(target_item)
|
||||
|
||||
def validate_target_asset(self):
|
||||
if self.target_asset:
|
||||
target_asset = self.get_asset_for_validation(self.target_asset)
|
||||
|
||||
if not target_asset.is_composite_asset:
|
||||
frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name))
|
||||
|
||||
if target_asset.item_code != self.target_item_code:
|
||||
frappe.throw(
|
||||
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
|
||||
)
|
||||
|
||||
if target_asset.status in ("Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(
|
||||
_("Target Asset {0} cannot be {1}").format(target_asset.name, target_asset.status)
|
||||
)
|
||||
|
||||
if target_asset.docstatus == 1:
|
||||
frappe.throw(_("Target Asset {0} cannot be submitted").format(target_asset.name))
|
||||
elif target_asset.docstatus == 2:
|
||||
frappe.throw(_("Target Asset {0} cannot be cancelled").format(target_asset.name))
|
||||
|
||||
if target_asset.company != self.company:
|
||||
frappe.throw(
|
||||
_("Target Asset {0} does not belong to company {1}").format(target_asset.name, self.company)
|
||||
)
|
||||
|
||||
def validate_consumed_stock_item(self):
|
||||
for d in self.stock_items:
|
||||
if d.item_code:
|
||||
@ -179,7 +212,23 @@ class AssetCapitalization(StockController):
|
||||
)
|
||||
|
||||
asset = self.get_asset_for_validation(d.asset)
|
||||
self.validate_asset(asset)
|
||||
|
||||
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Consumed Asset {1} cannot be {2}").format(d.idx, asset.name, asset.status)
|
||||
)
|
||||
|
||||
if asset.docstatus == 0:
|
||||
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be Draft").format(d.idx, asset.name))
|
||||
elif asset.docstatus == 2:
|
||||
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be cancelled").format(d.idx, asset.name))
|
||||
|
||||
if asset.company != self.company:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Consumed Asset {1} does not belong to company {2}").format(
|
||||
d.idx, asset.name, self.company
|
||||
)
|
||||
)
|
||||
|
||||
def validate_service_item(self):
|
||||
for d in self.service_items:
|
||||
@ -214,21 +263,12 @@ class AssetCapitalization(StockController):
|
||||
|
||||
def get_asset_for_validation(self, asset):
|
||||
return frappe.db.get_value(
|
||||
"Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1
|
||||
"Asset",
|
||||
asset,
|
||||
["name", "item_code", "company", "status", "docstatus", "is_composite_asset"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
def validate_asset(self, asset):
|
||||
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status))
|
||||
|
||||
if asset.docstatus == 0:
|
||||
frappe.throw(_("Asset {0} is Draft").format(asset.name))
|
||||
if asset.docstatus == 2:
|
||||
frappe.throw(_("Asset {0} is cancelled").format(asset.name))
|
||||
|
||||
if asset.company != self.company:
|
||||
frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company))
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_warehouse_details(self):
|
||||
for d in self.get("stock_items"):
|
||||
@ -495,16 +535,25 @@ class AssetCapitalization(StockController):
|
||||
)
|
||||
|
||||
def create_target_asset(self):
|
||||
if (
|
||||
self.entry_type != "Capitalization"
|
||||
or self.capitalization_method != "Create a new composite asset"
|
||||
):
|
||||
return
|
||||
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
|
||||
asset_doc = frappe.new_doc("Asset")
|
||||
asset_doc.company = self.company
|
||||
asset_doc.item_code = self.target_item_code
|
||||
asset_doc.is_existing_asset = 1
|
||||
asset_doc.is_composite_asset = 1
|
||||
asset_doc.location = self.target_asset_location
|
||||
asset_doc.available_for_use_date = self.posting_date
|
||||
asset_doc.purchase_date = self.posting_date
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.capitalized_in = self.name
|
||||
asset_doc.flags.ignore_validate = True
|
||||
asset_doc.flags.asset_created_via_asset_capitalization = True
|
||||
asset_doc.insert()
|
||||
@ -528,6 +577,28 @@ class AssetCapitalization(StockController):
|
||||
).format(get_link_to_form("Asset", asset_doc.name))
|
||||
)
|
||||
|
||||
def update_target_asset(self):
|
||||
if (
|
||||
self.entry_type != "Capitalization"
|
||||
or self.capitalization_method != "Choose a WIP composite asset"
|
||||
):
|
||||
return
|
||||
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
|
||||
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.capitalized_in = self.name
|
||||
asset_doc.flags.ignore_validate = True
|
||||
asset_doc.save()
|
||||
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Asset {0} has been updated. Please set the depreciation details if any and submit it."
|
||||
).format(get_link_to_form("Asset", asset_doc.name))
|
||||
)
|
||||
|
||||
def restore_consumed_asset_items(self):
|
||||
for item in self.asset_items:
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
@ -612,6 +683,33 @@ def get_target_item_details(item_code=None, company=None):
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_target_asset_details(asset=None, company=None):
|
||||
out = frappe._dict()
|
||||
|
||||
# Get Asset Details
|
||||
asset_details = frappe._dict()
|
||||
if asset:
|
||||
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
|
||||
if not asset_details:
|
||||
frappe.throw(_("Asset {0} does not exist").format(asset))
|
||||
|
||||
# Re-set item code from Asset
|
||||
out.target_item_code = asset_details.item_code
|
||||
|
||||
# Set Asset Details
|
||||
out.asset_name = asset_details.asset_name
|
||||
|
||||
if asset_details.item_code:
|
||||
out.target_fixed_asset_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=asset_details.item_code, company=company
|
||||
)
|
||||
else:
|
||||
out.target_fixed_asset_account = None
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_consumed_stock_item_details(args):
|
||||
if isinstance(args, str):
|
||||
@ -760,3 +858,30 @@ def get_service_item_details(args):
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items_tagged_to_wip_composite_asset(asset):
|
||||
fields = [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"batch_no",
|
||||
"serial_no",
|
||||
"stock_qty",
|
||||
"stock_uom",
|
||||
"warehouse",
|
||||
"cost_center",
|
||||
"qty",
|
||||
"valuation_rate",
|
||||
"amount",
|
||||
]
|
||||
|
||||
pi_items = frappe.get_all(
|
||||
"Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||
)
|
||||
|
||||
pr_items = frappe.get_all(
|
||||
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||
)
|
||||
|
||||
return pi_items + pr_items
|
||||
|
@ -58,6 +58,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Capitalization",
|
||||
capitalization_method="Create a new composite asset",
|
||||
target_item_code="Macbook Pro",
|
||||
target_asset_location="Test Location",
|
||||
stock_qty=stock_qty,
|
||||
@ -147,6 +148,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Capitalization",
|
||||
capitalization_method="Create a new composite asset",
|
||||
target_item_code="Macbook Pro",
|
||||
target_asset_location="Test Location",
|
||||
stock_qty=stock_qty,
|
||||
@ -211,6 +213,77 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
||||
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
||||
|
||||
def test_capitalization_with_wip_composite_asset(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
set_depreciation_settings_in_company(company=company)
|
||||
|
||||
stock_rate = 1000
|
||||
stock_qty = 2
|
||||
stock_amount = 2000
|
||||
|
||||
total_amount = 2000
|
||||
|
||||
wip_composite_asset = create_asset(
|
||||
asset_name="Asset Capitalization WIP Composite Asset",
|
||||
is_composite_asset=1,
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Capitalization",
|
||||
capitalization_method="Choose a WIP composite asset",
|
||||
target_asset=wip_composite_asset.name,
|
||||
target_asset_location="Test Location",
|
||||
stock_qty=stock_qty,
|
||||
stock_rate=stock_rate,
|
||||
service_expense_account="Expenses Included In Asset Valuation - TCP1",
|
||||
company=company,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.entry_type, "Capitalization")
|
||||
self.assertEqual(asset_capitalization.capitalization_method, "Choose a WIP composite asset")
|
||||
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||
|
||||
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
|
||||
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
|
||||
self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
|
||||
|
||||
self.assertEqual(asset_capitalization.total_value, total_amount)
|
||||
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||
|
||||
# Test Target Asset values
|
||||
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||
|
||||
# Test General Ledger Entries
|
||||
expected_gle = {
|
||||
"_Test Fixed Asset - TCP1": 2000,
|
||||
"_Test Warehouse - TCP1": -2000,
|
||||
}
|
||||
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||
|
||||
self.assertEqual(actual_gle, expected_gle)
|
||||
|
||||
# Test Stock Ledger Entries
|
||||
expected_sle = {
|
||||
("Capitalization Source Stock Item", "_Test Warehouse - TCP1"): {
|
||||
"actual_qty": -stock_qty,
|
||||
"stock_value_difference": -stock_amount,
|
||||
}
|
||||
}
|
||||
actual_sle = get_actual_sle_dict(asset_capitalization.name)
|
||||
self.assertEqual(actual_sle, expected_sle)
|
||||
|
||||
# Cancel Asset Capitalization and make test entries and status are reversed
|
||||
asset_capitalization.cancel()
|
||||
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
||||
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
||||
|
||||
def test_decapitalization_with_depreciation(self):
|
||||
# Variables
|
||||
purchase_date = "2020-01-01"
|
||||
@ -347,6 +420,7 @@ def create_asset_capitalization(**args):
|
||||
asset_capitalization.update(
|
||||
{
|
||||
"entry_type": args.entry_type or "Capitalization",
|
||||
"capitalization_method": args.capitalization_method or None,
|
||||
"company": company,
|
||||
"posting_date": args.posting_date or now.strftime("%Y-%m-%d"),
|
||||
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
|
||||
|
@ -12,6 +12,7 @@
|
||||
"column_break_5",
|
||||
"frequency_of_depreciation",
|
||||
"depreciation_start_date",
|
||||
"salvage_value_percentage",
|
||||
"expected_value_after_useful_life",
|
||||
"value_after_depreciation",
|
||||
"rate_of_depreciation"
|
||||
@ -91,12 +92,17 @@
|
||||
"fieldname": "daily_depreciation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Daily Depreciation"
|
||||
},
|
||||
{
|
||||
"fieldname": "salvage_value_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Salvage Value Percentage"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-10 22:10:36.576199",
|
||||
"modified": "2023-09-29 15:39:52.740594",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
@ -177,7 +177,7 @@ class AssetRepair(AccountsController):
|
||||
"item_code": stock_item.item_code,
|
||||
"qty": stock_item.consumed_quantity,
|
||||
"basic_rate": stock_item.valuation_rate,
|
||||
"serial_no": stock_item.serial_and_batch_bundle,
|
||||
"serial_and_batch_bundle": stock_item.serial_and_batch_bundle,
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project,
|
||||
},
|
||||
|
@ -24,6 +24,7 @@
|
||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||
"disable_last_purchase_rate",
|
||||
"show_pay_button",
|
||||
"use_transaction_date_exchange_rate",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
"column_break_11",
|
||||
@ -164,6 +165,13 @@
|
||||
"fieldname": "over_order_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Over Order Allowance (%)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
|
||||
"fieldname": "use_transaction_date_exchange_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Transaction Date Exchange Rate"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@ -171,7 +179,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-02 17:02:14.404622",
|
||||
"modified": "2023-10-16 16:22:03.201078",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
@ -477,6 +477,7 @@
|
||||
"depends_on": "eval:doc.is_subcontracted",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Supplier Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@ -1274,7 +1275,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-13 16:21:07.361700",
|
||||
"modified": "2023-10-01 20:58:07.851037",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -180,7 +180,6 @@
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "300px",
|
||||
"reqd": 1,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
@ -916,4 +915,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,6 @@
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "300px",
|
||||
"reqd": 1,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
@ -270,4 +269,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ frappe.ui.form.on("Supplier", {
|
||||
}, __("View"));
|
||||
|
||||
frm.add_custom_button(__('Accounts Payable'), function () {
|
||||
frappe.set_route('query-report', 'Accounts Payable', { supplier: frm.doc.name });
|
||||
frappe.set_route('query-report', 'Accounts Payable', { party_type: "Supplier", party: frm.doc.name });
|
||||
}, __("View"));
|
||||
|
||||
frm.add_custom_button(__('Bank Account'), function () {
|
||||
|
@ -167,8 +167,7 @@
|
||||
"label": "Supplier Group",
|
||||
"oldfieldname": "supplier_type",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Supplier Group",
|
||||
"reqd": 1
|
||||
"options": "Supplier Group"
|
||||
},
|
||||
{
|
||||
"default": "Company",
|
||||
@ -486,7 +485,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2023-09-21 12:24:20.398889",
|
||||
"modified": "2023-09-25 12:48:21.869563",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
@ -8,7 +8,7 @@ def get_data():
|
||||
"This is based on transactions against this Supplier. See timeline below for details"
|
||||
),
|
||||
"fieldname": "supplier",
|
||||
"non_standard_fieldnames": {"Payment Entry": "party_name", "Bank Account": "party"},
|
||||
"non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"},
|
||||
"transactions": [
|
||||
{"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]},
|
||||
{"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]},
|
||||
|
@ -207,11 +207,14 @@ def create_supplier(**args):
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": args.supplier_name,
|
||||
"default_currency": args.default_currency,
|
||||
"supplier_group": args.supplier_group or "Services",
|
||||
"supplier_type": args.supplier_type or "Company",
|
||||
"tax_withholding_category": args.tax_withholding_category,
|
||||
}
|
||||
).insert()
|
||||
)
|
||||
if not args.without_supplier_group:
|
||||
doc.supplier_group = args.supplier_group or "Services"
|
||||
|
||||
doc.insert()
|
||||
|
||||
return doc
|
||||
|
||||
|
@ -126,7 +126,6 @@
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "300px",
|
||||
"reqd": 1,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
@ -569,4 +568,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
@ -334,6 +334,11 @@ def make_default_records():
|
||||
"variable_label": "Total Ordered",
|
||||
"path": "get_ordered_qty",
|
||||
},
|
||||
{
|
||||
"param_name": "total_invoiced",
|
||||
"variable_label": "Total Invoiced",
|
||||
"path": "get_invoiced_qty",
|
||||
},
|
||||
]
|
||||
install_standing_docs = [
|
||||
{
|
||||
|
@ -440,6 +440,23 @@ def get_ordered_qty(scorecard):
|
||||
).run(as_list=True)[0][0] or 0
|
||||
|
||||
|
||||
def get_invoiced_qty(scorecard):
|
||||
"""Returns the total number of invoiced quantity (based on Purchase Invoice)"""
|
||||
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
|
||||
return (
|
||||
frappe.qb.from_(pi)
|
||||
.select(Sum(pi.total_qty))
|
||||
.where(
|
||||
(pi.supplier == scorecard.supplier)
|
||||
& (pi.docstatus == 1)
|
||||
& (pi.posting_date >= scorecard.get("start_date"))
|
||||
& (pi.posting_date <= scorecard.get("end_date"))
|
||||
)
|
||||
).run(as_list=True)[0][0] or 0
|
||||
|
||||
|
||||
def get_rfq_total_number(scorecard):
|
||||
"""Gets the total number of RFQs sent to supplier"""
|
||||
supplier = frappe.get_doc("Supplier", scorecard.supplier)
|
||||
|
@ -6,7 +6,7 @@ import copy
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import IfNull
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
from frappe.utils import date_diff, flt, getdate
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ def get_data(filters):
|
||||
po_item.qty,
|
||||
po_item.received_qty,
|
||||
(po_item.qty - po_item.received_qty).as_("pending_qty"),
|
||||
IfNull(pi_item.qty, 0).as_("billed_qty"),
|
||||
Sum(IfNull(pi_item.qty, 0)).as_("billed_qty"),
|
||||
po_item.base_amount.as_("amount"),
|
||||
(po_item.received_qty * po_item.base_rate).as_("received_qty_amount"),
|
||||
(po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"),
|
||||
|
@ -35,8 +35,12 @@ def get_data(filters):
|
||||
sq_item.parent,
|
||||
sq_item.item_code,
|
||||
sq_item.qty,
|
||||
sq.currency,
|
||||
sq_item.stock_qty,
|
||||
sq_item.amount,
|
||||
sq_item.base_rate,
|
||||
sq_item.base_amount,
|
||||
sq.price_list_currency,
|
||||
sq_item.uom,
|
||||
sq_item.stock_uom,
|
||||
sq_item.request_for_quotation,
|
||||
@ -105,7 +109,11 @@ def prepare_data(supplier_quotation_data, filters):
|
||||
"qty": data.get("qty"),
|
||||
"price": flt(data.get("amount") * exchange_rate, float_precision),
|
||||
"uom": data.get("uom"),
|
||||
"price_list_currency": data.get("price_list_currency"),
|
||||
"currency": data.get("currency"),
|
||||
"stock_uom": data.get("stock_uom"),
|
||||
"base_amount": flt(data.get("base_amount"), float_precision),
|
||||
"base_rate": flt(data.get("base_rate"), float_precision),
|
||||
"request_for_quotation": data.get("request_for_quotation"),
|
||||
"valid_till": data.get("valid_till"),
|
||||
"lead_time_days": data.get("lead_time_days"),
|
||||
@ -183,6 +191,8 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||
|
||||
group_by_columns = [
|
||||
{
|
||||
"fieldname": "supplier_name",
|
||||
@ -203,11 +213,18 @@ def get_columns(filters):
|
||||
columns = [
|
||||
{"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90},
|
||||
{"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"label": _("Currency"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Currency",
|
||||
"width": 110,
|
||||
},
|
||||
{
|
||||
"fieldname": "price",
|
||||
"label": _("Price"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"width": 110,
|
||||
},
|
||||
{
|
||||
@ -221,9 +238,23 @@ def get_columns(filters):
|
||||
"fieldname": "price_per_unit",
|
||||
"label": _("Price per Unit (Stock UOM)"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"fieldname": "base_amount",
|
||||
"label": _("Price ({0})").format(currency),
|
||||
"fieldtype": "Currency",
|
||||
"options": "price_list_currency",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"fieldname": "base_rate",
|
||||
"label": _("Price Per Unit ({0})").format(currency),
|
||||
"fieldtype": "Currency",
|
||||
"options": "price_list_currency",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"fieldname": "quotation",
|
||||
"label": _("Supplier Quotation"),
|
||||
|
@ -13,6 +13,7 @@ from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
cint,
|
||||
comma_and,
|
||||
flt,
|
||||
fmt_money,
|
||||
formatdate,
|
||||
@ -181,6 +182,17 @@ class AccountsController(TransactionBase):
|
||||
self.validate_party_account_currency()
|
||||
|
||||
if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
|
||||
if invalid_advances := [
|
||||
x for x in self.advances if not x.reference_type or not x.reference_name
|
||||
]:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Rows: {0} in {1} section are Invalid. Reference Name should point to a valid Payment Entry or Journal Entry."
|
||||
).format(
|
||||
frappe.bold(comma_and([x.idx for x in invalid_advances])), frappe.bold(_("Advance Payments"))
|
||||
)
|
||||
)
|
||||
|
||||
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
|
||||
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
||||
self.set_advances()
|
||||
@ -572,6 +584,17 @@ class AccountsController(TransactionBase):
|
||||
self.currency, self.company_currency, transaction_date, args
|
||||
)
|
||||
|
||||
if (
|
||||
self.currency
|
||||
and buying_or_selling == "Buying"
|
||||
and frappe.db.get_single_value("Buying Settings", "use_transaction_date_exchange_rate")
|
||||
and self.doctype == "Purchase Invoice"
|
||||
):
|
||||
self.use_transaction_date_exchange_rate = True
|
||||
self.conversion_rate = get_exchange_rate(
|
||||
self.currency, self.company_currency, transaction_date, args
|
||||
)
|
||||
|
||||
def set_missing_item_details(self, for_validate=False):
|
||||
"""set missing item values"""
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
@ -9,6 +9,8 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, flt
|
||||
|
||||
from erpnext.utilities.product import get_item_codes_by_attributes
|
||||
|
||||
|
||||
class ItemVariantExistsError(frappe.ValidationError):
|
||||
pass
|
||||
@ -24,7 +26,8 @@ class ItemTemplateCannotHaveStock(frappe.ValidationError):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_variant(template, args=None, variant=None, manufacturer=None, manufacturer_part_no=None):
|
||||
"""Validates Attributes and their Values, then looks for an exactly
|
||||
"""
|
||||
Validates Attributes and their Values, then looks for an exactly
|
||||
matching Item Variant
|
||||
|
||||
:param item: Template Item
|
||||
@ -34,13 +37,14 @@ def get_variant(template, args=None, variant=None, manufacturer=None, manufactur
|
||||
|
||||
if item_template.variant_based_on == "Manufacturer" and manufacturer:
|
||||
return make_variant_based_on_manufacturer(item_template, manufacturer, manufacturer_part_no)
|
||||
else:
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
if not args:
|
||||
frappe.throw(_("Please specify at least one attribute in the Attributes table"))
|
||||
return find_variant(template, args, variant)
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
if not args:
|
||||
frappe.throw(_("Please specify at least one attribute in the Attributes table"))
|
||||
|
||||
return find_variant(template, args, variant)
|
||||
|
||||
|
||||
def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part_no):
|
||||
@ -157,17 +161,6 @@ def get_attribute_values(item):
|
||||
|
||||
|
||||
def find_variant(template, args, variant_item_code=None):
|
||||
conditions = [
|
||||
"""(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})""".format(
|
||||
frappe.db.escape(key), frappe.db.escape(cstr(value))
|
||||
)
|
||||
for key, value in args.items()
|
||||
]
|
||||
|
||||
conditions = " or ".join(conditions)
|
||||
|
||||
from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes
|
||||
|
||||
possible_variants = [
|
||||
i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code
|
||||
]
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold, throw
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.utils import cint, flt, get_link_to_form, nowtime
|
||||
|
||||
from erpnext.accounts.party import render_address
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
@ -288,7 +288,9 @@ class SellingController(StockController):
|
||||
last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
|
||||
|
||||
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
|
||||
throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate")
|
||||
throw_message(
|
||||
item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate (Moving Average)"
|
||||
)
|
||||
|
||||
def get_item_list(self):
|
||||
il = []
|
||||
@ -600,7 +602,9 @@ class SellingController(StockController):
|
||||
|
||||
for address_field, address_display_field in address_dict.items():
|
||||
if self.get(address_field):
|
||||
self.set(address_display_field, get_address_display(self.get(address_field)))
|
||||
self.set(
|
||||
address_display_field, render_address(self.get(address_field), check_permissions=False)
|
||||
)
|
||||
|
||||
def validate_for_duplicate_items(self):
|
||||
check_list, chk_dupl_itm = [], []
|
||||
|
@ -62,9 +62,12 @@ class StockController(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
|
||||
|
||||
if (
|
||||
cint(erpnext.is_perpetual_inventory_enabled(self.company))
|
||||
or provisional_accounting_for_non_stock_items
|
||||
or is_asset_pr
|
||||
):
|
||||
warehouse_account = get_warehouse_account_map(self.company)
|
||||
|
||||
@ -73,10 +76,7 @@ class StockController(AccountsController):
|
||||
gl_entries = self.get_gl_entries(warehouse_account)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
|
||||
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
|
||||
gl_entries = []
|
||||
gl_entries = self.get_asset_gl_entry(gl_entries)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
update_regional_gl_entries(gl_entries, self)
|
||||
|
||||
def validate_serialized_batch(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
@ -692,13 +692,21 @@ class StockController(AccountsController):
|
||||
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
|
||||
|
||||
def validate_internal_transfer(self):
|
||||
if (
|
||||
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
|
||||
and self.is_internal_transfer()
|
||||
):
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"):
|
||||
if self.is_internal_transfer():
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
else:
|
||||
self.validate_internal_transfer_warehouse()
|
||||
|
||||
def validate_internal_transfer_warehouse(self):
|
||||
for row in self.items:
|
||||
if row.get("target_warehouse"):
|
||||
row.target_warehouse = None
|
||||
|
||||
if row.get("from_warehouse"):
|
||||
row.from_warehouse = None
|
||||
|
||||
def validate_in_transit_warehouses(self):
|
||||
if (
|
||||
@ -855,8 +863,9 @@ class StockController(AccountsController):
|
||||
|
||||
@frappe.whitelist()
|
||||
def show_accounting_ledger_preview(company, doctype, docname):
|
||||
filters = {"company": company, "include_dimensions": 1}
|
||||
filters = frappe._dict(company=company, include_dimensions=1)
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
doc.run_method("before_gl_preview")
|
||||
|
||||
gl_columns, gl_data = get_accounting_ledger_preview(doc, filters)
|
||||
|
||||
@ -867,8 +876,9 @@ def show_accounting_ledger_preview(company, doctype, docname):
|
||||
|
||||
@frappe.whitelist()
|
||||
def show_stock_ledger_preview(company, doctype, docname):
|
||||
filters = {"company": company}
|
||||
filters = frappe._dict(company=company)
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
doc.run_method("before_sl_preview")
|
||||
|
||||
sl_columns, sl_data = get_stock_ledger_preview(doc, filters)
|
||||
|
||||
@ -1216,3 +1226,8 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa
|
||||
repost_entries.append(repost_entry)
|
||||
|
||||
return repost_entries
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def update_regional_gl_entries(gl_list, doc):
|
||||
return
|
||||
|
@ -25,6 +25,9 @@ class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc: Document):
|
||||
self.doc = doc
|
||||
frappe.flags.round_off_applicable_accounts = []
|
||||
frappe.flags.round_row_wise_tax = frappe.db.get_single_value(
|
||||
"Accounts Settings", "round_row_wise_tax"
|
||||
)
|
||||
|
||||
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
|
||||
|
||||
@ -190,7 +193,9 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
item.net_rate = item.rate
|
||||
|
||||
if not item.qty and self.doc.get("is_return"):
|
||||
if (
|
||||
not item.qty and self.doc.get("is_return") and self.doc.get("doctype") != "Purchase Receipt"
|
||||
):
|
||||
item.amount = flt(-1 * item.rate, item.precision("amount"))
|
||||
elif not item.qty and self.doc.get("is_debit_note"):
|
||||
item.amount = flt(item.rate, item.precision("amount"))
|
||||
@ -368,6 +373,8 @@ class calculate_taxes_and_totals(object):
|
||||
for i, tax in enumerate(self.doc.get("taxes")):
|
||||
# tax_amount represents the amount of tax for the current step
|
||||
current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
|
||||
if frappe.flags.round_row_wise_tax:
|
||||
current_tax_amount = flt(current_tax_amount, tax.precision("tax_amount"))
|
||||
|
||||
# Adjust divisional loss to the last item
|
||||
if tax.charge_type == "Actual":
|
||||
@ -478,10 +485,19 @@ class calculate_taxes_and_totals(object):
|
||||
# store tax breakup for each item
|
||||
key = item.item_code or item.item_name
|
||||
item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate
|
||||
if tax.item_wise_tax_detail.get(key):
|
||||
item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
|
||||
if frappe.flags.round_row_wise_tax:
|
||||
item_wise_tax_amount = flt(item_wise_tax_amount, tax.precision("tax_amount"))
|
||||
if tax.item_wise_tax_detail.get(key):
|
||||
item_wise_tax_amount += flt(tax.item_wise_tax_detail[key][1], tax.precision("tax_amount"))
|
||||
tax.item_wise_tax_detail[key] = [
|
||||
tax_rate,
|
||||
flt(item_wise_tax_amount, tax.precision("tax_amount")),
|
||||
]
|
||||
else:
|
||||
if tax.item_wise_tax_detail.get(key):
|
||||
item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
|
||||
|
||||
tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
|
||||
tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
|
||||
|
||||
def round_off_totals(self, tax):
|
||||
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||
|
@ -379,7 +379,7 @@ def get_lead_details(lead, posting_date=None, company=None):
|
||||
}
|
||||
)
|
||||
|
||||
set_address_details(out, lead, "Lead")
|
||||
set_address_details(out, lead, "Lead", company=company)
|
||||
|
||||
taxes_and_charges = set_taxes(
|
||||
None,
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Concat_ws, Date
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@ -69,53 +70,41 @@ def get_columns():
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
`tabLead`.name,
|
||||
`tabLead`.lead_name,
|
||||
`tabLead`.status,
|
||||
`tabLead`.lead_owner,
|
||||
`tabLead`.territory,
|
||||
`tabLead`.source,
|
||||
`tabLead`.email_id,
|
||||
`tabLead`.mobile_no,
|
||||
`tabLead`.phone,
|
||||
`tabLead`.owner,
|
||||
`tabLead`.company,
|
||||
concat_ws(', ',
|
||||
trim(',' from `tabAddress`.address_line1),
|
||||
trim(',' from tabAddress.address_line2)
|
||||
) AS address,
|
||||
`tabAddress`.state,
|
||||
`tabAddress`.pincode,
|
||||
`tabAddress`.country
|
||||
FROM
|
||||
`tabLead` left join `tabDynamic Link` on (
|
||||
`tabLead`.name = `tabDynamic Link`.link_name and
|
||||
`tabDynamic Link`.parenttype = 'Address')
|
||||
left join `tabAddress` on (
|
||||
`tabAddress`.name=`tabDynamic Link`.parent)
|
||||
WHERE
|
||||
company = %(company)s
|
||||
AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s
|
||||
{conditions}
|
||||
ORDER BY
|
||||
`tabLead`.creation asc """.format(
|
||||
conditions=get_conditions(filters)
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
lead = frappe.qb.DocType("Lead")
|
||||
address = frappe.qb.DocType("Address")
|
||||
dynamic_link = frappe.qb.DocType("Dynamic Link")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(lead)
|
||||
.left_join(dynamic_link)
|
||||
.on((lead.name == dynamic_link.link_name) & (dynamic_link.parenttype == "Address"))
|
||||
.left_join(address)
|
||||
.on(address.name == dynamic_link.parent)
|
||||
.select(
|
||||
lead.name,
|
||||
lead.lead_name,
|
||||
lead.status,
|
||||
lead.lead_owner,
|
||||
lead.territory,
|
||||
lead.source,
|
||||
lead.email_id,
|
||||
lead.mobile_no,
|
||||
lead.phone,
|
||||
lead.owner,
|
||||
lead.company,
|
||||
(Concat_ws(", ", address.address_line1, address.address_line2)).as_("address"),
|
||||
address.state,
|
||||
address.pincode,
|
||||
address.country,
|
||||
)
|
||||
.where(lead.company == filters.company)
|
||||
.where(Date(lead.creation).between(filters.from_date, filters.to_date))
|
||||
)
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = []
|
||||
|
||||
if filters.get("territory"):
|
||||
conditions.append(" and `tabLead`.territory=%(territory)s")
|
||||
query = query.where(lead.territory == filters.get("territory"))
|
||||
|
||||
if filters.get("status"):
|
||||
conditions.append(" and `tabLead`.status=%(status)s")
|
||||
query = query.where(lead.status == filters.get("status"))
|
||||
|
||||
return " ".join(conditions) if conditions else ""
|
||||
return query.run(as_dict=1)
|
||||
|
@ -1,81 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
|
||||
from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
|
||||
from erpnext.e_commerce.product_data_engine.query import ProductQuery
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_product_filter_data(query_args=None):
|
||||
"""
|
||||
Returns filtered products and discount filters.
|
||||
:param query_args (dict): contains filters to get products list
|
||||
|
||||
Query Args filters:
|
||||
search (str): Search Term.
|
||||
field_filters (dict): Keys include item_group, brand, etc.
|
||||
attribute_filters(dict): Keys include Color, Size, etc.
|
||||
start (int): Offset items by
|
||||
item_group (str): Valid Item Group
|
||||
from_filters (bool): Set as True to jump to page 1
|
||||
"""
|
||||
if isinstance(query_args, str):
|
||||
query_args = json.loads(query_args)
|
||||
|
||||
query_args = frappe._dict(query_args)
|
||||
if query_args:
|
||||
search = query_args.get("search")
|
||||
field_filters = query_args.get("field_filters", {})
|
||||
attribute_filters = query_args.get("attribute_filters", {})
|
||||
start = cint(query_args.start) if query_args.get("start") else 0
|
||||
item_group = query_args.get("item_group")
|
||||
from_filters = query_args.get("from_filters")
|
||||
else:
|
||||
search, attribute_filters, item_group, from_filters = None, None, None, None
|
||||
field_filters = {}
|
||||
start = 0
|
||||
|
||||
# if new filter is checked, reset start to show filtered items from page 1
|
||||
if from_filters:
|
||||
start = 0
|
||||
|
||||
sub_categories = []
|
||||
if item_group:
|
||||
sub_categories = get_child_groups_for_website(item_group, immediate=True)
|
||||
|
||||
engine = ProductQuery()
|
||||
try:
|
||||
result = engine.query(
|
||||
attribute_filters, field_filters, search_term=search, start=start, item_group=item_group
|
||||
)
|
||||
except Exception:
|
||||
frappe.log_error("Product query with filter failed")
|
||||
return {"exc": "Something went wrong!"}
|
||||
|
||||
# discount filter data
|
||||
filters = {}
|
||||
discounts = result["discounts"]
|
||||
|
||||
if discounts:
|
||||
filter_engine = ProductFiltersBuilder()
|
||||
filters["discount_filters"] = filter_engine.get_discount_filters(discounts)
|
||||
|
||||
return {
|
||||
"items": result["items"] or [],
|
||||
"filters": filters,
|
||||
"settings": engine.settings,
|
||||
"sub_categories": sub_categories,
|
||||
"items_count": result["items_count"],
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_guest_redirect_on_action():
|
||||
return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action")
|
@ -1,58 +0,0 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("E Commerce Settings", {
|
||||
onload: function(frm) {
|
||||
if(frm.doc.__onload && frm.doc.__onload.quotation_series) {
|
||||
frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
|
||||
frm.refresh_field("quotation_series");
|
||||
}
|
||||
|
||||
frm.set_query('payment_gateway_account', function() {
|
||||
return { 'filters': { 'payment_channel': "Email" } };
|
||||
});
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.enabled) {
|
||||
frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html(
|
||||
`<div>${__("Follow these steps to create a landing page for your store")}:
|
||||
<a href="https://docs.erpnext.com/docs/user/manual/en/website/store-landing-page"
|
||||
style="color: var(--gray-600)">
|
||||
docs/store-landing-page
|
||||
</a>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
frappe.model.with_doctype("Website Item", () => {
|
||||
const web_item_meta = frappe.get_meta('Website Item');
|
||||
|
||||
const valid_fields = web_item_meta.fields.filter(df =>
|
||||
["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden
|
||||
).map(df =>
|
||||
({ label: df.label, value: df.fieldname })
|
||||
);
|
||||
|
||||
frm.get_field("filter_fields").grid.update_docfield_property(
|
||||
'fieldname', 'options', valid_fields
|
||||
);
|
||||
});
|
||||
},
|
||||
enabled: function(frm) {
|
||||
if (frm.doc.enabled === 1) {
|
||||
frm.set_value('enable_variants', 1);
|
||||
}
|
||||
else {
|
||||
frm.set_value('company', '');
|
||||
frm.set_value('price_list', '');
|
||||
frm.set_value('default_customer_group', '');
|
||||
frm.set_value('quotation_series', '');
|
||||
}
|
||||
},
|
||||
|
||||
enable_checkout: function(frm) {
|
||||
if (frm.doc.enable_checkout) {
|
||||
erpnext.utils.check_payments_app();
|
||||
}
|
||||
}
|
||||
});
|
@ -1,395 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-02-10 17:13:39.139103",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"products_per_page",
|
||||
"filter_categories_section",
|
||||
"enable_field_filters",
|
||||
"filter_fields",
|
||||
"enable_attribute_filters",
|
||||
"filter_attributes",
|
||||
"display_settings_section",
|
||||
"hide_variants",
|
||||
"enable_variants",
|
||||
"show_price",
|
||||
"column_break_9",
|
||||
"show_stock_availability",
|
||||
"show_quantity_in_website",
|
||||
"allow_items_not_in_stock",
|
||||
"column_break_13",
|
||||
"show_apply_coupon_code_in_website",
|
||||
"show_contact_us_button",
|
||||
"show_attachments",
|
||||
"section_break_18",
|
||||
"company",
|
||||
"price_list",
|
||||
"enabled",
|
||||
"store_page_docs",
|
||||
"column_break_21",
|
||||
"default_customer_group",
|
||||
"quotation_series",
|
||||
"checkout_settings_section",
|
||||
"enable_checkout",
|
||||
"show_price_in_quotation",
|
||||
"column_break_27",
|
||||
"save_quotations_as_draft",
|
||||
"payment_gateway_account",
|
||||
"payment_success_url",
|
||||
"add_ons_section",
|
||||
"enable_wishlist",
|
||||
"column_break_22",
|
||||
"enable_reviews",
|
||||
"column_break_23",
|
||||
"enable_recommendations",
|
||||
"item_search_settings_section",
|
||||
"redisearch_warning",
|
||||
"search_index_fields",
|
||||
"is_redisearch_enabled",
|
||||
"is_redisearch_loaded",
|
||||
"shop_by_category_section",
|
||||
"slideshow",
|
||||
"guest_display_settings_section",
|
||||
"hide_price_for_guest",
|
||||
"redirect_on_action"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "6",
|
||||
"fieldname": "products_per_page",
|
||||
"fieldtype": "Int",
|
||||
"label": "Products per Page"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "filter_categories_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Filters and Categories"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide_variants",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Variants"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "The field filters will also work as categories in the <b>Shop by Category</b> page.",
|
||||
"fieldname": "enable_field_filters",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Field Filters (Categories)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_attribute_filters",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Attribute Filters"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_field_filters",
|
||||
"fieldname": "filter_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Website Item Fields",
|
||||
"options": "Website Filter Field"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_attribute_filters",
|
||||
"fieldname": "filter_attributes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Attributes",
|
||||
"options": "Website Attribute"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Enable Shopping Cart"
|
||||
},
|
||||
{
|
||||
"depends_on": "doc.enabled",
|
||||
"fieldname": "store_page_docs",
|
||||
"fieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "display_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Display Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_attachments",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Public Attachments"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_price",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Price"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_stock_availability",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Stock Availability"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_variants",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Variant Selection"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_contact_us_button",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Contact Us Button"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "show_stock_availability",
|
||||
"fieldname": "show_quantity_in_website",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Stock Quantity"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_apply_coupon_code_in_website",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Apply Coupon Code"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_items_not_in_stock",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow items not in stock to be added to cart"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shopping Cart"
|
||||
},
|
||||
{
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"mandatory_depends_on": "eval: doc.enabled === 1",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "enabled",
|
||||
"description": "Prices will not be shown if Price List is not set",
|
||||
"fieldname": "price_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List",
|
||||
"mandatory_depends_on": "eval: doc.enabled === 1",
|
||||
"options": "Price List"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "default_customer_group",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Default Customer Group",
|
||||
"mandatory_depends_on": "eval: doc.enabled === 1",
|
||||
"options": "Customer Group"
|
||||
},
|
||||
{
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "quotation_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Quotation Series",
|
||||
"mandatory_depends_on": "eval: doc.enabled === 1"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.enable_checkout",
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "checkout_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Checkout Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_checkout",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Checkout"
|
||||
},
|
||||
{
|
||||
"default": "Orders",
|
||||
"depends_on": "enable_checkout",
|
||||
"description": "After payment completion redirect user to selected page.",
|
||||
"fieldname": "payment_success_url",
|
||||
"fieldtype": "Select",
|
||||
"label": "Payment Success Url",
|
||||
"mandatory_depends_on": "enable_checkout",
|
||||
"options": "\nOrders\nInvoices\nMy Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_27",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.enable_checkout == 0",
|
||||
"fieldname": "save_quotations_as_draft",
|
||||
"fieldtype": "Check",
|
||||
"label": "Save Quotations as Draft"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_checkout",
|
||||
"fieldname": "payment_gateway_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Gateway Account",
|
||||
"mandatory_depends_on": "enable_checkout",
|
||||
"options": "Payment Gateway Account"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "enable_field_filters",
|
||||
"fieldname": "shop_by_category_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shop by Category"
|
||||
},
|
||||
{
|
||||
"fieldname": "slideshow",
|
||||
"fieldtype": "Link",
|
||||
"label": "Slideshow",
|
||||
"options": "Website Slideshow"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "add_ons_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Add-ons"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_wishlist",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Wishlist"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_reviews",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Reviews and Ratings"
|
||||
},
|
||||
{
|
||||
"fieldname": "search_index_fields",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Search Index Fields",
|
||||
"mandatory_depends_on": "is_redisearch_enabled",
|
||||
"read_only_depends_on": "eval:!doc.is_redisearch_loaded"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "item_search_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Item Search Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_redisearch_loaded",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Redisearch Loaded"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_redisearch_loaded",
|
||||
"fieldname": "redisearch_warning",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Redisearch Warning",
|
||||
"options": "<p class=\"alert alert-warning\">Redisearch is not loaded. If you want to use the advanced product search feature, refer <a class=\"alert-link\" href=\"https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/articles/installing-redisearch\" target=\"_blank\">here</a>.</p>"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.show_price",
|
||||
"fieldname": "hide_price_for_guest",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Price for Guest"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "guest_display_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Guest Display Settings"
|
||||
},
|
||||
{
|
||||
"description": "Link to redirect Guest on actions that need login such as add to cart, wishlist, etc. <b>E.g.: /login</b>",
|
||||
"fieldname": "redirect_on_action",
|
||||
"fieldtype": "Data",
|
||||
"label": "Redirect on Action"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_22",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_23",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_recommendations",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Recommendations"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.enable_checkout == 0",
|
||||
"fieldname": "show_price_in_quotation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Price in Quotation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_redisearch_enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Redisearch",
|
||||
"read_only_depends_on": "eval:!doc.is_redisearch_loaded"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-01 18:35:56.106756",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "E Commerce Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import comma_and, flt, unique
|
||||
|
||||
from erpnext.e_commerce.redisearch_utils import (
|
||||
create_website_items_index,
|
||||
define_autocomplete_dictionary,
|
||||
get_indexable_web_fields,
|
||||
is_search_module_loaded,
|
||||
)
|
||||
|
||||
|
||||
class ShoppingCartSetupError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class ECommerceSettings(Document):
|
||||
def onload(self):
|
||||
self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
|
||||
|
||||
# flag >> if redisearch is installed and loaded
|
||||
self.is_redisearch_loaded = is_search_module_loaded()
|
||||
|
||||
def validate(self):
|
||||
self.validate_field_filters(self.filter_fields, self.enable_field_filters)
|
||||
self.validate_attribute_filters()
|
||||
self.validate_checkout()
|
||||
self.validate_search_index_fields()
|
||||
|
||||
if self.enabled:
|
||||
self.validate_price_list_exchange_rate()
|
||||
|
||||
frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings")
|
||||
|
||||
self.is_redisearch_enabled_pre_save = frappe.db.get_single_value(
|
||||
"E Commerce Settings", "is_redisearch_enabled"
|
||||
)
|
||||
|
||||
def after_save(self):
|
||||
self.create_redisearch_indexes()
|
||||
|
||||
def create_redisearch_indexes(self):
|
||||
# if redisearch is enabled (value changed) create indexes and dictionary
|
||||
value_changed = self.is_redisearch_enabled != self.is_redisearch_enabled_pre_save
|
||||
if self.is_redisearch_loaded and self.is_redisearch_enabled and value_changed:
|
||||
define_autocomplete_dictionary()
|
||||
create_website_items_index()
|
||||
|
||||
@staticmethod
|
||||
def validate_field_filters(filter_fields, enable_field_filters):
|
||||
if not (enable_field_filters and filter_fields):
|
||||
return
|
||||
|
||||
web_item_meta = frappe.get_meta("Website Item")
|
||||
valid_fields = [
|
||||
df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]
|
||||
]
|
||||
|
||||
for row in filter_fields:
|
||||
if row.fieldname not in valid_fields:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'"
|
||||
).format(row.idx, frappe.bold(row.fieldname))
|
||||
)
|
||||
|
||||
def validate_attribute_filters(self):
|
||||
if not (self.enable_attribute_filters and self.filter_attributes):
|
||||
return
|
||||
|
||||
# if attribute filters are enabled, hide_variants should be disabled
|
||||
self.hide_variants = 0
|
||||
|
||||
def validate_checkout(self):
|
||||
if self.enable_checkout and not self.payment_gateway_account:
|
||||
self.enable_checkout = 0
|
||||
|
||||
def validate_search_index_fields(self):
|
||||
if not self.search_index_fields:
|
||||
return
|
||||
|
||||
fields = self.search_index_fields.replace(" ", "")
|
||||
fields = unique(fields.strip(",").split(",")) # Remove extra ',' and remove duplicates
|
||||
|
||||
# All fields should be indexable
|
||||
allowed_indexable_fields = get_indexable_web_fields()
|
||||
|
||||
if not (set(fields).issubset(allowed_indexable_fields)):
|
||||
invalid_fields = list(set(fields).difference(allowed_indexable_fields))
|
||||
num_invalid_fields = len(invalid_fields)
|
||||
invalid_fields = comma_and(invalid_fields)
|
||||
|
||||
if num_invalid_fields > 1:
|
||||
frappe.throw(
|
||||
_("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields))
|
||||
)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields))
|
||||
)
|
||||
|
||||
self.search_index_fields = ",".join(fields)
|
||||
|
||||
def validate_price_list_exchange_rate(self):
|
||||
"Check if exchange rate exists for Price List currency (to Company's currency)."
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
if not self.enabled or not self.company or not self.price_list:
|
||||
return # this function is also called from hooks, check values again
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency")
|
||||
|
||||
if not company_currency:
|
||||
msg = f"Please specify currency in Company {self.company}"
|
||||
frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
|
||||
|
||||
if not price_list_currency:
|
||||
msg = f"Please specify currency in Price List {frappe.bold(self.price_list)}"
|
||||
frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
|
||||
|
||||
if price_list_currency != company_currency:
|
||||
from_currency, to_currency = price_list_currency, company_currency
|
||||
|
||||
# Get exchange rate checks Currency Exchange Records too
|
||||
exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling")
|
||||
|
||||
if not flt(exchange_rate):
|
||||
msg = f"Missing Currency Exchange Rates for {from_currency}-{to_currency}"
|
||||
frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError)
|
||||
|
||||
def validate_tax_rule(self):
|
||||
if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"):
|
||||
frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
|
||||
|
||||
def get_tax_master(self, billing_territory):
|
||||
tax_master = self.get_name_from_territory(
|
||||
billing_territory, "sales_taxes_and_charges_masters", "sales_taxes_and_charges_master"
|
||||
)
|
||||
return tax_master and tax_master[0] or None
|
||||
|
||||
def get_shipping_rules(self, shipping_territory):
|
||||
return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule")
|
||||
|
||||
def on_change(self):
|
||||
old_doc = self.get_doc_before_save()
|
||||
|
||||
if old_doc:
|
||||
old_fields = old_doc.search_index_fields
|
||||
new_fields = self.search_index_fields
|
||||
|
||||
# if search index fields get changed
|
||||
if not (new_fields == old_fields):
|
||||
create_website_items_index()
|
||||
|
||||
|
||||
def validate_cart_settings(doc=None, method=None):
|
||||
frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate")
|
||||
|
||||
|
||||
def get_shopping_cart_settings():
|
||||
return frappe.get_cached_doc("E Commerce Settings")
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def is_cart_enabled():
|
||||
return get_shopping_cart_settings().enabled
|
||||
|
||||
|
||||
def show_quantity_in_website():
|
||||
return get_shopping_cart_settings().show_quantity_in_website
|
||||
|
||||
|
||||
def check_shopping_cart_enabled():
|
||||
if not get_shopping_cart_settings().enabled:
|
||||
frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)
|
||||
|
||||
|
||||
def show_attachments():
|
||||
return get_shopping_cart_settings().show_attachments
|
@ -1,53 +0,0 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
|
||||
ShoppingCartSetupError,
|
||||
)
|
||||
|
||||
|
||||
class TestECommerceSettings(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_tax_rule_validation(self):
|
||||
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
|
||||
frappe.db.commit() # nosemgrep
|
||||
|
||||
cart_settings = frappe.get_doc("E Commerce Settings")
|
||||
cart_settings.enabled = 1
|
||||
if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"):
|
||||
self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule)
|
||||
|
||||
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1")
|
||||
|
||||
def test_invalid_filter_fields(self):
|
||||
"Check if Item fields are blocked in E Commerce Settings filter fields."
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
setup_e_commerce_settings({"enable_field_filters": 1})
|
||||
|
||||
create_custom_field(
|
||||
"Item",
|
||||
dict(owner="Administrator", fieldname="test_data", label="Test", fieldtype="Data"),
|
||||
)
|
||||
settings = frappe.get_doc("E Commerce Settings")
|
||||
settings.append("filter_fields", {"fieldname": "test_data"})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, settings.save)
|
||||
|
||||
|
||||
def setup_e_commerce_settings(values_dict):
|
||||
"Accepts a dict of values that updates E Commerce Settings."
|
||||
if not values_dict:
|
||||
return
|
||||
|
||||
doc = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
|
||||
doc.update(values_dict)
|
||||
doc.save()
|
||||
|
||||
|
||||
test_dependencies = ["Tax Rule"]
|
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Item Review', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
@ -1,134 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"beta": 1,
|
||||
"creation": "2021-03-23 16:47:26.542226",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"website_item",
|
||||
"user",
|
||||
"customer",
|
||||
"column_break_3",
|
||||
"item",
|
||||
"published_on",
|
||||
"reviews_section",
|
||||
"review_title",
|
||||
"rating",
|
||||
"comment"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "website_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Website Item",
|
||||
"options": "Website Item",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "website_item.item_code",
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reviews_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reviews"
|
||||
},
|
||||
{
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Rating",
|
||||
"in_list_view": 1,
|
||||
"label": "Rating",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "comment",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Comment",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "review_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Review Title",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer",
|
||||
"options": "Customer",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "published_on",
|
||||
"fieldtype": "Data",
|
||||
"label": "Published on",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-10 12:08:58.119691",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "Item Review",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Website Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"report": 1,
|
||||
"role": "Customer",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_name
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
|
||||
get_shopping_cart_settings,
|
||||
)
|
||||
|
||||
|
||||
class UnverifiedReviewer(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class ItemReview(Document):
|
||||
def after_insert(self):
|
||||
# regenerate cache on review creation
|
||||
reviews_dict = get_queried_reviews(self.website_item)
|
||||
set_reviews_in_cache(self.website_item, reviews_dict)
|
||||
|
||||
def after_delete(self):
|
||||
# regenerate cache on review deletion
|
||||
reviews_dict = get_queried_reviews(self.website_item)
|
||||
set_reviews_in_cache(self.website_item, reviews_dict)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_reviews(web_item, start=0, end=10, data=None):
|
||||
"Get Website Item Review Data."
|
||||
start, end = cint(start), cint(end)
|
||||
settings = get_shopping_cart_settings()
|
||||
|
||||
# Get cached reviews for first page (start=0)
|
||||
# avoid cache when page is different
|
||||
from_cache = not bool(start)
|
||||
|
||||
if not data:
|
||||
data = frappe._dict()
|
||||
|
||||
if settings and settings.get("enable_reviews"):
|
||||
reviews_cache = frappe.cache().hget("item_reviews", web_item)
|
||||
if from_cache and reviews_cache:
|
||||
data = reviews_cache
|
||||
else:
|
||||
data = get_queried_reviews(web_item, start, end, data)
|
||||
if from_cache:
|
||||
set_reviews_in_cache(web_item, data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_queried_reviews(web_item, start=0, end=10, data=None):
|
||||
"""
|
||||
Query Website Item wise reviews and cache if needed.
|
||||
Cache stores only first page of reviews i.e. 10 reviews maximum.
|
||||
Returns:
|
||||
dict: Containing reviews, average ratings, % of reviews per rating and total reviews.
|
||||
"""
|
||||
if not data:
|
||||
data = frappe._dict()
|
||||
|
||||
data.reviews = frappe.db.get_all(
|
||||
"Item Review",
|
||||
filters={"website_item": web_item},
|
||||
fields=["*"],
|
||||
limit_start=start,
|
||||
limit_page_length=end,
|
||||
)
|
||||
|
||||
rating_data = frappe.db.get_all(
|
||||
"Item Review",
|
||||
filters={"website_item": web_item},
|
||||
fields=["avg(rating) as average, count(*) as total"],
|
||||
)[0]
|
||||
|
||||
data.average_rating = flt(rating_data.average, 1)
|
||||
data.average_whole_rating = flt(data.average_rating, 0)
|
||||
|
||||
# get % of reviews per rating
|
||||
reviews_per_rating = []
|
||||
for i in range(1, 6):
|
||||
count = frappe.db.get_all(
|
||||
"Item Review", filters={"website_item": web_item, "rating": i}, fields=["count(*) as count"]
|
||||
)[0].count
|
||||
|
||||
percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0
|
||||
reviews_per_rating.append(percent)
|
||||
|
||||
data.reviews_per_rating = reviews_per_rating
|
||||
data.total_reviews = rating_data.total
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def set_reviews_in_cache(web_item, reviews_dict):
|
||||
frappe.cache().hset("item_reviews", web_item, reviews_dict)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_item_review(web_item, title, rating, comment=None):
|
||||
"""Add an Item Review by a user if non-existent."""
|
||||
if frappe.session.user == "Guest":
|
||||
# guest user should not reach here ideally in the case they do via an API, throw error
|
||||
frappe.throw(_("You are not verified to write a review yet."), exc=UnverifiedReviewer)
|
||||
|
||||
if not frappe.db.exists("Item Review", {"user": frappe.session.user, "website_item": web_item}):
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Review",
|
||||
"user": frappe.session.user,
|
||||
"customer": get_customer(),
|
||||
"website_item": web_item,
|
||||
"item": frappe.db.get_value("Website Item", web_item, "item_code"),
|
||||
"review_title": title,
|
||||
"rating": rating,
|
||||
"comment": comment,
|
||||
}
|
||||
)
|
||||
doc.published_on = datetime.today().strftime("%d %B %Y")
|
||||
doc.insert()
|
||||
|
||||
|
||||
def get_customer(silent=False):
|
||||
"""
|
||||
silent: Return customer if exists else return nothing. Dont throw error.
|
||||
"""
|
||||
user = frappe.session.user
|
||||
contact_name = get_contact_name(user)
|
||||
customer = None
|
||||
|
||||
if contact_name:
|
||||
contact = frappe.get_doc("Contact", contact_name)
|
||||
for link in contact.links:
|
||||
if link.link_doctype == "Customer":
|
||||
customer = link.link_name
|
||||
break
|
||||
|
||||
if customer:
|
||||
return frappe.db.get_value("Customer", customer)
|
||||
elif silent:
|
||||
return None
|
||||
else:
|
||||
# should not reach here unless via an API
|
||||
frappe.throw(
|
||||
_("You are not a verified customer yet. Please contact us to proceed."), exc=UnverifiedReviewer
|
||||
)
|
@ -1,84 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.core.doctype.user_permission.test_user_permission import create_user
|
||||
|
||||
from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import (
|
||||
setup_e_commerce_settings,
|
||||
)
|
||||
from erpnext.e_commerce.doctype.item_review.item_review import (
|
||||
UnverifiedReviewer,
|
||||
add_item_review,
|
||||
get_item_reviews,
|
||||
)
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||
from erpnext.e_commerce.shopping_cart.cart import get_party
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
|
||||
class TestItemReview(unittest.TestCase):
|
||||
def setUp(self):
|
||||
item = make_item("Test Mobile Phone")
|
||||
if not frappe.db.exists("Website Item", {"item_code": "Test Mobile Phone"}):
|
||||
make_website_item(item, save=True)
|
||||
|
||||
setup_e_commerce_settings({"enable_reviews": 1})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
def tearDown(self):
|
||||
frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete()
|
||||
setup_e_commerce_settings({"enable_reviews": 0})
|
||||
|
||||
def test_add_and_get_item_reviews_from_customer(self):
|
||||
"Add / Get Reviews from a User that is a valid customer (has added to cart or purchased in the past)"
|
||||
# create user
|
||||
web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"})
|
||||
test_user = create_user("test_reviewer@example.com", "Customer")
|
||||
frappe.set_user(test_user.name)
|
||||
|
||||
# create customer and contact against user
|
||||
customer = get_party()
|
||||
|
||||
# post review on "Test Mobile Phone"
|
||||
try:
|
||||
add_item_review(web_item, "Great Product", 3, "Would recommend this product")
|
||||
review_name = frappe.db.get_value("Item Review", {"website_item": web_item})
|
||||
except Exception:
|
||||
self.fail(f"Error while publishing review for {web_item}")
|
||||
|
||||
review_data = get_item_reviews(web_item, 0, 10)
|
||||
|
||||
self.assertEqual(len(review_data.reviews), 1)
|
||||
self.assertEqual(review_data.average_rating, 3)
|
||||
self.assertEqual(review_data.reviews_per_rating[2], 100)
|
||||
|
||||
# tear down
|
||||
frappe.set_user("Administrator")
|
||||
frappe.delete_doc("Item Review", review_name)
|
||||
customer.delete()
|
||||
|
||||
def test_add_item_review_from_non_customer(self):
|
||||
"Check if logged in user (who is not a customer yet) is blocked from posting reviews."
|
||||
web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"})
|
||||
test_user = create_user("test_reviewer@example.com", "Customer")
|
||||
frappe.set_user(test_user.name)
|
||||
|
||||
with self.assertRaises(UnverifiedReviewer):
|
||||
add_item_review(web_item, "Great Product", 3, "Would recommend this product")
|
||||
|
||||
# tear down
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_add_item_reviews_from_guest_user(self):
|
||||
"Check if Guest user is blocked from posting reviews."
|
||||
web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"})
|
||||
frappe.set_user("Guest")
|
||||
|
||||
with self.assertRaises(UnverifiedReviewer):
|
||||
add_item_review(web_item, "Great Product", 3, "Would recommend this product")
|
||||
|
||||
# tear down
|
||||
frappe.set_user("Administrator")
|
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