Compare commits

..

1 Commits

Author SHA1 Message Date
Ankush Menat
28ce2958fa ci: Add fake passing tests when CI is skipped 2024-01-25 14:14:25 +05:30
288 changed files with 18833 additions and 106175 deletions

View File

@@ -1,40 +0,0 @@
#!/bin/bash
set -e
cd ~ || exit
echo "Setting Up Bench..."
pip install frappe-bench
bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)"
cd ./frappe-bench || exit
echo "Get ERPNext..."
bench get-app --skip-assets erpnext "${GITHUB_WORKSPACE}"
echo "Generating POT file..."
bench generate-pot-file --app erpnext
cd ./apps/erpnext || exit
echo "Configuring git user..."
git config user.email "developers@erpnext.com"
git config user.name "frappe-pr-bot"
echo "Setting the correct git remote..."
# Here, the git remote is a local file path by default. Let's change it to the upstream repo.
git remote set-url upstream https://github.com/frappe/erpnext.git
echo "Creating a new branch..."
isodate=$(date -u +"%Y-%m-%d")
branch_name="pot_${BASE_BRANCH}_${isodate}"
git checkout -b "${branch_name}"
echo "Commiting changes..."
git add .
git commit -m "chore: update POT file"
gh auth setup-git
git push -u upstream "${branch_name}"
echo "Creating a PR..."
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R frappe/erpnext

View File

@@ -1,38 +0,0 @@
# This workflow is agnostic to branches. Only maintain on develop branch.
# To add/remove branches just modify the matrix.
name: Regenerate POT file (translatable strings)
on:
schedule:
# 9:30 UTC => 3 PM IST Sunday
- cron: "30 9 * * 0"
workflow_dispatch:
jobs:
regeneratee-pot-file:
name: Release
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branch: ["develop"]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run script to update POT file
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/update_pot_file.sh
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
BASE_BRANCH: ${{ matrix.branch }}

View File

@@ -31,9 +31,6 @@ jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
NODE_ENV: "production"
WITH_COVERAGE: ${{ github.event_name != 'pull_request' }}
strategy:
fail-fast: false
@@ -120,11 +117,11 @@ jobs:
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
- name: Run Tests
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 4 --build-number ${{ matrix.container }}'
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --with-coverage --total-builds 4 --build-number ${{ matrix.container }}'
env:
TYPE: server
CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }}
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- name: Show bench output
if: ${{ always() }}
@@ -132,7 +129,6 @@ jobs:
- name: Upload coverage data
uses: actions/upload-artifact@v3
if: github.event_name != 'pull_request'
with:
name: coverage-${{ matrix.container }}
path: /home/runner/frappe-bench/sites/coverage.xml
@@ -141,7 +137,6 @@ jobs:
name: Coverage Wrap Up
needs: test
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Clone
uses: actions/checkout@v2
@@ -153,6 +148,5 @@ jobs:
uses: codecov/codecov-action@v2
with:
name: MariaDB
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
verbose: true

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
*.py~
.DS_Store
conf.py
locale
latest_updates.json
.wnf-lang-status
*.egg-info

View File

@@ -7,7 +7,8 @@
<p>ERP made simple</p>
</p>
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml/badge.svg?event=schedule)](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
[![UI](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml/badge.svg?branch=develop&event=schedule)](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext)
[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker)

View File

@@ -1,4 +1,3 @@
files:
- source: /erpnext/locale/main.pot
translation: /erpnext/locale/%two_letters_code%.po
pull_request_title: "chore: sync translations from crowdin"

View File

@@ -118,7 +118,6 @@ class Account(NestedSet):
self.validate_balance_must_be_debit_or_credit()
self.validate_account_currency()
self.validate_root_company_and_sync_account_to_children()
self.validate_receivable_payable_account_type()
def validate_parent_child_account_type(self):
if self.parent_account:
@@ -189,24 +188,6 @@ class Account(NestedSet):
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
)
def validate_receivable_payable_account_type(self):
doc_before_save = self.get_doc_before_save()
receivable_payable_types = ["Receivable", "Payable"]
if (
doc_before_save
and doc_before_save.account_type in receivable_payable_types
and doc_before_save.account_type != self.account_type
):
# check for ledger entries
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
msg = _(
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
).format(
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
)
frappe.msgprint(msg)
self.add_comment("Comment", msg)
def validate_root_details(self):
doc_before_save = self.get_doc_before_save()

View File

@@ -56,9 +56,7 @@
"Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {},
"Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {},
"Im\u00f3veis Destinados \u00e0 Venda": {},
"Insumos (materiais diretos)": {
"account_type": "Stock"
},
"Insumos (materiais diretos)": {},
"Insumos Agropecu\u00e1rios": {},
"Mercadorias para Revenda": {},
"Outras 11": {},
@@ -148,65 +146,6 @@
"root_type": "Asset"
},
"CUSTOS DE PRODU\u00c7\u00c3O": {
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
"Custos dos Produtos Vendidos em Geral": {
"account_type": "Cost of Goods Sold"
},
"Outros Custos 4": {},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
"Outras": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
"Outros Custos 6": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
"Outros Custos 5": {}
},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
"Custo dos Servi\u00e7os Prestados em Geral": {},
"Outros Custos": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
"Outros Custos 2": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
"Outros Custos 1": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
"Outros Custos 3": {}
}
}
},
"CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": {
"CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": {
"Alimenta\u00e7\u00e3o do Trabalhador": {},
@@ -682,9 +621,7 @@
"Receita das Unidades Imobili\u00e1rias Vendidas": {},
"Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {},
"Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {},
"Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {
"account_type": "Income Account"
},
"Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {},
"Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {}
}
}
@@ -708,6 +645,65 @@
}
},
"RESULTADO OPERACIONAL": {
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
"Custos dos Produtos Vendidos em Geral": {
"account_type": "Cost of Goods Sold"
},
"Outros Custos 4": {},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
"Outras": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
"Outros Custos 6": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
"Outros Custos 5": {}
},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
"Custo dos Servi\u00e7os Prestados em Geral": {},
"Outros Custos": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
"Outros Custos 2": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
"Outros Custos 1": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
"Outros Custos 3": {}
}
}
},
"DESPESAS OPERACIONAIS": {
"DESPESAS OPERACIONAIS 1": {
"DESPESAS OPERACIONAIS 2": {

View File

@@ -6,7 +6,6 @@ import unittest
import frappe
from frappe.test_runner import make_test_records
from frappe.utils import nowdate
from erpnext.accounts.doctype.account.account import (
InvalidAccountMergeError,
@@ -325,19 +324,6 @@ class TestAccount(unittest.TestCase):
acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save)
def test_account_balance(self):
from erpnext.accounts.utils import get_balance_on
if not frappe.db.exists("Account", "Test Percent Account %5 - _TC"):
acc = frappe.new_doc("Account")
acc.account_name = "Test Percent Account %5"
acc.parent_account = "Tax Assets - _TC"
acc.company = "_Test Company"
acc.insert()
balance = get_balance_on(account="Test Percent Account %5 - _TC", date=nowdate())
self.assertEqual(balance, 0)
def _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects

View File

@@ -11,10 +11,6 @@ from frappe.model import core_doctypes_list
from frappe.model.document import Document
from frappe.utils import cstr
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
get_allowed_types_from_settings,
)
class AccountingDimension(Document):
# begin: auto-generated types
@@ -110,7 +106,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
doc_count = len(get_accounting_dimensions())
count = 0
repostable_doctypes = get_allowed_types_from_settings()
for doctype in doclist:
@@ -126,7 +121,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"options": doc.document_type,
"insert_after": insert_after_field,
"owner": "Administrator",
"allow_on_submit": 1 if doctype in repostable_doctypes else 0,
}
meta = frappe.get_meta(doctype, cached=False)

View File

@@ -1,6 +1,7 @@
{
"actions": [],
"creation": "2013-06-24 15:49:57",
"description": "Settings for Accounts",
"doctype": "DocType",
"document_type": "Other",
"editable_grid": 1,
@@ -461,7 +462,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-30 14:04:26.553554",
"modified": "2023-11-20 09:37:47.650347",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -9,7 +9,6 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact,
)
from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
class BankAccount(Document):
@@ -53,19 +52,6 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
self.validate_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def validate_company(self):
if self.is_company_account and not self.company:

View File

@@ -5,9 +5,7 @@
import frappe
from frappe import _, msgprint
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, getdate
from pypika import Order
import erpnext
@@ -181,62 +179,39 @@ def get_payment_entries_for_bank_clearance(
pos_sales_invoices, pos_purchase_invoices = [], []
if include_pos_transactions:
si_payment = frappe.qb.DocType("Sales Invoice Payment")
si = frappe.qb.DocType("Sales Invoice")
acc = frappe.qb.DocType("Account")
pos_sales_invoices = frappe.db.sql(
"""
select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.customer as against_account, sip.clearance_date,
account.account_currency, 0 as credit
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
where
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
order by
si.posting_date ASC, si.name DESC
""",
{"account": account, "from": from_date, "to": to_date},
as_dict=1,
)
pos_sales_invoices = (
frappe.qb.from_(si_payment)
.inner_join(si)
.on(si_payment.parent == si.name)
.inner_join(acc)
.on(si_payment.account == acc.name)
.select(
ConstantColumn("Sales Invoice").as_("payment_document"),
si.name.as_("payment_entry"),
si_payment.reference_no.as_("cheque_number"),
si_payment.amount.as_("debit"),
si.posting_date,
si.customer.as_("against_account"),
si_payment.clearance_date,
acc.account_currency,
ConstantColumn(0).as_("credit"),
)
.where(
(si.docstatus == 1)
& (si_payment.account == account)
& (si.posting_date >= from_date)
& (si.posting_date <= to_date)
)
.orderby(si.posting_date)
.orderby(si.name, order=Order.desc)
).run(as_dict=True)
pi = frappe.qb.DocType("Purchase Invoice")
pos_purchase_invoices = (
frappe.qb.from_(pi)
.inner_join(acc)
.on(pi.cash_bank_account == acc.name)
.select(
ConstantColumn("Purchase Invoice").as_("payment_document"),
pi.name.as_("payment_entry"),
pi.paid_amount.as_("credit"),
pi.posting_date,
pi.supplier.as_("against_account"),
pi.clearance_date,
acc.account_currency,
ConstantColumn(0).as_("debit"),
)
.where(
(pi.docstatus == 1)
& (pi.cash_bank_account == account)
& (pi.posting_date >= from_date)
& (pi.posting_date <= to_date)
)
.orderby(pi.posting_date)
.orderby(pi.name, order=Order.desc)
).run(as_dict=True)
pos_purchase_invoices = frappe.db.sql(
"""
select
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
account.account_currency, 0 as debit
from `tabPurchase Invoice` pi, `tabAccount` account
where
pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
order by
pi.posting_date ASC, pi.name DESC
""",
{"account": account, "from": from_date, "to": to_date},
as_dict=1,
)
entries = (
list(payment_entries)

View File

@@ -80,8 +80,7 @@ class BankStatementImport(DataImport):
from frappe.utils.background_jobs import is_job_enqueued
from frappe.utils.scheduler import is_scheduler_inactive
run_now = frappe.flags.in_test or frappe.conf.developer_mode
if is_scheduler_inactive() and not run_now:
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
job_id = f"bank_statement_import::{self.name}"
@@ -98,7 +97,7 @@ class BankStatementImport(DataImport):
google_sheets_url=self.google_sheets_url,
bank=self.bank,
template_options=self.template_options,
now=run_now,
now=frappe.conf.developer_mode or frappe.flags.in_test,
)
return True

View File

@@ -94,13 +94,10 @@ class BankTransaction(Document):
pe.append(reference)
def update_allocated_amount(self):
allocated_amount = (
self.allocated_amount = (
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
)
unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
def before_submit(self):
self.allocate_payment_entries()

View File

@@ -32,16 +32,8 @@ class TestBankTransaction(FrappeTestCase):
frappe.db.delete(dt)
clear_loan_transactions()
make_pos_profile()
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
uniq_identifier = frappe.generate_hash(length=10)
gl_account = create_gl_account("_Test Bank " + uniq_identifier)
bank_account = create_bank_account(
gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
)
add_transactions(bank_account=bank_account)
add_vouchers(gl_account=gl_account)
add_transactions()
add_vouchers()
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
@@ -227,9 +219,7 @@ def clear_loan_transactions():
frappe.db.delete("Loan Repayment")
def create_bank_account(
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
):
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
try:
frappe.get_doc(
{
@@ -241,35 +231,21 @@ def create_bank_account(
pass
try:
bank_account = frappe.get_doc(
frappe.get_doc(
{
"doctype": "Bank Account",
"account_name": bank_account_name,
"account_name": "Checking Account",
"bank": bank_name,
"account": gl_account,
"account": account_name,
}
).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
return bank_account.name
def add_transactions():
create_bank_account()
def create_gl_account(gl_account_name="_Test Bank - _TC"):
gl_account = frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"parent_account": "Current Assets - _TC",
"account_type": "Bank",
"is_group": 0,
"account_name": gl_account_name,
}
).insert()
return gl_account.name
def add_transactions(bank_account="_Test Bank - _TC"):
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
@@ -277,7 +253,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-23",
"deposit": 1200,
"currency": "INR",
"bank_account": bank_account,
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
@@ -289,7 +265,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-23",
"deposit": 1700,
"currency": "INR",
"bank_account": bank_account,
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
@@ -301,7 +277,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-26",
"withdrawal": 690,
"currency": "INR",
"bank_account": bank_account,
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
@@ -313,7 +289,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-27",
"deposit": 3900,
"currency": "INR",
"bank_account": bank_account,
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
@@ -325,13 +301,13 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-27",
"withdrawal": 109080,
"currency": "INR",
"bank_account": bank_account,
"bank_account": "Checking Account - Citi Bank",
}
).insert()
doc.submit()
def add_vouchers(gl_account="_Test Bank - _TC"):
def add_vouchers():
try:
frappe.get_doc(
{
@@ -347,7 +323,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
@@ -366,14 +342,14 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pass
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
pe.submit()
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Nov 18"
pe.reference_date = "2018-11-01"
pe.insert()
@@ -404,10 +380,10 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
pi.cash_bank_account = gl_account
pi.cash_bank_account = "_Test Bank - _TC"
pi.insert()
pi.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.paid_amount = 690
@@ -416,7 +392,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pe.submit()
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.insert()
@@ -439,12 +415,16 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
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.append(
"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
)
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": "Cash", "account": gl_account, "amount": 109080})
si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
)
si.insert()
si.submit()

View File

@@ -41,7 +41,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Cost Center Number",
"read_only_depends_on": "eval:!doc.__islocal"
"read_only": 1
},
{
"fieldname": "parent_cost_center",
@@ -170,4 +170,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"states": []
}
}

View File

@@ -3,21 +3,16 @@
frappe.ui.form.on('Cost Center Allocation', {
setup: function(frm) {
let filters = {"is_group": 0};
if (frm.doc.company) {
$.extend(filters, {
"company": frm.doc.company
});
}
frm.set_query('main_cost_center', function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
};
});
frm.set_query('cost_center', 'allocation_percentages', function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
filters: filters
};
});
}

View File

@@ -85,14 +85,7 @@ class Dunning(AccountsController):
frappe.throw(
_(
"The currency of invoice {} ({}) is different from the currency of this dunning ({})."
).format(
frappe.get_desk_link(
"Sales Invoice",
row.sales_invoice,
),
invoice_currency,
self.currency,
)
).format(row.sales_invoice, invoice_currency, self.currency)
)
def validate_overdue_payments(self):

View File

@@ -3,7 +3,7 @@
"allow_import": 1,
"autoname": "field:year",
"creation": "2013-01-22 16:50:25",
"description": "Represents a Financial Year. All accounting entries and other major transactions are tracked against the Fiscal Year.",
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
@@ -82,12 +82,11 @@
"icon": "fa fa-calendar",
"idx": 1,
"links": [],
"modified": "2024-01-30 12:35:38.645968",
"modified": "2024-01-17 13:06:01.608953",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year",
"naming_rule": "By fieldname",
"owner": "Administrator",
"owner": "Administrator",
"permissions": [
{
"create": 1,
@@ -131,6 +130,5 @@
],
"show_name_in_global_search": 1,
"sort_field": "name",
"sort_order": "DESC",
"states": []
"sort_order": "DESC"
}

View File

