mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-16 19:58:49 +00:00
Compare commits
5 Commits
mariadb_li
...
gle_indexi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
401f276334 | ||
|
|
f25524ff95 | ||
|
|
34896f1cd1 | ||
|
|
4cbc92649f | ||
|
|
1b85fbf0e5 |
@@ -42,6 +42,3 @@ a308792ee7fda18a681e9181f4fd00b36385bc23
|
||||
# noisy typing refactoring of get_item_details
|
||||
7b7211ac79c248a79ba8a999ff34e734d874c0ae
|
||||
d827ed21adc7b36047e247cbb0dc6388d048a7f9
|
||||
|
||||
# `frappe.flags.in_test` => `frappe.in_test`
|
||||
7a482a69985c952de0e8193c9d4e086aee65ee6d
|
||||
|
||||
6
.github/helper/install.sh
vendored
6
.github/helper/install.sh
vendored
@@ -4,14 +4,10 @@ set -e
|
||||
|
||||
cd ~ || exit
|
||||
|
||||
|
||||
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash
|
||||
sudo apt update
|
||||
sudo apt remove mysql-server mysql-client
|
||||
sudo apt install libcups2-dev redis-server mariadb-client libmariadb-dev
|
||||
|
||||
pip cache remove mysqlclient
|
||||
|
||||
pip install frappe-bench
|
||||
|
||||
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
|
||||
@@ -70,7 +66,7 @@ sed -i 's/schedule:/# schedule:/g' Procfile
|
||||
sed -i 's/socketio:/# socketio:/g' Procfile
|
||||
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
||||
|
||||
bench get-app payments --branch develop
|
||||
bench get-app payments --branch ${githubbranch%"-hotfix"}
|
||||
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
||||
|
||||
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
|
||||
|
||||
3
.github/workflows/backport.yml
vendored
3
.github/workflows/backport.yml
vendored
@@ -5,9 +5,6 @@ on:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/docker-release.yml
vendored
4
.github/workflows/docker-release.yml
vendored
@@ -2,10 +2,6 @@ name: Trigger Docker build on release
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
curl:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
3
.github/workflows/docs-checker.yml
vendored
3
.github/workflows/docs-checker.yml
vendored
@@ -3,9 +3,6 @@ on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened, edited ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/initiate_release.yml
vendored
4
.github/workflows/initiate_release.yml
vendored
@@ -2,10 +2,6 @@
|
||||
# To add/remove versions just modify the matrix.
|
||||
|
||||
name: Create weekly release pull requests
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 9:30 UTC => 3 PM IST Tuesday
|
||||
|
||||
4
.github/workflows/labeller.yml
vendored
4
.github/workflows/labeller.yml
vendored
@@ -3,10 +3,6 @@ on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
3
.github/workflows/linters.yml
vendored
3
.github/workflows/linters.yml
vendored
@@ -3,9 +3,6 @@ name: Linters
|
||||
on:
|
||||
pull_request: { }
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
linters:
|
||||
|
||||
5
.github/workflows/patch.yml
vendored
5
.github/workflows/patch.yml
vendored
@@ -10,9 +10,6 @@ on:
|
||||
- '**.csv'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: patch-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
|
||||
cancel-in-progress: true
|
||||
@@ -39,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
python -m compileall -fq "${GITHUB_WORKSPACE}"
|
||||
python -m compileall -f "${GITHUB_WORKSPACE}"
|
||||
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||
then echo "Found merge conflicts"
|
||||
exit 1
|
||||
|
||||
3
.github/workflows/patch_faux.yml
vendored
3
.github/workflows/patch_faux.yml
vendored
@@ -11,9 +11,6 @@ on:
|
||||
- "**.html"
|
||||
- "**.csv"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -3,10 +3,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- version-13
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
|
||||
3
.github/workflows/run-indinvidual-tests.yml
vendored
3
.github/workflows/run-indinvidual-tests.yml
vendored
@@ -7,9 +7,6 @@ concurrency:
|
||||
group: server-individual-tests-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
discover:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -10,9 +10,6 @@ on:
|
||||
- "**.md"
|
||||
- "**.html"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
5
.github/workflows/server-tests-mariadb.yml
vendored
5
.github/workflows/server-tests-mariadb.yml
vendored
@@ -25,9 +25,6 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: server-mariadb-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
|
||||
cancel-in-progress: true
|
||||
@@ -68,7 +65,7 @@ jobs:
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
python -m compileall -fq "${GITHUB_WORKSPACE}"
|
||||
python -m compileall -f "${GITHUB_WORKSPACE}"
|
||||
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||
then echo "Found merge conflicts"
|
||||
exit 1
|
||||
|
||||
5
.github/workflows/server-tests-postgres.yml
vendored
5
.github/workflows/server-tests-postgres.yml
vendored
@@ -12,9 +12,6 @@ concurrency:
|
||||
group: server-postgres-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'postgres') }}
|
||||
@@ -53,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
python -m compileall -fq "${GITHUB_WORKSPACE}"
|
||||
python -m compileall -f "${GITHUB_WORKSPACE}"
|
||||
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||
then echo "Found merge conflicts"
|
||||
exit 1
|
||||
|
||||
@@ -57,7 +57,7 @@ def get_company_currency(company):
|
||||
|
||||
def set_perpetual_inventory(enable=1, company=None):
|
||||
if not company:
|
||||
company = "_Test Company" if frappe.in_test else get_default_company()
|
||||
company = "_Test Company" if frappe.flags.in_test else get_default_company()
|
||||
|
||||
company = frappe.get_doc("Company", company)
|
||||
company.enable_perpetual_inventory = enable
|
||||
@@ -77,7 +77,7 @@ def encode_company_abbr(name, company=None, abbr=None):
|
||||
|
||||
def is_perpetual_inventory_enabled(company):
|
||||
if not company:
|
||||
company = "_Test Company" if frappe.in_test else get_default_company()
|
||||
company = "_Test Company" if frappe.flags.in_test else get_default_company()
|
||||
|
||||
if not hasattr(frappe.local, "enable_perpetual_inventory"):
|
||||
frappe.local.enable_perpetual_inventory = {}
|
||||
|
||||
@@ -317,7 +317,7 @@ def get_already_booked_amount(doc, item):
|
||||
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
||||
|
||||
accounts_frozen_upto = frappe.get_single_value("Accounts Settings", "acc_frozen_upto")
|
||||
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
|
||||
|
||||
def _book_deferred_revenue_or_expense(
|
||||
item,
|
||||
@@ -526,7 +526,7 @@ def make_gl_entries(
|
||||
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
|
||||
frappe.db.commit()
|
||||
except Exception as e:
|
||||
if frappe.in_test:
|
||||
if frappe.flags.in_test:
|
||||
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
|
||||
raise e
|
||||
else:
|
||||
|
||||
@@ -92,7 +92,7 @@ class Account(NestedSet):
|
||||
super().on_update()
|
||||
|
||||
def onload(self):
|
||||
frozen_accounts_modifier = frappe.get_single_value("Accounts Settings", "frozen_accounts_modifier")
|
||||
frozen_accounts_modifier = frappe.db.get_single_value("Accounts Settings", "frozen_accounts_modifier")
|
||||
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
|
||||
self.set_onload("can_freeze_account", True)
|
||||
|
||||
@@ -602,7 +602,7 @@ def _ensure_idle_system():
|
||||
# 1. Correctness: It's next to impossible to ensure that renamed account is not being used *right now*.
|
||||
# 2. Performance: Renaming requires locking out many tables entirely and severely degrades performance.
|
||||
|
||||
if frappe.in_test:
|
||||
if frappe.flags.in_test:
|
||||
return
|
||||
|
||||
last_gl_update = None
|
||||
|
||||
@@ -139,11 +139,6 @@ frappe.treeview_settings["Account"] = {
|
||||
description: __(
|
||||
"Further accounts can be made under Groups, but entries can be made against non-Groups"
|
||||
),
|
||||
onchange: function () {
|
||||
if (!this.value) {
|
||||
this.layout.set_value("root_type", "");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldtype: "Select",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -96,20 +96,8 @@
|
||||
"account_number": "1132.000"
|
||||
},
|
||||
"account_number": "1130.000"
|
||||
},
|
||||
"Pajak Dibayar di Muka": {
|
||||
"PPN Masukan": {
|
||||
"account_number": "1151.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"PPh 23 Dibayar di Muka": {
|
||||
"account_number": "1152.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"account_number": "1150.000"
|
||||
},
|
||||
},
|
||||
"account_number": "1100.000"
|
||||
|
||||
},
|
||||
"Aktiva Tetap": {
|
||||
"Aktiva": {
|
||||
@@ -569,10 +557,6 @@
|
||||
"Hutang Pajak": {
|
||||
"account_number": "2141.000",
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"PPN Keluaran": {
|
||||
"account_number": "2142.000",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"account_number": "2140.000"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
class UnitTestAccountClosingBalance(UnitTestCase):
|
||||
"""
|
||||
Unit tests for AccountClosingBalance.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestAccountClosingBalance(IntegrationTestCase):
|
||||
|
||||
@@ -83,7 +83,7 @@ class AccountingDimension(Document):
|
||||
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
|
||||
|
||||
def after_insert(self):
|
||||
if frappe.in_test:
|
||||
if frappe.flags.in_test:
|
||||
make_dimension_in_accounting_doctypes(doc=self)
|
||||
else:
|
||||
frappe.enqueue(
|
||||
@@ -91,7 +91,7 @@ class AccountingDimension(Document):
|
||||
)
|
||||
|
||||
def on_trash(self):
|
||||
if frappe.in_test:
|
||||
if frappe.flags.in_test:
|
||||
delete_accounting_dimension(doc=self)
|
||||
else:
|
||||
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long", enqueue_after_commit=True)
|
||||
@@ -213,7 +213,7 @@ def delete_accounting_dimension(doc):
|
||||
|
||||
@frappe.whitelist()
|
||||
def disable_dimension(doc):
|
||||
if frappe.in_test:
|
||||
if frappe.flags.in_test:
|
||||
toggle_disabling(doc=doc)
|
||||
else:
|
||||
frappe.enqueue(toggle_disabling, doc=doc)
|
||||
|
||||
@@ -58,10 +58,6 @@ class TestAccountingDimension(IntegrationTestCase):
|
||||
self.assertEqual(gle1.get("department"), "_Test Department - _TC")
|
||||
|
||||
def test_mandatory(self):
|
||||
location = frappe.get_doc("Accounting Dimension", "Location")
|
||||
location.dimension_defaults[0].mandatory_for_bs = True
|
||||
location.save()
|
||||
|
||||
si = create_sales_invoice(do_not_save=1)
|
||||
si.append(
|
||||
"items",
|
||||
@@ -125,6 +121,7 @@ def create_dimension():
|
||||
"company": "_Test Company",
|
||||
"reference_document": "Location",
|
||||
"default_dimension": "Block 1",
|
||||
"mandatory_for_bs": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -22,21 +22,4 @@ frappe.ui.form.on("Accounts Settings", {
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
add_taxes_from_taxes_and_charges_template(frm) {
|
||||
toggle_tax_settings(frm, "add_taxes_from_taxes_and_charges_template");
|
||||
},
|
||||
add_taxes_from_item_tax_template(frm) {
|
||||
toggle_tax_settings(frm, "add_taxes_from_item_tax_template");
|
||||
},
|
||||
});
|
||||
|
||||
function toggle_tax_settings(frm, field_name) {
|
||||
if (frm.doc[field_name]) {
|
||||
const other_field =
|
||||
field_name === "add_taxes_from_item_tax_template"
|
||||
? "add_taxes_from_taxes_and_charges_template"
|
||||
: "add_taxes_from_item_tax_template";
|
||||
frm.set_value(other_field, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"column_break_17",
|
||||
"enable_common_party_accounting",
|
||||
"allow_multi_currency_invoices_against_single_party_account",
|
||||
"confirm_before_resetting_posting_date",
|
||||
"journals_section",
|
||||
"merge_similar_account_heads",
|
||||
"deferred_accounting_settings_section",
|
||||
@@ -32,7 +31,6 @@
|
||||
"determine_address_tax_category_from",
|
||||
"column_break_19",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"add_taxes_from_taxes_and_charges_template",
|
||||
"book_tax_discount_loss",
|
||||
"round_row_wise_tax",
|
||||
"print_settings",
|
||||
@@ -40,14 +38,8 @@
|
||||
"show_taxes_as_table_in_print",
|
||||
"column_break_12",
|
||||
"show_payment_schedule_in_print",
|
||||
"item_price_settings_section",
|
||||
"maintain_same_internal_transaction_rate",
|
||||
"column_break_feyo",
|
||||
"maintain_same_rate_action",
|
||||
"role_to_override_stop_action",
|
||||
"currency_exchange_section",
|
||||
"allow_stale",
|
||||
"allow_pegged_currencies_exchange_rates",
|
||||
"column_break_yuug",
|
||||
"stale_days",
|
||||
"section_break_jpd0",
|
||||
@@ -66,7 +58,6 @@
|
||||
"pos_tab",
|
||||
"pos_setting_section",
|
||||
"post_change_gl_entries",
|
||||
"column_break_xrnd",
|
||||
"assets_tab",
|
||||
"asset_settings_section",
|
||||
"calculate_depr_using_total_days",
|
||||
@@ -86,16 +77,11 @@
|
||||
"reports_tab",
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
"ignore_is_opening_check_for_reporting",
|
||||
"column_break_lvjk",
|
||||
"receivable_payable_remarks_length",
|
||||
"accounts_receivable_payable_tuning_section",
|
||||
"receivable_payable_fetch_method",
|
||||
"legacy_section",
|
||||
"ignore_is_opening_check_for_reporting",
|
||||
"payment_request_settings",
|
||||
"create_pr_in_draft_status",
|
||||
"budget_settings",
|
||||
"use_new_budget_controller"
|
||||
"create_pr_in_draft_status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -546,100 +532,14 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Posting Date Inheritance for Exchange Gain / Loss",
|
||||
"options": "Invoice\nPayment\nReconciliation Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_xrnd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Buffered Cursor",
|
||||
"fieldname": "receivable_payable_fetch_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Data Fetch Method",
|
||||
"options": "Buffered Cursor\nUnBuffered Cursor"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts_receivable_payable_tuning_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounts Receivable / Payable Tuning"
|
||||
},
|
||||
{
|
||||
"fieldname": "legacy_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Legacy Fields"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "maintain_same_internal_transaction_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Maintain Same Rate Throughout Internal Transaction"
|
||||
},
|
||||
{
|
||||
"default": "Stop",
|
||||
"depends_on": "maintain_same_internal_transaction_rate",
|
||||
"fieldname": "maintain_same_rate_action",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action if Same Rate is Not Maintained Throughout Internal Transaction",
|
||||
"mandatory_depends_on": "maintain_same_internal_transaction_rate",
|
||||
"options": "Stop\nWarn"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.maintain_same_internal_transaction_rate && doc.maintain_same_rate_action == 'Stop'",
|
||||
"fieldname": "role_to_override_stop_action",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Override Stop Action",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_settings",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Budget"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "use_new_budget_controller",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use New Budget Controller"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, user will be alerted before resetting posting date to current date in relevant transactions",
|
||||
"fieldname": "confirm_before_resetting_posting_date",
|
||||
"fieldtype": "Check",
|
||||
"label": "Confirm before resetting posting date"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_price_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Item Price Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_feyo",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "System will do an implicit conversion using the pegged currency. <br>\nEx: Instead of AED -> INR, system will do AED -> USD -> INR using the pegged exchange rate of AED against USD.",
|
||||
"documentation_url": "/app/pegged-currencies/Pegged Currencies",
|
||||
"fieldname": "allow_pegged_currencies_exchange_rates",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Implicit Pegged Currency Conversion"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If no taxes are set, and Taxes and Charges Template is selected, the system will automatically apply the taxes from the chosen template.",
|
||||
"fieldname": "add_taxes_from_taxes_and_charges_template",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically Add Taxes from Taxes and Charges Template"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-23 15:55:33.346398",
|
||||
"modified": "2025-01-23 13:15:44.077853",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
@@ -664,9 +564,8 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,7 @@ class AccountsSettings(Document):
|
||||
|
||||
acc_frozen_upto: DF.Date | None
|
||||
add_taxes_from_item_tax_template: DF.Check
|
||||
add_taxes_from_taxes_and_charges_template: DF.Check
|
||||
allow_multi_currency_invoices_against_single_party_account: DF.Check
|
||||
allow_pegged_currencies_exchange_rates: DF.Check
|
||||
allow_stale: DF.Check
|
||||
auto_reconcile_payments: DF.Check
|
||||
auto_reconciliation_job_trigger: DF.Int
|
||||
@@ -39,7 +37,6 @@ class AccountsSettings(Document):
|
||||
book_tax_discount_loss: DF.Check
|
||||
calculate_depr_using_total_days: DF.Check
|
||||
check_supplier_invoice_uniqueness: DF.Check
|
||||
confirm_before_resetting_posting_date: DF.Check
|
||||
create_pr_in_draft_status: DF.Check
|
||||
credit_controller: DF.Link | None
|
||||
delete_linked_ledger_entries: DF.Check
|
||||
@@ -53,17 +50,13 @@ class AccountsSettings(Document):
|
||||
general_ledger_remarks_length: DF.Int
|
||||
ignore_account_closing_balance: DF.Check
|
||||
ignore_is_opening_check_for_reporting: DF.Check
|
||||
maintain_same_internal_transaction_rate: DF.Check
|
||||
maintain_same_rate_action: DF.Literal["Stop", "Warn"]
|
||||
make_payment_via_journal_entry: DF.Check
|
||||
merge_similar_account_heads: DF.Check
|
||||
over_billing_allowance: DF.Currency
|
||||
post_change_gl_entries: DF.Check
|
||||
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
|
||||
receivable_payable_remarks_length: DF.Int
|
||||
reconciliation_queue_size: DF.Int
|
||||
role_allowed_to_over_bill: DF.Link | None
|
||||
role_to_override_stop_action: DF.Link | None
|
||||
round_row_wise_tax: DF.Check
|
||||
show_balance_in_coa: DF.Check
|
||||
show_inclusive_tax_in_print: DF.Check
|
||||
@@ -73,11 +66,9 @@ class AccountsSettings(Document):
|
||||
submit_journal_entries: DF.Check
|
||||
unlink_advance_payment_on_cancelation_of_order: DF.Check
|
||||
unlink_payment_on_cancellation_of_invoice: DF.Check
|
||||
use_new_budget_controller: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_auto_tax_settings()
|
||||
old_doc = self.get_doc_before_save()
|
||||
clear_cache = False
|
||||
|
||||
@@ -144,13 +135,3 @@ class AccountsSettings(Document):
|
||||
if self.has_value_changed("reconciliation_queue_size"):
|
||||
if cint(self.reconciliation_queue_size) < 5 or cint(self.reconciliation_queue_size) > 100:
|
||||
frappe.throw(_("Queue Size should be between 5 and 100"))
|
||||
|
||||
def validate_auto_tax_settings(self):
|
||||
if self.add_taxes_from_item_tax_template and self.add_taxes_from_taxes_and_charges_template:
|
||||
frappe.throw(
|
||||
_("You cannot enable both the settings '{0}' and '{1}'.").format(
|
||||
frappe.bold(self.meta.get_label("add_taxes_from_item_tax_template")),
|
||||
frappe.bold(self.meta.get_label("add_taxes_from_taxes_and_charges_template")),
|
||||
),
|
||||
title=_("Auto Tax Settings Error"),
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import nowdate, today
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
@@ -7,9 +7,6 @@ from frappe.tests import IntegrationTestCase
|
||||
from frappe.utils import add_months, getdate
|
||||
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
|
||||
set_default_account_for_mode_of_payment,
|
||||
)
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
@@ -146,7 +143,7 @@ def make_payment_entry():
|
||||
|
||||
supplier = create_supplier(supplier_name="_Test Supplier")
|
||||
pi = make_purchase_invoice(
|
||||
supplier=supplier.name,
|
||||
supplier=supplier,
|
||||
supplier_warehouse="_Test Warehouse - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
uom="Nos",
|
||||
@@ -175,13 +172,11 @@ def make_pos_sales_invoice():
|
||||
|
||||
customer = make_customer(customer="_Test Customer")
|
||||
|
||||
mode_of_payment = frappe.get_doc("Mode of Payment", "Wire Transfer")
|
||||
|
||||
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank Clearance - _TC")
|
||||
|
||||
si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1)
|
||||
si.set("payments", [])
|
||||
si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 1000})
|
||||
si.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank Clearance - _TC", "amount": 1000}
|
||||
)
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestBankReconciliationTool(AccountsTestMixin, IntegrationTestCase):
|
||||
{
|
||||
"doctype": "Bank Account",
|
||||
"account_name": "HDFC _current_",
|
||||
"bank": bank.name,
|
||||
"bank": bank,
|
||||
"is_company_account": True,
|
||||
"account": self.bank, # account from Chart of Accounts
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
|
||||
frm.get_field("import_file").df.options = {
|
||||
restrictions: {
|
||||
allowed_file_types: [".csv", ".xls", ".xlsx", ".TXT", ".txt"],
|
||||
allowed_file_types: [".csv", ".xls", ".xlsx"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -81,7 +81,6 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
|
||||
refresh(frm) {
|
||||
frm.page.hide_icon_group();
|
||||
frm.trigger("toggle_mt940_note");
|
||||
frm.trigger("update_indicators");
|
||||
frm.trigger("import_file");
|
||||
frm.trigger("show_import_log");
|
||||
@@ -193,24 +192,6 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
});
|
||||
},
|
||||
|
||||
import_mt940_fromat(frm) {
|
||||
frm.trigger("toggle_mt940_note");
|
||||
frm.save();
|
||||
},
|
||||
|
||||
toggle_mt940_note(frm) {
|
||||
if (!frm.doc.import_mt940_fromat) {
|
||||
frm.set_df_property("custom_delimiters", "hidden", 0);
|
||||
frm.set_df_property("google_sheets_url", "hidden", 0);
|
||||
frm.set_df_property("html_5", "hidden", 0);
|
||||
} else {
|
||||
frm.set_df_property("custom_delimiters", "hidden", 1);
|
||||
frm.set_df_property("google_sheets_url", "hidden", 1);
|
||||
frm.set_df_property("html_5", "hidden", 1);
|
||||
}
|
||||
frm.set_value("import_mt940_fromat", frm.doc.import_mt940_fromat);
|
||||
},
|
||||
|
||||
show_report_error_button(frm) {
|
||||
if (frm.doc.status === "Error") {
|
||||
frappe.db
|
||||
@@ -309,45 +290,23 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
.html(__("Loading import file..."))
|
||||
.appendTo(frm.get_field("import_preview").$wrapper);
|
||||
|
||||
frappe.run_serially([
|
||||
// Convert MT940 to CSV if .txt file
|
||||
() => {
|
||||
if (frm.doc.import_file && frm.doc.import_file.toLowerCase().endsWith(".txt")) {
|
||||
return frm
|
||||
.call({
|
||||
method: "convert_mt940_to_csv",
|
||||
args: {
|
||||
data_import: frm.doc.name,
|
||||
mt940_file_path: frm.doc.import_file,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
const file_url = r.message;
|
||||
frm.set_value("import_file", file_url);
|
||||
frm.save();
|
||||
});
|
||||
}
|
||||
frm.call({
|
||||
method: "get_preview_from_template",
|
||||
args: {
|
||||
data_import: frm.doc.name,
|
||||
import_file: frm.doc.import_file,
|
||||
google_sheets_url: frm.doc.google_sheets_url,
|
||||
},
|
||||
() => {
|
||||
frm.call({
|
||||
method: "get_preview_from_template",
|
||||
args: {
|
||||
data_import: frm.doc.name,
|
||||
import_file: frm.doc.import_file,
|
||||
google_sheets_url: frm.doc.google_sheets_url,
|
||||
},
|
||||
error_handlers: {
|
||||
TimestampMismatchError() {
|
||||
// ignore this error
|
||||
},
|
||||
},
|
||||
}).then((r) => {
|
||||
let preview_data = r.message;
|
||||
frm.events.show_import_preview(frm, preview_data);
|
||||
frm.events.show_import_warnings(frm, preview_data);
|
||||
});
|
||||
error_handlers: {
|
||||
TimestampMismatchError() {
|
||||
// ignore this error
|
||||
},
|
||||
},
|
||||
]);
|
||||
}).then((r) => {
|
||||
let preview_data = r.message;
|
||||
frm.events.show_import_preview(frm, preview_data);
|
||||
frm.events.show_import_warnings(frm, preview_data);
|
||||
});
|
||||
},
|
||||
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"bank_account",
|
||||
"bank",
|
||||
"column_break_4",
|
||||
"import_mt940_fromat",
|
||||
"custom_delimiters",
|
||||
"delimiter_options",
|
||||
"google_sheets_url",
|
||||
@@ -21,7 +20,6 @@
|
||||
"download_template",
|
||||
"status",
|
||||
"template_options",
|
||||
"use_csv_sniffer",
|
||||
"import_warnings_section",
|
||||
"template_warnings",
|
||||
"import_warnings",
|
||||
@@ -209,28 +207,14 @@
|
||||
"fieldname": "delimiter_options",
|
||||
"fieldtype": "Data",
|
||||
"label": "Delimiter options"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_csv_sniffer",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Use CSV Sniffer"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "import_mt940_fromat",
|
||||
"fieldtype": "Check",
|
||||
"label": "Import MT940 Fromat"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-11 02:23:22.159961",
|
||||
"modified": "2024-06-25 17:32:07.658250",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Statement Import",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -246,9 +230,8 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,15 @@
|
||||
|
||||
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
from datetime import date, datetime
|
||||
|
||||
import frappe
|
||||
import mt940
|
||||
import openpyxl
|
||||
from frappe import _
|
||||
from frappe.core.doctype.data_import.data_import import DataImport
|
||||
from frappe.core.doctype.data_import.importer import Importer, ImportFile
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.file_manager import get_file, save_file
|
||||
from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
@@ -39,7 +35,6 @@ class BankStatementImport(DataImport):
|
||||
delimiter_options: DF.Data | None
|
||||
google_sheets_url: DF.Data | None
|
||||
import_file: DF.Attach | None
|
||||
import_mt940_fromat: DF.Check
|
||||
import_type: DF.Literal["", "Insert New Records", "Update Existing Records"]
|
||||
mute_emails: DF.Check
|
||||
reference_doctype: DF.Link
|
||||
@@ -48,7 +43,6 @@ class BankStatementImport(DataImport):
|
||||
submit_after_import: DF.Check
|
||||
template_options: DF.Code | None
|
||||
template_warnings: DF.Code | None
|
||||
use_csv_sniffer: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -71,9 +65,8 @@ class BankStatementImport(DataImport):
|
||||
|
||||
self.template_warnings = ""
|
||||
|
||||
if self.import_file and not self.import_file.lower().endswith(".txt"):
|
||||
self.validate_import_file()
|
||||
self.validate_google_sheets_url()
|
||||
self.validate_import_file()
|
||||
self.validate_google_sheets_url()
|
||||
|
||||
def start_import(self):
|
||||
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
|
||||
@@ -86,7 +79,7 @@ class BankStatementImport(DataImport):
|
||||
from frappe.utils.background_jobs import is_job_enqueued
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
run_now = frappe.in_test or frappe.conf.developer_mode
|
||||
run_now = frappe.flags.in_test or frappe.conf.developer_mode
|
||||
if is_scheduler_inactive() and not run_now:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||
|
||||
@@ -111,68 +104,6 @@ class BankStatementImport(DataImport):
|
||||
return None
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_mt940_to_csv(data_import, mt940_file_path):
|
||||
doc = frappe.get_doc("Bank Statement Import", data_import)
|
||||
|
||||
file_doc, content = get_file(mt940_file_path)
|
||||
|
||||
if not is_mt940_format(content):
|
||||
frappe.throw(_("The uploaded file does not appear to be in valid MT940 format."))
|
||||
|
||||
if is_mt940_format(content) and not doc.import_mt940_fromat:
|
||||
frappe.throw(_("MT940 file detected. Please enable 'Import MT940 Format' to proceed."))
|
||||
|
||||
try:
|
||||
transactions = mt940.parse(content)
|
||||
except Exception as e:
|
||||
frappe.throw(_("Failed to parse MT940 format. Error: {0}").format(str(e)))
|
||||
|
||||
if not transactions:
|
||||
frappe.throw(_("Parsed file is not in valid MT940 format or contains no transactions."))
|
||||
|
||||
# Use in-memory file buffer instead of writing to temp file
|
||||
csv_buffer = io.StringIO()
|
||||
writer = csv.writer(csv_buffer)
|
||||
|
||||
headers = ["Date", "Deposit", "Withdrawal", "Description", "Reference Number", "Bank Account", "Currency"]
|
||||
writer.writerow(headers)
|
||||
|
||||
for txn in transactions:
|
||||
txn_date = getattr(txn, "date", None)
|
||||
raw_date = txn.data.get("date", "")
|
||||
|
||||
if txn_date:
|
||||
date_str = txn_date.strftime("%Y-%m-%d")
|
||||
elif isinstance(raw_date, date | datetime):
|
||||
date_str = raw_date.strftime("%Y-%m-%d")
|
||||
else:
|
||||
date_str = str(raw_date)
|
||||
|
||||
raw_amount = str(txn.data.get("amount", ""))
|
||||
parts = raw_amount.strip().split()
|
||||
amount_value = float(parts[0]) if parts else 0.0
|
||||
|
||||
deposit = amount_value if amount_value > 0 else ""
|
||||
withdrawal = abs(amount_value) if amount_value < 0 else ""
|
||||
description = txn.data.get("extra_details") or ""
|
||||
reference = txn.data.get("transaction_reference") or ""
|
||||
currency = txn.data.get("currency", "")
|
||||
|
||||
writer.writerow([date_str, deposit, withdrawal, description, reference, doc.bank_account, currency])
|
||||
|
||||
# Prepare in-memory CSV for upload
|
||||
csv_content = csv_buffer.getvalue().encode("utf-8")
|
||||
csv_buffer.close()
|
||||
|
||||
filename = f"{frappe.utils.now_datetime().strftime('%Y%m%d%H%M%S')}_converted_mt940.csv"
|
||||
|
||||
# Save to File Manager
|
||||
saved_file = save_file(filename, csv_content, doc.doctype, doc.name, is_private=True, df="import_file")
|
||||
|
||||
return saved_file.file_url
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
|
||||
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
|
||||
@@ -197,12 +128,6 @@ def download_import_log(data_import_name):
|
||||
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
|
||||
|
||||
|
||||
def is_mt940_format(content: str) -> bool:
|
||||
"""Check if the content has key MT940 tags"""
|
||||
required_tags = [":20:", ":25:", ":28C:", ":61:"]
|
||||
return all(tag in content for tag in required_tags)
|
||||
|
||||
|
||||
def parse_data_from_template(raw_data):
|
||||
data = []
|
||||
|
||||
@@ -354,7 +279,7 @@ def get_import_status(docname):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_import_logs(docname: str):
|
||||
frappe.has_permission("Bank Statement Import", throw=True)
|
||||
frappe.has_permission("Bank Statement Import")
|
||||
|
||||
return frappe.get_all(
|
||||
"Data Import Log",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import frappe
|
||||
from frappe.utils import flt
|
||||
from rapidfuzz import fuzz, process
|
||||
from rapidfuzz.utils import default_process
|
||||
|
||||
|
||||
class AutoMatchParty:
|
||||
@@ -26,7 +25,7 @@ class AutoMatchParty:
|
||||
deposit=self.deposit,
|
||||
).match()
|
||||
|
||||
fuzzy_matching_enabled = frappe.get_single_value("Accounts Settings", "enable_fuzzy_matching")
|
||||
fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching")
|
||||
if not result and fuzzy_matching_enabled:
|
||||
result = AutoMatchbyPartyNameDescription(
|
||||
bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit
|
||||
@@ -133,7 +132,6 @@ class AutoMatchbyPartyNameDescription:
|
||||
query=self.get(field),
|
||||
choices={row.get("name"): row.get("party_name") for row in names},
|
||||
scorer=fuzz.token_set_ratio,
|
||||
processor=default_process,
|
||||
)
|
||||
party_name, skip = self.process_fuzzy_result(result)
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"default": "ACC-BTN-.YYYY.-",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"options": "ACC-BTN-.YYYY.-",
|
||||
@@ -235,10 +236,9 @@
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-18 17:24:57.044666",
|
||||
"modified": "2023-11-18 18:32:47.203694",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Transaction",
|
||||
@@ -287,10 +287,9 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "date",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "bank_account",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class BankTransaction(Document):
|
||||
self.allocate_payment_entries()
|
||||
self.set_status()
|
||||
|
||||
if frappe.get_single_value("Accounts Settings", "enable_party_matching"):
|
||||
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
|
||||
self.auto_set_party()
|
||||
|
||||
def before_update_after_submit(self):
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||
|
||||
|
||||
class UnitTestBankTransaction(UnitTestCase):
|
||||
"""
|
||||
Unit tests for BankTransaction.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestAutoMatchParty(IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
@@ -6,15 +6,12 @@ import json
|
||||
import frappe
|
||||
from frappe import utils
|
||||
from frappe.model.docstatus import DocStatus
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||
get_linked_payments,
|
||||
reconcile_vouchers,
|
||||
)
|
||||
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
|
||||
set_default_account_for_mode_of_payment,
|
||||
)
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
@@ -24,6 +21,15 @@ from erpnext.tests.utils import if_lending_app_installed
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Cost Center"]
|
||||
|
||||
|
||||
class UnitTestBankTransaction(UnitTestCase):
|
||||
"""
|
||||
Unit tests for BankTransaction.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestBankTransaction(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
make_pos_profile()
|
||||
@@ -424,13 +430,15 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Wire Transfer"})
|
||||
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
|
||||
|
||||
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", gl_account)
|
||||
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
|
||||
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
|
||||
mode_of_payment.save()
|
||||
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
|
||||
si.is_pos = 1
|
||||
si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 109080})
|
||||
si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
class UnitTestBisectAccountingStatements(UnitTestCase):
|
||||
"""
|
||||
Unit tests for BisectAccountingStatements.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestBisectAccountingStatements(IntegrationTestCase):
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
class UnitTestBisectNodes(UnitTestCase):
|
||||
"""
|
||||
Unit tests for BisectNodes.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestBisectNodes(IntegrationTestCase):
|
||||
|
||||
@@ -23,11 +23,6 @@ frappe.ui.form.on("Budget", {
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
frappe.db.get_single_value("Accounts Settings", "use_new_budget_controller").then((value) => {
|
||||
if (!value) {
|
||||
frm.get_field("control_action_for_cumulative_expense_section").hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"budget_against",
|
||||
"company",
|
||||
"cost_center",
|
||||
"naming_series",
|
||||
"project",
|
||||
"fiscal_year",
|
||||
"column_break_3",
|
||||
@@ -28,10 +28,6 @@
|
||||
"applicable_on_booking_actual_expenses",
|
||||
"action_if_annual_budget_exceeded",
|
||||
"action_if_accumulated_monthly_budget_exceeded",
|
||||
"control_action_for_cumulative_expense_section",
|
||||
"applicable_on_cumulative_expense",
|
||||
"action_if_annual_exceeded_on_cumulative_expense",
|
||||
"action_if_accumulated_monthly_exceeded_on_cumulative_expense",
|
||||
"section_break_21",
|
||||
"accounts"
|
||||
],
|
||||
@@ -199,46 +195,19 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"options": "BUDGET-.YYYY.-",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"read_only": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "control_action_for_cumulative_expense_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Control Action for Cumulative Expense"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "(Purchase Order + Material Request + Actual Expense)",
|
||||
"fieldname": "applicable_on_cumulative_expense",
|
||||
"fieldtype": "Check",
|
||||
"label": "Applicable on Cumulative Expense"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.applicable_on_cumulative_expense == 1",
|
||||
"fieldname": "action_if_annual_exceeded_on_cumulative_expense",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action if Anual Budget Exceeded on Cumulative Expense",
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.applicable_on_cumulative_expense == 1",
|
||||
"fieldname": "action_if_accumulated_monthly_exceeded_on_cumulative_expense",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action if Accumulative Monthly Budget Exceeded on Cumulative Expense",
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-16 15:57:13.114981",
|
||||
"modified": "2024-03-27 13:06:42.675933",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Budget",
|
||||
@@ -262,9 +231,8 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -36,14 +36,11 @@ class Budget(Document):
|
||||
action_if_accumulated_monthly_budget_exceeded: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
action_if_accumulated_monthly_budget_exceeded_on_mr: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
action_if_accumulated_monthly_budget_exceeded_on_po: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
action_if_accumulated_monthly_exceeded_on_cumulative_expense: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
action_if_annual_budget_exceeded: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
action_if_annual_budget_exceeded_on_mr: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
action_if_annual_budget_exceeded_on_po: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
action_if_annual_exceeded_on_cumulative_expense: DF.Literal["", "Stop", "Warn", "Ignore"]
|
||||
amended_from: DF.Link | None
|
||||
applicable_on_booking_actual_expenses: DF.Check
|
||||
applicable_on_cumulative_expense: DF.Check
|
||||
applicable_on_material_request: DF.Check
|
||||
applicable_on_purchase_order: DF.Check
|
||||
budget_against: DF.Literal["", "Cost Center", "Project"]
|
||||
@@ -51,7 +48,7 @@ class Budget(Document):
|
||||
cost_center: DF.Link | None
|
||||
fiscal_year: DF.Link
|
||||
monthly_distribution: DF.Link | None
|
||||
naming_series: DF.Literal["BUDGET-.YYYY.-"]
|
||||
naming_series: DF.Data | None
|
||||
project: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
@@ -139,10 +136,13 @@ class Budget(Document):
|
||||
):
|
||||
self.applicable_on_booking_actual_expenses = 1
|
||||
|
||||
def before_naming(self):
|
||||
self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###"
|
||||
|
||||
|
||||
def validate_expense_against_budget(args, expense_amount=0):
|
||||
args = frappe._dict(args)
|
||||
if not frappe.db.count("Budget", cache=True):
|
||||
if not frappe.get_all("Budget", limit=1):
|
||||
return
|
||||
|
||||
if args.get("company") and not args.fiscal_year:
|
||||
@@ -151,7 +151,7 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
"Company", args.get("company"), "exception_budget_approver_role"
|
||||
)
|
||||
|
||||
if not frappe.db.get_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}):
|
||||
if not frappe.get_cached_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}): # nosec
|
||||
return
|
||||
|
||||
if not args.account:
|
||||
@@ -510,7 +510,7 @@ def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_ye
|
||||
accumulated_percentage = 0.0
|
||||
|
||||
while dt <= getdate(posting_date):
|
||||
if monthly_distribution and distribution:
|
||||
if monthly_distribution:
|
||||
accumulated_percentage += distribution.get(getdate(dt).strftime("%B"), 0)
|
||||
else:
|
||||
accumulated_percentage += 100.0 / 12
|
||||
|
||||
@@ -3,29 +3,18 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.utils import now_datetime, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.budget.budget import (
|
||||
BudgetError,
|
||||
get_accumulated_monthly_budget,
|
||||
get_actual_expense,
|
||||
)
|
||||
from erpnext.accounts.doctype.budget.budget import BudgetError, get_actual_expense
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Monthly Distribution"]
|
||||
|
||||
|
||||
class TestBudget(ERPNextTestSuite):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.make_monthly_distribution()
|
||||
cls.make_projects()
|
||||
|
||||
def setUp(self):
|
||||
frappe.db.set_single_value("Accounts Settings", "use_new_budget_controller", True)
|
||||
|
||||
class TestBudget(IntegrationTestCase):
|
||||
def test_monthly_budget_crossed_ignore(self):
|
||||
set_total_expense_zero(nowdate(), "cost_center")
|
||||
|
||||
@@ -54,13 +43,10 @@ class TestBudget(ERPNextTestSuite):
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
|
||||
accumulated_limit = get_accumulated_monthly_budget(
|
||||
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||
)
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
accumulated_limit + 1,
|
||||
40000,
|
||||
"_Test Cost Center - _TC",
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
@@ -77,13 +63,10 @@ class TestBudget(ERPNextTestSuite):
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
|
||||
accumulated_limit = get_accumulated_monthly_budget(
|
||||
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||
)
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
accumulated_limit + 1,
|
||||
40000,
|
||||
"_Test Cost Center - _TC",
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
@@ -140,7 +123,6 @@ class TestBudget(ERPNextTestSuite):
|
||||
|
||||
budget.load_from_db()
|
||||
budget.cancel()
|
||||
mr.cancel()
|
||||
|
||||
def test_monthly_budget_crossed_for_po(self):
|
||||
budget = make_budget(
|
||||
@@ -153,12 +135,7 @@ class TestBudget(ERPNextTestSuite):
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
|
||||
|
||||
accumulated_limit = get_accumulated_monthly_budget(
|
||||
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||
)
|
||||
po = create_purchase_order(
|
||||
transaction_date=nowdate(), qty=1, rate=accumulated_limit + 1, do_not_submit=True
|
||||
)
|
||||
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
|
||||
|
||||
po.set_missing_values()
|
||||
|
||||
@@ -176,13 +153,11 @@ class TestBudget(ERPNextTestSuite):
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
accumulated_limit = get_accumulated_monthly_budget(
|
||||
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
accumulated_limit + 1,
|
||||
40000,
|
||||
"_Test Cost Center - _TC",
|
||||
project=project,
|
||||
posting_date=nowdate(),
|
||||
@@ -297,13 +272,10 @@ class TestBudget(ERPNextTestSuite):
|
||||
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
|
||||
accumulated_limit = get_accumulated_monthly_budget(
|
||||
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||
)
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
accumulated_limit + 1,
|
||||
40000,
|
||||
"_Test Cost Center 2 - _TC",
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
@@ -330,13 +302,10 @@ class TestBudget(ERPNextTestSuite):
|
||||
budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
|
||||
accumulated_limit = get_accumulated_monthly_budget(
|
||||
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||
)
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
accumulated_limit + 1,
|
||||
40000,
|
||||
cost_center,
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
@@ -380,44 +349,6 @@ class TestBudget(ERPNextTestSuite):
|
||||
|
||||
self.assertRaises(BudgetError, jv.submit)
|
||||
|
||||
def test_action_for_cumulative_limit(self):
|
||||
set_total_expense_zero(nowdate(), "cost_center")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center", applicable_on_cumulative_expense=True)
|
||||
|
||||
accumulated_limit = get_accumulated_monthly_budget(
|
||||
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
accumulated_limit - 1,
|
||||
"_Test Cost Center - _TC",
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
jv.submit()
|
||||
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_exceeded_on_cumulative_expense", "Stop"
|
||||
)
|
||||
po = create_purchase_order(
|
||||
transaction_date=nowdate(), qty=1, rate=accumulated_limit + 1, do_not_submit=True
|
||||
)
|
||||
po.set_missing_values()
|
||||
|
||||
self.assertRaises(BudgetError, po.submit)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_exceeded_on_cumulative_expense", "Ignore"
|
||||
)
|
||||
po.submit()
|
||||
|
||||
budget.load_from_db()
|
||||
budget.cancel()
|
||||
po.cancel()
|
||||
jv.cancel()
|
||||
|
||||
|
||||
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
|
||||
if budget_against_field == "project":
|
||||
@@ -492,7 +423,6 @@ def make_budget(**args):
|
||||
|
||||
monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution")
|
||||
monthly_distribution.fiscal_year = fiscal_year
|
||||
monthly_distribution.save()
|
||||
|
||||
budget.fiscal_year = fiscal_year
|
||||
budget.monthly_distribution = "_Test Distribution"
|
||||
@@ -517,15 +447,6 @@ def make_budget(**args):
|
||||
args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
|
||||
)
|
||||
|
||||
if args.applicable_on_cumulative_expense:
|
||||
budget.applicable_on_cumulative_expense = 1
|
||||
budget.action_if_annual_exceeded_on_cumulative_expense = (
|
||||
args.action_if_annual_exceeded_on_cumulative_expense or "Warn"
|
||||
)
|
||||
budget.action_if_accumulated_monthly_exceeded_on_cumulative_expense = (
|
||||
args.action_if_accumulated_monthly_exceeded_on_cumulative_expense or "Warn"
|
||||
)
|
||||
|
||||
budget.insert()
|
||||
budget.submit()
|
||||
|
||||
|
||||
@@ -154,7 +154,3 @@ class CostCenterAllocation(Document):
|
||||
).format(d.cost_center),
|
||||
InvalidChildCostCenter,
|
||||
)
|
||||
|
||||
def clear_cache(self):
|
||||
frappe.clear_cache(doctype="Cost Center")
|
||||
return super().clear_cache()
|
||||
|
||||
@@ -36,7 +36,7 @@ class CurrencyExchangeSettings(Document):
|
||||
|
||||
def validate(self):
|
||||
self.set_parameters_and_result()
|
||||
if frappe.in_test or frappe.flags.in_install or frappe.flags.in_setup_wizard:
|
||||
if frappe.flags.in_test or frappe.flags.in_install or frappe.flags.in_setup_wizard:
|
||||
return
|
||||
response, value = self.validate_parameters()
|
||||
self.validate_result(response, value)
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe.model import mapper
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import add_days, nowdate, today
|
||||
|
||||
from erpnext import get_default_cost_center
|
||||
@@ -22,6 +22,15 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Company", "Cost Center"]
|
||||
|
||||
|
||||
class UnitTestDunning(UnitTestCase):
|
||||
"""
|
||||
Unit tests for Dunning.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestDunning(IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
@@ -123,7 +123,7 @@ def check_duplicate_fiscal_year(doc):
|
||||
)
|
||||
for fiscal_year, ysd, yed in year_start_end_dates:
|
||||
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
|
||||
not frappe.in_test
|
||||
not frappe.flags.in_test
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
|
||||
@@ -86,8 +86,7 @@
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Party Type",
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
@@ -105,8 +104,7 @@
|
||||
"label": "Cost Center",
|
||||
"oldfieldname": "cost_center",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"search_index": 1
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
@@ -243,8 +241,7 @@
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"search_index": 1
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
@@ -359,7 +356,7 @@
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2025-03-21 15:29:11.221890",
|
||||
"modified": "2025-04-21 22:37:16.349564",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GL Entry",
|
||||
@@ -390,8 +387,9 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "voucher_no,account,posting_date,against_voucher",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.model.naming import set_name_from_naming_options
|
||||
from frappe.utils import create_batch, flt, fmt_money, now
|
||||
from frappe.utils import flt, fmt_money, now
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
@@ -438,9 +438,14 @@ def update_against_account(voucher_type, voucher_no):
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
|
||||
frappe.db.add_index("GL Entry", ["posting_date", "company"])
|
||||
frappe.db.add_index("GL Entry", ["party_type", "party"])
|
||||
add_company_indexes()
|
||||
|
||||
|
||||
def add_company_indexes():
|
||||
"""Only add company indexes if more than one company exists."""
|
||||
if frappe.db.count("Company", {"name": ("not like", "%(Demo)%")}) > 1:
|
||||
frappe.db.add_index("GL Entry", ["posting_date", "company"])
|
||||
frappe.db.add_index("GL Entry", ["company"])
|
||||
|
||||
|
||||
def rename_gle_sle_docs():
|
||||
@@ -451,15 +456,12 @@ def rename_gle_sle_docs():
|
||||
def rename_temporarily_named_docs(doctype):
|
||||
"""Rename temporarily named docs using autoname options"""
|
||||
docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation", limit=50000)
|
||||
autoname = frappe.get_meta(doctype).autoname
|
||||
|
||||
for batch in create_batch(docs_to_rename, 100):
|
||||
for doc in batch:
|
||||
oldname = doc.name
|
||||
set_name_from_naming_options(autoname, doc)
|
||||
newname = doc.name
|
||||
frappe.db.sql(
|
||||
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
|
||||
(newname, now(), oldname),
|
||||
)
|
||||
frappe.db.commit()
|
||||
for doc in docs_to_rename:
|
||||
oldname = doc.name
|
||||
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
|
||||
newname = doc.name
|
||||
frappe.db.sql(
|
||||
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
|
||||
(newname, now(), oldname),
|
||||
auto_commit=True,
|
||||
)
|
||||
|
||||
@@ -197,7 +197,7 @@ frappe.ui.form.on("Invoice Discounting", {
|
||||
from_date: frm.doc.posting_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
categorize_by: "Categorize by Voucher (Consolidated)",
|
||||
group_by: "Group by Voucher (Consolidated)",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -39,16 +39,7 @@ class ItemTaxTemplate(Document):
|
||||
check_list = []
|
||||
for d in self.get("taxes"):
|
||||
if d.tax_type:
|
||||
account_type, account_company = frappe.get_cached_value(
|
||||
"Account", d.tax_type, ["account_type", "company"]
|
||||
)
|
||||
|
||||
if account_company != self.company:
|
||||
frappe.throw(
|
||||
_("Item Tax Row {0}: Account must belong to Company - {1}").format(
|
||||
d.idx, frappe.bold(self.company)
|
||||
)
|
||||
)
|
||||
account_type = frappe.get_cached_value("Account", d.tax_type, "account_type")
|
||||
|
||||
if account_type not in [
|
||||
"Tax",
|
||||
|
||||
@@ -20,39 +20,6 @@ frappe.ui.form.on("Journal Entry", {
|
||||
"Unreconcile Payment Entries",
|
||||
"Bank Transaction",
|
||||
];
|
||||
|
||||
frm.trigger("set_queries");
|
||||
},
|
||||
|
||||
set_queries(frm) {
|
||||
frm.set_query("periodic_entry_difference_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0,
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("stock_asset_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0,
|
||||
account_type: "Stock",
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_balance_for_periodic_accounting(frm) {
|
||||
frm.call({
|
||||
method: "get_balance_for_periodic_accounting",
|
||||
doc: frm.doc,
|
||||
callback: function (r) {
|
||||
refresh_field("accounts");
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
@@ -68,7 +35,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
finance_book: frm.doc.finance_book,
|
||||
categorize_by: "",
|
||||
group_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -13,21 +13,15 @@
|
||||
"title",
|
||||
"voucher_type",
|
||||
"naming_series",
|
||||
"finance_book",
|
||||
"process_deferred_accounting",
|
||||
"reversal_of",
|
||||
"tax_withholding_category",
|
||||
"column_break1",
|
||||
"from_template",
|
||||
"company",
|
||||
"posting_date",
|
||||
"finance_book",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"section_break_tcvw",
|
||||
"for_all_stock_asset_accounts",
|
||||
"column_break_wpau",
|
||||
"stock_asset_account",
|
||||
"periodic_entry_difference_account",
|
||||
"get_balance_for_periodic_accounting",
|
||||
"2_add_edit_gl_entries",
|
||||
"accounts",
|
||||
"section_break99",
|
||||
@@ -95,7 +89,7 @@
|
||||
"label": "Entry Type",
|
||||
"oldfieldname": "voucher_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nAsset Disposal\nPeriodic Accounting Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
|
||||
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -549,42 +543,6 @@
|
||||
"label": "Is System Generated",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||
"fieldname": "periodic_entry_difference_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Periodic Entry Difference Account",
|
||||
"mandatory_depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||
"fieldname": "section_break_tcvw",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Periodic Accounting"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "for_all_stock_asset_accounts",
|
||||
"fieldtype": "Check",
|
||||
"label": "For All Stock Asset Accounts"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.for_all_stock_asset_accounts === 0",
|
||||
"fieldname": "stock_asset_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Asset Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_wpau",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "get_balance_for_periodic_accounting",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Balance"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -599,7 +557,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2025-06-17 15:18:13.322681",
|
||||
"modified": "2024-07-18 15:32:29.413598",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
@@ -644,11 +602,10 @@
|
||||
"role": "Auditor"
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,6 @@ class JournalEntry(AccountsController):
|
||||
difference: DF.Currency
|
||||
due_date: DF.Date | None
|
||||
finance_book: DF.Link | None
|
||||
for_all_stock_asset_accounts: DF.Check
|
||||
from_template: DF.Link | None
|
||||
inter_company_journal_entry_reference: DF.Link | None
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
@@ -74,13 +73,11 @@ class JournalEntry(AccountsController):
|
||||
paid_loan: DF.Data | None
|
||||
pay_to_recd_from: DF.Data | None
|
||||
payment_order: DF.Link | None
|
||||
periodic_entry_difference_account: DF.Link | None
|
||||
posting_date: DF.Date
|
||||
process_deferred_accounting: DF.Link | None
|
||||
remark: DF.SmallText | None
|
||||
reversal_of: DF.Link | None
|
||||
select_print_heading: DF.Link | None
|
||||
stock_asset_account: DF.Link | None
|
||||
stock_entry: DF.Link | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
title: DF.Data | None
|
||||
@@ -103,8 +100,6 @@ class JournalEntry(AccountsController):
|
||||
"Write Off Entry",
|
||||
"Opening Entry",
|
||||
"Depreciation Entry",
|
||||
"Asset Disposal",
|
||||
"Periodic Accounting Entry",
|
||||
"Exchange Rate Revaluation",
|
||||
"Exchange Gain Or Loss",
|
||||
"Deferred Revenue",
|
||||
@@ -145,14 +140,15 @@ class JournalEntry(AccountsController):
|
||||
self.validate_credit_debit_note()
|
||||
self.validate_empty_accounts_table()
|
||||
self.validate_inter_company_accounts()
|
||||
self.validate_depr_account_and_depr_entry_voucher_type()
|
||||
self.validate_depr_entry_voucher_type()
|
||||
self.validate_company_in_accounting_dimension()
|
||||
self.validate_advance_accounts()
|
||||
|
||||
if self.docstatus == 0:
|
||||
self.apply_tax_withholding()
|
||||
|
||||
self.title = self.get_title()
|
||||
if not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
def validate_advance_accounts(self):
|
||||
journal_accounts = set([x.account for x in self.accounts])
|
||||
@@ -200,76 +196,7 @@ class JournalEntry(AccountsController):
|
||||
self.update_asset_value()
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance_for_periodic_accounting(self):
|
||||
self.validate_company_for_periodic_accounting()
|
||||
|
||||
stock_accounts = self.get_stock_accounts_for_periodic_accounting()
|
||||
self.set("accounts", [])
|
||||
for account in stock_accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||
account, self.posting_date, self.company
|
||||
)
|
||||
|
||||
difference_value = flt(stock_bal - account_bal, self.precision("difference"))
|
||||
|
||||
if difference_value == 0:
|
||||
frappe.msgprint(
|
||||
_("No difference found for stock account {0}").format(frappe.bold(account)),
|
||||
alert=True,
|
||||
)
|
||||
continue
|
||||
|
||||
self.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": account,
|
||||
"debit_in_account_currency": difference_value if difference_value > 0 else 0,
|
||||
"credit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
|
||||
},
|
||||
)
|
||||
|
||||
self.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.periodic_entry_difference_account,
|
||||
"credit_in_account_currency": difference_value if difference_value > 0 else 0,
|
||||
"debit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
|
||||
},
|
||||
)
|
||||
|
||||
def validate_company_for_periodic_accounting(self):
|
||||
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Periodic Accounting Entry is not allowed for company {0} with perpetual inventory enabled"
|
||||
).format(self.company)
|
||||
)
|
||||
|
||||
if not self.periodic_entry_difference_account:
|
||||
frappe.throw(_("Please select Periodic Accounting Entry Difference Account"))
|
||||
|
||||
def get_stock_accounts_for_periodic_accounting(self):
|
||||
if self.voucher_type != "Periodic Accounting Entry":
|
||||
return []
|
||||
|
||||
if self.for_all_stock_asset_accounts:
|
||||
return frappe.get_all(
|
||||
"Account",
|
||||
filters={
|
||||
"company": self.company,
|
||||
"account_type": "Stock",
|
||||
"root_type": "Asset",
|
||||
"is_group": 0,
|
||||
},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if not self.stock_asset_account:
|
||||
frappe.throw(_("Please select Stock Asset Account"))
|
||||
|
||||
return [self.stock_asset_account]
|
||||
self.update_booked_depreciation()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
# Flag will be set on Reconciliation
|
||||
@@ -305,6 +232,7 @@ class JournalEntry(AccountsController):
|
||||
self.unlink_inter_company_jv()
|
||||
self.unlink_asset_adjustment_entry()
|
||||
self.update_invoice_discounting()
|
||||
self.update_booked_depreciation(1)
|
||||
|
||||
def get_title(self):
|
||||
return self.pay_to_recd_from or self.accounts[0].account
|
||||
@@ -325,38 +253,21 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def validate_inter_company_accounts(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
doc = frappe.db.get_value(
|
||||
"Journal Entry",
|
||||
self.inter_company_journal_entry_reference,
|
||||
["company", "total_debit", "total_credit"],
|
||||
as_dict=True,
|
||||
)
|
||||
doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
|
||||
account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
|
||||
if account_currency == previous_account_currency:
|
||||
credit_precision = self.precision("total_credit")
|
||||
debit_precision = self.precision("total_debit")
|
||||
if (flt(self.total_credit, credit_precision) != flt(doc.total_debit, debit_precision)) or (
|
||||
flt(self.total_debit, debit_precision) != flt(doc.total_credit, credit_precision)
|
||||
):
|
||||
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
|
||||
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
|
||||
|
||||
def validate_depr_account_and_depr_entry_voucher_type(self):
|
||||
for d in self.get("accounts"):
|
||||
if d.account_type == "Depreciation":
|
||||
if self.voucher_type != "Depreciation Entry":
|
||||
frappe.throw(
|
||||
_("Journal Entry type should be set as Depreciation Entry for asset depreciation")
|
||||
)
|
||||
|
||||
if frappe.get_cached_value("Account", d.account, "root_type") != "Expense":
|
||||
frappe.throw(_("Account {0} should be of type Expense").format(d.account))
|
||||
def validate_depr_entry_voucher_type(self):
|
||||
if (
|
||||
any(d.account_type == "Depreciation" for d in self.get("accounts"))
|
||||
and self.voucher_type != "Depreciation Entry"
|
||||
):
|
||||
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
|
||||
|
||||
def validate_stock_accounts(self):
|
||||
if self.voucher_type == "Periodic Accounting Entry":
|
||||
# Skip validation for periodic accounting entry
|
||||
return
|
||||
|
||||
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
|
||||
for account in stock_accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||
@@ -459,11 +370,7 @@ class JournalEntry(AccountsController):
|
||||
self.remove(d)
|
||||
|
||||
def update_asset_value(self):
|
||||
self.update_asset_on_depreciation()
|
||||
self.update_asset_on_disposal()
|
||||
|
||||
def update_asset_on_depreciation(self):
|
||||
if self.voucher_type != "Depreciation Entry":
|
||||
if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry":
|
||||
return
|
||||
|
||||
for d in self.get("accounts"):
|
||||
@@ -473,59 +380,22 @@ class JournalEntry(AccountsController):
|
||||
and d.account_type == "Depreciation"
|
||||
and d.debit
|
||||
):
|
||||
asset = frappe.get_cached_doc("Asset", d.reference_name)
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.update_journal_entry_link_on_depr_schedule(asset, d)
|
||||
self.update_value_after_depreciation(asset, d.debit)
|
||||
fb_idx = 1
|
||||
if self.finance_book:
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
fb_idx = fb_row.idx
|
||||
break
|
||||
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||
fb_row.value_after_depreciation -= d.debit
|
||||
fb_row.db_update()
|
||||
else:
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
||||
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
||||
asset.set_status()
|
||||
asset.set_total_booked_depreciations()
|
||||
|
||||
def update_value_after_depreciation(self, asset, depr_amount):
|
||||
fb_idx = 1
|
||||
if self.finance_book:
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
fb_idx = fb_row.idx
|
||||
break
|
||||
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||
fb_row.value_after_depreciation -= depr_amount
|
||||
frappe.db.set_value(
|
||||
"Asset Finance Book", fb_row.name, "value_after_depreciation", fb_row.value_after_depreciation
|
||||
)
|
||||
|
||||
def update_journal_entry_link_on_depr_schedule(self, asset, je_row):
|
||||
depr_schedule = get_depr_schedule(asset.name, "Active", self.finance_book)
|
||||
for d in depr_schedule or []:
|
||||
if (
|
||||
d.schedule_date == self.posting_date
|
||||
and not d.journal_entry
|
||||
and d.depreciation_amount == flt(je_row.debit)
|
||||
):
|
||||
frappe.db.set_value("Depreciation Schedule", d.name, "journal_entry", self.name)
|
||||
|
||||
def update_asset_on_disposal(self):
|
||||
if self.voucher_type == "Asset Disposal":
|
||||
disposed_assets = []
|
||||
for d in self.get("accounts"):
|
||||
if (
|
||||
d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and d.reference_name not in disposed_assets
|
||||
):
|
||||
frappe.db.set_value(
|
||||
"Asset",
|
||||
d.reference_name,
|
||||
{
|
||||
"disposal_date": self.posting_date,
|
||||
"journal_entry_for_scrap": self.name,
|
||||
},
|
||||
)
|
||||
asset_doc = frappe.get_doc("Asset", d.reference_name)
|
||||
asset_doc.set_status()
|
||||
disposed_assets.append(d.reference_name)
|
||||
|
||||
def update_inter_company_jv(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
@@ -580,6 +450,25 @@ class JournalEntry(AccountsController):
|
||||
if status:
|
||||
inv_disc_doc.set_status(status=status)
|
||||
|
||||
def update_booked_depreciation(self, cancel=0):
|
||||
for d in self.get("accounts"):
|
||||
if (
|
||||
self.voucher_type == "Depreciation Entry"
|
||||
and d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
|
||||
and d.debit
|
||||
):
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
if cancel:
|
||||
fb_row.total_number_of_booked_depreciations -= 1
|
||||
else:
|
||||
fb_row.total_number_of_booked_depreciations += 1
|
||||
fb_row.db_update()
|
||||
break
|
||||
|
||||
def unlink_advance_entry_reference(self):
|
||||
for d in self.get("accounts"):
|
||||
if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
@@ -629,9 +518,9 @@ class JournalEntry(AccountsController):
|
||||
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||
fb_row.value_after_depreciation += d.debit
|
||||
fb_row.db_update()
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
|
||||
else:
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
|
||||
asset.set_status()
|
||||
asset.set_total_booked_depreciations()
|
||||
elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name:
|
||||
journal_entry_for_scrap = frappe.db.get_value(
|
||||
"Asset", d.reference_name, "journal_entry_for_scrap"
|
||||
@@ -1248,7 +1137,7 @@ class JournalEntry(AccountsController):
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
|
||||
merge_entries = frappe.get_single_value("Accounts Settings", "merge_similar_account_heads")
|
||||
merge_entries = frappe.db.get_single_value("Accounts Settings", "merge_similar_account_heads")
|
||||
|
||||
gl_map = self.build_gl_map()
|
||||
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
|
||||
@@ -1374,7 +1263,7 @@ class JournalEntry(AccountsController):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_default_bank_cash_account(
|
||||
company, account_type=None, mode_of_payment=None, account=None, *, fetch_balance=True
|
||||
company, account_type=None, mode_of_payment=None, account=None, ignore_permissions=False
|
||||
):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
|
||||
@@ -1409,14 +1298,15 @@ def get_default_bank_cash_account(
|
||||
account_details = frappe.get_cached_value(
|
||||
"Account", account, ["account_currency", "account_type"], as_dict=1
|
||||
)
|
||||
result = {
|
||||
"account": account,
|
||||
"account_currency": account_details.account_currency,
|
||||
"account_type": account_details.account_type,
|
||||
}
|
||||
if fetch_balance:
|
||||
result["balance"] = get_balance_on(account)
|
||||
return frappe._dict(result)
|
||||
|
||||
return frappe._dict(
|
||||
{
|
||||
"account": account,
|
||||
"balance": get_balance_on(account, ignore_account_permission=ignore_permissions),
|
||||
"account_currency": account_details.account_currency,
|
||||
"account_type": account_details.account_type,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return frappe._dict()
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import flt, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
@@ -10,6 +10,15 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInv
|
||||
from erpnext.exceptions import InvalidAccountCurrency
|
||||
|
||||
|
||||
class UnitTestJournalEntry(UnitTestCase):
|
||||
"""
|
||||
Unit tests for JournalEntry.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestJournalEntry(IntegrationTestCase):
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
class UnitTestLedgerHealthMonitor(UnitTestCase):
|
||||
"""
|
||||
Unit tests for LedgerHealthMonitor.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestLedgerHealthMonitor(IntegrationTestCase):
|
||||
|
||||
@@ -35,7 +35,7 @@ class LedgerMerge(Document):
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
if is_scheduler_inactive() and not frappe.in_test:
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
|
||||
|
||||
job_id = f"ledger_merge::{self.name}"
|
||||
@@ -47,7 +47,7 @@ class LedgerMerge(Document):
|
||||
event="ledger_merge",
|
||||
job_id=job_id,
|
||||
docname=self.name,
|
||||
now=frappe.conf.developer_mode or frappe.in_test,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@@ -178,9 +178,8 @@ def validate_loyalty_points(ref_doc, points_to_redeem):
|
||||
|
||||
loyalty_amount = flt(points_to_redeem * loyalty_program_details.conversion_factor)
|
||||
|
||||
total_amount = ref_doc.grand_total if ref_doc.is_rounded_total_disabled() else ref_doc.rounded_total
|
||||
if loyalty_amount > total_amount:
|
||||
frappe.throw(_("You can't redeem Loyalty Points having more value than the Total Amount."))
|
||||
if loyalty_amount > ref_doc.rounded_total:
|
||||
frappe.throw(_("You can't redeem Loyalty Points having more value than the Rounded Total."))
|
||||
|
||||
if not ref_doc.loyalty_amount and ref_doc.loyalty_amount != loyalty_amount:
|
||||
ref_doc.loyalty_amount = loyalty_amount
|
||||
|
||||
@@ -2,26 +2,8 @@
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
class TestModeofPayment(IntegrationTestCase):
|
||||
pass
|
||||
|
||||
|
||||
def set_default_account_for_mode_of_payment(mode_of_payment, company, account):
|
||||
mode_of_payment.reload()
|
||||
if frappe.db.exists(
|
||||
"Mode of Payment Account", {"parent": mode_of_payment.mode_of_payment, "company": company}
|
||||
):
|
||||
frappe.db.set_value(
|
||||
"Mode of Payment Account",
|
||||
{"parent": mode_of_payment.mode_of_payment, "company": company},
|
||||
"default_account",
|
||||
account,
|
||||
)
|
||||
return
|
||||
|
||||
mode_of_payment.append("accounts", {"company": company, "default_account": account})
|
||||
mode_of_payment.save()
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
[{
|
||||
"doctype": "Monthly Distribution",
|
||||
"distribution_id": "_Test Distribution",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
"percentages": [
|
||||
{
|
||||
"month": "January",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "February",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "March",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "April",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "May",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "June",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "July",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "August",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "September",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "October",
|
||||
"percentage_allocation": "8"
|
||||
}, {
|
||||
"month": "November",
|
||||
"percentage_allocation": "10"
|
||||
}, {
|
||||
"month": "December",
|
||||
"percentage_allocation": "10"
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -229,7 +229,7 @@ class OpeningInvoiceCreationTool(Document):
|
||||
else:
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
if is_scheduler_inactive() and not frappe.in_test:
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||
|
||||
job_id = f"opening_invoice::{self.name}"
|
||||
@@ -242,7 +242,7 @@ class OpeningInvoiceCreationTool(Document):
|
||||
event="opening_invoice_creation",
|
||||
job_id=job_id,
|
||||
invoices=invoices,
|
||||
now=frappe.conf.developer_mode or frappe.in_test,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
||||
create_dimension,
|
||||
@@ -15,6 +15,15 @@ from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_crea
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Customer", "Supplier", "Accounting Dimension"]
|
||||
|
||||
|
||||
class UnitTestOpeningInvoiceCreationTool(UnitTestCase):
|
||||
"""
|
||||
Unit tests for OpeningInvoiceCreationTool.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestOpeningInvoiceCreationTool(IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
@@ -410,7 +410,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
from_date: frm.doc.posting_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
categorize_by: "",
|
||||
group_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"party_name",
|
||||
"book_advance_payments_in_separate_party_account",
|
||||
"reconcile_on_advance_payment_date",
|
||||
"advance_reconciliation_takes_effect_on",
|
||||
"column_break_11",
|
||||
"bank_account",
|
||||
"party_bank_account",
|
||||
@@ -753,9 +754,18 @@
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "Oldest Of Invoice Or Advance",
|
||||
"fetch_from": "company.reconciliation_takes_effect_on",
|
||||
"fieldname": "advance_reconciliation_takes_effect_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Advance Reconciliation Takes Effect On",
|
||||
"no_copy": 1,
|
||||
"options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
@@ -767,7 +777,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2025-05-08 11:18:10.238085",
|
||||
"modified": "2025-03-24 16:18:19.920701",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
@@ -807,7 +817,6 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
|
||||
@@ -46,6 +46,7 @@ from erpnext.accounts.party import (
|
||||
from erpnext.accounts.utils import (
|
||||
cancel_exchange_gain_loss_journal,
|
||||
get_account_currency,
|
||||
get_balance_on,
|
||||
get_outstanding_invoices,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import (
|
||||
@@ -79,6 +80,9 @@ class PaymentEntry(AccountsController):
|
||||
PaymentEntryReference,
|
||||
)
|
||||
|
||||
advance_reconciliation_takes_effect_on: DF.Literal[
|
||||
"Advance Payment Date", "Oldest Of Invoice Or Advance", "Reconciliation Date"
|
||||
]
|
||||
amended_from: DF.Link | None
|
||||
apply_tax_withholding_amount: DF.Check
|
||||
auto_repeat: DF.Link | None
|
||||
@@ -498,7 +502,7 @@ class PaymentEntry(AccountsController):
|
||||
def delink_advance_entry_references(self):
|
||||
for reference in self.references:
|
||||
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
doc = frappe.get_lazy_doc(reference.reference_doctype, reference.reference_name)
|
||||
doc = frappe.get_doc(reference.reference_doctype, reference.reference_name)
|
||||
doc.delink_advance_entries(self.name)
|
||||
|
||||
def set_missing_values(self):
|
||||
@@ -661,7 +665,7 @@ class PaymentEntry(AccountsController):
|
||||
if not frappe.db.exists(d.reference_doctype, d.reference_name):
|
||||
frappe.throw(_("{0} {1} does not exist").format(d.reference_doctype, d.reference_name))
|
||||
|
||||
ref_doc = frappe.get_lazy_doc(d.reference_doctype, d.reference_name)
|
||||
ref_doc = frappe.get_doc(d.reference_doctype, d.reference_name)
|
||||
|
||||
if d.reference_doctype != "Journal Entry":
|
||||
if self.party != ref_doc.get(scrub(self.party_type)):
|
||||
@@ -1570,12 +1574,9 @@ class PaymentEntry(AccountsController):
|
||||
else:
|
||||
# For backwards compatibility
|
||||
# Supporting reposting on payment entries reconciled before select field introduction
|
||||
reconciliation_takes_effect_on = frappe.get_cached_value(
|
||||
"Company", self.company, "reconciliation_takes_effect_on"
|
||||
)
|
||||
if reconciliation_takes_effect_on == "Advance Payment Date":
|
||||
if self.advance_reconciliation_takes_effect_on == "Advance Payment Date":
|
||||
posting_date = self.posting_date
|
||||
elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
|
||||
elif self.advance_reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
|
||||
date_field = "posting_date"
|
||||
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
|
||||
date_field = "transaction_date"
|
||||
@@ -1585,7 +1586,7 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
if getdate(posting_date) < getdate(self.posting_date):
|
||||
posting_date = self.posting_date
|
||||
elif reconciliation_takes_effect_on == "Reconciliation Date":
|
||||
elif self.advance_reconciliation_takes_effect_on == "Reconciliation Date":
|
||||
posting_date = nowdate()
|
||||
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
|
||||
|
||||
@@ -1785,7 +1786,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
for d in self.get("references"):
|
||||
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
|
||||
frappe.get_lazy_doc(
|
||||
frappe.get_doc(
|
||||
d.reference_doctype, d.reference_name, for_update=True
|
||||
).set_total_advance_paid()
|
||||
|
||||
@@ -2080,7 +2081,7 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
# Re allocate amount to those references which have PR set (Higher priority)
|
||||
for ref in self.references:
|
||||
if not (ref.reference_doctype and ref.reference_name and ref.payment_request):
|
||||
if not ref.payment_request:
|
||||
continue
|
||||
|
||||
# fetch outstanding_amount of `Reference` (Payment Term) and `Payment Request` to allocate new amount
|
||||
@@ -2131,7 +2132,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
# Re allocate amount to those references which have no PR (Lower priority)
|
||||
for ref in self.references:
|
||||
if ref.payment_request or not (ref.reference_doctype and ref.reference_name):
|
||||
if ref.payment_request:
|
||||
continue
|
||||
|
||||
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
|
||||
@@ -2447,7 +2448,7 @@ def get_outstanding_reference_documents(args, validate=False):
|
||||
accounts = get_party_account(
|
||||
args.get("party_type"), args.get("party"), args.get("company"), include_advance=True
|
||||
)
|
||||
advance_account = accounts[1] if len(accounts) > 1 else None
|
||||
advance_account = accounts[1] if len(accounts) >= 1 else None
|
||||
|
||||
if party_account == advance_account:
|
||||
party_account = accounts[0]
|
||||
@@ -2785,6 +2786,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
|
||||
party_account = get_party_account(party_type, party, company)
|
||||
account_currency = get_account_currency(party_account)
|
||||
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
|
||||
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
|
||||
party_name = frappe.db.get_value(party_type, party, _party_name)
|
||||
|
||||
@@ -2796,6 +2798,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
"party_account": party_account,
|
||||
"party_name": party_name,
|
||||
"party_account_currency": account_currency,
|
||||
"account_balance": account_balance,
|
||||
"party_bank_account": party_bank_account,
|
||||
"bank_account": bank_account,
|
||||
}
|
||||
@@ -2813,9 +2816,12 @@ def get_account_details(account, date, cost_center=None):
|
||||
if not account_list:
|
||||
frappe.throw(_("Account: {0} is not permitted under Payment Entry").format(account))
|
||||
|
||||
account_balance = get_balance_on(account, date, cost_center=cost_center, ignore_account_permission=True)
|
||||
|
||||
return frappe._dict(
|
||||
{
|
||||
"account_currency": get_account_currency(account),
|
||||
"account_balance": account_balance,
|
||||
"account_type": frappe.get_cached_value("Account", account, "account_type"),
|
||||
}
|
||||
)
|
||||
@@ -2865,7 +2871,7 @@ def get_reference_details(
|
||||
):
|
||||
total_amount = outstanding_amount = exchange_rate = account = None
|
||||
|
||||
ref_doc = frappe.get_lazy_doc(reference_doctype, reference_name)
|
||||
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
|
||||
|
||||
# Only applies for Reverse Payment Entries
|
||||
@@ -2960,10 +2966,11 @@ def get_payment_entry(
|
||||
party_type=None,
|
||||
payment_type=None,
|
||||
reference_date=None,
|
||||
ignore_permissions=False,
|
||||
created_from_payment_request=False,
|
||||
):
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (100.0 + over_billing_allowance):
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
|
||||
|
||||
@@ -2981,14 +2988,14 @@ def get_payment_entry(
|
||||
)
|
||||
|
||||
# bank or cash
|
||||
bank = get_bank_cash_account(doc, bank_account)
|
||||
bank = get_bank_cash_account(doc, bank_account, ignore_permissions=ignore_permissions)
|
||||
|
||||
# if default bank or cash account is not set in company master and party has default company bank account, fetch it
|
||||
if party_type in ["Customer", "Supplier"] and not bank:
|
||||
party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type)))
|
||||
if party_bank_account:
|
||||
account = frappe.db.get_value("Bank Account", party_bank_account, "account")
|
||||
bank = get_bank_cash_account(doc, account)
|
||||
bank = get_bank_cash_account(doc, account, ignore_permissions=ignore_permissions)
|
||||
|
||||
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
|
||||
@@ -3018,8 +3025,6 @@ def get_payment_entry(
|
||||
party_account_currency if payment_type == "Receive" else bank.account_currency
|
||||
)
|
||||
pe.paid_to_account_currency = party_account_currency if payment_type == "Pay" else bank.account_currency
|
||||
pe.paid_from_account_type = frappe.db.get_value("Account", pe.paid_from, "account_type")
|
||||
pe.paid_to_account_type = frappe.db.get_value("Account", pe.paid_to, "account_type")
|
||||
pe.paid_amount = paid_amount
|
||||
pe.received_amount = received_amount
|
||||
pe.letter_head = doc.get("letter_head")
|
||||
@@ -3103,7 +3108,7 @@ def get_payment_entry(
|
||||
if party_account and bank:
|
||||
if discount_amount:
|
||||
base_total_discount_loss = 0
|
||||
if frappe.get_single_value("Accounts Settings", "book_tax_discount_loss"):
|
||||
if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"):
|
||||
base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts)
|
||||
|
||||
set_pending_discount_loss(
|
||||
@@ -3299,22 +3304,18 @@ def update_accounting_dimensions(pe, doc):
|
||||
pe.set(dimension, doc.get(dimension))
|
||||
|
||||
|
||||
def get_bank_cash_account(doc, bank_account):
|
||||
def get_bank_cash_account(doc, bank_account, ignore_permissions=False):
|
||||
bank = get_default_bank_cash_account(
|
||||
doc.company,
|
||||
"Bank",
|
||||
mode_of_payment=doc.get("mode_of_payment"),
|
||||
account=bank_account,
|
||||
fetch_balance=False,
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
|
||||
if not bank:
|
||||
bank = get_default_bank_cash_account(
|
||||
doc.company,
|
||||
"Cash",
|
||||
mode_of_payment=doc.get("mode_of_payment"),
|
||||
account=bank_account,
|
||||
fetch_balance=False,
|
||||
doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
|
||||
)
|
||||
|
||||
return bank
|
||||
@@ -3387,25 +3388,26 @@ def set_paid_amount_and_received_amount(
|
||||
if party_account_currency == bank.account_currency:
|
||||
paid_amount = received_amount = abs(outstanding_amount)
|
||||
else:
|
||||
# settings if it is for receive
|
||||
paid_amount = abs(outstanding_amount)
|
||||
if bank_amount:
|
||||
received_amount = bank_amount
|
||||
else:
|
||||
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
|
||||
if bank and company_currency != bank.account_currency:
|
||||
# doc currency can be different from bank currency
|
||||
posting_date = doc.get("posting_date") or doc.get("transaction_date")
|
||||
conversion_rate = get_exchange_rate(
|
||||
bank.account_currency, party_account_currency, posting_date
|
||||
)
|
||||
received_amount = paid_amount / conversion_rate
|
||||
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
|
||||
if payment_type == "Receive":
|
||||
paid_amount = abs(outstanding_amount)
|
||||
if bank_amount:
|
||||
received_amount = bank_amount
|
||||
else:
|
||||
received_amount = paid_amount * doc.get("conversion_rate", 1)
|
||||
|
||||
# if payment type is pay, then paid amount and received amount are swapped
|
||||
if payment_type == "Pay":
|
||||
paid_amount, received_amount = received_amount, paid_amount
|
||||
if bank and company_currency != bank.account_currency:
|
||||
received_amount = paid_amount / doc.get("conversion_rate", 1)
|
||||
else:
|
||||
received_amount = paid_amount * doc.get("conversion_rate", 1)
|
||||
else:
|
||||
received_amount = abs(outstanding_amount)
|
||||
if bank_amount:
|
||||
paid_amount = bank_amount
|
||||
else:
|
||||
if bank and company_currency != bank.account_currency:
|
||||
paid_amount = received_amount / doc.get("conversion_rate", 1)
|
||||
else:
|
||||
# if party account currency and bank currency is different then populate paid amount as well
|
||||
paid_amount = received_amount * doc.get("conversion_rate", 1)
|
||||
|
||||
return paid_amount, received_amount
|
||||
|
||||
@@ -3462,7 +3464,7 @@ def set_pending_discount_loss(pe, doc, discount_amount, base_total_discount_loss
|
||||
|
||||
# If tax loss booking is enabled, pending loss will be rounding loss.
|
||||
# Otherwise it will be the total discount loss.
|
||||
book_tax_loss = frappe.get_single_value("Accounts Settings", "book_tax_discount_loss")
|
||||
book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss")
|
||||
account_type = "round_off_account" if book_tax_loss else "default_discount_account"
|
||||
|
||||
pe.append(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import add_days, flt, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
@@ -28,6 +28,15 @@ from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Currency Exchange"]
|
||||
|
||||
|
||||
class UnitTestPaymentEntry(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PaymentEntry.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestPaymentEntry(IntegrationTestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
@@ -13,6 +13,15 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
|
||||
class UnitTestPaymentLedgerEntry(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PaymentLedgerEntry.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestPaymentLedgerEntry(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
self.ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import getdate
|
||||
|
||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
|
||||
@@ -17,6 +17,15 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
|
||||
|
||||
class UnitTestPaymentOrder(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PaymentOrder.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestPaymentOrder(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
|
||||
|
||||
@@ -407,7 +407,7 @@ class PaymentReconciliation(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_auto_process_enabled(self):
|
||||
return frappe.get_single_value("Accounts Settings", "auto_reconcile_payments")
|
||||
return frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments")
|
||||
|
||||
@frappe.whitelist()
|
||||
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
|
||||
@@ -532,7 +532,7 @@ class PaymentReconciliation(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def reconcile(self):
|
||||
if frappe.get_single_value("Accounts Settings", "auto_reconcile_payments"):
|
||||
if frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
|
||||
running_doc = is_any_doc_running(
|
||||
dict(
|
||||
company=self.company,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
|
||||
from frappe.utils.data import getdate as convert_to_date
|
||||
|
||||
@@ -21,6 +21,15 @@ from erpnext.stock.doctype.item.test_item import create_item
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"]
|
||||
|
||||
|
||||
class UnitTestPaymentReconciliation(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PaymentReconciliation.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestPaymentReconciliation(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
@@ -1979,9 +1988,7 @@ class TestPaymentReconciliation(IntegrationTestCase):
|
||||
|
||||
def test_reconciliation_on_closed_period_payment(self):
|
||||
# create backdated fiscal year
|
||||
first_fy_start_date = frappe.db.get_value(
|
||||
"Fiscal Year", {"disabled": 0}, [{"MIN": "year_start_date"}]
|
||||
)
|
||||
first_fy_start_date = frappe.db.get_value("Fiscal Year", {"disabled": 0}, "min(year_start_date)")
|
||||
prev_fy_start_date = add_years(first_fy_start_date, -1)
|
||||
prev_fy_end_date = add_days(first_fy_start_date, -1)
|
||||
create_fiscal_year(
|
||||
|
||||
@@ -672,12 +672,7 @@ def get_amount(ref_doc, payment_account=None):
|
||||
|
||||
dt = ref_doc.doctype
|
||||
if dt in ["Sales Order", "Purchase Order"]:
|
||||
advance_amount = flt(ref_doc.advance_paid)
|
||||
if ref_doc.party_account_currency != ref_doc.currency:
|
||||
advance_amount = flt(flt(ref_doc.advance_paid) / ref_doc.conversion_rate)
|
||||
|
||||
grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - advance_amount
|
||||
|
||||
grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - ref_doc.advance_paid
|
||||
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if (
|
||||
dt == "Sales Invoice"
|
||||
|
||||
@@ -6,7 +6,7 @@ import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
|
||||
@@ -58,6 +58,15 @@ payment_method = [
|
||||
]
|
||||
|
||||
|
||||
class UnitTestPaymentRequest(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PaymentRequest.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestPaymentRequest(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
for payment_gateway in payment_gateways:
|
||||
@@ -788,28 +797,29 @@ class TestPaymentRequest(IntegrationTestCase):
|
||||
pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1)
|
||||
self.assertEqual(pr.grand_total, si.outstanding_amount)
|
||||
|
||||
def test_partial_paid_invoice_with_submitted_payment_entry(self):
|
||||
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "PURINV0001"
|
||||
pe.reference_date = frappe.utils.nowdate()
|
||||
pe.paid_amount = 2500
|
||||
pe.references[0].allocated_amount = 2500
|
||||
pe.save()
|
||||
pe.submit()
|
||||
pe.cancel()
|
||||
def test_partial_paid_invoice_with_submitted_payment_entry(self):
|
||||
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "PURINV0002"
|
||||
pe.reference_date = frappe.utils.nowdate()
|
||||
pe.paid_amount = 2500
|
||||
pe.references[0].allocated_amount = 2500
|
||||
pe.save()
|
||||
pe.submit()
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "PURINV0001"
|
||||
pe.reference_date = frappe.utils.nowdate()
|
||||
pe.paid_amount = 2500
|
||||
pe.references[0].allocated_amount = 2500
|
||||
pe.save()
|
||||
pe.submit()
|
||||
pe.cancel()
|
||||
|
||||
pi.load_from_db()
|
||||
pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1)
|
||||
self.assertEqual(pr.grand_total, pi.outstanding_amount)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "PURINV0002"
|
||||
pe.reference_date = frappe.utils.nowdate()
|
||||
pe.paid_amount = 2500
|
||||
pe.references[0].allocated_amount = 2500
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
pi.load_from_db()
|
||||
pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1)
|
||||
self.assertEqual(pr.grand_total, pi.outstanding_amount)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Pegged Currencies", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-05-30 11:47:03.670913",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"pegged_currencies_item_section",
|
||||
"pegged_currency_item"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "pegged_currencies_item_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pegged_currency_item",
|
||||
"fieldtype": "Table",
|
||||
"options": "Pegged Currency Details"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-02 11:46:31.936714",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pegged Currencies",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PeggedCurrencies(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.pegged_currencies.pegged_currencies import PeggedCurrencies
|
||||
|
||||
pegged_currency_item: DF.Table[PeggedCurrencies]
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class UnitTestPeggedCurrencies(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PeggedCurrencies.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class IntegrationTestPeggedCurrencies(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for PeggedCurrencies.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-05-30 11:59:28.219277",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"source_currency",
|
||||
"pegged_against",
|
||||
"pegged_exchange_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "source_currency",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Currency",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "pegged_exchange_rate",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Exchange Rate"
|
||||
},
|
||||
{
|
||||
"fieldname": "pegged_against",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Pegged Against",
|
||||
"options": "Currency"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-17 14:11:16.521193",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pegged Currency Details",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PeggedCurrencyDetails(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
pegged_against: DF.Link | None
|
||||
pegged_exchange_rate: DF.Data | None
|
||||
source_currency: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -47,7 +47,7 @@ frappe.ui.form.on("Period Closing Voucher", {
|
||||
from_date: frm.doc.posting_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
categorize_by: "",
|
||||
group_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -133,12 +133,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = (
|
||||
"GL Entry",
|
||||
"Stock Ledger Entry",
|
||||
"Payment Ledger Entry",
|
||||
"Account Closing Balance",
|
||||
)
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||
self.block_if_future_closing_voucher_exists()
|
||||
self.db_set("gle_processing_status", "In Progress")
|
||||
self.cancel_gl_entries()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="clearfix"></div>
|
||||
<div class="box">
|
||||
<div class="grid-body" style="background-color: transparent;">
|
||||
<div class="grid-body">
|
||||
<div class="rows text-center">
|
||||
|
||||
<!-- Sales summary section -->
|
||||
<div>
|
||||
<h6 class="text-center uppercase">{{ _("Sales Summary") }}</h6>
|
||||
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Sales Summary") }}</h6>
|
||||
<div class="tax-break-up" style="overflow-x: auto;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<!-- Mode of payment section -->
|
||||
<div>
|
||||
<h6 class="text-center uppercase">{{ _("Mode of Payments") }}</h6>
|
||||
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Mode of Payments") }}</h6>
|
||||
<div class="tax-break-up" style="overflow-x: auto;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
@@ -57,7 +57,7 @@
|
||||
<!-- Taxes section -->
|
||||
{% if data.taxes %}
|
||||
<div>
|
||||
<h6 class="text-center uppercase">{{ _("Taxes") }}</h6>
|
||||
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Taxes") }}</h6>
|
||||
<div class="tax-break-up" style="overflow-x: auto;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("POS Closing Entry", {
|
||||
onload: async function (frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log", "Sales Invoice"];
|
||||
onload: function (frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log"];
|
||||
frm.set_query("pos_profile", function (doc) {
|
||||
return {
|
||||
filters: { user: doc.user },
|
||||
@@ -36,6 +36,8 @@ frappe.ui.form.on("POS Closing Entry", {
|
||||
}
|
||||
});
|
||||
|
||||
set_html_data(frm);
|
||||
|
||||
if (frm.doc.docstatus == 1) {
|
||||
if (!frm.doc.posting_date) {
|
||||
frm.set_value("posting_date", frappe.datetime.nowdate());
|
||||
@@ -80,7 +82,7 @@ frappe.ui.form.on("POS Closing Entry", {
|
||||
frappe.run_serially([
|
||||
() => frappe.dom.freeze(__("Loading Invoices! Please Wait...")),
|
||||
() => frm.trigger("set_opening_amounts"),
|
||||
() => frm.trigger("get_invoices"),
|
||||
() => frm.trigger("get_pos_invoices"),
|
||||
() => frappe.dom.unfreeze(),
|
||||
]);
|
||||
}
|
||||
@@ -100,9 +102,9 @@ frappe.ui.form.on("POS Closing Entry", {
|
||||
});
|
||||
},
|
||||
|
||||
get_invoices(frm) {
|
||||
get_pos_invoices(frm) {
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_invoices",
|
||||
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices",
|
||||
args: {
|
||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||
@@ -110,14 +112,51 @@ frappe.ui.form.on("POS Closing Entry", {
|
||||
user: frm.doc.user,
|
||||
},
|
||||
callback: (r) => {
|
||||
let inv_docs = r.message.invoices;
|
||||
set_transaction_form_data(inv_docs, frm);
|
||||
refresh_payments(r.message.payments, frm);
|
||||
add_taxes(r.message.taxes, frm);
|
||||
let pos_docs = r.message;
|
||||
set_form_data(pos_docs, frm);
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
before_save: async function (frm) {
|
||||
frappe.dom.freeze(__("Processing Sales! Please Wait..."));
|
||||
|
||||
frm.set_value("grand_total", 0);
|
||||
frm.set_value("net_total", 0);
|
||||
frm.set_value("total_quantity", 0);
|
||||
frm.set_value("taxes", []);
|
||||
|
||||
for (let row of frm.doc.payment_reconciliation) {
|
||||
row.expected_amount = row.opening_amount;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices",
|
||||
args: {
|
||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||
pos_profile: frm.doc.pos_profile,
|
||||
user: frm.doc.user,
|
||||
},
|
||||
callback: (r) => {
|
||||
let pos_invoices = r.message;
|
||||
for (let doc of pos_invoices) {
|
||||
frm.doc.grand_total += flt(doc.grand_total);
|
||||
frm.doc.net_total += flt(doc.net_total);
|
||||
frm.doc.total_quantity += flt(doc.total_qty);
|
||||
refresh_payments(doc, frm, false);
|
||||
refresh_taxes(doc, frm);
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
}
|
||||
},
|
||||
}),
|
||||
]);
|
||||
frappe.dom.unfreeze();
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("POS Closing Entry Detail", {
|
||||
@@ -127,37 +166,37 @@ frappe.ui.form.on("POS Closing Entry Detail", {
|
||||
},
|
||||
});
|
||||
|
||||
function set_transaction_form_data(data, frm) {
|
||||
function set_form_data(data, frm) {
|
||||
data.forEach((d) => {
|
||||
add_to_transaction(d, frm);
|
||||
add_to_pos_transaction(d, frm);
|
||||
frm.doc.grand_total += flt(d.grand_total);
|
||||
frm.doc.net_total += flt(d.net_total);
|
||||
frm.doc.total_quantity += flt(d.total_qty);
|
||||
frm.doc.total_taxes_and_charges += flt(d.total_taxes_and_charges);
|
||||
refresh_payments(d, frm, true);
|
||||
refresh_taxes(d, frm);
|
||||
});
|
||||
}
|
||||
|
||||
function add_to_transaction(d, frm) {
|
||||
const field = d.doctype === "POS Invoice" ? "pos_invoices" : "sales_invoices";
|
||||
frm.add_child(field, {
|
||||
function add_to_pos_transaction(d, frm) {
|
||||
frm.add_child("pos_transactions", {
|
||||
pos_invoice: d.name,
|
||||
posting_date: d.posting_date,
|
||||
grand_total: d.grand_total,
|
||||
customer: d.customer,
|
||||
is_return: d.is_return,
|
||||
return_against: d.return_against,
|
||||
...(d.doctype === "POS Invoice" && { pos_invoice: d.name }),
|
||||
...(d.doctype === "Sales Invoice" && { sales_invoice: d.name }),
|
||||
});
|
||||
}
|
||||
|
||||
function refresh_payments(payments, frm) {
|
||||
payments.forEach((p) => {
|
||||
function refresh_payments(d, frm, is_new) {
|
||||
d.payments.forEach((p) => {
|
||||
const payment = frm.doc.payment_reconciliation.find(
|
||||
(pay) => pay.mode_of_payment === p.mode_of_payment
|
||||
);
|
||||
if (p.account == d.account_for_change_amount) {
|
||||
p.amount -= flt(d.change_amount);
|
||||
}
|
||||
if (payment) {
|
||||
payment.expected_amount += flt(p.amount);
|
||||
payment.closing_amount = payment.expected_amount;
|
||||
if (is_new) payment.closing_amount = payment.expected_amount;
|
||||
payment.difference = payment.closing_amount - payment.expected_amount;
|
||||
} else {
|
||||
frm.add_child("payment_reconciliation", {
|
||||
@@ -170,33 +209,47 @@ function refresh_payments(payments, frm) {
|
||||
});
|
||||
}
|
||||
|
||||
function add_taxes(taxes, frm) {
|
||||
taxes.forEach((t) => {
|
||||
frm.add_child("taxes", {
|
||||
account_head: t.account_head,
|
||||
amount: t.tax_amount,
|
||||
});
|
||||
function refresh_taxes(d, frm) {
|
||||
d.taxes.forEach((t) => {
|
||||
const tax = frm.doc.taxes.find((tx) => tx.account_head === t.account_head && tx.rate === t.rate);
|
||||
if (tax) {
|
||||
tax.amount += flt(t.tax_amount);
|
||||
} else {
|
||||
frm.add_child("taxes", {
|
||||
account_head: t.account_head,
|
||||
rate: t.rate,
|
||||
amount: t.tax_amount,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function reset_values(frm) {
|
||||
frm.set_value("pos_invoices", []);
|
||||
frm.set_value("sales_invoices", []);
|
||||
frm.set_value("pos_transactions", []);
|
||||
frm.set_value("payment_reconciliation", []);
|
||||
frm.set_value("taxes", []);
|
||||
frm.set_value("grand_total", 0);
|
||||
frm.set_value("net_total", 0);
|
||||
frm.set_value("total_taxes_and_charges", 0);
|
||||
frm.set_value("total_quantity", 0);
|
||||
}
|
||||
|
||||
function refresh_fields(frm) {
|
||||
frm.refresh_field("pos_invoices");
|
||||
frm.refresh_field("sales_invoices");
|
||||
frm.refresh_field("pos_transactions");
|
||||
frm.refresh_field("payment_reconciliation");
|
||||
frm.refresh_field("taxes");
|
||||
frm.refresh_field("grand_total");
|
||||
frm.refresh_field("net_total");
|
||||
frm.refresh_field("total_taxes_and_charges");
|
||||
frm.refresh_field("total_quantity");
|
||||
}
|
||||
|
||||
function set_html_data(frm) {
|
||||
if (frm.doc.docstatus === 1 && frm.doc.status == "Submitted") {
|
||||
frappe.call({
|
||||
method: "get_payment_reconciliation_details",
|
||||
doc: frm.doc,
|
||||
callback: (r) => {
|
||||
frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,19 +20,17 @@
|
||||
"pos_profile",
|
||||
"user",
|
||||
"section_break_12",
|
||||
"pos_invoices",
|
||||
"sales_invoices",
|
||||
"taxes_and_charges_section",
|
||||
"taxes",
|
||||
"section_break_13",
|
||||
"column_break_16",
|
||||
"total_quantity",
|
||||
"column_break_ywgl",
|
||||
"net_total",
|
||||
"total_taxes_and_charges",
|
||||
"grand_total",
|
||||
"pos_transactions",
|
||||
"section_break_9",
|
||||
"payment_reconciliation_details",
|
||||
"section_break_11",
|
||||
"payment_reconciliation",
|
||||
"section_break_13",
|
||||
"grand_total",
|
||||
"net_total",
|
||||
"total_quantity",
|
||||
"column_break_16",
|
||||
"taxes",
|
||||
"failure_description_section",
|
||||
"error_message",
|
||||
"section_break_14",
|
||||
@@ -74,12 +72,10 @@
|
||||
"label": "User Details"
|
||||
},
|
||||
{
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -88,13 +84,11 @@
|
||||
},
|
||||
{
|
||||
"fetch_from": "pos_opening_entry.pos_profile",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "pos_profile",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "POS Profile",
|
||||
"options": "POS Profile",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -105,6 +99,16 @@
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus==1",
|
||||
"fieldname": "payment_reconciliation_details",
|
||||
"fieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -117,6 +121,7 @@
|
||||
"options": "POS Closing Entry Detail"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "section_break_13",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -171,12 +176,17 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_transactions",
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Transactions",
|
||||
"options": "POS Invoice Reference"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_opening_entry",
|
||||
"fieldtype": "Link",
|
||||
"label": "POS Opening Entry",
|
||||
"options": "POS Opening Entry",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -217,41 +227,8 @@
|
||||
"label": "Posting Time",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_invoices",
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Transactions",
|
||||
"options": "POS Invoice Reference",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_invoices",
|
||||
"fieldtype": "Table",
|
||||
"label": "Sales Invoice Transactions",
|
||||
"options": "Sales Invoice Reference",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "taxes_and_charges_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Taxes and Charges"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ywgl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_taxes_and_charges",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Taxes and Charges",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
{
|
||||
@@ -259,7 +236,7 @@
|
||||
"link_fieldname": "pos_closing_entry"
|
||||
}
|
||||
],
|
||||
"modified": "2025-06-06 12:00:31.955176",
|
||||
"modified": "2024-03-27 13:10:14.073467",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry",
|
||||
@@ -308,9 +285,8 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder import functions as fn
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import flt, get_datetime
|
||||
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
consolidate_pos_invoices,
|
||||
@@ -31,9 +28,8 @@ class POSClosingEntry(StatusUpdater):
|
||||
from erpnext.accounts.doctype.pos_closing_entry_taxes.pos_closing_entry_taxes import (
|
||||
POSClosingEntryTaxes,
|
||||
)
|
||||
from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import POSInvoiceReference
|
||||
from erpnext.accounts.doctype.sales_invoice_reference.sales_invoice_reference import (
|
||||
SalesInvoiceReference,
|
||||
from erpnext.accounts.doctype.pos_invoice_reference.pos_invoice_reference import (
|
||||
POSInvoiceReference,
|
||||
)
|
||||
|
||||
amended_from: DF.Link | None
|
||||
@@ -44,53 +40,30 @@ class POSClosingEntry(StatusUpdater):
|
||||
payment_reconciliation: DF.Table[POSClosingEntryDetail]
|
||||
period_end_date: DF.Datetime
|
||||
period_start_date: DF.Datetime
|
||||
pos_invoices: DF.Table[POSInvoiceReference]
|
||||
pos_opening_entry: DF.Link
|
||||
pos_profile: DF.Link
|
||||
pos_transactions: DF.Table[POSInvoiceReference]
|
||||
posting_date: DF.Date
|
||||
posting_time: DF.Time
|
||||
sales_invoices: DF.Table[SalesInvoiceReference]
|
||||
status: DF.Literal["Draft", "Submitted", "Queued", "Failed", "Cancelled"]
|
||||
taxes: DF.Table[POSClosingEntryTaxes]
|
||||
total_quantity: DF.Float
|
||||
total_taxes_and_charges: DF.Currency
|
||||
user: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.set_posting_date_and_time()
|
||||
self.fetch_invoice_type()
|
||||
self.validate_pos_opening_entry()
|
||||
self.validate_invoice_mode()
|
||||
self.posting_date = self.posting_date or frappe.utils.nowdate()
|
||||
self.posting_time = self.posting_time or frappe.utils.nowtime()
|
||||
|
||||
def set_posting_date_and_time(self):
|
||||
if self.posting_date:
|
||||
self.posting_date = frappe.utils.nowdate()
|
||||
if self.posting_time:
|
||||
self.posting_time = frappe.utils.nowtime()
|
||||
|
||||
def fetch_invoice_type(self):
|
||||
self.invoice_type = frappe.db.get_single_value("POS Settings", "invoice_type")
|
||||
|
||||
def validate_pos_opening_entry(self):
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
def validate_invoice_mode(self):
|
||||
if self.invoice_type == "POS Invoice":
|
||||
self.validate_duplicate_pos_invoices()
|
||||
self.validate_pos_invoices()
|
||||
|
||||
if self.invoice_type == "Sales Invoice":
|
||||
if len(self.pos_invoices) != 0:
|
||||
frappe.throw(_("POS Invoices can't be added when Sales Invoice is enabled"))
|
||||
|
||||
self.validate_duplicate_sales_invoices()
|
||||
self.validate_sales_invoices()
|
||||
self.validate_duplicate_pos_invoices()
|
||||
self.validate_pos_invoices()
|
||||
|
||||
def validate_duplicate_pos_invoices(self):
|
||||
pos_occurences = {}
|
||||
for idx, inv in enumerate(self.pos_invoices, 1):
|
||||
for idx, inv in enumerate(self.pos_transactions, 1):
|
||||
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
|
||||
|
||||
error_list = []
|
||||
@@ -105,7 +78,7 @@ class POSClosingEntry(StatusUpdater):
|
||||
|
||||
def validate_pos_invoices(self):
|
||||
invalid_rows = []
|
||||
for d in self.pos_invoices:
|
||||
for d in self.pos_transactions:
|
||||
invalid_row = {"idx": d.idx}
|
||||
pos_invoice = frappe.db.get_values(
|
||||
"POS Invoice",
|
||||
@@ -141,70 +114,13 @@ class POSClosingEntry(StatusUpdater):
|
||||
|
||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||
|
||||
def validate_duplicate_sales_invoices(self):
|
||||
sales_invoice_occurrences = {}
|
||||
for idx, inv in enumerate(self.sales_invoices, 1):
|
||||
sales_invoice_occurrences.setdefault(inv.sales_invoice, []).append(idx)
|
||||
|
||||
error_list = []
|
||||
for key, value in sales_invoice_occurrences.items():
|
||||
if len(value) > 1:
|
||||
error_list.append(
|
||||
_("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value))
|
||||
)
|
||||
|
||||
if error_list:
|
||||
frappe.throw(error_list, title=_("Duplicate Sales Invoices found"), as_list=True)
|
||||
|
||||
def validate_sales_invoices(self):
|
||||
invalid_rows = []
|
||||
for d in self.sales_invoices:
|
||||
invalid_row = {"idx": d.idx}
|
||||
sales_invoice = frappe.db.get_values(
|
||||
"Sales Invoice",
|
||||
d.sales_invoice,
|
||||
[
|
||||
"pos_profile",
|
||||
"docstatus",
|
||||
"is_pos",
|
||||
"owner",
|
||||
"is_created_using_pos",
|
||||
"is_consolidated",
|
||||
"pos_closing_entry",
|
||||
],
|
||||
as_dict=1,
|
||||
)[0]
|
||||
if sales_invoice.pos_closing_entry:
|
||||
invalid_row.setdefault("msg", []).append(_("Sales Invoice is already consolidated"))
|
||||
invalid_rows.append(invalid_row)
|
||||
continue
|
||||
if sales_invoice.is_pos == 0:
|
||||
invalid_row.setdefault("msg", []).append(_("Sales Invoice does not have Payments"))
|
||||
if sales_invoice.is_created_using_pos == 0:
|
||||
invalid_row.setdefault("msg", []).append(_("Sales Invoice is not created using POS"))
|
||||
if sales_invoice.pos_profile != self.pos_profile:
|
||||
invalid_row.setdefault("msg", []).append(
|
||||
_("POS Profile doesn't match {}").format(frappe.bold(self.pos_profile))
|
||||
)
|
||||
if sales_invoice.docstatus != 1:
|
||||
invalid_row.setdefault("msg", []).append(_("Sales Invoice is not submitted"))
|
||||
if sales_invoice.owner != self.user:
|
||||
invalid_row.setdefault("msg", []).append(
|
||||
_("Sales Invoice isn't created by user {}").format(frappe.bold(self.owner))
|
||||
)
|
||||
|
||||
if invalid_row.get("msg"):
|
||||
invalid_rows.append(invalid_row)
|
||||
|
||||
if not invalid_rows:
|
||||
return
|
||||
|
||||
error_list = []
|
||||
for row in invalid_rows:
|
||||
for msg in row.get("msg"):
|
||||
error_list.append(_("Row #{}: {}").format(row.get("idx"), msg))
|
||||
|
||||
frappe.throw(error_list, title=_("Invalid Sales Invoices"), as_list=True)
|
||||
@frappe.whitelist()
|
||||
def get_payment_reconciliation_details(self):
|
||||
currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
return frappe.render_template(
|
||||
"erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
||||
{"data": self, "currency": currency},
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
consolidate_pos_invoices(closing_entry=self)
|
||||
@@ -214,13 +130,9 @@ class POSClosingEntry(StatusUpdater):
|
||||
docname=f"POS Opening Entry/{self.pos_opening_entry}",
|
||||
)
|
||||
|
||||
self.update_sales_invoices_closing_entry()
|
||||
|
||||
def on_cancel(self):
|
||||
unconsolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
self.update_sales_invoices_closing_entry(cancel=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def retry(self):
|
||||
consolidate_pos_invoices(closing_entry=self)
|
||||
@@ -231,12 +143,6 @@ class POSClosingEntry(StatusUpdater):
|
||||
opening_entry.set_status()
|
||||
opening_entry.save()
|
||||
|
||||
def update_sales_invoices_closing_entry(self, cancel=False):
|
||||
for d in self.sales_invoices:
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice", d.sales_invoice, "pos_closing_entry", self.name if not cancel else None
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@@ -246,79 +152,23 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoices(start, end, pos_profile, user):
|
||||
invoice_doctype = frappe.db.get_single_value("POS Settings", "invoice_type")
|
||||
|
||||
sales_inv_query = build_invoice_query("Sales Invoice", user, pos_profile, start, end)
|
||||
|
||||
query = sales_inv_query
|
||||
|
||||
if invoice_doctype == "POS Invoice":
|
||||
pos_inv_query = build_invoice_query("POS Invoice", user, pos_profile, start, end)
|
||||
query = query + pos_inv_query
|
||||
|
||||
query = query.orderby(query.timestamp)
|
||||
invoices = query.run(as_dict=1)
|
||||
|
||||
data = {"invoices": invoices, "payments": get_payments(invoices), "taxes": get_taxes(invoices)}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_payments(invoices):
|
||||
if not len(invoices):
|
||||
return
|
||||
|
||||
invoices_name = [d.name for d in invoices]
|
||||
|
||||
SalesInvoicePayment = DocType("Sales Invoice Payment")
|
||||
query = (
|
||||
frappe.qb.from_(SalesInvoicePayment)
|
||||
.where(
|
||||
(SalesInvoicePayment.parenttype.isin(["Sales Invoice", "POS Invoice"]))
|
||||
& (SalesInvoicePayment.parent.isin(invoices_name))
|
||||
)
|
||||
.groupby(SalesInvoicePayment.mode_of_payment)
|
||||
.select(
|
||||
SalesInvoicePayment.mode_of_payment,
|
||||
SalesInvoicePayment.account,
|
||||
fn.Sum(SalesInvoicePayment.amount).as_("amount"),
|
||||
)
|
||||
def get_pos_invoices(start, end, pos_profile, user):
|
||||
data = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name, timestamp(posting_date, posting_time) as "timestamp"
|
||||
from
|
||||
`tabPOS Invoice`
|
||||
where
|
||||
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
|
||||
""",
|
||||
(user, pos_profile),
|
||||
as_dict=1,
|
||||
)
|
||||
data = query.run(as_dict=1)
|
||||
|
||||
change_amount_by_account = {}
|
||||
for d in invoices:
|
||||
change_amount_by_account.setdefault(d.account_for_change_amount, 0)
|
||||
change_amount_by_account[d.account_for_change_amount] += flt(d.change_amount)
|
||||
|
||||
for d in data:
|
||||
if change_amount_by_account.get(d.account):
|
||||
d.amount -= flt(change_amount_by_account.get(d.account))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_taxes(invoices):
|
||||
if not len(invoices):
|
||||
return
|
||||
|
||||
invoices_name = [d.name for d in invoices]
|
||||
|
||||
SalesInvoiceTaxesCharges = DocType("Sales Taxes and Charges")
|
||||
query = (
|
||||
frappe.qb.from_(SalesInvoiceTaxesCharges)
|
||||
.where(
|
||||
(SalesInvoiceTaxesCharges.parenttype.isin(["Sales Invoice", "POS Invoice"]))
|
||||
& (SalesInvoiceTaxesCharges.parent.isin(invoices_name))
|
||||
)
|
||||
.groupby(SalesInvoiceTaxesCharges.account_head)
|
||||
.select(
|
||||
SalesInvoiceTaxesCharges.account_head,
|
||||
fn.Sum(SalesInvoiceTaxesCharges.tax_amount_after_discount_amount).as_("tax_amount"),
|
||||
)
|
||||
)
|
||||
data = query.run(as_dict=1)
|
||||
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
||||
# need to get taxes and payments so can't avoid get_doc
|
||||
data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data]
|
||||
|
||||
return data
|
||||
|
||||
@@ -334,98 +184,69 @@ def make_closing_entry_from_opening(opening_entry):
|
||||
closing_entry.grand_total = 0
|
||||
closing_entry.net_total = 0
|
||||
closing_entry.total_quantity = 0
|
||||
closing_entry.total_taxes_and_charges = 0
|
||||
|
||||
data = get_invoices(
|
||||
invoices = get_pos_invoices(
|
||||
closing_entry.period_start_date,
|
||||
closing_entry.period_end_date,
|
||||
closing_entry.pos_profile,
|
||||
closing_entry.user,
|
||||
)
|
||||
|
||||
pos_invoices = []
|
||||
sales_invoices = []
|
||||
taxes = [
|
||||
frappe._dict({"account_head": tx.account_head, "amount": tx.tax_amount}) for tx in data.get("taxes")
|
||||
]
|
||||
payments = [
|
||||
frappe._dict(
|
||||
{
|
||||
"mode_of_payment": p.mode_of_payment,
|
||||
"opening_amount": 0,
|
||||
"expected_amount": p.amount,
|
||||
}
|
||||
pos_transactions = []
|
||||
taxes = []
|
||||
payments = []
|
||||
for detail in opening_entry.balance_details:
|
||||
payments.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"mode_of_payment": detail.mode_of_payment,
|
||||
"opening_amount": detail.opening_amount,
|
||||
"expected_amount": detail.opening_amount,
|
||||
}
|
||||
)
|
||||
)
|
||||
for p in data.get("payments")
|
||||
]
|
||||
|
||||
for d in data.get("invoices"):
|
||||
invoice = "pos_invoice" if d.doctype == "POS Invoice" else "sales_invoice"
|
||||
invoice_data = frappe._dict(
|
||||
{
|
||||
invoice: d.name,
|
||||
"posting_date": d.posting_date,
|
||||
"grand_total": d.grand_total,
|
||||
"customer": d.customer,
|
||||
"is_return": d.is_return,
|
||||
"return_against": d.return_against,
|
||||
}
|
||||
for d in invoices:
|
||||
pos_transactions.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"pos_invoice": d.name,
|
||||
"posting_date": d.posting_date,
|
||||
"grand_total": d.grand_total,
|
||||
"customer": d.customer,
|
||||
}
|
||||
)
|
||||
)
|
||||
if d.doctype == "POS Invoice":
|
||||
pos_invoices.append(invoice_data)
|
||||
else:
|
||||
sales_invoices.append(invoice_data)
|
||||
|
||||
closing_entry.grand_total += flt(d.grand_total)
|
||||
closing_entry.net_total += flt(d.net_total)
|
||||
closing_entry.total_quantity += flt(d.total_qty)
|
||||
closing_entry.total_taxes_and_charges += flt(d.total_taxes_and_charges)
|
||||
|
||||
closing_entry.set("pos_invoices", pos_invoices)
|
||||
closing_entry.set("sales_invoices", sales_invoices)
|
||||
for t in d.taxes:
|
||||
existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate]
|
||||
if existing_tax:
|
||||
existing_tax[0].amount += flt(t.tax_amount)
|
||||
else:
|
||||
taxes.append(
|
||||
frappe._dict({"account_head": t.account_head, "rate": t.rate, "amount": t.tax_amount})
|
||||
)
|
||||
|
||||
for p in d.payments:
|
||||
existing_pay = [pay for pay in payments if pay.mode_of_payment == p.mode_of_payment]
|
||||
if existing_pay:
|
||||
existing_pay[0].expected_amount += flt(p.amount)
|
||||
else:
|
||||
payments.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"mode_of_payment": p.mode_of_payment,
|
||||
"opening_amount": 0,
|
||||
"expected_amount": p.amount,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
closing_entry.set("pos_transactions", pos_transactions)
|
||||
closing_entry.set("payment_reconciliation", payments)
|
||||
closing_entry.set("taxes", taxes)
|
||||
|
||||
return closing_entry
|
||||
|
||||
|
||||
def build_invoice_query(invoice_doctype, user, pos_profile, start, end):
|
||||
InvoiceDocType = DocType(invoice_doctype)
|
||||
query = (
|
||||
frappe.qb.from_(InvoiceDocType)
|
||||
.select(
|
||||
InvoiceDocType.name,
|
||||
InvoiceDocType.customer,
|
||||
InvoiceDocType.posting_date,
|
||||
InvoiceDocType.grand_total,
|
||||
InvoiceDocType.net_total,
|
||||
InvoiceDocType.total_qty,
|
||||
InvoiceDocType.total_taxes_and_charges,
|
||||
InvoiceDocType.change_amount,
|
||||
InvoiceDocType.account_for_change_amount,
|
||||
InvoiceDocType.is_return,
|
||||
InvoiceDocType.return_against,
|
||||
fn.Timestamp(InvoiceDocType.posting_date, InvoiceDocType.posting_time).as_("timestamp"),
|
||||
ConstantColumn(invoice_doctype).as_("doctype"),
|
||||
)
|
||||
.where(
|
||||
(InvoiceDocType.owner == user)
|
||||
& (InvoiceDocType.docstatus == 1)
|
||||
& (InvoiceDocType.is_pos == 1)
|
||||
& (InvoiceDocType.pos_profile == pos_profile)
|
||||
& (
|
||||
(fn.Timestamp(InvoiceDocType.posting_date, InvoiceDocType.posting_time) >= start)
|
||||
& (fn.Timestamp(InvoiceDocType.posting_date, InvoiceDocType.posting_time) <= end)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if invoice_doctype == "POS Invoice":
|
||||
query = query.where(fn.IfNull(InvoiceDocType.consolidated_invoice, "").eq(""))
|
||||
else:
|
||||
query = query.where(
|
||||
(InvoiceDocType.is_created_using_pos == 1)
|
||||
& fn.IfNull(InvoiceDocType.pos_closing_entry, "").eq("")
|
||||
)
|
||||
|
||||
return query
|
||||
|
||||
@@ -12,10 +12,10 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
|
||||
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
|
||||
make_closing_entry_from_opening,
|
||||
)
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.selling.page.point_of_sale.point_of_sale import get_items
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
@@ -25,18 +25,8 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
|
||||
class TestPOSClosingEntry(IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.db.sql("delete from `tabPOS Opening Entry`")
|
||||
cls.enterClassContext(cls.change_settings("POS Settings", {"invoice_type": "POS Invoice"}))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabPOS Opening Entry`")
|
||||
|
||||
def setUp(self):
|
||||
# Make stock available for POS Sales
|
||||
frappe.db.sql("delete from `tabPOS Opening Entry`")
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
|
||||
|
||||
def tearDown(self):
|
||||
@@ -92,8 +82,6 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
||||
"""
|
||||
Test if quantity is calculated correctly for an item in POS Closing Entry
|
||||
"""
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
@@ -171,10 +159,6 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
||||
"""
|
||||
|
||||
create_dimension()
|
||||
location = frappe.get_doc("Accounting Dimension", "Location")
|
||||
location.dimension_defaults[0].mandatory_for_bs = True
|
||||
location.save()
|
||||
|
||||
pos_profile = make_pos_profile(do_not_insert=1, do_not_set_accounting_dimension=1)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos_profile.insert)
|
||||
@@ -212,6 +196,9 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
|
||||
init_user_and_profile,
|
||||
)
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
consolidate_pos_invoices,
|
||||
)
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
@@ -302,172 +289,6 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
||||
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||
self.assertEqual(batch_qty_with_pos, 10.0)
|
||||
|
||||
@IntegrationTestCase.change_settings("POS Settings", {"invoice_type": "Sales Invoice"})
|
||||
def test_closing_entries_with_sales_invoice(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_si = create_sales_invoice(
|
||||
qty=10, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
|
||||
)
|
||||
pos_si.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
|
||||
pos_si.save()
|
||||
pos_si.submit()
|
||||
|
||||
pos_si2 = create_sales_invoice(
|
||||
qty=5, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=11
|
||||
)
|
||||
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
|
||||
pos_si2.save()
|
||||
pos_si2.submit()
|
||||
|
||||
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||
payment = pcv_doc.payment_reconciliation[0]
|
||||
|
||||
self.assertEqual(payment.mode_of_payment, "Cash")
|
||||
|
||||
for d in pcv_doc.payment_reconciliation:
|
||||
if d.mode_of_payment == "Cash":
|
||||
d.closing_amount = 1500
|
||||
|
||||
pcv_doc.submit()
|
||||
|
||||
self.assertEqual(pcv_doc.total_quantity, 15)
|
||||
self.assertEqual(pcv_doc.net_total, 1500)
|
||||
|
||||
pos_si2.reload()
|
||||
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc.name)
|
||||
|
||||
def test_sales_invoice_in_pos_invoice_mode(self):
|
||||
"""
|
||||
Test Sales Invoice and Return Sales Invoice creation during POS Invoice mode.
|
||||
"""
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
|
||||
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
|
||||
|
||||
pos_inv = create_pos_invoice(rate=100, do_not_save=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
self.assertRaises(frappe.ValidationError, pos_inv.save)
|
||||
|
||||
pcv_doc1 = make_closing_entry_from_opening(opening_entry1)
|
||||
for d in pcv_doc1.payment_reconciliation:
|
||||
if d.mode_of_payment == "Cash":
|
||||
d.closing_amount = 300
|
||||
|
||||
pcv_doc1.submit()
|
||||
self.assertTrue(pcv_doc1.name)
|
||||
|
||||
pos_si1.reload()
|
||||
pos_si2.reload()
|
||||
self.assertEqual(pos_si1.pos_closing_entry, pcv_doc1.name)
|
||||
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc1.name)
|
||||
|
||||
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
|
||||
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
|
||||
|
||||
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
|
||||
pos_si3 = create_sales_invoice(
|
||||
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
|
||||
)
|
||||
pos_si3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
self.assertRaises(frappe.ValidationError, pos_si3.save)
|
||||
|
||||
# Trying to create Return Sales Invoice.
|
||||
pos_rsi1 = make_sales_return(pos_si1.name)
|
||||
pos_rsi1.save()
|
||||
pos_rsi1.submit()
|
||||
|
||||
self.assertEqual(pos_rsi1.paid_amount, -100)
|
||||
|
||||
pcv_doc2 = make_closing_entry_from_opening(opening_entry2)
|
||||
pcv_doc2.submit()
|
||||
|
||||
self.assertTrue(pcv_doc2.name)
|
||||
|
||||
pos_rsi1.reload()
|
||||
self.assertEqual(pos_rsi1.pos_closing_entry, pcv_doc2.name)
|
||||
|
||||
self.assertIn(pos_inv1.name, [d.pos_invoice for d in pcv_doc2.pos_invoices])
|
||||
self.assertNotIn(pos_inv2.name, [d.sales_invoice for d in pcv_doc2.sales_invoices])
|
||||
self.assertIn(pos_rsi1.name, [d.sales_invoice for d in pcv_doc2.sales_invoices])
|
||||
self.assertEqual(pcv_doc2.grand_total, 200)
|
||||
|
||||
def test_pos_invoice_in_sales_invoice_mode(self):
|
||||
"""
|
||||
Test POS Invoice and Return POS Invoice creation during Sales Invoice mode.
|
||||
"""
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
|
||||
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
|
||||
|
||||
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
|
||||
pos_sinv = create_sales_invoice(
|
||||
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
|
||||
)
|
||||
pos_sinv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
self.assertRaises(frappe.ValidationError, pos_sinv.save)
|
||||
|
||||
pcv_doc1 = make_closing_entry_from_opening(opening_entry1)
|
||||
for d in pcv_doc1.payment_reconciliation:
|
||||
if d.mode_of_payment == "Cash":
|
||||
d.closing_amount = 300
|
||||
|
||||
pcv_doc1.submit()
|
||||
|
||||
self.assertTrue(pcv_doc1.name)
|
||||
|
||||
self.assertIn(pos_inv1.name, [d.pos_invoice for d in pcv_doc1.pos_invoices])
|
||||
self.assertEqual(pcv_doc1.grand_total, 300)
|
||||
|
||||
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
|
||||
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
|
||||
|
||||
pos_inv3 = create_pos_invoice(rate=100, do_not_save=1)
|
||||
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
self.assertRaises(frappe.ValidationError, pos_inv3.save)
|
||||
|
||||
# Creating Return POS Invoice
|
||||
pos_rinv2 = make_sales_return(pos_inv2.name)
|
||||
pos_rinv2.save()
|
||||
pos_rinv2.submit()
|
||||
|
||||
pos_rinv2.reload()
|
||||
self.assertIsNotNone(pos_rinv2.consolidated_invoice)
|
||||
|
||||
# Getting Sales Invoice created during POS Invoice submission.
|
||||
pos_rinv2_si = frappe.get_doc("Sales Invoice", pos_rinv2.consolidated_invoice)
|
||||
self.assertEqual(pos_rinv2_si.is_return, 1)
|
||||
self.assertEqual(pos_rinv2_si.paid_amount, -200)
|
||||
|
||||
pcv_doc2 = make_closing_entry_from_opening(opening_entry2)
|
||||
for d in pcv_doc1.payment_reconciliation:
|
||||
if d.mode_of_payment == "Cash":
|
||||
d.closing_amount = 100
|
||||
|
||||
pcv_doc2.submit()
|
||||
self.assertTrue(pcv_doc2.name)
|
||||
|
||||
pos_si1.reload()
|
||||
pos_si2.reload()
|
||||
pos_rinv2_si.reload()
|
||||
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc2.name)
|
||||
self.assertEqual(pos_rinv2_si.pos_closing_entry, pcv_doc2.name)
|
||||
|
||||
|
||||
def init_user_and_profile(**args):
|
||||
user = "test@example.com"
|
||||
@@ -502,31 +323,3 @@ def get_test_item_qty(pos_profile):
|
||||
"actual_qty"
|
||||
)
|
||||
return test_item_qty
|
||||
|
||||
|
||||
def create_multiple_sales_invoices(pos_profile):
|
||||
pos_si1 = create_sales_invoice(qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1)
|
||||
pos_si1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
pos_si1.save()
|
||||
pos_si1.submit()
|
||||
|
||||
pos_si2 = create_sales_invoice(qty=2, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1)
|
||||
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 200})
|
||||
pos_si2.save()
|
||||
pos_si2.submit()
|
||||
|
||||
return pos_si1, pos_si2
|
||||
|
||||
|
||||
def create_multiple_pos_invoices(pos_profile):
|
||||
pos_inv1 = create_pos_invoice(pos_profile=pos_profile.name, rate=100, do_not_save=1)
|
||||
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
pos_inv1.save()
|
||||
pos_inv1.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(pos_profile=pos_profile.name, qty=2, do_not_save=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 200})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
|
||||
return pos_inv1, pos_inv2
|
||||
|
||||
@@ -6,9 +6,17 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account_head",
|
||||
"rate",
|
||||
"amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Rate",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
@@ -27,16 +35,15 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-06 11:54:02.414461",
|
||||
"modified": "2024-03-27 13:10:14.420657",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry Taxes",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ class POSClosingEntryTaxes(Document):
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
rate: DF.Percent
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
||||
@@ -323,15 +323,3 @@ frappe.ui.form.on("POS Invoice", {
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Sales Invoice Payment", {
|
||||
mode_of_payment: function (frm) {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "set_account_for_mode_of_payment",
|
||||
callback: function (r) {
|
||||
refresh_field("payments");
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.mapper import map_child_doc, map_doc
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
@@ -18,10 +17,13 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
)
|
||||
from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.controllers.queries import item_query as _item_query
|
||||
from erpnext.controllers.sales_and_purchase_return import get_sales_invoice_item_from_consolidated_invoice
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
|
||||
class PartialPaymentValidationError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class POSInvoice(SalesInvoice):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
@@ -195,7 +197,6 @@ class POSInvoice(SalesInvoice):
|
||||
# run on validate method of selling controller
|
||||
super(SalesInvoice, self).validate()
|
||||
self.validate_pos_opening_entry()
|
||||
self.validate_is_pos_using_sales_invoice()
|
||||
self.validate_auto_set_posting_time()
|
||||
self.validate_mode_of_payment()
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
@@ -243,9 +244,6 @@ class POSInvoice(SalesInvoice):
|
||||
update_coupon_code_count(self.coupon_code, "used")
|
||||
self.clear_unallocated_mode_of_payments()
|
||||
|
||||
if self.is_return and self.invoice_type_in_pos == "Sales Invoice":
|
||||
self.create_and_add_consolidated_sales_invoice()
|
||||
|
||||
def before_cancel(self):
|
||||
if (
|
||||
self.consolidated_invoice
|
||||
@@ -276,8 +274,6 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc.delete_loyalty_point_entry()
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
|
||||
|
||||
@@ -291,47 +287,6 @@ class POSInvoice(SalesInvoice):
|
||||
sip = frappe.qb.DocType("Sales Invoice Payment")
|
||||
frappe.qb.from_(sip).delete().where(sip.parent == self.name).where(sip.amount == 0).run()
|
||||
|
||||
def create_and_add_consolidated_sales_invoice(self):
|
||||
sales_inv = self.create_return_sales_invoice()
|
||||
self.db_set("consolidated_invoice", sales_inv.name)
|
||||
self.set_status(update=True)
|
||||
|
||||
def create_return_sales_invoice(self):
|
||||
return_sales_invoice = frappe.new_doc("Sales Invoice")
|
||||
return_sales_invoice.is_pos = 1
|
||||
return_sales_invoice.is_return = 1
|
||||
map_doc(self, return_sales_invoice, table_map={"doctype": return_sales_invoice.doctype})
|
||||
return_sales_invoice.is_created_using_pos = 1
|
||||
return_sales_invoice.is_consolidated = 1
|
||||
return_sales_invoice.return_against = frappe.db.get_value(
|
||||
"POS Invoice", self.return_against, "consolidated_invoice"
|
||||
)
|
||||
items, taxes, payments = [], [], []
|
||||
for d in self.items:
|
||||
si_item = map_child_doc(d, return_sales_invoice, {"doctype": "Sales Invoice Item"})
|
||||
si_item.pos_invoice = self.name
|
||||
si_item.pos_invoice_item = d.name
|
||||
si_item.sales_invoice_item = get_sales_invoice_item_from_consolidated_invoice(
|
||||
self.return_against, d.pos_invoice_item
|
||||
)
|
||||
items.append(si_item)
|
||||
|
||||
for d in self.get("taxes"):
|
||||
tax = map_child_doc(d, return_sales_invoice, {"doctype": "Sales Taxes and Charges"})
|
||||
taxes.append(tax)
|
||||
|
||||
for d in self.get("payments"):
|
||||
payment = map_child_doc(d, return_sales_invoice, {"doctype": "Sales Invoice Payment"})
|
||||
payments.append(payment)
|
||||
|
||||
return_sales_invoice.set("items", items)
|
||||
return_sales_invoice.set("taxes", taxes)
|
||||
return_sales_invoice.set("payments", payments)
|
||||
return_sales_invoice.save()
|
||||
return_sales_invoice.submit()
|
||||
|
||||
return return_sales_invoice
|
||||
|
||||
def delink_serial_and_batch_bundle(self):
|
||||
for row in self.items:
|
||||
if row.serial_and_batch_bundle:
|
||||
@@ -423,11 +378,6 @@ class POSInvoice(SalesInvoice):
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
|
||||
def validate_is_pos_using_sales_invoice(self):
|
||||
self.invoice_type_in_pos = frappe.db.get_single_value("POS Settings", "invoice_type")
|
||||
if self.invoice_type_in_pos == "Sales Invoice" and not self.is_return:
|
||||
frappe.throw(_("Sales Invoice mode is activated in POS. Please create Sales Invoice instead."))
|
||||
|
||||
def validate_serialised_or_batched_item(self):
|
||||
error_msg = []
|
||||
for d in self.get("items"):
|
||||
@@ -552,6 +502,20 @@ class POSInvoice(SalesInvoice):
|
||||
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
|
||||
validate_loyalty_points(self, self.loyalty_points)
|
||||
|
||||
def validate_full_payment(self):
|
||||
invoice_total = flt(self.rounded_total) or flt(self.grand_total)
|
||||
|
||||
if self.docstatus == 1:
|
||||
if self.is_return and self.paid_amount != invoice_total:
|
||||
frappe.throw(
|
||||
msg=_("Partial Payment in POS Invoice is not allowed."), exc=PartialPaymentValidationError
|
||||
)
|
||||
|
||||
if self.paid_amount < invoice_total:
|
||||
frappe.throw(
|
||||
msg=_("Partial Payment in POS Invoice is not allowed."), exc=PartialPaymentValidationError
|
||||
)
|
||||
|
||||
def set_status(self, update=False, status=None, update_modified=True):
|
||||
if self.is_new():
|
||||
if self.get("amended_from"):
|
||||
|
||||
@@ -7,12 +7,8 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
|
||||
set_default_account_for_mode_of_payment,
|
||||
)
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import PartialPaymentValidationError, make_sales_return
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import PartialPaymentValidationError
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
@@ -29,7 +25,6 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
|
||||
cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice"))
|
||||
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
@@ -37,15 +32,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||
|
||||
cls.test_user, cls.pos_profile = init_user_and_profile()
|
||||
cls.opening_entry = create_opening_entry(cls.pos_profile, cls.test_user.name)
|
||||
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
|
||||
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
opening_entry_doc = frappe.get_doc("POS Opening Entry", cls.opening_entry.name)
|
||||
opening_entry_doc.cancel()
|
||||
create_opening_entry(cls.pos_profile, cls.test_user)
|
||||
|
||||
def tearDown(self):
|
||||
if frappe.session.user != "Administrator":
|
||||
@@ -248,8 +235,12 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
pos = create_pos_invoice(qty=10, do_not_save=True)
|
||||
|
||||
pos.set("payments", [])
|
||||
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 500})
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "amount": 500, "default": 1})
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
|
||||
)
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500, "default": 1}
|
||||
)
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
|
||||
@@ -288,7 +279,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "amount": 1000, "default": 1})
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
@@ -329,7 +322,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "amount": 2000, "default": 1})
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2000, "default": 1}
|
||||
)
|
||||
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
@@ -340,7 +335,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
# partial return 1
|
||||
pos_return1.get("items")[0].qty = -1
|
||||
pos_return1.set("payments", [])
|
||||
pos_return1.append("payments", {"mode_of_payment": "Cash", "amount": -1000, "default": 1})
|
||||
pos_return1.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
|
||||
)
|
||||
pos_return1.paid_amount = -1000
|
||||
pos_return1.submit()
|
||||
pos_return1.reload()
|
||||
@@ -357,7 +354,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
# partial return 2
|
||||
pos_return2 = make_sales_return(pos.name)
|
||||
pos_return2.set("payments", [])
|
||||
pos_return2.append("payments", {"mode_of_payment": "Cash", "amount": -1000, "default": 1})
|
||||
pos_return2.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
|
||||
)
|
||||
pos_return2.paid_amount = -1000
|
||||
pos_return2.submit()
|
||||
|
||||
@@ -377,8 +376,10 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
)
|
||||
|
||||
pos.set("payments", [])
|
||||
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 50})
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "amount": 60, "default": 1})
|
||||
pos.append("payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 50})
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60, "default": 1}
|
||||
)
|
||||
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
@@ -396,7 +397,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
pos_inv = create_pos_invoice(rate=10000, do_not_save=1)
|
||||
pos_inv.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 9000},
|
||||
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 9000},
|
||||
)
|
||||
pos_inv.insert()
|
||||
self.assertRaises(PartialPaymentValidationError, pos_inv.submit)
|
||||
@@ -428,7 +429,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
@@ -447,7 +450,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
|
||||
pos2.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
|
||||
pos2.insert()
|
||||
self.assertRaises(frappe.ValidationError, pos2.submit)
|
||||
@@ -497,7 +502,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
|
||||
pos2.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
|
||||
pos2.insert()
|
||||
self.assertRaises(frappe.ValidationError, pos2.submit)
|
||||
@@ -562,7 +569,9 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
)
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.set("payments", [])
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "amount": 1000, "default": 1})
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
pos = pos.save().submit()
|
||||
|
||||
# make a return
|
||||
@@ -608,7 +617,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
|
||||
inv.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 10000},
|
||||
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
|
||||
)
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
@@ -640,7 +649,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
pos_inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
|
||||
pos_inv.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 10000},
|
||||
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
|
||||
)
|
||||
pos_inv.paid_amount = 10000
|
||||
pos_inv.submit()
|
||||
@@ -655,7 +664,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
inv.loyalty_amount = inv.loyalty_points * before_lp_details.conversion_factor
|
||||
inv.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 10000 - inv.loyalty_amount},
|
||||
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000 - inv.loyalty_amount},
|
||||
)
|
||||
inv.paid_amount = 10000
|
||||
inv.submit()
|
||||
@@ -676,12 +685,12 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270})
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 270})
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 3200})
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
|
||||
@@ -702,7 +711,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
|
||||
pos_inv.append(
|
||||
"taxes",
|
||||
{
|
||||
@@ -719,7 +728,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
|
||||
pos_inv2.additional_discount_percentage = 10
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 540})
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 540})
|
||||
pos_inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
@@ -757,7 +766,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
|
||||
pos_inv.append(
|
||||
"taxes",
|
||||
{
|
||||
@@ -772,7 +781,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||
|
||||
pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "amount": 400})
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 400})
|
||||
pos_inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
@@ -817,7 +826,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
pos_inv1 = create_pos_invoice(item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1)
|
||||
pos_inv1.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 4500},
|
||||
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 4500},
|
||||
)
|
||||
pos_inv1.items[0].batch_no = batch_no
|
||||
pos_inv1.save()
|
||||
@@ -838,7 +847,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
)
|
||||
pos_inv2.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 3000},
|
||||
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3000},
|
||||
)
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
@@ -878,7 +887,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
)
|
||||
pos_inv1.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 300},
|
||||
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300},
|
||||
)
|
||||
pos_inv1.save()
|
||||
pos_inv1.submit()
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc, map_doc
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
||||
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
@@ -16,7 +16,6 @@ from frappe.utils.scheduler import is_scheduler_inactive
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_checks_for_pl_and_bs_accounts,
|
||||
)
|
||||
from erpnext.controllers.sales_and_purchase_return import get_sales_invoice_item_from_consolidated_invoice
|
||||
from erpnext.controllers.taxes_and_totals import ItemWiseTaxDetail
|
||||
|
||||
|
||||
@@ -239,7 +238,7 @@ class POSInvoiceMergeLog(Document):
|
||||
si_item.pos_invoice = doc.name
|
||||
si_item.pos_invoice_item = item.name
|
||||
if doc.is_return:
|
||||
si_item.sales_invoice_item = get_sales_invoice_item_from_consolidated_invoice(
|
||||
si_item.sales_invoice_item = get_sales_invoice_item(
|
||||
doc.return_against, item.pos_invoice_item
|
||||
)
|
||||
if item.serial_and_batch_bundle:
|
||||
@@ -259,7 +258,6 @@ class POSInvoiceMergeLog(Document):
|
||||
if not found:
|
||||
tax.charge_type = "Actual"
|
||||
tax.idx = idx
|
||||
tax.row_id = None
|
||||
idx += 1
|
||||
tax.included_in_print_rate = 0
|
||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||
@@ -305,17 +303,10 @@ class POSInvoiceMergeLog(Document):
|
||||
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
|
||||
accounting_dimensions_fields = [d.fieldname for d in accounting_dimensions]
|
||||
dimension_values = frappe.db.get_value(
|
||||
"POS Profile",
|
||||
{"name": invoice.pos_profile},
|
||||
[*accounting_dimensions_fields, "cost_center", "project"],
|
||||
as_dict=1,
|
||||
"POS Profile", {"name": invoice.pos_profile}, accounting_dimensions_fields, as_dict=1
|
||||
)
|
||||
for dimension in accounting_dimensions:
|
||||
dimension_value = (
|
||||
data[0].get(dimension.fieldname)
|
||||
if data[0].get(dimension.fieldname)
|
||||
else dimension_values.get(dimension.fieldname)
|
||||
)
|
||||
dimension_value = dimension_values.get(dimension.fieldname)
|
||||
|
||||
if not dimension_value and (dimension.mandatory_for_pl or dimension.mandatory_for_bs):
|
||||
frappe.throw(
|
||||
@@ -327,14 +318,6 @@ class POSInvoiceMergeLog(Document):
|
||||
|
||||
invoice.set(dimension.fieldname, dimension_value)
|
||||
|
||||
invoice.set(
|
||||
"cost_center",
|
||||
data[0].get("cost_center") if data[0].get("cost_center") else dimension_values.get("cost_center"),
|
||||
)
|
||||
invoice.set(
|
||||
"project", data[0].get("project") if data[0].get("project") else dimension_values.get("project")
|
||||
)
|
||||
|
||||
if self.merge_invoices_based_on == "Customer Group":
|
||||
invoice.flags.ignore_pos_profile = True
|
||||
invoice.pos_profile = ""
|
||||
@@ -354,7 +337,7 @@ class POSInvoiceMergeLog(Document):
|
||||
for doc in invoice_docs:
|
||||
doc.load_from_db()
|
||||
inv = sales_invoice
|
||||
if doc.is_return and credit_notes:
|
||||
if doc.is_return:
|
||||
for key, value in credit_notes.items():
|
||||
if doc.name in value:
|
||||
inv = key
|
||||
@@ -463,37 +446,12 @@ def get_invoice_customer_map(pos_invoices):
|
||||
pos_invoice_customer_map.setdefault(customer, [])
|
||||
pos_invoice_customer_map[customer].append(invoice)
|
||||
|
||||
for customer, invoices in pos_invoice_customer_map.items():
|
||||
pos_invoice_customer_map[customer] = split_invoices_by_accounting_dimension(invoices)
|
||||
|
||||
return pos_invoice_customer_map
|
||||
|
||||
|
||||
def split_invoices_by_accounting_dimension(pos_invoices):
|
||||
# pos_invoices = {
|
||||
# {'dim_field1': 'dim_field1_value1', 'dim_field2': 'dim_field2_value1'}: [],
|
||||
# {'dim_field1': 'dim_field1_value2', 'dim_field2': 'dim_field2_value1'}: []
|
||||
# }
|
||||
pos_invoice_accounting_dimensions_map = {}
|
||||
for invoice in pos_invoices:
|
||||
dimension_fields = [d.fieldname for d in get_checks_for_pl_and_bs_accounts()]
|
||||
accounting_dimensions = frappe.db.get_value(
|
||||
"POS Invoice", invoice.pos_invoice, [*dimension_fields, "cost_center", "project"], as_dict=1
|
||||
)
|
||||
|
||||
accounting_dimensions_dic_hash = hashlib.sha256(
|
||||
json.dumps(accounting_dimensions).encode()
|
||||
).hexdigest()
|
||||
|
||||
pos_invoice_accounting_dimensions_map.setdefault(accounting_dimensions_dic_hash, [])
|
||||
pos_invoice_accounting_dimensions_map[accounting_dimensions_dic_hash].append(invoice)
|
||||
|
||||
return pos_invoice_accounting_dimensions_map
|
||||
|
||||
|
||||
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
|
||||
invoices = pos_invoices or (closing_entry and closing_entry.get("pos_invoices"))
|
||||
if frappe.in_test and not invoices:
|
||||
invoices = pos_invoices or (closing_entry and closing_entry.get("pos_transactions"))
|
||||
if frappe.flags.in_test and not invoices:
|
||||
invoices = get_all_unconsolidated_invoices()
|
||||
|
||||
invoice_by_customer = get_invoice_customer_map(invoices)
|
||||
@@ -510,7 +468,7 @@ def unconsolidate_pos_invoices(closing_entry):
|
||||
"POS Invoice Merge Log", filters={"pos_closing_entry": closing_entry.name}, pluck="name"
|
||||
)
|
||||
|
||||
if len(closing_entry.pos_invoices) >= 10:
|
||||
if len(closing_entry.pos_transactions) >= 10:
|
||||
closing_entry.set_status(update=True, status="Queued")
|
||||
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
|
||||
else:
|
||||
@@ -574,21 +532,20 @@ def split_invoices(invoices):
|
||||
|
||||
def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
try:
|
||||
for customer, invoices_acc_dim in invoice_by_customer.items():
|
||||
for invoices in invoices_acc_dim.values():
|
||||
for _invoices in split_invoices(invoices):
|
||||
merge_log = frappe.new_doc("POS Invoice Merge Log")
|
||||
merge_log.posting_date = (
|
||||
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
|
||||
)
|
||||
merge_log.posting_time = (
|
||||
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
|
||||
)
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
|
||||
merge_log.set("pos_invoices", _invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
for customer, invoices in invoice_by_customer.items():
|
||||
for _invoices in split_invoices(invoices):
|
||||
merge_log = frappe.new_doc("POS Invoice Merge Log")
|
||||
merge_log.posting_date = (
|
||||
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
|
||||
)
|
||||
merge_log.posting_time = (
|
||||
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
|
||||
)
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
|
||||
merge_log.set("pos_invoices", _invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status="Submitted")
|
||||
closing_entry.db_set("error_message", "")
|
||||
@@ -655,7 +612,7 @@ def enqueue_job(job, **kwargs):
|
||||
timeout=10000,
|
||||
event="processing_merge_logs",
|
||||
job_id=job_id,
|
||||
now=frappe.conf.developer_mode or frappe.in_test,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
)
|
||||
|
||||
if job == create_merge_logs:
|
||||
@@ -667,7 +624,7 @@ def enqueue_job(job, **kwargs):
|
||||
|
||||
|
||||
def check_scheduler_status():
|
||||
if is_scheduler_inactive() and not frappe.in_test:
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
||||
|
||||
|
||||
@@ -676,3 +633,26 @@ def get_error_message(message) -> str:
|
||||
return message["message"]
|
||||
except Exception:
|
||||
return str(message)
|
||||
|
||||
|
||||
def get_sales_invoice_item(return_against_pos_invoice, pos_invoice_item):
|
||||
try:
|
||||
SalesInvoice = DocType("Sales Invoice")
|
||||
SalesInvoiceItem = DocType("Sales Invoice Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(SalesInvoice)
|
||||
.from_(SalesInvoiceItem)
|
||||
.select(SalesInvoiceItem.name)
|
||||
.where(
|
||||
(SalesInvoice.name == SalesInvoiceItem.parent)
|
||||
& (SalesInvoice.is_return == 0)
|
||||
& (SalesInvoiceItem.pos_invoice == return_against_pos_invoice)
|
||||
& (SalesInvoiceItem.pos_invoice_item == pos_invoice_item)
|
||||
)
|
||||
)
|
||||
|
||||
result = query.run(as_dict=True)
|
||||
return result[0].name if result else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -3,332 +3,131 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
|
||||
set_default_account_for_mode_of_payment,
|
||||
)
|
||||
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
|
||||
make_closing_entry_from_opening,
|
||||
)
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
consolidate_pos_invoices,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_serial_nos_from_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
class UnitTestPosInvoiceMergeLog(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PosInvoiceMergeLog.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
frappe.db.sql("delete from `tabPOS Opening Entry`")
|
||||
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
|
||||
cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice"))
|
||||
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
|
||||
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
|
||||
|
||||
def setUp(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidated_invoice_creation(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
try:
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
|
||||
pos_inv3.save()
|
||||
pos_inv3.submit()
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
|
||||
pos_inv3.save()
|
||||
pos_inv3.submit()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
|
||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidated_credit_note_creation(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
try:
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
|
||||
pos_inv3.save()
|
||||
pos_inv3.submit()
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.set("payments", [])
|
||||
pos_inv_cn.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -100})
|
||||
pos_inv_cn.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": -200}
|
||||
)
|
||||
pos_inv_cn.paid_amount = -300
|
||||
pos_inv_cn.submit()
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
|
||||
pos_inv3.save()
|
||||
pos_inv3.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.set("payments", [])
|
||||
pos_inv_cn.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -100}
|
||||
)
|
||||
pos_inv_cn.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": -200}
|
||||
)
|
||||
pos_inv_cn.paid_amount = -300
|
||||
pos_inv_cn.submit()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
||||
pos_inv_cn.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
||||
consolidated_credit_note = frappe.get_doc("Sales Invoice", pos_inv_cn.consolidated_invoice)
|
||||
self.assertEqual(consolidated_credit_note.is_return, 1)
|
||||
self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, "Cash")
|
||||
self.assertEqual(consolidated_credit_note.payments[0].amount, -100)
|
||||
self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, "Bank Draft")
|
||||
self.assertEqual(consolidated_credit_note.payments[1].amount, -200)
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
|
||||
pos_inv_cn.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
||||
consolidated_credit_note = frappe.get_doc("Sales Invoice", pos_inv_cn.consolidated_invoice)
|
||||
self.assertEqual(consolidated_credit_note.is_return, 1)
|
||||
self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, "Cash")
|
||||
self.assertEqual(consolidated_credit_note.payments[0].amount, -100)
|
||||
self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, "Bank Draft")
|
||||
self.assertEqual(consolidated_credit_note.payments[1].amount, -200)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidated_invoice_item_taxes(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
try:
|
||||
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 9,
|
||||
},
|
||||
)
|
||||
inv.insert()
|
||||
inv.payments[0].amount = inv.grand_total
|
||||
inv.save()
|
||||
inv.submit()
|
||||
|
||||
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
inv2.get("items")[0].item_code = "_Test Item 2"
|
||||
inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 5,
|
||||
},
|
||||
)
|
||||
inv2.insert()
|
||||
inv2.payments[0].amount = inv.grand_total
|
||||
inv2.save()
|
||||
inv2.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
|
||||
inv.load_from_db()
|
||||
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
|
||||
expected_item_wise_tax_detail = {
|
||||
"_Test Item": {
|
||||
"tax_rate": 9,
|
||||
"tax_amount": 9,
|
||||
"net_amount": 100,
|
||||
},
|
||||
"_Test Item 2": {
|
||||
"tax_rate": 5,
|
||||
"tax_amount": 5,
|
||||
"net_amount": 100,
|
||||
},
|
||||
}
|
||||
self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail)
|
||||
|
||||
def test_consolidation_round_off_error_1(self):
|
||||
"""
|
||||
Test round off error in consolidated invoice creation if POS Invoice has inclusive tax
|
||||
"""
|
||||
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
|
||||
inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
|
||||
inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertEqual(consolidated_invoice.outstanding_amount, 0)
|
||||
self.assertEqual(consolidated_invoice.status, "Paid")
|
||||
|
||||
def test_consolidation_round_off_error_2(self):
|
||||
"""
|
||||
Test the same case as above but with an Unpaid POS Invoice
|
||||
"""
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
|
||||
inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
|
||||
inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
|
||||
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
|
||||
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800})
|
||||
inv3.insert()
|
||||
inv3.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
|
||||
self.assertEqual(consolidated_invoice.status, "Paid")
|
||||
|
||||
@IntegrationTestCase.change_settings(
|
||||
"System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}
|
||||
)
|
||||
def test_consolidation_round_off_error_3(self):
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
item_rates = [69, 59, 29]
|
||||
for _i in [1, 2]:
|
||||
inv = create_pos_invoice(is_return=1, do_not_save=1)
|
||||
inv.items = []
|
||||
for rate in item_rates:
|
||||
inv.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": -1,
|
||||
"rate": rate,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
},
|
||||
)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
@@ -337,56 +136,283 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 15,
|
||||
"included_in_print_rate": 1,
|
||||
"rate": 9,
|
||||
},
|
||||
)
|
||||
inv.payments = []
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -157})
|
||||
inv.paid_amount = -157
|
||||
inv.insert()
|
||||
inv.payments[0].amount = inv.grand_total
|
||||
inv.save()
|
||||
inv.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
inv2.get("items")[0].item_code = "_Test Item 2"
|
||||
inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 5,
|
||||
},
|
||||
)
|
||||
inv2.insert()
|
||||
inv2.payments[0].amount = inv.grand_total
|
||||
inv2.save()
|
||||
inv2.submit()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertEqual(consolidated_invoice.status, "Return")
|
||||
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
|
||||
consolidate_pos_invoices()
|
||||
inv.load_from_db()
|
||||
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
|
||||
expected_item_wise_tax_detail = {
|
||||
"_Test Item": {
|
||||
"tax_rate": 9,
|
||||
"tax_amount": 9,
|
||||
"net_amount": 100,
|
||||
},
|
||||
"_Test Item 2": {
|
||||
"tax_rate": 5,
|
||||
"tax_amount": 5,
|
||||
"net_amount": 100,
|
||||
},
|
||||
}
|
||||
self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail)
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidation_round_off_error_1(self):
|
||||
"""
|
||||
Test round off error in consolidated invoice creation if POS Invoice has inclusive tax
|
||||
"""
|
||||
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
try:
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
|
||||
init_user_and_profile()
|
||||
|
||||
inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
|
||||
inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
|
||||
inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertEqual(consolidated_invoice.outstanding_amount, 0)
|
||||
self.assertEqual(consolidated_invoice.status, "Paid")
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidation_round_off_error_2(self):
|
||||
"""
|
||||
Test the same case as above but with an Unpaid POS Invoice
|
||||
"""
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
try:
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
|
||||
init_user_and_profile()
|
||||
|
||||
inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
|
||||
inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
|
||||
inv2.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 7.5,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
|
||||
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
|
||||
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800})
|
||||
inv3.insert()
|
||||
inv3.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
|
||||
self.assertEqual(consolidated_invoice.status, "Paid")
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
@IntegrationTestCase.change_settings(
|
||||
"System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}
|
||||
)
|
||||
def test_consolidation_round_off_error_3(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
try:
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
init_user_and_profile()
|
||||
|
||||
item_rates = [69, 59, 29]
|
||||
for _i in [1, 2]:
|
||||
inv = create_pos_invoice(is_return=1, do_not_save=1)
|
||||
inv.items = []
|
||||
for rate in item_rates:
|
||||
inv.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": -1,
|
||||
"rate": rate,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
},
|
||||
)
|
||||
inv.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 15,
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
inv.payments = []
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -157})
|
||||
inv.paid_amount = -157
|
||||
inv.save()
|
||||
inv.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertEqual(consolidated_invoice.status, "Return")
|
||||
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidation_rounding_adjustment(self):
|
||||
"""
|
||||
Test if the rounding adjustment is calculated correctly
|
||||
"""
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
try:
|
||||
make_stock_entry(
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
item_code="_Test Item",
|
||||
rate=8000,
|
||||
qty=10,
|
||||
)
|
||||
|
||||
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
init_user_and_profile()
|
||||
|
||||
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
|
||||
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
|
||||
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
|
||||
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
|
||||
consolidate_pos_invoices()
|
||||
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_serial_no_case_1(self):
|
||||
"""
|
||||
@@ -401,93 +427,48 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
||||
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item(self)
|
||||
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
try:
|
||||
se = make_serialized_item(self)
|
||||
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
init_user_and_profile()
|
||||
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.paid_amount = -100
|
||||
pos_inv_cn.submit()
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.paid_amount = -100
|
||||
pos_inv_cn.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
pos_inv2 = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
pos_inv2.load_from_db()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
|
||||
pos_inv.load_from_db()
|
||||
pos_inv2.load_from_db()
|
||||
|
||||
def test_separate_consolidated_invoice_for_different_accounting_dimensions(self):
|
||||
"""
|
||||
Creating 3 POS Invoices where first POS Invoice has different Cost Center than the other two.
|
||||
Consolidate the Invoices.
|
||||
Check whether the first POS Invoice is consolidated with a separate Sales Invoice than the other two.
|
||||
Check whether the second and third POS Invoice are consolidated with the same Sales Invoice.
|
||||
"""
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
|
||||
|
||||
create_cost_center(cost_center_name="_Test POS Cost Center 1", is_group=0)
|
||||
create_cost_center(cost_center_name="_Test POS Cost Center 2", is_group=0)
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
|
||||
pos_inv.cost_center = "_Test POS Cost Center 1 - _TC"
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
|
||||
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
|
||||
pos_inv2.save()
|
||||
pos_inv2.submit()
|
||||
|
||||
pos_inv3 = create_pos_invoice(rate=2300, do_not_submit=1)
|
||||
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
|
||||
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
|
||||
pos_inv3.save()
|
||||
pos_inv3.submit()
|
||||
|
||||
closing_entry = make_closing_entry_from_opening(opening_entry)
|
||||
closing_entry.insert()
|
||||
closing_entry.submit()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
||||
pos_inv2.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv2.consolidated_invoice))
|
||||
|
||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
|
||||
self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
"ignore_pricing_rule",
|
||||
"allow_rate_change",
|
||||
"allow_discount_change",
|
||||
"set_grand_total_to_default_mop",
|
||||
"action_on_new_invoice",
|
||||
"disable_grand_total_to_default_mop",
|
||||
"section_break_23",
|
||||
"item_groups",
|
||||
"column_break_25",
|
||||
@@ -403,6 +402,12 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Receipt on Order Complete"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disable_grand_total_to_default_mop",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable auto setting Grand Total to default Payment Mode"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
@@ -410,22 +415,8 @@
|
||||
"oldfieldname": "cost_center",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "set_grand_total_to_default_mop",
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Grand Total to Default Payment Method"
|
||||
},
|
||||
{
|
||||
"default": "Always Ask",
|
||||
"fieldname": "action_on_new_invoice",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action on New Invoice",
|
||||
"options": "Always Ask\nSave Changes and Load New Invoice\nDiscard Changes and Load New Invoice"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
@@ -451,7 +442,7 @@
|
||||
"link_fieldname": "pos_profile"
|
||||
}
|
||||
],
|
||||
"modified": "2025-05-23 12:12:32.247652",
|
||||
"modified": "2025-04-09 11:35:13.779613",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
||||
@@ -28,9 +28,6 @@ class POSProfile(Document):
|
||||
from erpnext.accounts.doctype.pos_profile_user.pos_profile_user import POSProfileUser
|
||||
|
||||
account_for_change_amount: DF.Link | None
|
||||
action_on_new_invoice: DF.Literal[
|
||||
"Always Ask", "Save Changes and Load New Invoice", "Discard Changes and Load New Invoice"
|
||||
]
|
||||
allow_discount_change: DF.Check
|
||||
allow_rate_change: DF.Check
|
||||
applicable_for_users: DF.Table[POSProfileUser]
|
||||
@@ -43,6 +40,7 @@ class POSProfile(Document):
|
||||
currency: DF.Link
|
||||
customer: DF.Link | None
|
||||
customer_groups: DF.Table[POSCustomerGroup]
|
||||
disable_grand_total_to_default_mop: DF.Check
|
||||
disable_rounded_total: DF.Check
|
||||
disabled: DF.Check
|
||||
expense_account: DF.Link | None
|
||||
@@ -58,7 +56,6 @@ class POSProfile(Document):
|
||||
project: DF.Link | None
|
||||
select_print_heading: DF.Link | None
|
||||
selling_price_list: DF.Link | None
|
||||
set_grand_total_to_default_mop: DF.Check
|
||||
tax_category: DF.Link | None
|
||||
taxes_and_charges: DF.Link | None
|
||||
tc_name: DF.Link | None
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"invoice_type",
|
||||
"section_break_gyos",
|
||||
"invoice_fields",
|
||||
"pos_search_fields"
|
||||
],
|
||||
@@ -14,7 +12,7 @@
|
||||
{
|
||||
"fieldname": "invoice_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Additional Fields",
|
||||
"label": "POS Field",
|
||||
"options": "POS Field"
|
||||
},
|
||||
{
|
||||
@@ -22,23 +20,11 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Search Fields",
|
||||
"options": "POS Search Fields"
|
||||
},
|
||||
{
|
||||
"default": "Sales Invoice",
|
||||
"description": "The system will create a Sales Invoice or a POS Invoice from the POS interface based on this setting. For high-volume transactions, it is recommended to use POS Invoice.",
|
||||
"fieldname": "invoice_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Invoice Type Created via POS Screen",
|
||||
"options": "Sales Invoice\nPOS Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_gyos",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-06 11:36:44.885353",
|
||||
"modified": "2024-03-27 13:10:17.083132",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Settings",
|
||||
@@ -70,9 +56,8 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -21,16 +21,10 @@ class POSSettings(Document):
|
||||
from erpnext.accounts.doctype.pos_search_fields.pos_search_fields import POSSearchFields
|
||||
|
||||
invoice_fields: DF.Table[POSField]
|
||||
invoice_type: DF.Literal["Sales Invoice", "POS Invoice"]
|
||||
pos_search_fields: DF.Table[POSSearchFields]
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
old_doc = self.get_doc_before_save()
|
||||
|
||||
if old_doc.invoice_type != self.invoice_type:
|
||||
self.validate_invoice_type()
|
||||
|
||||
self.validate_invoice_fields()
|
||||
|
||||
def validate_invoice_fields(self):
|
||||
@@ -42,15 +36,3 @@ class POSSettings(Document):
|
||||
frappe.throw(
|
||||
title=_("Duplicate POS Fields"), msg=_("'{0}' has been already added.").format(field)
|
||||
)
|
||||
|
||||
def validate_invoice_type(self):
|
||||
pos_opening_entries_count = frappe.db.count(
|
||||
"POS Opening Entry", filters={"docstatus": 1, "status": "Open"}
|
||||
)
|
||||
if pos_opening_entries_count:
|
||||
frappe.throw(
|
||||
_("{0} cannot be changed with opened Opening Entries.").format(
|
||||
frappe.bold(_("Invoice Type"))
|
||||
),
|
||||
title=_("Invoice Document Type Selection Error"),
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
@@ -15,6 +15,15 @@ from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
|
||||
|
||||
class UnitTestPricingRule(UnitTestCase):
|
||||
"""
|
||||
Unit tests for PricingRule.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestPricingRule(IntegrationTestCase):
|
||||
def setUp(self):
|
||||
delete_existing_pricing_rules()
|
||||
|
||||
@@ -28,7 +28,7 @@ def get_pricing_rules(args, doc=None):
|
||||
pricing_rules = []
|
||||
values = {}
|
||||
|
||||
if not frappe.db.count("Pricing Rule", cache=True):
|
||||
if not frappe.db.exists("Pricing Rule", {"disable": 0, args.transaction_type: 1}):
|
||||
return
|
||||
|
||||
for apply_on in ["Item Code", "Item Group", "Brand"]:
|
||||
@@ -115,8 +115,8 @@ def _get_pricing_rules(apply_on, args, values):
|
||||
if apply_on_field == "item_code":
|
||||
if args.get("uom", None):
|
||||
item_conditions += (
|
||||
" and ({child_doc}.uom={item_uom} or IFNULL({child_doc}.uom, '')='')".format(
|
||||
child_doc=child_doc, item_uom=frappe.db.escape(args.get("uom"))
|
||||
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
|
||||
child_doc=child_doc, item_uom=args.get("uom")
|
||||
)
|
||||
)
|
||||
if "variant_of" not in args:
|
||||
@@ -128,8 +128,8 @@ def _get_pricing_rules(apply_on, args, values):
|
||||
elif apply_on_field == "item_group":
|
||||
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
|
||||
if args.get("uom", None):
|
||||
item_conditions += " and ({child_doc}.uom={item_uom} or IFNULL({child_doc}.uom, '')='')".format(
|
||||
child_doc=child_doc, item_uom=frappe.db.escape(args.get("uom"))
|
||||
item_conditions += " and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
|
||||
child_doc=child_doc, item_uom=args.get("uom")
|
||||
)
|
||||
|
||||
conditions += get_other_conditions(conditions, values, args)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user