@@ -13,9 +13,16 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency
from erpnext.exceptions import (
InvalidAccountCurrency,
InvalidAccountDimensionError,
MandatoryAccountDimensionError,
)
exclude_from_linked_with = True
@@ -91,6 +98,7 @@ class GLEntry(Document):
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
@@ -200,6 +208,42 @@ class GLEntry(Document):
)
)
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]
if self.account == account:
if value["is_mandatory"] and not self.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
),
MandatoryAccountDimensionError,
)
if value["allow_or_restrict"] == "Allow":
if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
else:
if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
def check_pl_account(self):
if (
self.is_opening == "Yes"

View File

@@ -14,25 +14,6 @@ frappe.ui.form.on("Journal Entry", {
refresh: function(frm) {
erpnext.toggle_naming_series();
if (frm.doc.repost_required && frm.doc.docstatus===1) {
frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."));
frm.add_custom_button(__('Repost Accounting Entries'),
() => {
frm.call({
doc: frm.doc,
method: 'repost_accounting_entries',
freeze: true,
freeze_message: __('Reposting...'),
callback: (r) => {
if (!r.exc) {
frappe.msgprint(__('Accounting Entries are reposted.'));
frm.refresh();
}
}
});
}).removeClass('btn-default').addClass('btn-warning');
}
if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = {
@@ -203,6 +184,7 @@ var update_jv_details = function(doc, r) {
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
frappe.model.set_value(row.doctype, row.name, "account", d.account)
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
});
refresh_field("accounts");
}
@@ -211,6 +193,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
onload() {
this.load_defaults();
this.setup_queries();
this.setup_balance_formatter();
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
@@ -309,6 +292,19 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
}
setup_balance_formatter() {
const formatter = function(value, df, options, doc) {
var currency = frappe.meta.get_field_currency(df, doc);
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
return "<div style='text-align: right'>"
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
+ " " + dr_or_cr
+ "</div>";
};
this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
}
reference_name(doc, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn);
@@ -404,22 +400,23 @@ frappe.ui.form.on("Journal Entry Account", {
if(!d.account && d.party_type && d.party) {
if(!frm.doc.company) frappe.throw(__("Please select Company"));
return frm.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance",
child: d,
args: {
company: frm.doc.company,
party_type: d.party_type,
party: d.party,
cost_center: d.cost_center
}
});
}
},
cost_center: function(frm, dt, dn) {
erpnext.journal_entry.set_account_details(frm, dt, dn);
erpnext.journal_entry.set_account_balance(frm, dt, dn);
},
account: function(frm, dt, dn) {
erpnext.journal_entry.set_account_details(frm, dt, dn);
erpnext.journal_entry.set_account_balance(frm, dt, dn);
},
debit_in_account_currency: function(frm, cdt, cdn) {
@@ -603,14 +600,14 @@ $.extend(erpnext.journal_entry, {
});
$.extend(erpnext.journal_entry, {
set_account_details: function(frm, dt, dn) {
set_account_balance: function(frm, dt, dn) {
var d = locals[dt][dn];
if(d.account) {
if(!frm.doc.company) frappe.throw(__("Please select Company first"));
if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
return frappe.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
args: {
account: d.account,
date: frm.doc.posting_date,
@@ -618,6 +615,7 @@ $.extend(erpnext.journal_entry, {
debit: flt(d.debit_in_account_currency),
credit: flt(d.credit_in_account_currency),
exchange_rate: d.exchange_rate,
cost_center: d.cost_center
},
callback: function(r) {
if(r.message) {

View File

@@ -64,8 +64,7 @@
"stock_entry",
"subscription_section",
"auto_repeat",
"amended_from",
"repost_required"
"amended_from"
],
"fields": [
{
@@ -544,15 +543,6 @@
"label": "Is System Generated",
"no_copy": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "repost_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Repost Required",
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -568,7 +558,6 @@
}
],
"modified": "2023-11-23 12:11:04.128015",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -13,10 +13,6 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
get_party_account_based_on_invoice_discounting,
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
@@ -144,6 +140,7 @@ class JournalEntry(AccountsController):
self.set_print_format_fields()
self.validate_credit_debit_note()
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
@@ -153,24 +150,6 @@ class JournalEntry(AccountsController):
if not self.title:
self.title = self.get_title()
def validate_for_repost(self):
validate_docs_for_voucher_types(["Journal Entry"])
validate_docs_for_deferred_accounting([self.name], [])
def submit(self):
if len(self.accounts) > 100:
msgprint(_("The task has been enqueued as a background job."), alert=True)
self.queue_action("submit", timeout=4600)
else:
return self._submit()
def cancel(self):
if len(self.accounts) > 100:
msgprint(_("The task has been enqueued as a background job."), alert=True)
self.queue_action("cancel", timeout=4600)
else:
return self._cancel()
def on_submit(self):
self.validate_cheque_info()
self.check_credit_limit()
@@ -180,15 +159,6 @@ class JournalEntry(AccountsController):
self.update_inter_company_jv()
self.update_invoice_discounting()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
self.needs_repost = self.check_if_fields_updated(
fields_to_check=[], child_tables={"accounts": []}
)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller
super(JournalEntry, self).on_cancel()
@@ -217,8 +187,8 @@ class JournalEntry(AccountsController):
def update_advance_paid(self):
advance_paid = frappe._dict()
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
"advance_payment_customer_doctypes"
) + frappe.get_hooks("advance_payment_supplier_doctypes")
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in advance_payment_doctypes:
@@ -578,28 +548,17 @@ class JournalEntry(AccountsController):
elif d.party_type == "Supplier" and flt(d.credit) > 0:
frappe.throw(_("Row {0}: Advance against Supplier must be debit").format(d.idx))
def system_generated_gain_loss(self):
return (
self.voucher_type == "Exchange Gain Or Loss"
and self.multi_currency
and self.is_system_generated
)
def validate_against_jv(self):
for d in self.get("accounts"):
if d.reference_type == "Journal Entry":
account_root_type = frappe.get_cached_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0 and not self.system_generated_gain_loss():
if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(
_(
"Row #{0}: For {1}, you can select reference document only if account gets credited"
).format(d.idx, d.account)
)
elif (
account_root_type == "Liability"
and flt(d.credit) > 0
and not self.system_generated_gain_loss()
):
elif account_root_type == "Liability" and flt(d.credit) > 0:
frappe.throw(
_(
"Row #{0}: For {1}, you can select reference document only if account gets debited"
@@ -631,7 +590,7 @@ class JournalEntry(AccountsController):
for jvd in against_entries:
if flt(jvd[dr_or_cr]) > 0:
valid = True
if not valid and not self.system_generated_gain_loss():
if not valid:
frappe.throw(
_("Against Journal Entry {0} does not have any unmatched {1} entry").format(
d.reference_name, dr_or_cr
@@ -1179,11 +1138,24 @@ class JournalEntry(AccountsController):
if not self.get("accounts"):
frappe.throw(_("Accounts table cannot be blank."))
def set_account_and_party_balance(self):
account_balance = {}
party_balance = {}
for d in self.get("accounts"):
if d.account not in account_balance:
account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date)
if (d.party_type, d.party) not in party_balance:
party_balance[(d.party_type, d.party)] = get_balance_on(
party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company
)
d.account_balance = account_balance[d.account]
d.party_balance = party_balance[(d.party_type, d.party)]
@frappe.whitelist()
def get_default_bank_cash_account(
company, account_type=None, mode_of_payment=None, account=None, ignore_permissions=False
):
def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
if mode_of_payment:
@@ -1221,7 +1193,7 @@ def get_default_bank_cash_account(
return frappe._dict(
{
"account": account,
"balance": get_balance_on(account, ignore_account_permission=ignore_permissions),
"balance": get_balance_on(account),
"account_currency": account_details.account_currency,
"account_type": account_details.account_type,
}
@@ -1346,6 +1318,8 @@ def get_payment_entry(ref_doc, args):
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
"account_currency": args.get("party_account_currency")
or get_account_currency(args.get("party_account")),
"balance": get_balance_on(args.get("party_account")),
"party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")),
"exchange_rate": exchange_rate,
args.get("amount_field_party"): args.get("amount"),
"is_advance": args.get("is_advance"),
@@ -1493,23 +1467,30 @@ def get_outstanding(args):
@frappe.whitelist()
def get_party_account_and_currency(company, party_type, party):
def get_party_account_and_balance(company, party_type, party, cost_center=None):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
account = get_party_account(party_type, party, company)
account_balance = get_balance_on(account=account, cost_center=cost_center)
party_balance = get_balance_on(
party_type=party_type, party=party, company=company, cost_center=cost_center
)
return {
"account": account,
"balance": account_balance,
"party_balance": party_balance,
"account_currency": frappe.get_cached_value("Account", account, "account_currency"),
}
@frappe.whitelist()
def get_account_details_and_party_type(
account, date, company, debit=None, credit=None, exchange_rate=None
def get_account_balance_and_party_type(
account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None
):
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
@@ -1529,6 +1510,7 @@ def get_account_details_and_party_type(
party_type = ""
grid_values = {
"balance": get_balance_on(account, date, cost_center=cost_center),
"party_type": party_type,
"account_type": account_details.account_type,
"account_currency": account_details.account_currency or company_currency,

View File

@@ -166,37 +166,43 @@ class TestJournalEntry(unittest.TestCase):
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
self.voucher_no = jv.name
gl_entries = frappe.db.sql(
"""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
jv.name,
as_dict=1,
)
self.fields = [
"account",
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
]
self.assertTrue(gl_entries)
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 5000,
},
{
"account": "_Test Bank USD - _TC",
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
"debit": 5000,
"debit_in_account_currency": 100,
"credit": 0,
"credit_in_account_currency": 0,
},
]
"_Test Bank - _TC": {
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 5000,
},
}
self.check_gl_entries()
for field in (
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
# cancel
jv.cancel()
@@ -222,37 +228,43 @@ class TestJournalEntry(unittest.TestCase):
rjv.posting_date = nowdate()
rjv.submit()
self.voucher_no = rjv.name
gl_entries = frappe.db.sql(
"""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
rjv.name,
as_dict=1,
)
self.fields = [
"account",
"account_currency",
"debit",
"credit",
"debit_in_account_currency",
"credit_in_account_currency",
]
self.assertTrue(gl_entries)
self.expected_gle = [
{
"account": "_Test Bank USD - _TC",
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 100,
},
{
"account": "Sales - _TC",
"Sales - _TC": {
"account_currency": "INR",
"debit": 5000,
"debit_in_account_currency": 5000,
"credit": 0,
"credit_in_account_currency": 0,
},
]
}
self.check_gl_entries()
for field in (
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD
@@ -332,25 +344,23 @@ class TestJournalEntry(unittest.TestCase):
jv.insert()
jv.submit()
self.voucher_no = jv.name
expected_values = {
"_Test Cash - _TC": {"cost_center": cost_center},
"_Test Bank - _TC": {"cost_center": cost_center},
}
self.fields = [
"account",
"cost_center",
]
gl_entries = frappe.db.sql(
"""select account, cost_center, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
jv.name,
as_dict=1,
)
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"cost_center": cost_center,
},
{
"account": "_Test Cash - _TC",
"cost_center": cost_center,
},
]
self.assertTrue(gl_entries)
self.check_gl_entries()
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_jv_with_project(self):
from erpnext.projects.doctype.project.test_project import make_project
@@ -377,22 +387,23 @@ class TestJournalEntry(unittest.TestCase):
jv.insert()
jv.submit()
self.voucher_no = jv.name
expected_values = {
"_Test Cash - _TC": {"project": project_name},
"_Test Bank - _TC": {"project": project_name},
}
self.fields = ["account", "project"]
gl_entries = frappe.db.sql(
"""select account, project, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
jv.name,
as_dict=1,
)
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"project": project_name,
},
{
"account": "_Test Cash - _TC",
"project": project_name,
},
]
self.assertTrue(gl_entries)
self.check_gl_entries()
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
@@ -415,79 +426,6 @@ class TestJournalEntry(unittest.TestCase):
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
self.assertEqual(expected_account_balance, account_balance)
def test_repost_accounting_entries(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
# Configure Repost Accounting Ledger for JVs
settings = frappe.get_doc("Repost Accounting Ledger Settings")
if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]:
settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True})
settings.save()
# Create JV with defaut cost center - _Test Cost Center
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
jv.multi_currency = 0
jv.submit()
# Check GL entries before reposting
self.voucher_no = jv.name
self.fields = [
"account",
"debit_in_account_currency",
"credit_in_account_currency",
"cost_center",
]
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"debit_in_account_currency": 0,
"credit_in_account_currency": 100,
"cost_center": "_Test Cost Center - _TC",
},
{
"account": "_Test Cash - _TC",
"debit_in_account_currency": 100,
"credit_in_account_currency": 0,
"cost_center": "_Test Cost Center - _TC",
},
]
self.check_gl_entries()
# Change cost center for bank account - _Test Cost Center for BS Account
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC"
jv.save()
# Check if repost flag gets set on update after submit
self.assertTrue(jv.repost_required)
jv.repost_accounting_entries()
# Check GL entries after reposting
jv.load_from_db()
self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC"
self.check_gl_entries()
def check_gl_entries(self):
gl = frappe.qb.DocType("GL Entry")
query = frappe.qb.from_(gl)
for field in self.fields:
query = query.select(gl[field])
query = query.where(
(gl.voucher_type == "Journal Entry")
& (gl.voucher_no == self.voucher_no)
& (gl.is_cancelled == 0)
).orderby(gl.account)
gl_entries = query.run(as_dict=True)
for i in range(len(self.expected_gle)):
for field in self.fields:
self.assertEqual(self.expected_gle[i][field], gl_entries[i][field])
def make_journal_entry(
account1,

View File

@@ -9,10 +9,12 @@
"field_order": [
"account",
"account_type",
"balance",
"col_break1",
"bank_account",
"party_type",
"party",
"party_balance",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -62,7 +64,17 @@
"print_hide": 1
},
{
"allow_on_submit": 1,
"fieldname": "balance",
"fieldtype": "Currency",
"label": "Account Balance",
"no_copy": 1,
"oldfieldname": "balance",
"oldfieldtype": "Data",
"options": "account_currency",
"print_hide": 1,
"read_only": 1
},
{
"default": ":Company",
"description": "If Income or Expense",
"fieldname": "cost_center",
@@ -95,6 +107,14 @@
"label": "Party",
"options": "party_type"
},
{
"fieldname": "party_balance",
"fieldtype": "Currency",
"label": "Party Balance",
"options": "account_currency",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "currency_section",
"fieldtype": "Section Break",
@@ -203,7 +223,6 @@
"no_copy": 1
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
@@ -267,7 +286,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-02-05 01:10:50.224840",
"modified": "2023-12-03 23:21:22.205409",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -1,77 +1,173 @@
{
"actions": [],
"autoname": "field:distribution_id",
"creation": "2013-01-10 16:34:05",
"description": "Helps you distribute the Budget/Target across months if you have seasonality in your business.",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"distribution_id",
"fiscal_year",
"percentages"
],
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:distribution_id",
"beta": 0,
"creation": "2013-01-10 16:34:05",
"custom": 0,
"description": "**Monthly Distribution** helps you distribute the Budget/Target across months if you have seasonality in your business.",
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"description": "Name of the Monthly Distribution",
"fieldname": "distribution_id",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Distribution Name",
"oldfieldname": "distribution_id",
"oldfieldtype": "Data",
"reqd": 1,
"unique": 1
},
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Name of the Monthly Distribution",
"fieldname": "distribution_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Distribution Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "distribution_id",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"fieldname": "fiscal_year",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Fiscal Year",
"oldfieldname": "fiscal_year",
"oldfieldtype": "Select",
"options": "Fiscal Year",
"search_index": 1
},
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fiscal_year",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Fiscal Year",
"length": 0,
"no_copy": 0,
"oldfieldname": "fiscal_year",
"oldfieldtype": "Select",
"options": "Fiscal Year",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"fieldname": "percentages",
"fieldtype": "Table",
"label": "Monthly Distribution Percentages",
"oldfieldname": "budget_distribution_details",
"oldfieldtype": "Table",
"options": "Monthly Distribution Percentage"
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "percentages",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Monthly Distribution Percentages",
"length": 0,
"no_copy": 0,
"oldfieldname": "budget_distribution_details",
"oldfieldtype": "Table",
"options": "Monthly Distribution Percentage",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"icon": "fa fa-bar-chart",
"idx": 1,
"links": [],
"modified": "2024-01-30 13:57:55.802744",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Monthly Distribution",
"naming_rule": "By fieldname",
"owner": "Administrator",
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-bar-chart",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-11-21 14:54:35.998761",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Monthly Distribution",
"name_case": "Title Case",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
},
{
"permlevel": 2,
"read": 1,
"report": 1,
"role": "Accounts Manager"
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 2,
"print": 0,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
}

View File

@@ -149,7 +149,7 @@ frappe.ui.form.on('Payment Entry', {
},
refresh: function(frm) {
erpnext.hide_company(frm);
erpnext.hide_company();
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
frm.events.show_general_ledger(frm);

View File

@@ -87,14 +87,12 @@
"status",
"custom_remarks",
"remarks",
"base_in_words",
"column_break_16",
"letter_head",
"print_heading",
"bank",
"bank_account_no",
"payment_order",
"in_words",
"subscription_section",
"auto_repeat",
"amended_from",
@@ -749,20 +747,6 @@
"hidden": 1,
"label": "Book Advance Payments in Separate Party Account",
"read_only": 1
},
{
"fieldname": "base_in_words",
"fieldtype": "Small Text",
"label": "In Words (Company Currency)",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "in_words",
"fieldtype": "Small Text",
"label": "In Words",
"print_hide": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,

View File

@@ -178,7 +178,6 @@ class PaymentEntry(AccountsController):
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked()
self.set_status()
self.set_total_in_words()
def on_submit(self):
if self.difference_amount:
@@ -471,10 +470,7 @@ class PaymentEntry(AccountsController):
)
def set_missing_ref_details(
self,
force: bool = False,
update_ref_details_only_for: list | None = None,
ref_exchange_rate: float | None = None,
self, force: bool = False, update_ref_details_only_for: list | None = None
) -> None:
for d in self.get("references"):
if d.allocated_amount:
@@ -487,8 +483,6 @@ class PaymentEntry(AccountsController):
ref_details = get_reference_details(
d.reference_doctype, d.reference_name, self.party_account_currency
)
if ref_exchange_rate:
ref_details.update({"exchange_rate": ref_exchange_rate})
for field, value in ref_details.items():
if d.exchange_gain_loss:
@@ -792,21 +786,6 @@ class PaymentEntry(AccountsController):
self.db_set("status", self.status, update_modified=True)
def set_total_in_words(self):
from frappe.utils import money_in_words
if self.payment_type in ("Pay", "Internal Transfer"):
base_amount = abs(self.base_paid_amount)
amount = abs(self.paid_amount)
currency = self.paid_from_account_currency
elif self.payment_type == "Receive":
base_amount = abs(self.base_received_amount)
amount = abs(self.received_amount)
currency = self.paid_to_account_currency
self.base_in_words = money_in_words(base_amount, self.company_currency)
self.in_words = money_in_words(amount, currency)
def set_tax_withholding(self):
if self.party_type != "Supplier":
return
@@ -948,8 +927,8 @@ class PaymentEntry(AccountsController):
def calculate_base_allocated_amount_for_reference(self, d) -> float:
base_allocated_amount = 0
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
"advance_payment_customer_doctypes"
) + frappe.get_hooks("advance_payment_supplier_doctypes")
if d.reference_doctype in advance_payment_doctypes:
# When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type.
# This is so there are no Exchange Gain/Loss generated for such doctypes
@@ -1037,19 +1016,19 @@ class PaymentEntry(AccountsController):
)
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
included_taxes = self.get_included_taxes()
if self.payment_type == "Receive":
self.difference_amount = base_party_amount - self.base_received_amount + included_taxes
self.difference_amount = base_party_amount - self.base_received_amount
elif self.payment_type == "Pay":
self.difference_amount = self.base_paid_amount - base_party_amount - included_taxes
self.difference_amount = self.base_paid_amount - base_party_amount
else:
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) - included_taxes
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
included_taxes = self.get_included_taxes()
self.difference_amount = flt(
self.difference_amount - total_deductions, self.precision("difference_amount")
self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")
)
def get_included_taxes(self):
@@ -1449,8 +1428,8 @@ class PaymentEntry(AccountsController):
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
"advance_payment_customer_doctypes"
) + frappe.get_hooks("advance_payment_supplier_doctypes")
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
@@ -2225,7 +2204,6 @@ def get_payment_entry(
party_type=None,
payment_type=None,
reference_date=None,
ignore_permissions=False,
):
doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
@@ -2248,14 +2226,14 @@ def get_payment_entry(
)
# bank or cash
bank = get_bank_cash_account(doc, bank_account, ignore_permissions=ignore_permissions)
bank = get_bank_cash_account(doc, bank_account)
# 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, ignore_permissions=ignore_permissions)
bank = get_bank_cash_account(doc, account)
paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
@@ -2395,13 +2373,9 @@ def update_accounting_dimensions(pe, doc):
pe.set(dimension, doc.get(dimension))
def get_bank_cash_account(doc, bank_account, ignore_permissions=False):
def get_bank_cash_account(doc, bank_account):
bank = get_default_bank_cash_account(
doc.company,
"Bank",
mode_of_payment=doc.get("mode_of_payment"),
account=bank_account,
ignore_permissions=ignore_permissions,
doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
)
if not bank:

View File

@@ -4,13 +4,9 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import getdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
create_bank_account,
create_gl_account,
)
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_payment_entry,
make_payment_order,
@@ -18,32 +14,28 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
class TestPaymentOrder(FrappeTestCase):
class TestPaymentOrder(unittest.TestCase):
def setUp(self):
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
uniq_identifier = frappe.generate_hash(length=10)
self.gl_account = create_gl_account("_Test Bank " + uniq_identifier)
self.bank_account = create_bank_account(
gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier
)
create_bank_account()
def tearDown(self):
frappe.db.rollback()
for bt in frappe.get_all("Payment Order"):
doc = frappe.get_doc("Payment Order", bt.name)
doc.cancel()
doc.delete()
def test_payment_order_creation_against_payment_entry(self):
purchase_invoice = make_purchase_invoice()
payment_entry = get_payment_entry(
"Purchase Invoice", purchase_invoice.name, bank_account=self.gl_account
"Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC"
)
payment_entry.reference_no = "_Test_Payment_Order"
payment_entry.reference_date = getdate()
payment_entry.party_bank_account = self.bank_account
payment_entry.party_bank_account = "Checking Account - Citi Bank"
payment_entry.insert()
payment_entry.submit()
doc = create_payment_order_against_payment_entry(
payment_entry, "Payment Entry", self.bank_account
)
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
reference_doc = doc.get("references")[0]
self.assertEqual(reference_doc.reference_name, payment_entry.name)
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
@@ -51,13 +43,13 @@ class TestPaymentOrder(FrappeTestCase):
self.assertEqual(reference_doc.amount, 250)
def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
def create_payment_order_against_payment_entry(ref_doc, order_type):
payment_order = frappe.get_doc(
dict(
doctype="Payment Order",
company="_Test Company",
payment_order_type=order_type,
company_bank_account=bank_account,
company_bank_account="Checking Account - Citi Bank",
)
)
doc = make_payment_order(ref_doc.name, payment_order)

View File

@@ -41,7 +41,6 @@
{
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Company",
"options": "Company",
"reqd": 1
@@ -230,7 +229,7 @@
"is_virtual": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-18 11:56:20.234667",
"modified": "2023-12-14 13:38:16.264013",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",

View File

@@ -633,12 +633,7 @@ class PaymentReconciliation(Document):
journals_map = frappe._dict(
frappe.db.get_all(
"Journal Entry Account",
filters={
"parent": ("in", journals),
"account": ("in", [self.receivable_payable_account]),
"party_type": self.party_type,
"party": self.party,
},
filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])},
fields=[
"parent as `name`",
"exchange_rate",

View File

@@ -56,7 +56,6 @@ class TestPaymentReconciliation(FrappeTestCase):
self.expense_account = "Cost of Goods Sold - _PR"
self.debit_to = "Debtors - _PR"
self.creditors = "Creditors - _PR"
self.cash = "Cash - _PR"
# create bank account
if frappe.db.exists("Account", "HDFC - _PR"):
@@ -487,91 +486,6 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_payment_against_foreign_currency_journal(self):
transaction_date = nowdate()
self.supplier = "_Test Supplier USD"
self.supplier2 = make_supplier("_Test Supplier2 USD", "USD")
amount = 100
exc_rate1 = 80
exc_rate2 = 83
je = frappe.new_doc("Journal Entry")
je.posting_date = transaction_date
je.company = self.company
je.user_remark = "test"
je.multi_currency = 1
je.set(
"accounts",
[
{
"account": self.creditors_usd,
"party_type": "Supplier",
"party": self.supplier,
"exchange_rate": exc_rate1,
"cost_center": self.cost_center,
"credit": amount * exc_rate1,
"credit_in_account_currency": amount,
},
{
"account": self.creditors_usd,
"party_type": "Supplier",
"party": self.supplier2,
"exchange_rate": exc_rate2,
"cost_center": self.cost_center,
"credit": amount * exc_rate2,
"credit_in_account_currency": amount,
},
{
"account": self.expense_account,
"cost_center": self.cost_center,
"debit": (amount * exc_rate1) + (amount * exc_rate2),
"debit_in_account_currency": (amount * exc_rate1) + (amount * exc_rate2),
},
],
)
je.save().submit()
pe = self.create_payment_entry(amount=amount, posting_date=transaction_date)
pe.payment_type = "Pay"
pe.party_type = "Supplier"
pe.party = self.supplier
pe.paid_to = self.creditors_usd
pe.paid_from = self.cash
pe.paid_amount = 8000
pe.received_amount = 100
pe.target_exchange_rate = exc_rate1
pe.paid_to_account_currency = "USD"
pe.save().submit()
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.receivable_payable_account = self.creditors_usd
pr.minimum_invoice_amount = pr.maximum_invoice_amount = amount
pr.from_invoice_date = pr.to_invoice_date = transaction_date
pr.from_payment_date = pr.to_payment_date = transaction_date
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# There should no difference_amount as the Journal and Payment have same exchange rate - 'exc_rate1'
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
journals = frappe.db.get_all(
"Journal Entry Account",
filters={"reference_type": je.doctype, "reference_name": je.name, "docstatus": 1},
fields=["parent"],
)
self.assertEqual([], journals)
def test_journal_against_invoice(self):
transaction_date = nowdate()
amount = 100
@@ -677,70 +591,6 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(si.status, "Paid")
self.assertEqual(si.outstanding_amount, 0)
def test_invoice_status_after_cr_note_cancellation(self):
# This test case is made after the 'always standalone Credit/Debit notes' feature is introduced
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
cr_note = self.create_sales_invoice(
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
cr_note.is_return = 1
cr_note.return_against = si.name
cr_note = cr_note.save().submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pr.get_unreconciled_entries()
self.assertEqual(pr.get("invoices"), [])
self.assertEqual(pr.get("payments"), [])
journals = frappe.db.get_all(
"Journal Entry",
filters={
"is_system_generated": 1,
"docstatus": 1,
"voucher_type": "Credit Note",
"reference_type": si.doctype,
"reference_name": si.name,
},
pluck="name",
)
self.assertEqual(len(journals), 1)
# assert status and outstanding
si.reload()
self.assertEqual(si.status, "Credit Note Issued")
self.assertEqual(si.outstanding_amount, 0)
cr_note.reload()
cr_note.cancel()
# 'Credit Note' Journal should be auto cancelled
journals = frappe.db.get_all(
"Journal Entry",
filters={
"is_system_generated": 1,
"docstatus": 1,
"voucher_type": "Credit Note",
"reference_type": si.doctype,
"reference_name": si.name,
},
pluck="name",
)
self.assertEqual(len(journals), 0)
# assert status and outstanding
si.reload()
self.assertEqual(si.status, "Unpaid")
self.assertEqual(si.outstanding_amount, 100)
def test_cr_note_partial_against_invoice(self):
transaction_date = nowdate()
amount = 100
@@ -1334,17 +1184,3 @@ def make_customer(customer_name, currency=None):
return customer.name
else:
return customer_name
def make_supplier(supplier_name, currency=None):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.new_doc("Supplier")
supplier.supplier_name = supplier_name
supplier.type = "Individual"
if currency:
supplier.default_currency = currency
supplier.save()
return supplier.name
else:
return supplier_name

View File

@@ -25,10 +25,6 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
})
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
if(frm.doc.status == 'Failed'){
frm.set_intro(__("Failure: {0}", [frm.doc.failed_reason]), "red");
}
if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
frm.add_custom_button(__('Resend Payment Email'), function(){

View File

@@ -7,7 +7,6 @@
"field_order": [
"payment_request_type",
"transaction_date",
"failed_reason",
"column_break_2",
"naming_series",
"mode_of_payment",
@@ -390,22 +389,13 @@
"options": "Payment Request",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "failed_reason",
"fieldtype": "Data",
"hidden": 1,
"label": "Reason for Failure",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-01-20 00:37:06.988919",
"modified": "2023-09-27 09:51:42.277638",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
@@ -443,4 +433,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -3,7 +3,6 @@ import json
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate
from frappe.utils.background_jobs import enqueue
@@ -107,8 +106,6 @@ class PaymentRequest(Document):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
if not ref_amount:
frappe.throw(_("Payment Entry is already created"))
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
frappe.throw(
@@ -173,8 +170,8 @@ class PaymentRequest(Document):
self.request_phone_payment()
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
"advance_payment_customer_doctypes"
) + frappe.get_hooks("advance_payment_supplier_doctypes")
if self.reference_doctype in advance_payment_doctypes:
# set advance payment status
ref_doc.set_total_advance_paid()
@@ -219,8 +216,8 @@ class PaymentRequest(Document):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
"advance_payment_customer_doctypes"
) + frappe.get_hooks("advance_payment_supplier_doctypes")
if self.reference_doctype in advance_payment_doctypes:
# set advance payment status
ref_doc.set_total_advance_paid()
@@ -456,8 +453,6 @@ def make_payment_request(**args):
gateway_account = get_gateway_details(args) or frappe._dict()
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
if not grand_total:
frappe.throw(_("Payment Entry is already created"))
if args.loyalty_points and args.dt == "Sales Order":
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
@@ -548,7 +543,6 @@ def get_amount(ref_doc, payment_account=None):
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
grand_total -= get_paid_amount_against_order(dt, ref_doc.name)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
if ref_doc.party_account_currency == ref_doc.currency:
@@ -568,7 +562,10 @@ def get_amount(ref_doc, payment_account=None):
elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
return grand_total
if grand_total > 0:
return grand_total
else:
frappe.throw(_("Payment Entry is already created"))
def get_existing_payment_request_amount(ref_dt, ref_dn):
@@ -751,27 +748,3 @@ def validate_payment(doc, method=None):
doc.reference_docname
)
)
def get_paid_amount_against_order(dt, dn):
pe_ref = frappe.qb.DocType("Payment Entry Reference")
if dt == "Sales Order":
inv_dt, inv_field = "Sales Invoice Item", "sales_order"
else:
inv_dt, inv_field = "Purchase Invoice Item", "purchase_order"
inv_item = frappe.qb.DocType(inv_dt)
return (
frappe.qb.from_(pe_ref)
.select(
Sum(pe_ref.allocated_amount),
)
.where(
(pe_ref.docstatus == 1)
& (
(pe_ref.reference_name == dn)
| pe_ref.reference_name.isin(
frappe.qb.from_(inv_item).select(inv_item.parent).where(inv_item[inv_field] == dn).distinct()
)
)
)
).run()[0][0] or 0

View File

@@ -16,9 +16,6 @@ frappe.listview_settings['Payment Request'] = {
else if(doc.status == "Paid") {
return [__("Paid"), "blue", "status,=,Paid"];
}
else if(doc.status == "Failed") {
return [__("Failed"), "red", "status,=,Failed"];
}
else if(doc.status == "Cancelled") {
return [__("Cancelled"), "red", "status,=,Cancelled"];
}

View File

@@ -11,6 +11,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_lo
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
SalesInvoice,
get_bank_cash_account,
get_mode_of_payment_info,
update_multi_mode_option,
)
@@ -207,6 +208,7 @@ class POSInvoice(SalesInvoice):
self.validate_stock_availablility()
self.validate_return_items_qty()
self.set_status()
self.set_account_for_mode_of_payment()
self.validate_pos()
self.validate_payment_amount()
self.validate_loyalty_transaction()
@@ -641,6 +643,11 @@ class POSInvoice(SalesInvoice):
update_multi_mode_option(self, pos_profile)
self.paid_amount = 0
def set_account_for_mode_of_payment(self):
for pay in self.payments:
if not pay.account:
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
@frappe.whitelist()
def create_payment_request(self):
for pay in self.payments:

View File

@@ -93,7 +93,7 @@ class TestPOSInvoice(unittest.TestCase):
inv.save()
self.assertEqual(inv.net_total, 4298.24)
self.assertEqual(inv.net_total, 4298.25)
self.assertEqual(inv.grand_total, 4900.00)
def test_tax_calculation_with_multiple_items(self):

View File

@@ -80,16 +80,13 @@
"target_warehouse",
"quality_inspection",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"batch_no",
"col_break5",
"allow_zero_valuation_rate",
"serial_no",
"item_tax_rate",
"actual_batch_qty",
"actual_qty",
"section_break_tlhi",
"serial_no",
"column_break_ciit",
"batch_no",
"edit_references",
"sales_order",
"so_detail",
@@ -631,13 +628,13 @@
"options": "Quality Inspection"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 1,
"label": "Batch No",
"options": "Batch",
"print_hide": 1
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "col_break5",
@@ -652,14 +649,14 @@
"print_hide": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"fieldtype": "Small Text",
"hidden": 1,
"in_list_view": 1,
"label": "Serial No",
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text"
"oldfieldtype": "Small Text",
"read_only": 1
},
{
"fieldname": "item_tax_rate",
@@ -827,33 +824,17 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_tlhi",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_ciit",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2024-02-25 15:50:17.140269",
"modified": "2023-11-14 18:33:22.585715",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",

View File

@@ -72,7 +72,7 @@ class POSInvoiceItem(Document):
rate_with_margin: DF.Currency
sales_order: DF.Link | None
serial_and_batch_bundle: DF.Link | None
serial_no: DF.Text | None
serial_no: DF.SmallText | None
service_end_date: DF.Date | None
service_start_date: DF.Date | None
service_stop_date: DF.Date | None
@@ -82,7 +82,6 @@ class POSInvoiceItem(Document):
target_warehouse: DF.Link | None
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
weight_per_unit: DF.Float
weight_uom: DF.Link | None

View File

@@ -351,7 +351,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
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)
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
finally:
frappe.set_user("Administrator")

View File

@@ -120,6 +120,18 @@ def get_statement_dict(doc, get_statement_dict=False):
statement_dict = {}
ageing = ""
err_journals = None
if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
err_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": doc.company,
"docstatus": 1,
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
},
as_list=True,
)
for entry in doc.customers:
if doc.include_ageing:
ageing = set_ageing(doc, entry)
@@ -132,8 +144,8 @@ def get_statement_dict(doc, get_statement_dict=False):
)
filters = get_common_filters(doc)
if doc.ignore_exchange_rate_revaluation_journals:
filters.update({"ignore_err": True})
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if doc.report == "General Ledger":
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))

View File

@@ -22,8 +22,6 @@
"is_paid",
"is_return",
"return_against",
"update_billed_amount_in_purchase_order",
"update_billed_amount_in_purchase_receipt",
"apply_tds",
"tax_withholding_category",
"amended_from",
@@ -414,20 +412,6 @@
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"depends_on": "eval: doc.is_return",
"fieldname": "update_billed_amount_in_purchase_order",
"fieldtype": "Check",
"label": "Update Billed Amount in Purchase Order"
},
{
"default": "1",
"depends_on": "eval: doc.is_return",
"fieldname": "update_billed_amount_in_purchase_receipt",
"fieldtype": "Check",
"label": "Update Billed Amount in Purchase Receipt"
},
{
"fieldname": "section_addresses",
"fieldtype": "Section Break",
@@ -1269,7 +1253,6 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1
},
@@ -1629,7 +1612,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2024-02-25 11:20:28.366808",
"modified": "2023-11-29 15:35:44.697496",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -193,7 +193,6 @@ class PurchaseInvoice(BuyingController):
supplied_items: DF.Table[PurchaseReceiptItemSupplied]
supplier: DF.Link
supplier_address: DF.Link | None
supplier_group: DF.Link | None
supplier_name: DF.Data | None
supplier_warehouse: DF.Link | None
tax_category: DF.Link | None
@@ -215,8 +214,6 @@ class PurchaseInvoice(BuyingController):
total_qty: DF.Float
total_taxes_and_charges: DF.Currency
unrealized_profit_loss_account: DF.Link | None
update_billed_amount_in_purchase_order: DF.Check
update_billed_amount_in_purchase_receipt: DF.Check
update_stock: DF.Check
use_company_roundoff_cost_center: DF.Check
use_transaction_date_exchange_rate: DF.Check
@@ -682,11 +679,6 @@ class PurchaseInvoice(BuyingController):
super(PurchaseInvoice, self).on_submit()
self.check_prev_docstatus()
if self.is_return and not self.update_billed_amount_in_purchase_order:
# NOTE status updating bypassed for is_return
self.status_updater = []
self.update_status_updater_args()
self.update_prevdoc_status()
@@ -704,7 +696,6 @@ class PurchaseInvoice(BuyingController):
# Updating stock ledger should always be called after updating prevdoc status,
# because updating ordered qty in bin depends upon updated ordered qty in PO
if self.update_stock == 1:
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
if self.is_old_subcontracting_flow:
@@ -732,7 +723,6 @@ class PurchaseInvoice(BuyingController):
"cash_bank_account",
"write_off_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
@@ -1025,14 +1015,9 @@ class PurchaseInvoice(BuyingController):
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:
provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value(
"Purchase Receipt Item",
item.pr_detail,
["provisional_expense_account", "qty", "base_rate"],
)
provisional_account = provisional_account or self.get_company_default(
"default_provisional_account"
)
provisional_account = frappe.db.get_value(
"Purchase Receipt Item", item.pr_detail, "provisional_expense_account"
) or self.get_company_default("default_provisional_account")
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
if not purchase_receipt_doc:
@@ -1049,18 +1034,13 @@ class PurchaseInvoice(BuyingController):
"voucher_detail_no": item.pr_detail,
"account": provisional_account,
},
"name",
["name"],
)
if expense_booked_in_pr:
# Intentionally passing purchase invoice item to handle partial billing
purchase_receipt_doc.add_provisional_gl_entry(
item,
gl_entries,
self.posting_date,
provisional_account,
reverse=1,
item_amount=(min(item.qty, pr_qty) * pr_base_rate),
item, gl_entries, self.posting_date, provisional_account, reverse=1
)
if not self.is_internal_transfer():
@@ -1445,10 +1425,6 @@ class PurchaseInvoice(BuyingController):
self.check_on_hold_or_closed_status()
if self.is_return and not self.update_billed_amount_in_purchase_order:
# NOTE status updating bypassed for is_return
self.status_updater = []
self.update_status_updater_args()
self.update_prevdoc_status()
@@ -1543,9 +1519,6 @@ class PurchaseInvoice(BuyingController):
frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}").format(pi))
def update_billing_status_in_pr(self, update_modified=True):
if self.is_return and not self.update_billed_amount_in_purchase_receipt:
return
updated_pr = []
po_details = []

View File

@@ -1539,7 +1539,18 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
def test_provisional_accounting_entry(self):
setup_provisional_accounting()
create_item("_Test Non Stock Item", is_stock_item=0)
provisional_account = create_account(
account_name="Provision Account",
parent_account="Current Liabilities - _TC",
company="_Test Company",
)
company = frappe.get_doc("Company", "_Test Company")
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()
pr = make_purchase_receipt(
item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2)
@@ -1583,97 +1594,8 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
toggle_provisional_accounting_setting()
def test_provisional_accounting_entry_for_over_billing(self):
setup_provisional_accounting()
# Configure Buying Settings to allow rate change
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
# Create PR: rate = 1000, qty = 5
pr = make_purchase_receipt(
item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2)
)
# Overbill PR: rate = 2000, qty = 10
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.items[0].qty = 10
pi.items[0].rate = 2000
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
pi.save()
pi.submit()
expected_gle = [
["Cost of Goods Sold - _TC", 20000, 0, add_days(pr.posting_date, -1)],
["Creditors - _TC", 0, 20000, add_days(pr.posting_date, -1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 5000, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
["Provision Account - _TC", 0, 5000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
# Cancel purchase invoice to check reverse provisional entry cancellation
pi.cancel()
expected_gle_for_purchase_receipt_post_pi_cancel = [
["Provision Account - _TC", 0, 5000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
]
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
toggle_provisional_accounting_setting()
def test_provisional_accounting_entry_for_partial_billing(self):
setup_provisional_accounting()
# Configure Buying Settings to allow rate change
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
# Create PR: rate = 1000, qty = 5
pr = make_purchase_receipt(
item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2)
)
# Partially bill PR: rate = 500, qty = 2
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.items[0].qty = 2
pi.items[0].rate = 500
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
pi.save()
pi.submit()
expected_gle = [
["Cost of Goods Sold - _TC", 1000, 0, add_days(pr.posting_date, -1)],
["Creditors - _TC", 0, 1000, add_days(pr.posting_date, -1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 5000, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
["Provision Account - _TC", 0, 1000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 1000, 0, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
toggle_provisional_accounting_setting()
company.enable_provisional_accounting_for_non_stock_items = 0
company.save()
def test_adjust_incoming_rate(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
@@ -2073,21 +1995,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
def test_debit_note_with_account_mismatch(self):
new_creditors = create_account(
parent_account="Accounts Payable - _TC",
account_name="Creditors 2",
company="_Test Company",
account_type="Payable",
)
pi = make_purchase_invoice(qty=1, rate=1000)
dr_note = make_purchase_invoice(
qty=-1, rate=1000, is_return=1, return_against=pi.name, do_not_save=True
)
dr_note.credit_to = new_creditors
self.assertRaises(frappe.ValidationError, dr_note.save)
def test_debit_note_without_item(self):
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
pi.items[0].item_code = ""
@@ -2108,92 +2015,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
return_pi.submit()
self.assertEqual(return_pi.docstatus, 1)
def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
batch_item = make_item(
"_Test Purchase Invoice Batch Item For Rejected Qty",
properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
).name
serial_item = make_item(
"_Test Purchase Invoice Serial Item for Rejected Qty",
properties={"has_serial_no": 1, "is_stock_item": 1},
).name
rej_warehouse = create_warehouse("_Test Purchase INV Warehouse For Rejected Qty")
batch_no = "BATCH-PI-BNU-TPRBI-0001"
serial_nos = ["SNU-PI-TPRSI-0001", "SNU-PI-TPRSI-0002", "SNU-PI-TPRSI-0003"]
if not frappe.db.exists("Batch", batch_no):
frappe.get_doc(
{
"doctype": "Batch",
"batch_id": batch_no,
"item": batch_item,
}
).insert()
for serial_no in serial_nos:
if not frappe.db.exists("Serial No", serial_no):
frappe.get_doc(
{
"doctype": "Serial No",
"item_code": serial_item,
"serial_no": serial_no,
}
).insert()
pi = make_purchase_invoice(
item_code=batch_item,
received_qty=10,
qty=8,
rejected_qty=2,
update_stock=1,
rejected_warehouse=rej_warehouse,
use_serial_batch_fields=1,
batch_no=batch_no,
rate=100,
do_not_submit=1,
)
pi.append(
"items",
{
"item_code": serial_item,
"qty": 2,
"rate": 100,
"base_rate": 100,
"item_name": serial_item,
"uom": "Nos",
"stock_uom": "Nos",
"conversion_factor": 1,
"rejected_qty": 1,
"warehouse": pi.items[0].warehouse,
"rejected_warehouse": rej_warehouse,
"use_serial_batch_fields": 1,
"serial_no": "\n".join(serial_nos[:2]),
"rejected_serial_no": serial_nos[2],
},
)
pi.save()
pi.submit()
pi.reload()
for row in pi.items:
self.assertTrue(row.serial_and_batch_bundle)
self.assertTrue(row.rejected_serial_and_batch_bundle)
if row.item_code == batch_item:
self.assertEqual(row.batch_no, batch_no)
else:
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
self.assertEqual(row.rejected_serial_no, serial_nos[2])
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(
@@ -2301,7 +2122,7 @@ def make_purchase_invoice(**args):
pi.cost_center = args.parent_cost_center
bundle_id = None
if not args.use_serial_batch_fields and ((args.get("batch_no") or args.get("serial_no"))):
if args.get("batch_no") or args.get("serial_no"):
batches = {}
qty = args.qty if args.qty is not None else 5
item_code = args.item or args.item_code or "_Test Item"
@@ -2348,9 +2169,6 @@ def make_purchase_invoice(**args):
"rejected_warehouse": args.rejected_warehouse or "",
"asset_location": args.location or "",
"allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0,
"use_serial_batch_fields": args.get("use_serial_batch_fields") or 0,
"batch_no": args.get("batch_no") if args.get("use_serial_batch_fields") else "",
"serial_no": args.get("serial_no") if args.get("use_serial_batch_fields") else "",
},
)
@@ -2441,26 +2259,4 @@ def make_purchase_invoice_against_cost_center(**args):
return pi
def setup_provisional_accounting(**args):
args = frappe._dict(args)
create_item("_Test Non Stock Item", is_stock_item=0)
company = args.company or "_Test Company"
provisional_account = create_account(
account_name=args.account_name or "Provision Account",
parent_account=args.parent_account or "Current Liabilities - _TC",
company=company,
)
toggle_provisional_accounting_setting(
enable=1, company=company, provisional_account=provisional_account
)
def toggle_provisional_accounting_setting(**args):
args = frappe._dict(args)
company = frappe.get_doc("Company", args.company or "_Test Company")
company.enable_provisional_accounting_for_non_stock_items = args.enable or 0
company.default_provisional_account = args.provisional_account
company.save()
test_records = frappe.get_test_records("Purchase Invoice")

View File

@@ -62,19 +62,16 @@
"rm_supp_cost",
"warehouse_section",
"warehouse",
"add_serial_batch_bundle",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"col_br_wh",
"from_warehouse",
"quality_inspection",
"add_serial_batch_bundle",
"serial_and_batch_bundle",
"serial_no",
"col_br_wh",
"rejected_warehouse",
"rejected_serial_and_batch_bundle",
"section_break_rqbe",
"serial_no",
"rejected_serial_no",
"column_break_vbbb",
"batch_no",
"rejected_serial_no",
"manufacture_details",
"manufacturer",
"column_break_13",
@@ -443,11 +440,13 @@
"print_hide": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 1,
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
@@ -455,18 +454,21 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No"
"hidden": 1,
"label": "Serial No",
"read_only": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "rejected_serial_no",
"fieldtype": "Text",
"label": "Rejected Serial No",
"no_copy": 1,
"print_hide": 1
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "accounting",
@@ -889,7 +891,7 @@
"label": "Apply TDS"
},
{
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@@ -899,7 +901,7 @@
"search_index": 1
},
{
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "rejected_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle",
@@ -914,31 +916,16 @@
"options": "Asset"
},
{
"depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"depends_on": "eval:parent.update_stock === 1",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "section_break_rqbe",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_vbbb",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-02-04 14:11:52.742228",
"modified": "2024-01-21 19:46:25.537861",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -88,7 +88,6 @@ class PurchaseInvoiceItem(Document):
stock_uom_rate: DF.Currency
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency
warehouse: DF.Link | None
weight_per_unit: DF.Float

View File

@@ -3,7 +3,7 @@
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-10 16:34:08",
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain a list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\", etc.",
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
@@ -77,7 +77,7 @@
"icon": "fa fa-money",
"idx": 1,
"links": [],
"modified": "2024-01-30 13:08:09.537242",
"modified": "2022-05-16 16:15:29.059370",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges Template",

View File

@@ -88,7 +88,6 @@ class RepostAccountingLedger(Document):
).append(gle.update({"old": True}))
def generate_preview_data(self):
frappe.flags.through_repost_accounting_ledger = True
self.gl_entries = []
self.get_existing_ledger_entries()
for x in self.vouchers:
@@ -142,7 +141,6 @@ class RepostAccountingLedger(Document):
@frappe.whitelist()
def start_repost(account_repost_doc=str) -> None:
frappe.flags.through_repost_accounting_ledger = True
if account_repost_doc:
repost_doc = frappe.get_doc("Repost Accounting Ledger", account_repost_doc)

View File

@@ -14,8 +14,23 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.setup(doc);
}
company() {
super.company();
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
let me = this;
if (this.frm.doc.company) {
frappe.call({
method:
"erpnext.accounts.party.get_party_account",
args: {
party_type: 'Customer',
party: this.frm.doc.customer,
company: this.frm.doc.company
},
callback: (response) => {
if (response) me.frm.set_value("debit_to", response.message);
},
});
}
}
onload() {
var me = this;

View File

@@ -270,7 +270,7 @@ class SalesInvoice(SellingController):
super(SalesInvoice, self).validate()
self.validate_auto_set_posting_time()
if not (self.is_pos or self.is_debit_note):
if not self.is_pos:
self.so_dn_required()
self.set_tax_withholding()
@@ -421,8 +421,7 @@ class SalesInvoice(SellingController):
self.calculate_taxes_and_totals()
def before_save(self):
self.set_account_for_mode_of_payment()
self.set_paid_amount()
set_account_for_mode_of_payment(self)
def on_submit(self):
self.validate_pos_paid_amount()
@@ -447,11 +446,6 @@ class SalesInvoice(SellingController):
# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
if self.update_stock == 1:
for table_name in ["items", "packed_items"]:
if not self.get(table_name):
continue
self.make_bundle_using_old_serial_batch_fields(table_name)
self.update_stock_ledger()
# this sequence because outstanding may get -ve
@@ -718,6 +712,9 @@ class SalesInvoice(SellingController):
):
data.sales_invoice = sales_invoice
def on_update(self):
self.set_paid_amount()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
fields_to_check = [
@@ -727,7 +724,6 @@ class SalesInvoice(SellingController):
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {
"items": ("income_account", "expense_account", "discount_account"),
@@ -749,11 +745,6 @@ class SalesInvoice(SellingController):
self.paid_amount = paid_amount
self.base_paid_amount = base_paid_amount
def set_account_for_mode_of_payment(self):
for payment in self.payments:
if not payment.account:
payment.account = get_bank_cash_account(payment.mode_of_payment, self.company).get("account")
def validate_time_sheets_are_submitted(self):
for data in self.timesheets:
if data.time_sheet:
@@ -1483,7 +1474,9 @@ class SalesInvoice(SellingController):
"credit_in_account_currency": payment_mode.base_amount
if self.party_account_currency == self.company_currency
else payment_mode.amount,
"against_voucher": self.name,
"against_voucher": self.return_against
if cint(self.is_return) and self.return_against
else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
},
@@ -2120,6 +2113,12 @@ def make_sales_return(source_name, target_doc=None):
return make_return_doc("Sales Invoice", source_name, target_doc)
def set_account_for_mode_of_payment(self):
for data in self.payments:
if not data.account:
data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account")
def get_inter_company_details(doc, doctype):
if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"]:
parties = frappe.db.get_all(

View File

@@ -323,8 +323,7 @@ class TestSalesInvoice(FrappeTestCase):
si.insert()
# with inclusive tax
self.assertEqual(si.items[0].net_amount, 3947.37)
self.assertEqual(si.net_total, si.base_net_total)
self.assertEqual(si.items[0].net_amount, 3947.368421052631)
self.assertEqual(si.net_total, 3947.37)
self.assertEqual(si.grand_total, 5000)
@@ -668,7 +667,7 @@ class TestSalesInvoice(FrappeTestCase):
62.5,
625.0,
50,
499.98,
499.97600115194473,
],
"_Test Item Home Desktop 200": [
190.66,
@@ -679,7 +678,7 @@ class TestSalesInvoice(FrappeTestCase):
190.66,
953.3,
150,
750,
749.9968530500239,
],
}
@@ -692,21 +691,20 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(d.get(k), expected_values[d.item_code][i])
# check net total
self.assertEqual(si.base_net_total, si.net_total)
self.assertEqual(si.net_total, 1249.98)
self.assertEqual(si.net_total, 1249.97)
self.assertEqual(si.total, 1578.3)
# check tax calculation
expected_values = {
"keys": ["tax_amount", "total"],
"_Test Account Excise Duty - _TC": [140, 1389.98],
"_Test Account Education Cess - _TC": [2.8, 1392.78],
"_Test Account S&H Education Cess - _TC": [1.4, 1394.18],
"_Test Account CST - _TC": [27.88, 1422.06],
"_Test Account VAT - _TC": [156.25, 1578.31],
"_Test Account Customs Duty - _TC": [125, 1703.31],
"_Test Account Shipping Charges - _TC": [100, 1803.31],
"_Test Account Discount - _TC": [-180.33, 1622.98],
"_Test Account Excise Duty - _TC": [140, 1389.97],
"_Test Account Education Cess - _TC": [2.8, 1392.77],
"_Test Account S&H Education Cess - _TC": [1.4, 1394.17],
"_Test Account CST - _TC": [27.88, 1422.05],
"_Test Account VAT - _TC": [156.25, 1578.30],
"_Test Account Customs Duty - _TC": [125, 1703.30],
"_Test Account Shipping Charges - _TC": [100, 1803.30],
"_Test Account Discount - _TC": [-180.33, 1622.97],
}
for d in si.get("taxes"):
@@ -742,7 +740,7 @@ class TestSalesInvoice(FrappeTestCase):
"base_rate": 2500,
"base_amount": 25000,
"net_rate": 40,
"net_amount": 399.98,
"net_amount": 399.9808009215558,
"base_net_rate": 2000,
"base_net_amount": 19999,
},
@@ -756,7 +754,7 @@ class TestSalesInvoice(FrappeTestCase):
"base_rate": 7500,
"base_amount": 37500,
"net_rate": 118.01,
"net_amount": 590.05,
"net_amount": 590.0531205155963,
"base_net_rate": 5900.5,
"base_net_amount": 29502.5,
},
@@ -794,13 +792,8 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(si.base_grand_total, 60795)
self.assertEqual(si.grand_total, 1215.90)
# no rounding adjustment as the Smallest Currency Fraction Value of USD is 0.01
if frappe.db.get_value("Currency", "USD", "smallest_currency_fraction_value") < 0.01:
self.assertEqual(si.rounding_adjustment, 0.10)
self.assertEqual(si.base_rounding_adjustment, 5.0)
else:
self.assertEqual(si.rounding_adjustment, 0.0)
self.assertEqual(si.base_rounding_adjustment, 0.0)
self.assertEqual(si.rounding_adjustment, 0.01)
self.assertEqual(si.base_rounding_adjustment, 0.50)
def test_outstanding(self):
w = self.make()
@@ -1105,44 +1098,6 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(pos.grand_total, 100.0)
self.assertEqual(pos.write_off_amount, 10)
def test_ledger_entries_of_return_pos_invoice(self):
make_pos_profile()
pos = create_sales_invoice(do_not_save=True)
pos.is_pos = 1
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos.save().submit()
self.assertEqual(pos.outstanding_amount, 0.0)
self.assertEqual(pos.status, "Paid")
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
pos_return = make_sales_return(pos.name)
pos_return.save().submit()
pos_return.reload()
pos.reload()
self.assertEqual(pos_return.is_return, 1)
self.assertEqual(pos_return.return_against, pos.name)
self.assertEqual(pos_return.outstanding_amount, 0.0)
self.assertEqual(pos_return.status, "Return")
self.assertEqual(pos.outstanding_amount, 0.0)
self.assertEqual(pos.status, "Credit Note Issued")
expected = (
("Cash - _TC", 0.0, 100.0, pos_return.name, None),
("Debtors - _TC", 0.0, 100.0, pos_return.name, pos_return.name),
("Debtors - _TC", 100.0, 0.0, pos_return.name, pos_return.name),
("Sales - _TC", 100.0, 0.0, pos_return.name, None),
)
res = frappe.db.get_all(
"GL Entry",
filters={"voucher_no": pos_return.name, "is_cancelled": 0},
fields=["account", "debit", "credit", "voucher_no", "against_voucher"],
order_by="account, debit, credit",
as_list=1,
)
self.assertEqual(expected, res)
def test_pos_with_no_gl_entry_for_change_amount(self):
frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 0)
@@ -1588,19 +1543,6 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
def test_return_invoice_with_account_mismatch(self):
debtors2 = create_account(
parent_account="Accounts Receivable - _TC",
account_name="Debtors 2",
company="_Test Company",
account_type="Receivable",
)
si = create_sales_invoice(qty=1, rate=1000)
cr_note = create_sales_invoice(
qty=-1, rate=1000, is_return=1, return_against=si.name, debit_to=debtors2, do_not_save=True
)
self.assertRaises(frappe.ValidationError, cr_note.save)
def test_gle_made_when_asset_is_returned(self):
create_asset_data()
asset = create_asset(item_code="Macbook Pro")
@@ -2140,7 +2082,7 @@ class TestSalesInvoice(FrappeTestCase):
def test_rounding_adjustment_2(self):
si = create_sales_invoice(rate=400, do_not_save=True)
for rate in [400.25, 600.30, 100.65]:
for rate in [400, 600, 100]:
si.append(
"items",
{
@@ -2166,18 +2108,17 @@ class TestSalesInvoice(FrappeTestCase):
)
si.save()
si.submit()
self.assertEqual(si.net_total, si.base_net_total)
self.assertEqual(si.net_total, 1272.20)
self.assertEqual(si.grand_total, 1501.20)
self.assertEqual(si.total_taxes_and_charges, 229)
self.assertEqual(si.rounding_adjustment, -0.20)
self.assertEqual(si.net_total, 1271.19)
self.assertEqual(si.grand_total, 1500)
self.assertEqual(si.total_taxes_and_charges, 228.82)
self.assertEqual(si.rounding_adjustment, -0.01)
expected_values = [
["_Test Account Service Tax - _TC", 0.0, 114.50],
["_Test Account VAT - _TC", 0.0, 114.50],
[si.debit_to, 1501, 0.0],
["Round Off - _TC", 0.20, 0.0],
["Sales - _TC", 0.0, 1272.20],
["_Test Account Service Tax - _TC", 0.0, 114.41],
["_Test Account VAT - _TC", 0.0, 114.41],
[si.debit_to, 1500, 0.0],
["Round Off - _TC", 0.01, 0.01],
["Sales - _TC", 0.0, 1271.18],
]
gl_entries = frappe.db.sql(
@@ -2235,8 +2176,7 @@ class TestSalesInvoice(FrappeTestCase):
si.save()
si.submit()
self.assertEqual(si.net_total, si.base_net_total)
self.assertEqual(si.net_total, 4007.15)
self.assertEqual(si.net_total, 4007.16)
self.assertEqual(si.grand_total, 4488.02)
self.assertEqual(si.total_taxes_and_charges, 480.86)
self.assertEqual(si.rounding_adjustment, -0.02)
@@ -2248,7 +2188,7 @@ class TestSalesInvoice(FrappeTestCase):
["_Test Account Service Tax - _TC", 0.0, 240.43],
["_Test Account VAT - _TC", 0.0, 240.43],
["Sales - _TC", 0.0, 4007.15],
["Round Off - _TC", 0.01, 0.0],
["Round Off - _TC", 0.02, 0.01],
]
)
@@ -3615,33 +3555,6 @@ class TestSalesInvoice(FrappeTestCase):
check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry")
set_advance_flag(company="_Test Company", flag=0, default_account="")
def test_pulling_advance_based_on_debit_to(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
debtors2 = create_account(
parent_account="Accounts Receivable - _TC",
account_name="Debtors 2",
company="_Test Company",
account_type="Receivable",
)
si = create_sales_invoice(do_not_submit=True)
si.debit_to = debtors2
si.save()
pe = create_payment_entry(
company=si.company,
payment_type="Receive",
party_type="Customer",
party=si.customer,
paid_from=debtors2,
paid_to="Cash - _TC",
paid_amount=1000,
)
pe.submit()
advances = si.get_advance_entries()
self.assertEqual(1, len(advances))
self.assertEqual(advances[0].reference_name, pe.name)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -83,17 +83,14 @@
"quality_inspection",
"pick_serial_and_batch",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"batch_no",
"incoming_rate",
"col_break5",
"allow_zero_valuation_rate",
"incoming_rate",
"serial_no",
"item_tax_rate",
"actual_batch_qty",
"actual_qty",
"section_break_eoec",
"serial_no",
"column_break_ytgd",
"batch_no",
"edit_references",
"sales_order",
"so_detail",
@@ -603,11 +600,12 @@
"options": "Quality Inspection"
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 1,
"label": "Batch No",
"options": "Batch",
"read_only": 1,
"search_index": 1
},
{
@@ -623,12 +621,13 @@
"print_hide": 1
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Serial No",
"oldfieldname": "serial_no",
"oldfieldtype": "Small Text"
"oldfieldtype": "Small Text",
"read_only": 1
},
{
"fieldname": "item_group",
@@ -892,7 +891,6 @@
"read_only": 1
},
{
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@@ -906,27 +904,12 @@
"fieldname": "pick_serial_and_batch",
"fieldtype": "Button",
"label": "Pick Serial / Batch No"
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "section_break_eoec",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_ytgd",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-02-25 15:56:44.828634",
"modified": "2023-12-29 13:03:14.121298",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -75,7 +75,7 @@ class SalesInvoiceItem(Document):
sales_invoice_item: DF.Data | None
sales_order: DF.Link | None
serial_and_batch_bundle: DF.Link | None
serial_no: DF.Text | None
serial_no: DF.SmallText | None
service_end_date: DF.Date | None
service_start_date: DF.Date | None
service_stop_date: DF.Date | None
@@ -86,7 +86,6 @@ class SalesInvoiceItem(Document):
target_warehouse: DF.Link | None
total_weight: DF.Float
uom: DF.Link
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None
weight_per_unit: DF.Float
weight_uom: DF.Link | None

View File

@@ -8,7 +8,6 @@
"default",
"mode_of_payment",
"amount",
"reference_no",
"column_break_3",
"account",
"type",
@@ -76,16 +75,11 @@
"hidden": 1,
"label": "Default",
"read_only": 1
},
{
"fieldname": "reference_no",
"fieldtype": "Data",
"label": "Reference No"
}
],
"istable": 1,
"links": [],
"modified": "2024-01-23 16:20:06.436979",
"modified": "2020-08-03 12:45:39.986598",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",
@@ -93,6 +87,5 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
"sort_order": "DESC"
}

View File

@@ -23,7 +23,6 @@ class SalesInvoicePayment(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
reference_no: DF.Data | None
type: DF.ReadOnly | None
# end: auto-generated types

View File

@@ -3,7 +3,7 @@
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-10 16:34:09",
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain a list of tax heads and also other expense/income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.",
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
@@ -79,7 +79,7 @@
"icon": "fa fa-money",
"idx": 1,
"links": [],
"modified": "2024-01-30 13:07:28.801104",
"modified": "2022-05-16 16:14:52.061672",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges Template",

View File

@@ -546,7 +546,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
"GL Entry",
{
"is_cancelled": 0,
"party_type": "Customer",
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", vouchers],
@@ -561,7 +560,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
conditions = []
conditions.append(ple.amount.lt(0))
conditions.append(ple.delinked == 0)
conditions.append(ple.party_type == "Customer")
conditions.append(ple.party.isin(parties))
conditions.append(ple.voucher_no == ple.against_voucher_no)
conditions.append(ple.company == inv.company)
@@ -581,7 +579,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
{
"is_cancelled": 0,
"credit": [">", 0],
"party_type": "Customer",
"party": ["in", parties],
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
"company": inv.company,

View File

@@ -8,7 +8,6 @@ from frappe.utils import today
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
@@ -50,16 +49,6 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
)
return pe
def create_sales_order(self):
so = make_sales_order(
company=self.company,
customer=self.customer,
item=self.item,
rate=100,
transaction_date=today(),
)
return so
def test_01_unreconcile_invoice(self):
si1 = self.create_sales_invoice()
si2 = self.create_sales_invoice()
@@ -325,41 +314,3 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
),
1,
)
def test_05_unreconcile_order(self):
so = self.create_sales_order()
pe = self.create_payment_entry()
# Allocation payment against Sales Order
pe.paid_amount = 100
pe.append(
"references",
{"reference_doctype": so.doctype, "reference_name": so.name, "allocated_amount": 100},
)
pe.save().submit()
# Assert 'Advance Paid'
so.reload()
self.assertEqual(so.advance_paid, 100)
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
self.assertEqual(len(unreconcile.allocations), 1)
allocations = [x.reference_name for x in unreconcile.allocations]
self.assertEquals([so.name], allocations)
# unreconcile so
unreconcile.save().submit()
# Assert 'Advance Paid'
so.reload()
pe.reload()
self.assertEqual(so.advance_paid, 0)
self.assertEqual(len(pe.references), 0)
self.assertEqual(pe.unallocated_amount, 100)

View File

@@ -82,11 +82,6 @@ class UnreconcilePayment(Document):
update_voucher_outstanding(
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
)
if doc.doctype in frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks(
"advance_payment_receivable_doctypes"
):
doc.set_total_advance_paid()
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)

View File

@@ -13,13 +13,9 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.utils import create_payment_ledger_entry
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
def make_gl_entries(
@@ -359,7 +355,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
process_debit_credit_difference(gl_map)
dimension_filter_map = get_dimension_filter_map()
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
@@ -367,7 +362,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
for entry in gl_map:
validate_allowed_dimensions(entry, dimension_filter_map)
make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -706,39 +700,3 @@ def set_as_cancel(voucher_type, voucher_no):
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no),
)
def validate_allowed_dimensions(gl_entry, dimension_filter_map):
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]
if gl_entry.account == account:
if value["is_mandatory"] and not gl_entry.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
),
MandatoryAccountDimensionError,
)
if value["allow_or_restrict"] == "Allow":
if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(gl_entry.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(gl_entry.account),
),
InvalidAccountDimensionError,
)
else:
if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(gl_entry.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(gl_entry.account),
),
InvalidAccountDimensionError,
)

View File

@@ -1,3 +0,0 @@
<h3>{{ _("Fiscal Year") }}</h3>
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>

View File

@@ -11,21 +11,19 @@
"event": "New",
"idx": 0,
"is_standard": 1,
"message_type": "HTML",
"modified": "2023-11-17 08:54:51.532104",
"message": "<h3>{{_(\"Fiscal Year\")}}</h3>\n\n<p>{{ _(\"New fiscal year created :- \") }} {{ doc.name }}</p>",
"modified": "2018-04-25 14:30:38.588534",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Notification for new fiscal year",
"owner": "Administrator",
"recipients": [
{
"receiver_by_role": "Accounts User"
"email_by_role": "Accounts User"
},
{
"receiver_by_role": "Accounts Manager"
"email_by_role": "Accounts Manager"
}
],
"send_system_notification": 0,
"send_to_all_assignees": 0,
"subject": "Notification for new fiscal year {{ doc.name }}"
}
}

View File

@@ -0,0 +1,3 @@
<h3>{{_("Fiscal Year")}}</h3>
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>

View File

@@ -9,7 +9,7 @@ from frappe import _, msgprint, qb, scrub
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import Abs, Count, Date, Sum
from frappe.query_builder.functions import Abs, Date, Sum
from frappe.utils import (
add_days,
add_months,
@@ -784,37 +784,34 @@ def get_timeline_data(doctype, name):
from frappe.desk.form.load import get_communication_data
out = {}
fields = "creation, count(*)"
after = add_years(None, -1).strftime("%Y-%m-%d")
group_by = "group by Date(creation)"
data = get_communication_data(
doctype,
name,
after=after,
group_by="group by communication_date",
fields="C.communication_date as communication_date, count(C.name)",
group_by="group by creation",
fields="C.creation as creation, count(C.name)",
as_dict=False,
)
# fetch and append data from Activity Log
activity_log = frappe.qb.DocType("Activity Log")
data += (
frappe.qb.from_(activity_log)
.select(activity_log.communication_date, Count(activity_log.name))
.where(
(
((activity_log.reference_doctype == doctype) & (activity_log.reference_name == name))
| ((activity_log.timeline_doctype == doctype) & (activity_log.timeline_name == name))
| (
(activity_log.reference_doctype.isin(["Quotation", "Opportunity"]))
& (activity_log.timeline_name == name)
)
)
& (activity_log.status != "Success")
& (activity_log.creation > after)
)
.groupby(activity_log.communication_date)
.orderby(activity_log.communication_date, order=frappe.qb.desc)
).run()
data += frappe.db.sql(
"""select {fields}
from `tabActivity Log`
where (reference_doctype=%(doctype)s and reference_name=%(name)s)
or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
and status!='Success' and creation > {after}
{group_by} order by creation desc
""".format(
fields=fields, group_by=group_by, after=after
),
{"doctype": doctype, "name": name},
as_dict=False,
)
timeline_items = dict(data)

View File

@@ -5,7 +5,7 @@
<div class="row {% if df.bold %}important{% endif %} data-field">
<div class="col-xs-{{ "9" if df.fieldtype=="Check" else "5" }}
{%- if doc.align_labels_right %} text-right{%- endif -%}">
<label>{{ _(df.label, context=df.parent) }}</label>
<label>{{ _(df.label) }}</label>
</div>
<div class="col-xs-{{ "3" if df.fieldtype=="Check" else "7" }} value">
{% if doc.get(df.fieldname) != None -%}

View File

@@ -5,7 +5,7 @@
from collections import OrderedDict
import frappe
from frappe import _, qb, query_builder, scrub
from frappe import _, qb, scrub
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Date, Substring, Sum
from frappe.utils import cint, cstr, flt, getdate, nowdate
@@ -83,10 +83,7 @@ class ReceivablePayableReport(object):
self.skip_total_row = 1
if self.filters.get("in_party_currency"):
if self.filters.get("party") and len(self.filters.get("party")) == 1:
self.skip_total_row = 0
else:
self.skip_total_row = 1
self.skip_total_row = 1
def get_data(self):
self.get_ple_entries()
@@ -579,8 +576,6 @@ class ReceivablePayableReport(object):
def get_future_payments_from_payment_entry(self):
pe = frappe.qb.DocType("Payment Entry")
pe_ref = frappe.qb.DocType("Payment Entry Reference")
ifelse = query_builder.CustomFunction("IF", ["condition", "then", "else"])
return (
frappe.qb.from_(pe)
.inner_join(pe_ref)
@@ -592,11 +587,6 @@ class ReceivablePayableReport(object):
(pe.posting_date).as_("future_date"),
(pe_ref.allocated_amount).as_("future_amount"),
(pe.reference_no).as_("future_ref"),
ifelse(
pe.payment_type == "Receive",
pe.source_exchange_rate * pe_ref.allocated_amount,
pe.target_exchange_rate * pe_ref.allocated_amount,
).as_("future_amount_in_base_currency"),
)
.where(
(pe.docstatus < 2)
@@ -633,24 +623,13 @@ class ReceivablePayableReport(object):
query = query.select(
Sum(jea.debit_in_account_currency - jea.credit_in_account_currency).as_("future_amount")
)
query = query.select(Sum(jea.debit - jea.credit).as_("future_amount_in_base_currency"))
else:
query = query.select(
Sum(jea.credit_in_account_currency - jea.debit_in_account_currency).as_("future_amount")
)
query = query.select(Sum(jea.credit - jea.debit).as_("future_amount_in_base_currency"))
else:
query = query.select(
Sum(jea.debit if self.account_type == "Payable" else jea.credit).as_(
"future_amount_in_base_currency"
)
)
query = query.select(
Sum(
jea.debit_in_account_currency
if self.account_type == "Payable"
else jea.credit_in_account_currency
).as_("future_amount")
Sum(jea.debit if self.account_type == "Payable" else jea.credit).as_("future_amount")
)
query = query.having(qb.Field("future_amount") > 0)
@@ -666,19 +645,14 @@ class ReceivablePayableReport(object):
row.remaining_balance = row.outstanding
row.future_amount = 0.0
for future in self.future_payments.get((row.voucher_no, row.party), []):
if self.filters.in_party_currency:
future_amount_field = "future_amount"
else:
future_amount_field = "future_amount_in_base_currency"
if row.remaining_balance > 0 and future.get(future_amount_field):
if future.get(future_amount_field) > row.outstanding:
if row.remaining_balance > 0 and future.future_amount:
if future.future_amount > row.outstanding:
row.future_amount = row.outstanding
future[future_amount_field] = future.get(future_amount_field) - row.outstanding
future.future_amount = future.future_amount - row.outstanding
row.remaining_balance = 0
else:
row.future_amount += future.get(future_amount_field)
future[future_amount_field] = 0
row.future_amount += future.future_amount
future.future_amount = 0
row.remaining_balance = row.outstanding - row.future_amount
row.setdefault("future_ref", []).append(

View File

@@ -772,92 +772,3 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
# post sorting output should be [[Additional Debtors, ...], [Debtors, ...]]
report_output = sorted(report_output, key=lambda x: x[0])
self.assertEqual(expected_data, report_output)
def test_future_payments_on_foreign_currency(self):
self.customer2 = (
frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "Jane Doe",
"type": "Individual",
"default_currency": "USD",
}
)
.insert()
.submit()
)
si = self.create_sales_invoice(do_not_submit=True)
si.posting_date = add_days(today(), -1)
si.customer = self.customer2
si.currency = "USD"
si.conversion_rate = 80
si.debit_to = self.debtors_usd
si.save().submit()
# full payment in USD
pe = get_payment_entry(si.doctype, si.name)
pe.posting_date = add_days(today(), 1)
pe.base_received_amount = 7500
pe.received_amount = 7500
pe.source_exchange_rate = 75
pe.save().submit()
filters = frappe._dict(
{
"company": self.company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
"show_future_payments": True,
"in_party_currency": False,
}
)
report = execute(filters)[1]
self.assertEqual(len(report), 1)
expected_data = [8000.0, 8000.0, 500.0, 7500.0]
row = report[0]
self.assertEqual(
expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount]
)
filters.in_party_currency = True
report = execute(filters)[1]
self.assertEqual(len(report), 1)
expected_data = [100.0, 100.0, 0.0, 100.0]
row = report[0]
self.assertEqual(
expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount]
)
pe.cancel()
# partial payment in USD on a future date
pe = get_payment_entry(si.doctype, si.name)
pe.posting_date = add_days(today(), 1)
pe.base_received_amount = 6750
pe.received_amount = 6750
pe.source_exchange_rate = 75
pe.paid_amount = 90 # in USD
pe.references[0].allocated_amount = 90
pe.save().submit()
filters.in_party_currency = False
report = execute(filters)[1]
self.assertEqual(len(report), 1)
expected_data = [8000.0, 8000.0, 1250.0, 6750.0]
row = report[0]
self.assertEqual(
expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount]
)
filters.in_party_currency = True
report = execute(filters)[1]
self.assertEqual(len(report), 1)
expected_data = [100.0, 100.0, 10.0, 90.0]
row = report[0]
self.assertEqual(
expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount]
)

View File

@@ -8,20 +8,6 @@ frappe.query_reports["Balance Sheet"] = $.extend(
erpnext.utils.add_dimensions("Balance Sheet", 10);
frappe.query_reports["Balance Sheet"]["filters"].push(
{
"fieldname": "selected_view",
"label": __("Select View"),
"fieldtype": "Select",
"options": [
{ "value": "Report", "label": __("Report View") },
{ "value": "Growth", "label": __("Growth View") }
],
"default": "Report",
"reqd": 1
},
);
frappe.query_reports["Balance Sheet"]["filters"].push({
fieldname: "accumulated_values",
label: __("Accumulated Values"),

View File

@@ -203,14 +203,8 @@ frappe.query_reports["General Ledger"] = {
"fieldname": "show_remarks",
"label": __("Show Remarks"),
"fieldtype": "Check"
},
{
"fieldname": "ignore_err",
"label": __("Ignore Exchange Rate Revaluation Journals"),
"fieldtype": "Check"
}
]
}

View File

@@ -241,19 +241,6 @@ def get_conditions(filters):
if filters.get("against_voucher_no"):
conditions.append("against_voucher=%(against_voucher_no)s")
if filters.get("ignore_err"):
err_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": filters.get("company"),
"docstatus": 1,
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
},
as_list=True,
)
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if filters.get("voucher_no_not_in"):
conditions.append("voucher_no not in %(voucher_no_not_in)s")

View File

@@ -3,7 +3,7 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, today
from frappe.utils import today
from erpnext.accounts.report.general_ledger.general_ledger import execute
@@ -148,105 +148,3 @@ class TestGeneralLedger(FrappeTestCase):
self.assertEqual(data[2]["credit"], 900)
self.assertEqual(data[3]["debit"], 100)
self.assertEqual(data[3]["credit"], 100)
def test_ignore_exchange_rate_journals_filter(self):
# create a new account with USD currency
account_name = "Test Debtors USD"
company = "_Test Company"
account = frappe.get_doc(
{
"account_name": account_name,
"is_group": 0,
"company": company,
"root_type": "Asset",
"report_type": "Balance Sheet",
"account_currency": "USD",
"parent_account": "Accounts Receivable - _TC",
"account_type": "Receivable",
"doctype": "Account",
}
)
account.insert(ignore_if_duplicate=True)
# create a JV to debit 1000 USD at 75 exchange rate
jv = frappe.new_doc("Journal Entry")
jv.posting_date = today()
jv.company = company
jv.multi_currency = 1
jv.cost_center = "_Test Cost Center - _TC"
jv.set(
"accounts",
[
{
"account": account.name,
"party_type": "Customer",
"party": "_Test Customer USD",
"debit_in_account_currency": 1000,
"credit_in_account_currency": 0,
"exchange_rate": 75,
"cost_center": "_Test Cost Center - _TC",
},
{
"account": "Cash - _TC",
"debit_in_account_currency": 0,
"credit_in_account_currency": 75000,
"cost_center": "_Test Cost Center - _TC",
},
],
)
jv.save()
jv.submit()
revaluation = frappe.new_doc("Exchange Rate Revaluation")
revaluation.posting_date = today()
revaluation.company = company
accounts = revaluation.get_accounts_data()
revaluation.extend("accounts", accounts)
row = revaluation.accounts[0]
row.new_exchange_rate = 83
row.new_balance_in_base_currency = flt(
row.new_exchange_rate * flt(row.balance_in_account_currency)
)
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
revaluation.set_total_gain_loss()
revaluation = revaluation.save().submit()
# post journal entry for Revaluation doc
frappe.db.set_value(
"Company", company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
)
revaluation_jv = revaluation.make_jv_for_revaluation()
revaluation_jv.cost_center = "_Test Cost Center - _TC"
for acc in revaluation_jv.get("accounts"):
acc.cost_center = "_Test Cost Center - _TC"
revaluation_jv.save()
revaluation_jv.submit()
# With ignore_err enabled
columns, data = execute(
frappe._dict(
{
"company": company,
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"ignore_err": True,
}
)
)
self.assertNotIn(revaluation_jv.name, set([x.voucher_no for x in data]))
# Without ignore_err enabled
columns, data = execute(
frappe._dict(
{
"company": company,
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"ignore_err": False,
}
)
)
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))

View File

@@ -975,7 +975,7 @@ class GrossProfitGenerator(object):
& (sle.is_cancelled == 0)
)
.orderby(sle.item_code)
.orderby(sle.warehouse, sle.posting_datetime, sle.creation, order=Order.desc)
.orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
.run(as_dict=True)
)

View File

@@ -357,13 +357,7 @@ def get_conditions(filters, additional_conditions=None):
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"):
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
lft, rgt = frappe.db.get_all(
"Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
)[0]
conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
else:
conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
if filters.get("brand"):
conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""

View File

@@ -163,7 +163,7 @@ def get_entries(filters):
"""select
voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher
from `tabGL Entry`
where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') and is_cancelled = 0 {0}
where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') {0}
""".format(
get_conditions(filters)
),

View File

@@ -8,21 +8,6 @@ frappe.query_reports["Profit and Loss Statement"] = $.extend(
erpnext.utils.add_dimensions("Profit and Loss Statement", 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{
"fieldname": "selected_view",
"label": __("Select View"),
"fieldtype": "Select",
"options": [
{ "value": "Report", "label": __("Report View") },
{ "value": "Growth", "label": __("Growth View") },
{ "value": "Margin", "label": __("Margin View") },
],
"default": "Report",
"reqd": 1
},
);
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "accumulated_values",
label: __("Accumulated Values"),

View File

@@ -63,14 +63,16 @@ def get_result(
tax_amount += entry.credit - entry.debit
# infer tax withholding category from the account if it's the single account for this category
tax_withholding_category = tds_accounts.get(entry.account)
rate = tax_rate_map.get(tax_withholding_category)
# or else the consolidated value from the voucher document
if not tax_withholding_category:
# or else from the party default
tax_withholding_category = tax_category_map.get(name)
# or else from the party default
rate = tax_rate_map.get(tax_withholding_category)
if not tax_withholding_category:
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
rate = tax_rate_map.get(tax_withholding_category)
rate = tax_rate_map.get(tax_withholding_category)
if net_total_map.get(name):
if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount
@@ -242,7 +244,7 @@ def get_columns(filters):
"width": 120,
},
{
"label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"),
"label": _("Tax Amount"),
"fieldname": "tax_amount",
"fieldtype": "Float",
"width": 120,
@@ -293,7 +295,7 @@ def get_tds_docs(filters):
tds_accounts = {}
for tds_acc in _tds_accounts:
# if it turns out not to be the only tax withholding category, then don't include in the map
if tds_acc["account"] in tds_accounts:
if tds_accounts.get(tds_acc["account"]):
tds_accounts[tds_acc["account"]] = None
else:
tds_accounts[tds_acc["account"]] = tds_acc["parent"]
@@ -352,6 +354,9 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
if filters.get("to_date"):
query = query.where(gle.posting_date <= filters.get("to_date"))
if bank_accounts:
query = query.where(gle.against.notin(bank_accounts))
if filters.get("party"):
party = [filters.get("party")]
jv_condition = gle.against.isin(party) | (
@@ -363,14 +368,7 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
(gle.voucher_type == "Journal Entry")
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
)
query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
if bank_accounts:
query = query.where(
gle.against.notin(bank_accounts) & (gle.account.isin(tds_accounts) & jv_condition)
| gle.party.isin(party)
)
query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
return query
@@ -410,7 +408,7 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
"paid_amount_after_tax",
"base_paid_amount",
],
"Journal Entry": ["total_amount"],
"Journal Entry": ["tax_withholding_category", "total_amount"],
}
entries = frappe.get_all(

View File

@@ -5,6 +5,7 @@ import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import today
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_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
@@ -16,63 +17,36 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.accounts.utils import get_fiscal_year
class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.clear_old_entries()
create_tax_accounts()
create_tcs_category()
def test_tax_withholding_for_customers(self):
create_tax_category(cumulative_threshold=300)
frappe.db.set_value("Customer", "_Test Customer", "tax_withholding_category", "TCS")
si = create_sales_invoice(rate=1000)
pe = create_tcs_payment_entry()
jv = create_tcs_journal_entry()
filters = frappe._dict(
company="_Test Company", party_type="Customer", from_date=today(), to_date=today()
)
result = execute(filters)[1]
expected_values = [
# Check for JV totals using back calculation logic
[jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
[si.name, "TCS", 0.075, 1000, 0.52, 1000.52],
]
self.check_expected_values(result, expected_values)
def test_single_account_for_multiple_categories(self):
create_tax_category("TDS - 1", rate=10, account="TDS - _TC")
inv_1 = make_purchase_invoice(rate=1000, do_not_submit=True)
inv_1.tax_withholding_category = "TDS - 1"
inv_1.submit()
create_tax_category("TDS - 2", rate=20, account="TDS - _TC")
inv_2 = make_purchase_invoice(rate=1000, do_not_submit=True)
inv_2.tax_withholding_category = "TDS - 2"
inv_2.submit()
result = execute(
frappe._dict(company="_Test Company", party_type="Supplier", from_date=today(), to_date=today())
)[1]
expected_values = [
[inv_1.name, "TDS - 1", 10, 5000, 500, 5500],
[inv_2.name, "TDS - 2", 20, 5000, 1000, 6000],
]
self.check_expected_values(result, expected_values)
def check_expected_values(self, result, expected_values):
for i in range(len(result)):
voucher = frappe._dict(result[i])
voucher_expected_values = expected_values[i]
voucher_actual_values = (
voucher.ref_no,
voucher.section_code,
voucher.rate,
voucher.base_total,
voucher.tax_amount,
voucher.grand_total,
)
self.assertSequenceEqual(voucher_actual_values, voucher_expected_values)
self.assertEqual(voucher.ref_no, voucher_expected_values[0])
self.assertEqual(voucher.section_code, voucher_expected_values[1])
self.assertEqual(voucher.rate, voucher_expected_values[2])
self.assertEqual(voucher.base_total, voucher_expected_values[3])
self.assertAlmostEqual(voucher.tax_amount, voucher_expected_values[4])
self.assertAlmostEqual(voucher.grand_total, voucher_expected_values[5])
def tearDown(self):
self.clear_old_entries()
@@ -93,20 +67,24 @@ def create_tax_accounts():
).insert(ignore_if_duplicate=True)
def create_tax_category(category="TCS", rate=0.075, account="TCS - _TC", cumulative_threshold=0):
def create_tcs_category():
fiscal_year = get_fiscal_year(today(), company="_Test Company")
from_date = fiscal_year[1]
to_date = fiscal_year[2]
create_tax_withholding_category(
category_name=category,
rate=rate,
tax_category = create_tax_withholding_category(
category_name="TCS",
rate=0.075,
from_date=from_date,
to_date=to_date,
account=account,
cumulative_threshold=cumulative_threshold,
account="TCS - _TC",
cumulative_threshold=300,
)
customer = frappe.get_doc("Customer", "_Test Customer")
customer.tax_withholding_category = "TCS"
customer.save()
def create_tcs_payment_entry():
payment_entry = create_payment_entry(
@@ -131,32 +109,3 @@ def create_tcs_payment_entry():
)
payment_entry.submit()
return payment_entry
def create_tcs_journal_entry():
jv = frappe.new_doc("Journal Entry")
jv.posting_date = today()
jv.company = "_Test Company"
jv.set(
"accounts",
[
{
"account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 10000,
},
{
"account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"debit_in_account_currency": 9992.5,
},
{
"account": "TCS - _TC",
"debit_in_account_currency": 7.5,
},
],
)
jv.insert()
return jv.submit()

View File

@@ -78,14 +78,8 @@ frappe.query_reports["Trial Balance"] = {
"options": erpnext.get_presentation_currency_list()
},
{
"fieldname": "with_period_closing_entry_for_opening",
"label": __("With Period Closing Entry For Opening Balances"),
"fieldtype": "Check",
"default": 1
},
{
"fieldname": "with_period_closing_entry_for_current_period",
"label": __("Period Closing Entry For Current Period"),
"fieldname": "with_period_closing_entry",
"label": __("Period Closing Entry"),
"fieldtype": "Check",
"default": 1
},

View File

@@ -116,7 +116,7 @@ def get_data(filters):
max_rgt,
filters,
gl_entries_by_account,
ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
ignore_closing_entries=not flt(filters.with_period_closing_entry),
ignore_opening_entries=True,
)
@@ -249,7 +249,7 @@ def get_opening_balance(
):
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
if not flt(filters.with_period_closing_entry_for_opening):
if not flt(filters.with_period_closing_entry):
if doctype == "Account Closing Balance":
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
else:

View File

@@ -237,7 +237,7 @@ def get_balance_on(
)
else:
cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center),))
cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
if account:
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
@@ -258,7 +258,7 @@ def get_balance_on(
if acc.account_currency == frappe.get_cached_value("Company", acc.company, "default_currency"):
in_account_currency = False
else:
cond.append("""gle.account = %s """ % (frappe.db.escape(account),))
cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
if account_type:
accounts = frappe.db.get_all(
@@ -278,11 +278,11 @@ def get_balance_on(
if party_type and party:
cond.append(
"""gle.party_type = %s and gle.party = %s """
% (frappe.db.escape(party_type), frappe.db.escape(party))
% (frappe.db.escape(party_type), frappe.db.escape(party, percent=False))
)
if company:
cond.append("""gle.company = %s """ % (frappe.db.escape(company)))
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
if account or (party_type and party) or account_type:
precision = get_currency_precision()
@@ -348,7 +348,7 @@ def get_count_on(account, fieldname, date):
% (acc.lft, acc.rgt)
)
else:
cond.append("""gle.account = %s """ % (frappe.db.escape(account),))
cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
entries = frappe.db.sql(
"""
@@ -622,8 +622,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
# Update Advance Paid in SO/PO since they might be getting unlinked
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
"advance_payment_customer_doctypes"
) + frappe.get_hooks("advance_payment_supplier_doctypes")
if jv_detail.get("reference_type") in advance_payment_doctypes:
frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
@@ -699,8 +699,8 @@ def update_reference_in_payment_entry(
# Update Advance Paid in SO/PO since they are getting unlinked
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
"advance_payment_customer_doctypes"
) + frappe.get_hooks("advance_payment_supplier_doctypes")
if existing_row.get("reference_doctype") in advance_payment_doctypes:
frappe.get_doc(
existing_row.reference_doctype, existing_row.reference_name
@@ -725,7 +725,7 @@ def update_reference_in_payment_entry(
payment_entry.setup_party_account_field()
payment_entry.set_missing_values()
if not skip_ref_details_update_for_pe:
payment_entry.set_missing_ref_details(ref_exchange_rate=d.exchange_rate or None)
payment_entry.set_missing_ref_details()
payment_entry.set_amounts()
payment_entry.make_exchange_gain_loss_journal(
@@ -982,6 +982,46 @@ def get_currency_precision():
return precision
def get_stock_rbnb_difference(posting_date, company):
stock_items = frappe.db.sql_list(
"""select distinct item_code
from `tabStock Ledger Entry` where company=%s""",
company,
)
pr_valuation_amount = frappe.db.sql(
"""
select sum(pr_item.valuation_rate * pr_item.qty * pr_item.conversion_factor)
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name = pr_item.parent and pr.docstatus=1 and pr.company=%s
and pr.posting_date <= %s and pr_item.item_code in (%s)"""
% ("%s", "%s", ", ".join(["%s"] * len(stock_items))),
tuple([company, posting_date] + stock_items),
)[0][0]
pi_valuation_amount = frappe.db.sql(
"""
select sum(pi_item.valuation_rate * pi_item.qty * pi_item.conversion_factor)
from `tabPurchase Invoice Item` pi_item, `tabPurchase Invoice` pi
where pi.name = pi_item.parent and pi.docstatus=1 and pi.company=%s
and pi.posting_date <= %s and pi_item.item_code in (%s)"""
% ("%s", "%s", ", ".join(["%s"] * len(stock_items))),
tuple([company, posting_date] + stock_items),
)[0][0]
# Balance should be
stock_rbnb = flt(pr_valuation_amount, 2) - flt(pi_valuation_amount, 2)
# Balance as per system
stock_rbnb_account = "Stock Received But Not Billed - " + frappe.get_cached_value(
"Company", company, "abbr"
)
sys_bal = get_balance_on(stock_rbnb_account, posting_date, in_account_currency=False)
# Amount should be credited
return flt(stock_rbnb) + flt(sys_bal)
def get_held_invoices(party_type, party):
"""
Returns a list of names Purchase Invoices for the given party that are on hold
@@ -1388,7 +1428,8 @@ def sort_stock_vouchers_by_posting_date(
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
.groupby(sle.voucher_type, sle.voucher_no)
.orderby(sle.posting_datetime)
.orderby(sle.posting_date)
.orderby(sle.posting_time)
.orderby(sle.creation)
).run(as_dict=True)
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]

View File

@@ -571,16 +571,16 @@ frappe.ui.form.on('Asset', {
indicator: 'red'
});
}
frappe.db.get_value('Item', item.item_code, 'is_grouped_asset', (r) => {
var asset_quantity = r.is_grouped_asset ? item.qty : 1;
var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset');
var asset_quantity = is_grouped_asset ? item.qty : 1;
var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
frm.set_value('gross_purchase_amount', purchase_amount);
frm.set_value('purchase_receipt_amount', purchase_amount);
frm.set_value('asset_quantity', asset_quantity);
frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
if(item.asset_location) { frm.set_value('location', item.asset_location); }
frm.set_value('gross_purchase_amount', purchase_amount);
frm.set_value('purchase_receipt_amount', purchase_amount);
frm.set_value('asset_quantity', asset_quantity);
frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
if(item.asset_location) { frm.set_value('location', item.asset_location); }
});
},
set_depreciation_rate: function(frm, row) {

View File

@@ -561,14 +561,15 @@ def modify_depreciation_schedule_for_asset_repairs(asset, notes):
def reverse_depreciation_entry_made_after_disposal(asset, date):
for row in asset.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
if not asset_depr_schedule_doc or not asset_depr_schedule_doc.get("depreciation_schedule"):
if not asset_depr_schedule_doc:
continue
for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if schedule.schedule_date == date and schedule.journal_entry:
if schedule.schedule_date == date:
if not disposal_was_made_on_original_schedule_date(
schedule_idx, row, date
) or disposal_happens_in_the_future(date):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()

View File

@@ -6,7 +6,7 @@ frappe.provide("erpnext.assets");
erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.stock.StockController {
setup() {
this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle', 'Asset Movement'];
this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
this.setup_posting_date_time_check();
}

View File

@@ -126,7 +126,6 @@ class AssetCapitalization(StockController):
self.create_target_asset()
def on_submit(self):
self.make_bundle_using_old_serial_batch_fields()
self.update_stock_ledger()
self.make_gl_entries()
self.update_target_asset()
@@ -138,7 +137,6 @@ class AssetCapitalization(StockController):
"Repost Item Valuation",
"Serial and Batch Bundle",
"Asset",
"Asset Movement",
)
self.cancel_target_asset()
self.update_stock_ledger()
@@ -148,7 +146,7 @@ class AssetCapitalization(StockController):
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
asset_doc.db_set("capitalized_in", None)
frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
if asset_doc.docstatus == 1:
asset_doc.cancel()

View File

@@ -63,7 +63,7 @@
},
{
"columns": 1,
"fetch_from": "item_code.stock_uom",
"fetch_from": "stock_item_code.stock_uom",
"fieldname": "uom",
"fieldtype": "Link",
"in_list_view": 1,
@@ -110,7 +110,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-05 11:23:40.766844",
"modified": "2021-09-08 15:52:08.598100",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Service Item",
@@ -118,6 +118,5 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -18,12 +18,9 @@
"amount",
"batch_and_serial_no_section",
"serial_and_batch_bundle",
"use_serial_batch_fields",
"column_break_13",
"section_break_bfqc",
"serial_no",
"column_break_mbuv",
"batch_no",
"serial_no",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
@@ -42,13 +39,13 @@
"reqd": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"no_copy": 1,
"options": "Batch",
"print_hide": 1
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "section_break_6",
@@ -65,7 +62,7 @@
},
{
"columns": 1,
"fetch_from": "item_code.stock_uom",
"fetch_from": "stock_item_code.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"in_list_view": 1,
@@ -105,12 +102,12 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Serial No",
"print_hide": 1
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "item_code",
@@ -151,34 +148,18 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1
},
{
"default": "0",
"fieldname": "use_serial_batch_fields",
"fieldtype": "Check",
"label": "Use Serial No / Batch Fields"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "section_break_bfqc",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_mbuv",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-05 11:22:57.346889",
"modified": "2023-04-06 01:10:17.947952",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Stock Item",

View File

@@ -24,10 +24,9 @@ class AssetCapitalizationStockItem(Document):
parentfield: DF.Data
parenttype: DF.Data
serial_and_batch_bundle: DF.Link | None
serial_no: DF.Text | None
serial_no: DF.SmallText | None
stock_qty: DF.Float
stock_uom: DF.Link
use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency
warehouse: DF.Link
# end: auto-generated types

View File

@@ -418,13 +418,14 @@ class AssetDepreciationSchedule(Document):
)
# Adjust depreciation amount in the last period based on the expected value after useful life
if (
n == cint(final_number_of_depreciations) - 1
and flt(value_after_depreciation) != flt(row.expected_value_after_useful_life)
) or flt(value_after_depreciation) < flt(row.expected_value_after_useful_life):
depreciation_amount += flt(value_after_depreciation) - flt(
row.expected_value_after_useful_life
if row.expected_value_after_useful_life and (
(
n == cint(final_number_of_depreciations) - 1
and value_after_depreciation != row.expected_value_after_useful_life
)
or value_after_depreciation < row.expected_value_after_useful_life
):
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
skip_row = True
if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
@@ -812,11 +813,15 @@ def make_draft_asset_depr_schedules_if_not_present(asset_doc):
asset_depr_schedules_names = []
for row in asset_doc.get("finance_books"):
asset_depr_schedule = get_asset_depr_schedule_name(
asset_doc.name, ["Draft", "Active"], row.finance_book
draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
asset_doc.name, "Draft", row.finance_book
)
if not asset_depr_schedule:
active_asset_depr_schedule_name = get_asset_depr_schedule_name(
asset_doc.name, "Active", row.finance_book
)
if not draft_asset_depr_schedule_name and not active_asset_depr_schedule_name:
name = make_draft_asset_depr_schedule(asset_doc, row)
asset_depr_schedules_names.append(name)
@@ -992,20 +997,16 @@ def get_asset_depr_schedule_doc(asset_name, status, finance_book=None):
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
if finance_book is None:
finance_book_filter = ["finance_book", "is", "not set"]
else:
finance_book_filter = ["finance_book", "is", "not set"]
if finance_book:
finance_book_filter = ["finance_book", "=", finance_book]
if isinstance(status, str):
status = [status]
return frappe.db.get_value(
doctype="Asset Depreciation Schedule",
filters=[
["asset", "=", asset_name],
finance_book_filter,
["status", "in", status],
["status", "=", status],
],
)

View File

@@ -1,6 +1,7 @@
{
"actions": [],
"creation": "2013-06-25 11:04:03",
"description": "Settings for Buying Module",
"doctype": "DocType",
"document_type": "Other",
"engine": "InnoDB",
@@ -151,7 +152,6 @@
},
{
"default": "1",
"depends_on": "eval: frappe.boot.versions && frappe.boot.versions.payments",
"fieldname": "show_pay_button",
"fieldtype": "Check",
"label": "Show Pay Button in Purchase Order Portal"
@@ -214,7 +214,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-31 13:34:18.101256",
"modified": "2024-01-12 16:42:01.894346",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -457,7 +457,6 @@ class PurchaseOrder(BuyingController):
self.update_ordered_qty()
self.update_reserved_qty_for_subcontract()
self.update_subcontracting_order_status()
self.update_blanket_order()
self.notify_update()
clear_doctype_notifications(self)
@@ -645,7 +644,6 @@ class PurchaseOrder(BuyingController):
update_sco_status(sco, "Closed" if self.status == "Closed" else None)
@frappe.request_cache
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
"""get last purchase rate for an item"""

View File

@@ -822,30 +822,6 @@ class TestPurchaseOrder(FrappeTestCase):
# To test if the PO does NOT have a Blanket Order
self.assertEqual(po_doc.items[0].blanket_order, None)
def test_blanket_order_on_po_close_and_open(self):
# Step - 1: Create Blanket Order
bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10)
# Step - 2: Create Purchase Order
po = create_purchase_order(
item_code="_Test Item", qty=5, against_blanket_order=1, against_blanket=bo.name
)
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
# Step - 3: Close Purchase Order
po.update_status("Closed")
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 0)
# Step - 4: Re-Open Purchase Order
po.update_status("Re-open")
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
@@ -1072,38 +1048,6 @@ class TestPurchaseOrder(FrappeTestCase):
frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated"
)
def test_po_billed_amount_against_return_entry(self):
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note
# Create a Purchase Order and Fully Bill it
po = create_purchase_order()
pi = make_pi_from_po(po.name)
pi.insert()
pi.submit()
# Debit Note - 50% Qty & enable updating PO billed amount
pi_return = make_debit_note(pi.name)
pi_return.items[0].qty = -5
pi_return.update_billed_amount_in_purchase_order = 1
pi_return.submit()
# Check if the billed amount reduced
po.reload()
self.assertEqual(po.per_billed, 50)
pi_return.reload()
pi_return.cancel()
# Debit Note - 50% Qty & disable updating PO billed amount
pi_return = make_debit_note(pi.name)
pi_return.items[0].qty = -5
pi_return.update_billed_amount_in_purchase_order = 0
pi_return.submit()
# Check if the billed amount stayed the same
po.reload()
self.assertEqual(po.per_billed, 100)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
@@ -1204,7 +1148,6 @@ def create_purchase_order(**args):
"schedule_date": add_days(nowdate(), 1),
"include_exploded_items": args.get("include_exploded_items", 1),
"against_blanket_order": args.against_blanket_order,
"against_blanket": args.against_blanket,
"material_request": args.material_request,
"material_request_item": args.material_request_item,
},

View File

@@ -545,6 +545,7 @@
"fieldname": "blanket_order",
"fieldtype": "Link",
"label": "Blanket Order",
"no_copy": 1,
"options": "Blanket Order"
},
{
@@ -552,6 +553,7 @@
"fieldname": "blanket_order_rate",
"fieldtype": "Currency",
"label": "Blanket Order Rate",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
@@ -915,7 +917,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-02-05 11:23:24.859435",
"modified": "2023-11-24 13:24:41.298416",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -206,30 +206,10 @@ class RequestforQuotation(BuyingController):
contact.save(ignore_permissions=True)
if rfq_supplier.supplier:
self.update_user_in_supplier(rfq_supplier.supplier, user.name)
if not rfq_supplier.contact:
# return contact to later update, RFQ supplier row's contact
return contact.name
def update_user_in_supplier(self, supplier, user):
"""Update user in Supplier."""
if not frappe.db.exists("Portal User", {"parent": supplier, "user": user}):
supplier_doc = frappe.get_doc("Supplier", supplier)
supplier_doc.append(
"portal_users",
{
"user": user,
},
)
supplier_doc.flags.ignore_validate = True
supplier_doc.flags.ignore_mandatory = True
supplier_doc.flags.ignore_permissions = True
supplier_doc.save()
def create_user(self, rfq_supplier, link):
user = frappe.get_doc(
{
@@ -266,10 +246,6 @@ class RequestforQuotation(BuyingController):
"user_fullname": full_name,
}
)
if not self.email_template:
return
email_template = frappe.get_doc("Email Template", self.email_template)
message = frappe.render_template(email_template.response_, doc_args)
subject = frappe.render_template(email_template.subject, doc_args)

View File

@@ -149,33 +149,6 @@ class TestRequestforQuotation(FrappeTestCase):
get_pdf(rfq.name, rfq.get("suppliers")[0].supplier)
self.assertEqual(frappe.local.response.type, "pdf")
def test_portal_user_with_new_supplier(self):
supplier_doc = frappe.get_doc(
{
"doctype": "Supplier",
"supplier_name": "Test Supplier for RFQ",
"supplier_group": "_Test Supplier Group",
}
).insert()
self.assertFalse(supplier_doc.portal_users)
rfq = make_request_for_quotation(
supplier_data=[
{
"supplier": supplier_doc.name,
"supplier_name": supplier_doc.supplier_name,
"email_id": "123_testrfquser@example.com",
}
],
do_not_submit=True,
)
for rfq_supplier in rfq.suppliers:
rfq.update_supplier_contact(rfq_supplier, rfq.get_link())
supplier_doc.reload()
self.assertTrue(supplier_doc.portal_users[0].user)
def make_request_for_quotation(**args) -> "RequestforQuotation":
"""

View File

@@ -46,7 +46,6 @@ from erpnext.accounts.party import (
from erpnext.accounts.utils import (
create_gain_loss_journal,
get_account_currency,
get_currency_precision,
get_fiscal_years,
validate_fiscal_year,
)
@@ -203,7 +202,6 @@ class AccountsController(TransactionBase):
self.validate_party()
self.validate_currency()
self.validate_party_account_currency()
self.validate_return_against_account()
if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
if invalid_advances := [
@@ -217,19 +215,6 @@ class AccountsController(TransactionBase):
)
)
if self.get("is_return") and self.get("return_against") and not self.get("is_pos"):
# if self.get("is_return") and self.get("return_against"):
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
frappe.msgprint(
_(
"{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
).format(
document_type,
get_link_to_form("Payment Reconciliation"),
get_link_to_form(self.doctype, self.get("return_against")),
)
)
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
@@ -347,12 +332,6 @@ class AccountsController(TransactionBase):
ple = frappe.qb.DocType("Payment Ledger Entry")
frappe.qb.from_(ple).delete().where(
(ple.voucher_type == self.doctype) & (ple.voucher_no == self.name)
| (
(ple.against_voucher_type == self.doctype)
& (ple.against_voucher_no == self.name)
& ple.delinked
== 1
)
).run()
frappe.db.sql(
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
@@ -371,20 +350,6 @@ class AccountsController(TransactionBase):
for bundle in bundles:
frappe.delete_doc("Serial and Batch Bundle", bundle.name)
def validate_return_against_account(self):
if (
self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against
):
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
cr_dr_account_label = "Debit To" if self.doctype == "Sales Invoice" else "Credit To"
cr_dr_account = self.get(cr_dr_account_field)
if frappe.get_value(self.doctype, self.return_against, cr_dr_account_field) != cr_dr_account:
frappe.throw(
_("'{0}' account: '{1}' should match the Return Against Invoice").format(
frappe.bold(cr_dr_account_label), frappe.bold(cr_dr_account)
)
)
def validate_deferred_income_expense_account(self):
field_map = {
"Sales Invoice": "deferred_revenue_account",
@@ -713,7 +678,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
@@ -1136,24 +1101,21 @@ class AccountsController(TransactionBase):
self.append("advances", advance_row)
def get_advance_entries(self, include_unallocated=True):
party_account = []
if self.doctype == "Sales Invoice":
party_type = "Customer"
party = self.customer
amount_field = "credit_in_account_currency"
order_field = "sales_order"
order_doctype = "Sales Order"
party_account.append(self.debit_to)
else:
party_type = "Supplier"
party = self.supplier
amount_field = "debit_in_account_currency"
order_field = "purchase_order"
order_doctype = "Purchase Order"
party_account.append(self.credit_to)
party_account.extend(
get_party_account(party_type, party=party, company=self.company, include_advance=True)
party_account = get_party_account(
party_type, party=party, company=self.company, include_advance=True
)
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
@@ -1302,12 +1264,10 @@ class AccountsController(TransactionBase):
# These are generated by Sales/Purchase Invoice during reconciliation and advance allocation.
# and below logic is only for such scenarios
if args:
precision = get_currency_precision()
for arg in args:
# Advance section uses `exchange_gain_loss` and reconciliation uses `difference_amount`
if (
flt(arg.get("difference_amount", 0), precision) != 0
or flt(arg.get("exchange_gain_loss", 0), precision) != 0
arg.get("difference_amount", 0) != 0 or arg.get("exchange_gain_loss", 0) != 0
) and arg.get("difference_account"):
party_account = arg.get("account")
@@ -1501,24 +1461,6 @@ class AccountsController(TransactionBase):
x.update({dim.fieldname: self.get(dim.fieldname)})
reconcile_against_document(lst, active_dimensions=active_dimensions)
def cancel_system_generated_credit_debit_notes(self):
# Cancel 'Credit/Debit' Note Journal Entries, if found.
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
voucher_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
journals = frappe.db.get_all(
"Journal Entry",
filters={
"is_system_generated": 1,
"reference_type": self.doctype,
"reference_name": self.name,
"voucher_type": voucher_type,
"docstatus": 1,
},
pluck="name",
)
for x in journals:
frappe.get_doc("Journal Entry", x).cancel()
def on_cancel(self):
from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
remove_from_bank_transaction,
@@ -1531,8 +1473,6 @@ class AccountsController(TransactionBase):
remove_from_bank_transaction(self.doctype, self.name)
if self.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
self.cancel_system_generated_credit_debit_notes()
# Cancel Exchange Gain/Loss Journal before unlinking
cancel_exchange_gain_loss_journal(self)
@@ -1853,9 +1793,9 @@ class AccountsController(TransactionBase):
def set_total_advance_paid(self):
ple = frappe.qb.DocType("Payment Ledger Entry")
if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"):
party = self.customer
if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"):
party = self.supplier
advance = (
frappe.qb.from_(ple)
@@ -1921,9 +1861,9 @@ class AccountsController(TransactionBase):
"docstatus": 1,
},
)
if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"):
new_status = "Requested" if prs else "Not Requested"
if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"):
new_status = "Initiated" if prs else "Not Initiated"
if new_status == self.advance_payment_status:
@@ -2459,20 +2399,27 @@ class AccountsController(TransactionBase):
doc_before_update = self.get_doc_before_save()
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Parent Level Accounts excluding party account
fields_to_check += accounting_dimensions
for field in fields_to_check:
if doc_before_update.get(field) != self.get(field):
return True
# Check if opening entry check updated
needs_repost = doc_before_update.get("is_opening") != self.is_opening
# Check for child tables
for table in child_tables:
if check_if_child_table_updated(
doc_before_update.get(table), self.get(table), child_tables[table]
):
return True
if not needs_repost:
# Parent Level Accounts excluding party account
fields_to_check += accounting_dimensions
for field in fields_to_check:
if doc_before_update.get(field) != self.get(field):
needs_repost = 1
break
return False
if not needs_repost:
# Check for child tables
for table in child_tables:
needs_repost = check_if_child_table_updated(
doc_before_update.get(table), self.get(table), child_tables[table]
)
if needs_repost:
break
return needs_repost
@frappe.whitelist()
def repost_accounting_entries(self):
@@ -2709,7 +2656,7 @@ def get_advance_journal_entries(
if order_list:
q = q.where(
(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list))
(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_type).isin(order_list))
)
q = q.orderby(journal_entry.posting_date)
@@ -3501,12 +3448,15 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
def check_if_child_table_updated(
child_table_before_update, child_table_after_update, fields_to_check
):
fields_to_check = list(fields_to_check) + get_accounting_dimensions() + ["cost_center", "project"]
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Check if any field affecting accounting entry is altered
for index, item in enumerate(child_table_before_update):
for index, item in enumerate(child_table_after_update):
for field in fields_to_check:
if child_table_after_update[index].get(field) != item.get(field):
if child_table_before_update[index].get(field) != item.get(field):
return True
for dimension in accounting_dimensions:
if child_table_before_update[index].get(dimension) != item.get(dimension):
return True
return False

View File

@@ -217,8 +217,8 @@ class BuyingController(SubcontractingController):
lc_voucher_data = frappe.db.sql(
"""select sum(applicable_charges), cost_center
from `tabLanded Cost Item`
where docstatus = 1 and purchase_receipt_item = %s and receipt_document = %s""",
(d.name, self.name),
where docstatus = 1 and purchase_receipt_item = %s""",
d.name,
)
d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
@@ -559,7 +559,7 @@ class BuyingController(SubcontractingController):
{
"incoming_rate": incoming_rate,
"recalculate_rate": 1
if (self.is_subcontracted and (d.bom or d.get("fg_item"))) or d.from_warehouse
if (self.is_subcontracted and (d.bom or d.fg_item)) or d.from_warehouse
else 0,
}
)
@@ -824,8 +824,7 @@ class BuyingController(SubcontractingController):
if self.doctype == "Purchase Invoice" and not self.get("update_stock"):
return
asset_movement = frappe.db.get_value("Asset Movement", {"reference_name": self.name}, "name")
frappe.delete_doc("Asset Movement", asset_movement, force=1)
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
def validate_schedule_date(self):
if not self.get("items"):

View File

@@ -439,21 +439,9 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
filtered_batches = get_filterd_batches(batches)
if filters.get("is_inward"):
filtered_batches.extend(get_empty_batches(filters))
return filtered_batches
def get_empty_batches(filters):
return frappe.get_all(
"Batch",
fields=["name", "batch_qty"],
filters={"item": filters.get("item_code"), "batch_qty": 0.0},
as_list=1,
)
def get_filterd_batches(data):
batches = OrderedDict()
@@ -741,24 +729,17 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
conditions, bin_conditions = [], []
filter_dict = get_doctype_wise_filters(filters)
warehouse_field = "name"
meta = frappe.get_meta("Warehouse")
if meta.get("show_title_field_in_link") and meta.get("title_field"):
searchfield = meta.get("title_field")
warehouse_field = meta.get("title_field")
query = """select `tabWarehouse`.`{warehouse_field}`,
query = """select `tabWarehouse`.name,
CONCAT_WS(' : ', 'Actual Qty', ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
from `tabWarehouse` left join `tabBin`
on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
where
`tabWarehouse`.`{key}` like {txt}
{fcond} {mcond}
order by ifnull(`tabBin`.actual_qty, 0) desc, `tabWarehouse`.`{warehouse_field}` asc
order by ifnull(`tabBin`.actual_qty, 0) desc
limit
{page_len} offset {start}
""".format(
warehouse_field=warehouse_field,
bin_conditions=get_filters_cond(
doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
),

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