diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml
index ef38974ae27..ee60bad1049 100644
--- a/.github/workflows/initiate_release.yml
+++ b/.github/workflows/initiate_release.yml
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
jobs:
- release:
+ stable-release:
name: Release
runs-on: ubuntu-latest
strategy:
@@ -30,3 +30,23 @@ jobs:
head: version-${{ matrix.version }}-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
+
+ beta-release:
+ name: Release
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+
+ steps:
+ - uses: octokit/request-action@v2.x
+ with:
+ route: POST /repos/{owner}/{repo}/pulls
+ owner: frappe
+ repo: erpnext
+ title: |-
+ "chore: release v15 beta"
+ body: "Automated beta release."
+ base: version-15-beta
+ head: develop
+ env:
+ GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
diff --git a/.github/workflows/release_notes.yml b/.github/workflows/release_notes.yml
new file mode 100644
index 00000000000..e765a66f691
--- /dev/null
+++ b/.github/workflows/release_notes.yml
@@ -0,0 +1,38 @@
+# This action:
+#
+# 1. Generates release notes using github API.
+# 2. Strips unnecessary info like chore/style etc from notes.
+# 3. Updates release info.
+
+# This action needs to be maintained on all branches that do releases.
+
+name: 'Release Notes'
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag_name:
+ description: 'Tag of release like v13.0.0'
+ required: true
+ type: string
+ release:
+ types: [released]
+
+permissions:
+ contents: read
+
+jobs:
+ regen-notes:
+ name: 'Regenerate release notes'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Update notes
+ run: |
+ NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
+ RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
+ gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"
+
+ env:
+ GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
+ RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 45e04ee6b0f..fb49ef3a423 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -136,7 +136,7 @@ def convert_deferred_revenue_to_income(
send_mail(deferred_process)
-def get_booking_dates(doc, item, posting_date=None):
+def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
if not posting_date:
posting_date = add_days(today(), -1)
@@ -146,39 +146,42 @@ def get_booking_dates(doc, item, posting_date=None):
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
)
- prev_gl_entry = frappe.db.sql(
- """
- select name, posting_date from `tabGL Entry` where company=%s and account=%s and
- voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
- and is_cancelled = 0
- order by posting_date desc limit 1
- """,
- (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
- as_dict=True,
- )
+ if not prev_posting_date:
+ prev_gl_entry = frappe.db.sql(
+ """
+ select name, posting_date from `tabGL Entry` where company=%s and account=%s and
+ voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+ and is_cancelled = 0
+ order by posting_date desc limit 1
+ """,
+ (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
+ as_dict=True,
+ )
- prev_gl_via_je = frappe.db.sql(
- """
- SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
- WHERE p.name = c.parent and p.company=%s and c.account=%s
- and c.reference_type=%s and c.reference_name=%s
- and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
- """,
- (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
- as_dict=True,
- )
+ prev_gl_via_je = frappe.db.sql(
+ """
+ SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
+ WHERE p.name = c.parent and p.company=%s and c.account=%s
+ and c.reference_type=%s and c.reference_name=%s
+ and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
+ """,
+ (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
+ as_dict=True,
+ )
- if prev_gl_via_je:
- if (not prev_gl_entry) or (
- prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
- ):
- prev_gl_entry = prev_gl_via_je
+ if prev_gl_via_je:
+ if (not prev_gl_entry) or (
+ prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
+ ):
+ prev_gl_entry = prev_gl_via_je
+
+ if prev_gl_entry:
+ start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
+ else:
+ start_date = item.service_start_date
- if prev_gl_entry:
- start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else:
- start_date = item.service_start_date
-
+ start_date = getdate(add_days(prev_posting_date, 1))
end_date = get_last_day(start_date)
if end_date >= item.service_end_date:
end_date = item.service_end_date
@@ -341,9 +344,15 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
def _book_deferred_revenue_or_expense(
- item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
+ item,
+ via_journal_entry,
+ submit_journal_entry,
+ book_deferred_entries_based_on,
+ prev_posting_date=None,
):
- start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
+ start_date, end_date, last_gl_entry = get_booking_dates(
+ doc, item, posting_date=posting_date, prev_posting_date=prev_posting_date
+ )
if not (start_date and end_date):
return
@@ -377,9 +386,12 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if not amount:
return
+ gl_posting_date = end_date
+ prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
- end_date = get_last_day(add_days(accounts_frozen_upto, 1))
+ gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
+ prev_posting_date = end_date
if via_journal_entry:
book_revenue_via_journal_entry(
@@ -388,7 +400,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
debit_account,
amount,
base_amount,
- end_date,
+ gl_posting_date,
project,
account_currency,
item.cost_center,
@@ -404,7 +416,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
against,
amount,
base_amount,
- end_date,
+ gl_posting_date,
project,
account_currency,
item.cost_center,
@@ -418,7 +430,11 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense(
- item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
+ item,
+ via_journal_entry,
+ submit_journal_entry,
+ book_deferred_entries_based_on,
+ prev_posting_date,
)
via_journal_entry = cint(
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/nl_grootboekschema.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/nl_grootboekschema.json
index 58b91227f79..9fb47bb02dd 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/nl_grootboekschema.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/nl_grootboekschema.json
@@ -2,75 +2,13 @@
"country_code": "nl",
"name": "Netherlands - Grootboekschema",
"tree": {
- "FABRIKAGEREKENINGEN": {
- "is_group": 1,
- "root_type": "Expense"
- },
"FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": {
"Bank": {
"RABO Bank": {
"account_type": "Bank"
},
"account_type": "Bank"
- },
- "KORTLOPENDE SCHULDEN": {
- "Af te dragen Btw-verlegd": {
- "account_type": "Tax"
- },
- "Afdracht loonheffing": {},
- "Btw af te dragen hoog": {
- "account_type": "Tax"
- },
- "Btw af te dragen laag": {
- "account_type": "Tax"
- },
- "Btw af te dragen overig": {
- "account_type": "Tax"
- },
- "Btw oude jaren": {
- "account_type": "Tax"
- },
- "Btw te vorderen hoog": {
- "account_type": "Tax"
- },
- "Btw te vorderen laag": {
- "account_type": "Tax"
- },
- "Btw te vorderen overig": {
- "account_type": "Tax"
- },
- "Btw-afdracht": {
- "account_type": "Tax"
- },
- "Crediteuren": {
- "account_type": "Payable"
- },
- "Dividend": {},
- "Dividendbelasting": {},
- "Energiekosten 1": {},
- "Investeringsaftrek": {},
- "Loonheffing": {},
- "Overige te betalen posten": {},
- "Pensioenpremies 1": {},
- "Premie WIR": {},
- "Rekening-courant inkoopvereniging": {},
- "Rente": {},
- "Sociale lasten 1": {},
- "Stock Recieved niet gefactureerd": {
- "account_type": "Stock Received But Not Billed"
- },
- "Tanti\u00e8mes 1": {},
- "Te vorderen Btw-verlegd": {
- "account_type": "Tax"
- },
- "Telefoon/telefax 1": {},
- "Termijnen onderh. werk": {},
- "Vakantiedagen": {},
- "Vakantiegeld 1": {},
- "Vakantiezegels": {},
- "Vennootschapsbelasting": {},
- "Vooruit ontvangen bedr.": {}
- },
+ },
"LIQUIDE MIDDELEN": {
"ABN-AMRO bank": {},
"Bankbetaalkaarten": {},
@@ -91,6 +29,110 @@
},
"account_type": "Cash"
},
+ "TUSSENREKENINGEN": {
+ "Betaalwijze cadeaubonnen": {
+ "account_type": "Cash"
+ },
+ "Betaalwijze chipknip": {
+ "account_type": "Cash"
+ },
+ "Betaalwijze contant": {
+ "account_type": "Cash"
+ },
+ "Betaalwijze pin": {
+ "account_type": "Cash"
+ },
+ "Inkopen Nederland hoog": {
+ "account_type": "Cash"
+ },
+ "Inkopen Nederland laag": {
+ "account_type": "Cash"
+ },
+ "Inkopen Nederland onbelast": {
+ "account_type": "Cash"
+ },
+ "Inkopen Nederland overig": {
+ "account_type": "Cash"
+ },
+ "Inkopen Nederland verlegd": {
+ "account_type": "Cash"
+ },
+ "Inkopen binnen EU hoog": {
+ "account_type": "Cash"
+ },
+ "Inkopen binnen EU laag": {
+ "account_type": "Cash"
+ },
+ "Inkopen binnen EU overig": {
+ "account_type": "Cash"
+ },
+ "Inkopen buiten EU hoog": {
+ "account_type": "Cash"
+ },
+ "Inkopen buiten EU laag": {
+ "account_type": "Cash"
+ },
+ "Inkopen buiten EU overig": {
+ "account_type": "Cash"
+ },
+ "Kassa 1": {
+ "account_type": "Cash"
+ },
+ "Kassa 2": {
+ "account_type": "Cash"
+ },
+ "Netto lonen": {
+ "account_type": "Cash"
+ },
+ "Tegenrekening Inkopen": {
+ "account_type": "Cash"
+ },
+ "Tussenrek. autom. betalingen": {
+ "account_type": "Cash"
+ },
+ "Tussenrek. autom. loonbetalingen": {
+ "account_type": "Cash"
+ },
+ "Tussenrek. cadeaubonbetalingen": {
+ "account_type": "Cash"
+ },
+ "Tussenrekening balans": {
+ "account_type": "Cash"
+ },
+ "Tussenrekening chipknip": {
+ "account_type": "Cash"
+ },
+ "Tussenrekening correcties": {
+ "account_type": "Cash"
+ },
+ "Tussenrekening pin": {
+ "account_type": "Cash"
+ },
+ "Vraagposten": {
+ "account_type": "Cash"
+ },
+ "VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
+ "Emballage": {},
+ "Gereed product 1": {},
+ "Gereed product 2": {},
+ "Goederen 1": {},
+ "Goederen 2": {},
+ "Goederen in consignatie": {},
+ "Goederen onderweg": {},
+ "Grondstoffen 1": {},
+ "Grondstoffen 2": {},
+ "Halffabrikaten 1": {},
+ "Halffabrikaten 2": {},
+ "Hulpstoffen 1": {},
+ "Hulpstoffen 2": {},
+ "Kantoorbenodigdheden": {},
+ "Onderhanden werk": {},
+ "Verpakkingsmateriaal": {},
+ "Zegels": {},
+ "root_type": "Asset"
+ },
+ "root_type": "Asset"
+ },
"VORDERINGEN": {
"Debiteuren": {
"account_type": "Receivable"
@@ -104,278 +146,299 @@
"Voorziening dubieuze debiteuren": {}
},
"root_type": "Asset"
- },
- "INDIRECTE KOSTEN": {
+ },
+ "KORTLOPENDE SCHULDEN": {
+ "Af te dragen Btw-verlegd": {
+ "account_type": "Tax"
+ },
+ "Afdracht loonheffing": {},
+ "Btw af te dragen hoog": {
+ "account_type": "Tax"
+ },
+ "Btw af te dragen laag": {
+ "account_type": "Tax"
+ },
+ "Btw af te dragen overig": {
+ "account_type": "Tax"
+ },
+ "Btw oude jaren": {
+ "account_type": "Tax"
+ },
+ "Btw te vorderen hoog": {
+ "account_type": "Tax"
+ },
+ "Btw te vorderen laag": {
+ "account_type": "Tax"
+ },
+ "Btw te vorderen overig": {
+ "account_type": "Tax"
+ },
+ "Btw-afdracht": {
+ "account_type": "Tax"
+ },
+ "Crediteuren": {
+ "account_type": "Payable"
+ },
+ "Dividend": {},
+ "Dividendbelasting": {},
+ "Energiekosten 1": {},
+ "Investeringsaftrek": {},
+ "Loonheffing": {},
+ "Overige te betalen posten": {},
+ "Pensioenpremies 1": {},
+ "Premie WIR": {},
+ "Rekening-courant inkoopvereniging": {},
+ "Rente": {},
+ "Sociale lasten 1": {},
+ "Stock Recieved niet gefactureerd": {
+ "account_type": "Stock Received But Not Billed"
+ },
+ "Tanti\u00e8mes 1": {},
+ "Te vorderen Btw-verlegd": {
+ "account_type": "Tax"
+ },
+ "Telefoon/telefax 1": {},
+ "Termijnen onderh. werk": {},
+ "Vakantiedagen": {},
+ "Vakantiegeld 1": {},
+ "Vakantiezegels": {},
+ "Vennootschapsbelasting": {},
+ "Vooruit ontvangen bedr.": {},
+ "is_group": 1,
+ "root_type": "Liability"
+ },
+ "FABRIKAGEREKENINGEN": {
"is_group": 1,
- "root_type": "Expense"
- },
- "KOSTENREKENINGEN": {
- "AFSCHRIJVINGEN": {
- "Aanhangwagens": {},
- "Aankoopkosten": {},
- "Aanloopkosten": {},
- "Auteursrechten": {},
- "Bedrijfsgebouwen": {},
- "Bedrijfsinventaris": {
+ "root_type": "Expense",
+ "INDIRECTE KOSTEN": {
+ "is_group": 1,
+ "root_type": "Expense"
+ },
+ "KOSTENREKENINGEN": {
+ "AFSCHRIJVINGEN": {
+ "Aanhangwagens": {},
+ "Aankoopkosten": {},
+ "Aanloopkosten": {},
+ "Auteursrechten": {},
+ "Bedrijfsgebouwen": {},
+ "Bedrijfsinventaris": {
+ "account_type": "Depreciation"
+ },
+ "Drankvergunningen": {},
+ "Fabrieksinventaris": {
+ "account_type": "Depreciation"
+ },
+ "Gebouwen": {},
+ "Gereedschappen": {},
+ "Goodwill": {},
+ "Grondverbetering": {},
+ "Heftrucks": {},
+ "Kantine-inventaris": {},
+ "Kantoorinventaris": {
+ "account_type": "Depreciation"
+ },
+ "Kantoormachines": {},
+ "Licenties": {},
+ "Machines 1": {},
+ "Magazijninventaris": {},
+ "Octrooien": {},
+ "Ontwikkelingskosten": {},
+ "Pachtersinvestering": {},
+ "Parkeerplaats": {},
+ "Personenauto's": {
+ "account_type": "Depreciation"
+ },
+ "Rijwielen en bromfietsen": {},
+ "Tonnagevergunningen": {},
+ "Verbouwingen": {},
+ "Vergunningen": {},
+ "Voorraadverschillen": {},
+ "Vrachtauto's": {},
+ "Winkels": {},
+ "Woon-winkelhuis": {},
"account_type": "Depreciation"
},
- "Drankvergunningen": {},
- "Fabrieksinventaris": {
- "account_type": "Depreciation"
+ "ALGEMENE KOSTEN": {
+ "Accountantskosten": {},
+ "Advieskosten": {},
+ "Assuranties 1": {},
+ "Bankkosten": {},
+ "Juridische kosten": {},
+ "Overige algemene kosten": {},
+ "Toev. Ass. eigen risico": {}
},
- "Gebouwen": {},
- "Gereedschappen": {},
- "Goodwill": {},
- "Grondverbetering": {},
- "Heftrucks": {},
- "Kantine-inventaris": {},
- "Kantoorinventaris": {
- "account_type": "Depreciation"
+ "BEDRIJFSKOSTEN": {
+ "Assuranties 2": {},
+ "Energie (krachtstroom)": {},
+ "Gereedschappen 1": {},
+ "Hulpmaterialen 1": {},
+ "Huur inventaris": {},
+ "Huur machines": {},
+ "Leasing invent.operational": {},
+ "Leasing mach. operational": {},
+ "Onderhoud inventaris": {},
+ "Onderhoud machines": {},
+ "Ophalen/vervoer afval": {},
+ "Overige bedrijfskosten": {}
},
- "Kantoormachines": {},
- "Licenties": {},
- "Machines 1": {},
- "Magazijninventaris": {},
- "Octrooien": {},
- "Ontwikkelingskosten": {},
- "Pachtersinvestering": {},
- "Parkeerplaats": {},
- "Personenauto's": {
- "account_type": "Depreciation"
+ "FINANCIERINGSKOSTEN 1": {
+ "Overige rentebaten": {},
+ "Overige rentelasten": {},
+ "Rente bankkrediet": {},
+ "Rente huurkoopcontracten": {},
+ "Rente hypotheek": {},
+ "Rente leasecontracten": {},
+ "Rente lening o/g": {},
+ "Rente lening u/g": {}
},
- "Rijwielen en bromfietsen": {},
- "Tonnagevergunningen": {},
- "Verbouwingen": {},
- "Vergunningen": {},
- "Voorraadverschillen": {},
- "Vrachtauto's": {},
- "Winkels": {},
- "Woon-winkelhuis": {},
- "account_type": "Depreciation"
- },
- "ALGEMENE KOSTEN": {
- "Accountantskosten": {},
- "Advieskosten": {},
- "Assuranties 1": {},
- "Bankkosten": {},
- "Juridische kosten": {},
- "Overige algemene kosten": {},
- "Toev. Ass. eigen risico": {}
- },
- "BEDRIJFSKOSTEN": {
- "Assuranties 2": {},
- "Energie (krachtstroom)": {},
- "Gereedschappen 1": {},
- "Hulpmaterialen 1": {},
- "Huur inventaris": {},
- "Huur machines": {},
- "Leasing invent.operational": {},
- "Leasing mach. operational": {},
- "Onderhoud inventaris": {},
- "Onderhoud machines": {},
- "Ophalen/vervoer afval": {},
- "Overige bedrijfskosten": {}
- },
- "FINANCIERINGSKOSTEN 1": {
- "Overige rentebaten": {},
- "Overige rentelasten": {},
- "Rente bankkrediet": {},
- "Rente huurkoopcontracten": {},
- "Rente hypotheek": {},
- "Rente leasecontracten": {},
- "Rente lening o/g": {},
- "Rente lening u/g": {}
- },
- "HUISVESTINGSKOSTEN": {
- "Assurantie onroerend goed": {},
- "Belastingen onr. Goed": {},
- "Energiekosten": {},
- "Groot onderhoud onr. Goed": {},
- "Huur": {},
- "Huurwaarde woongedeelte": {},
- "Onderhoud onroerend goed": {},
- "Ontvangen huren": {},
- "Overige huisvestingskosten": {},
- "Pacht": {},
- "Schoonmaakkosten": {},
- "Toevoeging egalisatieres. Groot onderhoud": {}
- },
- "KANTOORKOSTEN": {
- "Administratiekosten": {},
- "Contributies/abonnementen": {},
- "Huur kantoorapparatuur": {},
- "Internetaansluiting": {},
- "Kantoorbenodigdh./drukw.": {},
- "Onderhoud kantoorinvent.": {},
- "Overige kantoorkosten": {},
- "Porti": {},
- "Telefoon/telefax": {}
- },
- "OVERIGE BATEN EN LASTEN": {
- "Betaalde schadevergoed.": {},
- "Boekverlies vaste activa": {},
- "Boekwinst van vaste activa": {},
- "K.O. regeling OB": {},
- "Kasverschillen": {},
- "Kosten loonbelasting": {},
- "Kosten omzetbelasting": {},
- "Nadelige koersverschillen": {},
- "Naheffing bedrijfsver.": {},
- "Ontvangen schadevergoed.": {},
- "Overige baten": {},
- "Overige lasten": {},
- "Voordelige koersverschil.": {}
- },
- "PERSONEELSKOSTEN": {
- "Autokostenvergoeding": {},
- "Bedrijfskleding": {},
- "Belastingvrije uitkeringen": {},
- "Bijzondere beloningen": {},
- "Congressen, seminars en symposia": {},
- "Gereedschapsgeld": {},
- "Geschenken personeel": {},
- "Gratificaties": {},
- "Inhouding pensioenpremies": {},
- "Inhouding sociale lasten": {},
- "Kantinekosten": {},
- "Lonen en salarissen": {},
- "Loonwerk": {},
- "Managementvergoedingen": {},
- "Opleidingskosten": {},
- "Oprenting stamrechtverpl.": {},
- "Overhevelingstoeslag": {},
- "Overige kostenverg.": {},
- "Overige personeelskosten": {},
- "Overige uitkeringen": {},
- "Pensioenpremies": {},
- "Provisie 1": {},
- "Reiskosten": {},
- "Rijwielvergoeding": {},
- "Sociale lasten": {},
- "Tanti\u00e8mes": {},
- "Thuiswerkers": {},
- "Toev. Backservice pens.verpl.": {},
- "Toevoeging pensioenverpl.": {},
- "Uitkering ziekengeld": {},
- "Uitzendkrachten": {},
- "Vakantiebonnen": {},
- "Vakantiegeld": {},
- "Vergoeding studiekosten": {},
- "Wervingskosten personeel": {}
- },
- "VERKOOPKOSTEN": {
- "Advertenties": {},
- "Afschrijving dubieuze deb.": {},
- "Beurskosten": {},
- "Etalagekosten": {},
- "Exportkosten": {},
- "Kascorrecties": {},
- "Overige verkoopkosten": {},
- "Provisie": {},
- "Reclame": {},
- "Reis en verblijfkosten": {},
- "Relatiegeschenken": {},
- "Representatiekosten": {},
- "Uitgaande vrachten": {},
- "Veilingkosten": {},
- "Verpakkingsmateriaal 1": {},
- "Websitekosten": {}
- },
- "VERVOERSKOSTEN": {
- "Assuranties auto's": {},
- "Brandstoffen": {},
- "Leasing auto's": {},
- "Onderhoud personenauto's": {},
- "Onderhoud vrachtauto's": {},
- "Overige vervoerskosten": {},
- "Priv\u00e9-gebruik auto's": {},
- "Wegenbelasting": {}
- },
- "root_type": "Expense"
- },
- "TUSSENREKENINGEN": {
- "Betaalwijze cadeaubonnen": {
- "account_type": "Cash"
- },
- "Betaalwijze chipknip": {
- "account_type": "Cash"
- },
- "Betaalwijze contant": {
- "account_type": "Cash"
- },
- "Betaalwijze pin": {
- "account_type": "Cash"
- },
- "Inkopen Nederland hoog": {
- "account_type": "Cash"
- },
- "Inkopen Nederland laag": {
- "account_type": "Cash"
- },
- "Inkopen Nederland onbelast": {
- "account_type": "Cash"
- },
- "Inkopen Nederland overig": {
- "account_type": "Cash"
- },
- "Inkopen Nederland verlegd": {
- "account_type": "Cash"
- },
- "Inkopen binnen EU hoog": {
- "account_type": "Cash"
- },
- "Inkopen binnen EU laag": {
- "account_type": "Cash"
- },
- "Inkopen binnen EU overig": {
- "account_type": "Cash"
- },
- "Inkopen buiten EU hoog": {
- "account_type": "Cash"
- },
- "Inkopen buiten EU laag": {
- "account_type": "Cash"
- },
- "Inkopen buiten EU overig": {
- "account_type": "Cash"
- },
- "Kassa 1": {
- "account_type": "Cash"
- },
- "Kassa 2": {
- "account_type": "Cash"
- },
- "Netto lonen": {
- "account_type": "Cash"
- },
- "Tegenrekening Inkopen": {
- "account_type": "Cash"
- },
- "Tussenrek. autom. betalingen": {
- "account_type": "Cash"
- },
- "Tussenrek. autom. loonbetalingen": {
- "account_type": "Cash"
- },
- "Tussenrek. cadeaubonbetalingen": {
- "account_type": "Cash"
- },
- "Tussenrekening balans": {
- "account_type": "Cash"
- },
- "Tussenrekening chipknip": {
- "account_type": "Cash"
- },
- "Tussenrekening correcties": {
- "account_type": "Cash"
- },
- "Tussenrekening pin": {
- "account_type": "Cash"
- },
- "Vraagposten": {
- "account_type": "Cash"
- },
- "root_type": "Asset"
+ "HUISVESTINGSKOSTEN": {
+ "Assurantie onroerend goed": {},
+ "Belastingen onr. Goed": {},
+ "Energiekosten": {},
+ "Groot onderhoud onr. Goed": {},
+ "Huur": {},
+ "Huurwaarde woongedeelte": {},
+ "Onderhoud onroerend goed": {},
+ "Ontvangen huren": {},
+ "Overige huisvestingskosten": {},
+ "Pacht": {},
+ "Schoonmaakkosten": {},
+ "Toevoeging egalisatieres. Groot onderhoud": {}
+ },
+ "KANTOORKOSTEN": {
+ "Administratiekosten": {},
+ "Contributies/abonnementen": {},
+ "Huur kantoorapparatuur": {},
+ "Internetaansluiting": {},
+ "Kantoorbenodigdh./drukw.": {},
+ "Onderhoud kantoorinvent.": {},
+ "Overige kantoorkosten": {},
+ "Porti": {},
+ "Telefoon/telefax": {}
+ },
+ "OVERIGE BATEN EN LASTEN": {
+ "Betaalde schadevergoed.": {},
+ "Boekverlies vaste activa": {},
+ "Boekwinst van vaste activa": {},
+ "K.O. regeling OB": {},
+ "Kasverschillen": {},
+ "Kosten loonbelasting": {},
+ "Kosten omzetbelasting": {},
+ "Nadelige koersverschillen": {},
+ "Naheffing bedrijfsver.": {},
+ "Ontvangen schadevergoed.": {},
+ "Overige baten": {},
+ "Overige lasten": {},
+ "Voordelige koersverschil.": {}
+ },
+ "PERSONEELSKOSTEN": {
+ "Autokostenvergoeding": {},
+ "Bedrijfskleding": {},
+ "Belastingvrije uitkeringen": {},
+ "Bijzondere beloningen": {},
+ "Congressen, seminars en symposia": {},
+ "Gereedschapsgeld": {},
+ "Geschenken personeel": {},
+ "Gratificaties": {},
+ "Inhouding pensioenpremies": {},
+ "Inhouding sociale lasten": {},
+ "Kantinekosten": {},
+ "Lonen en salarissen": {},
+ "Loonwerk": {},
+ "Managementvergoedingen": {},
+ "Opleidingskosten": {},
+ "Oprenting stamrechtverpl.": {},
+ "Overhevelingstoeslag": {},
+ "Overige kostenverg.": {},
+ "Overige personeelskosten": {},
+ "Overige uitkeringen": {},
+ "Pensioenpremies": {},
+ "Provisie 1": {},
+ "Reiskosten": {},
+ "Rijwielvergoeding": {},
+ "Sociale lasten": {},
+ "Tanti\u00e8mes": {},
+ "Thuiswerkers": {},
+ "Toev. Backservice pens.verpl.": {},
+ "Toevoeging pensioenverpl.": {},
+ "Uitkering ziekengeld": {},
+ "Uitzendkrachten": {},
+ "Vakantiebonnen": {},
+ "Vakantiegeld": {},
+ "Vergoeding studiekosten": {},
+ "Wervingskosten personeel": {}
+ },
+ "VERKOOPKOSTEN": {
+ "Advertenties": {},
+ "Afschrijving dubieuze deb.": {},
+ "Beurskosten": {},
+ "Etalagekosten": {},
+ "Exportkosten": {},
+ "Kascorrecties": {},
+ "Overige verkoopkosten": {},
+ "Provisie": {},
+ "Reclame": {},
+ "Reis en verblijfkosten": {},
+ "Relatiegeschenken": {},
+ "Representatiekosten": {},
+ "Uitgaande vrachten": {},
+ "Veilingkosten": {},
+ "Verpakkingsmateriaal 1": {},
+ "Websitekosten": {}
+ },
+ "VERVOERSKOSTEN": {
+ "Assuranties auto's": {},
+ "Brandstoffen": {},
+ "Leasing auto's": {},
+ "Onderhoud personenauto's": {},
+ "Onderhoud vrachtauto's": {},
+ "Overige vervoerskosten": {},
+ "Priv\u00e9-gebruik auto's": {},
+ "Wegenbelasting": {}
+ },
+ "VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
+ "Betalingskort. crediteuren": {},
+ "Garantiekosten": {},
+ "Hulpmaterialen": {},
+ "Inkomende vrachten": {
+ "account_type": "Expenses Included In Valuation"
+ },
+ "Inkoop import buiten EU hoog": {},
+ "Inkoop import buiten EU laag": {},
+ "Inkoop import buiten EU overig": {},
+ "Inkoopbonussen": {},
+ "Inkoopkosten": {},
+ "Inkoopprovisie": {},
+ "Inkopen BTW verlegd": {},
+ "Inkopen EU hoog tarief": {},
+ "Inkopen EU laag tarief": {},
+ "Inkopen EU overig": {},
+ "Inkopen hoog": {},
+ "Inkopen laag": {},
+ "Inkopen nul": {},
+ "Inkopen overig": {},
+ "Invoerkosten": {},
+ "Kosten inkoopvereniging": {},
+ "Kostprijs omzet grondstoffen": {
+ "account_type": "Cost of Goods Sold"
+ },
+ "Kostprijs omzet handelsgoederen": {},
+ "Onttrekking uitgev.garantie": {},
+ "Priv\u00e9-gebruik goederen": {},
+ "Stock aanpassing": {
+ "account_type": "Stock Adjustment"
+ },
+ "Tegenrekening inkoop": {},
+ "Toev. Voorz. incour. grondst.": {},
+ "Toevoeging garantieverpl.": {},
+ "Toevoeging voorz. incour. handelsgoed.": {},
+ "Uitbesteed werk": {},
+ "Voorz. Incourourant grondst.": {},
+ "Voorz.incour. handelsgoed.": {},
+ "root_type": "Expense"
+ },
+ "root_type": "Expense"
+ }
},
"VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": {
"EIGEN VERMOGEN": {
@@ -602,7 +665,7 @@
"account_type": "Equity"
}
},
- "root_type": "Asset"
+ "root_type": "Equity"
},
"VERKOOPRESULTATEN": {
"Diensten fabric. 0% niet-EU": {},
@@ -627,67 +690,6 @@
"Verleende Kredietbep. fabricage": {},
"Verleende Kredietbep. handel": {},
"root_type": "Income"
- },
- "VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
- "Betalingskort. crediteuren": {},
- "Garantiekosten": {},
- "Hulpmaterialen": {},
- "Inkomende vrachten": {
- "account_type": "Expenses Included In Valuation"
- },
- "Inkoop import buiten EU hoog": {},
- "Inkoop import buiten EU laag": {},
- "Inkoop import buiten EU overig": {},
- "Inkoopbonussen": {},
- "Inkoopkosten": {},
- "Inkoopprovisie": {},
- "Inkopen BTW verlegd": {},
- "Inkopen EU hoog tarief": {},
- "Inkopen EU laag tarief": {},
- "Inkopen EU overig": {},
- "Inkopen hoog": {},
- "Inkopen laag": {},
- "Inkopen nul": {},
- "Inkopen overig": {},
- "Invoerkosten": {},
- "Kosten inkoopvereniging": {},
- "Kostprijs omzet grondstoffen": {
- "account_type": "Cost of Goods Sold"
- },
- "Kostprijs omzet handelsgoederen": {},
- "Onttrekking uitgev.garantie": {},
- "Priv\u00e9-gebruik goederen": {},
- "Stock aanpassing": {
- "account_type": "Stock Adjustment"
- },
- "Tegenrekening inkoop": {},
- "Toev. Voorz. incour. grondst.": {},
- "Toevoeging garantieverpl.": {},
- "Toevoeging voorz. incour. handelsgoed.": {},
- "Uitbesteed werk": {},
- "Voorz. Incourourant grondst.": {},
- "Voorz.incour. handelsgoed.": {},
- "root_type": "Expense"
- },
- "VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
- "Emballage": {},
- "Gereed product 1": {},
- "Gereed product 2": {},
- "Goederen 1": {},
- "Goederen 2": {},
- "Goederen in consignatie": {},
- "Goederen onderweg": {},
- "Grondstoffen 1": {},
- "Grondstoffen 2": {},
- "Halffabrikaten 1": {},
- "Halffabrikaten 2": {},
- "Hulpstoffen 1": {},
- "Hulpstoffen 2": {},
- "Kantoorbenodigdheden": {},
- "Onderhanden werk": {},
- "Verpakkingsmateriaal": {},
- "Zegels": {},
- "root_type": "Asset"
}
}
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 81ff6a52db1..15c84d462f1 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -271,6 +271,12 @@ def get_dimensions(with_cost_center_and_project=False):
as_dict=1,
)
+ if isinstance(with_cost_center_and_project, str):
+ if with_cost_center_and_project.lower().strip() == "true":
+ with_cost_center_and_project = True
+ else:
+ with_cost_center_and_project = False
+
if with_cost_center_and_project:
dimension_filters.extend(
[
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 35d606ba3ae..6667193a54c 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -8,9 +8,6 @@ frappe.ui.form.on('Bank', {
},
refresh: function(frm) {
add_fields_to_mapping_table(frm);
-
- frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' };
-
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) {
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
index 0647a5ccf38..d961ead642d 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -19,7 +19,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
onload: function (frm) {
// Set default filter dates
- today = frappe.datetime.get_today()
+ let today = frappe.datetime.get_today()
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger('bank_account');
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 598db642f33..3b5698b118a 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -93,6 +93,12 @@ class ExchangeRateRevaluation(Document):
return True
+ def fetch_and_calculate_accounts_data(self):
+ accounts = self.get_accounts_data()
+ if accounts:
+ for acc in accounts:
+ self.append("accounts", acc)
+
@frappe.whitelist()
def get_accounts_data(self):
self.validate_mandatory()
@@ -252,8 +258,8 @@ class ExchangeRateRevaluation(Document):
new_balance_in_base_currency = 0
new_balance_in_account_currency = 0
- current_exchange_rate = calculate_exchange_rate_using_last_gle(
- company, d.account, d.party_type, d.party
+ current_exchange_rate = (
+ calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
)
gain_loss = new_balance_in_account_currency - (
diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py
index 7b2575d2c32..42c0e512386 100644
--- a/erpnext/accounts/doctype/finance_book/test_finance_book.py
+++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py
@@ -13,7 +13,7 @@ class TestFinanceBook(unittest.TestCase):
finance_book = create_finance_book()
# create jv entry
- jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank - _TC", "Debtors - _TC", 100, save=False)
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer"})
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.js b/erpnext/accounts/doctype/fiscal_year/fiscal_year.js
index bc77dac1cdd..508b2eaf2a4 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.js
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.js
@@ -8,17 +8,6 @@ frappe.ui.form.on('Fiscal Year', {
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1));
}
},
- refresh: function (frm) {
- if (!frm.doc.__islocal && (frm.doc.name != frappe.sys_defaults.fiscal_year)) {
- frm.add_custom_button(__("Set as Default"), () => frm.events.set_as_default(frm));
- frm.set_intro(__("To set this Fiscal Year as Default, click on 'Set as Default'"));
- } else {
- frm.set_intro("");
- }
- },
- set_as_default: function(frm) {
- return frm.call('set_as_default');
- },
year_start_date: function(frm) {
if (!frm.doc.is_short_year) {
let year_end_date =
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
index 9d1b99b29b1..0dfe569ec9a 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
@@ -4,28 +4,12 @@
import frappe
from dateutil.relativedelta import relativedelta
-from frappe import _, msgprint
+from frappe import _
from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate
class FiscalYear(Document):
- @frappe.whitelist()
- def set_as_default(self):
- frappe.db.set_single_value("Global Defaults", "current_fiscal_year", self.name)
- global_defaults = frappe.get_doc("Global Defaults")
- global_defaults.check_permission("write")
- global_defaults.on_update()
-
- # clear cache
- frappe.clear_cache()
-
- msgprint(
- _(
- "{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect."
- ).format(self.name)
- )
-
def validate(self):
self.validate_dates()
self.validate_overlap()
@@ -68,13 +52,6 @@ class FiscalYear(Document):
frappe.cache().delete_value("fiscal_years")
def on_trash(self):
- global_defaults = frappe.get_doc("Global Defaults")
- if global_defaults.current_fiscal_year == self.name:
- frappe.throw(
- _(
- "You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings"
- ).format(self.name)
- )
frappe.cache().delete_value("fiscal_years")
def validate_overlap(self):
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
index b42d712d88a..87f0ad10483 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
@@ -35,6 +35,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
+ "in_filter": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company",
@@ -56,7 +57,7 @@
}
],
"links": [],
- "modified": "2022-01-18 21:11:23.105589",
+ "modified": "2023-07-09 18:11:23.105589",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
@@ -102,4 +103,4 @@
"states": [],
"title_field": "title",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 73b19115434..e7aca79d08b 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -43,7 +43,7 @@ class TestJournalEntry(unittest.TestCase):
frappe.db.sql(
"""select name from `tabJournal Entry Account`
where account = %s and docstatus = 1 and parent = %s""",
- ("_Test Receivable - _TC", test_voucher.name),
+ ("Debtors - _TC", test_voucher.name),
)
)
@@ -273,7 +273,7 @@ class TestJournalEntry(unittest.TestCase):
jv.submit()
# create jv in USD, but account currency in INR
- jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank - _TC", "Debtors - _TC", 100, save=False)
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
diff --git a/erpnext/accounts/doctype/journal_entry/test_records.json b/erpnext/accounts/doctype/journal_entry/test_records.json
index 5077305cf22..dafcf56abdb 100644
--- a/erpnext/accounts/doctype/journal_entry/test_records.json
+++ b/erpnext/accounts/doctype/journal_entry/test_records.json
@@ -6,7 +6,7 @@
"doctype": "Journal Entry",
"accounts": [
{
- "account": "_Test Receivable - _TC",
+ "account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 400.0,
@@ -70,7 +70,7 @@
"doctype": "Journal Entry",
"accounts": [
{
- "account": "_Test Receivable - _TC",
+ "account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 0.0,
diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json
index 69330577ab3..7e345d84ead 100644
--- a/erpnext/accounts/doctype/party_account/party_account.json
+++ b/erpnext/accounts/doctype/party_account/party_account.json
@@ -6,7 +6,8 @@
"engine": "InnoDB",
"field_order": [
"company",
- "account"
+ "account",
+ "advance_account"
],
"fields": [
{
@@ -22,14 +23,20 @@
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 1,
- "label": "Account",
+ "label": "Default Account",
+ "options": "Account"
+ },
+ {
+ "fieldname": "advance_account",
+ "fieldtype": "Link",
+ "label": "Advance Account",
"options": "Account"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-04-04 12:31:02.994197",
+ "modified": "2023-06-06 14:15:42.053150",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Party Account",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index bac84db2315..0701435dfc7 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -319,6 +319,10 @@ frappe.ui.form.on('Payment Entry', {
}
},
+ company: function(frm){
+ frm.trigger('party');
+ },
+
party: function(frm) {
if (frm.doc.contact_email || frm.doc.contact_person) {
frm.set_value("contact_email", "");
@@ -733,7 +737,6 @@ frappe.ui.form.on('Payment Entry', {
if(r.message) {
var total_positive_outstanding = 0;
var total_negative_outstanding = 0;
-
$.each(r.message, function(i, d) {
var c = frm.add_child("references");
c.reference_doctype = d.voucher_type;
@@ -744,6 +747,7 @@ frappe.ui.form.on('Payment Entry', {
c.bill_no = d.bill_no;
c.payment_term = d.payment_term;
c.allocated_amount = d.allocated_amount;
+ c.account = d.account;
if(!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) {
if(flt(d.outstanding_amount) > 0)
@@ -1459,4 +1463,4 @@ frappe.ui.form.on('Payment Entry', {
});
}
},
-})
+})
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 6224d4038d6..d7b6a198df8 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -19,6 +19,7 @@
"party_type",
"party",
"party_name",
+ "book_advance_payments_in_separate_party_account",
"column_break_11",
"bank_account",
"party_bank_account",
@@ -735,12 +736,21 @@
"fieldname": "get_outstanding_orders",
"fieldtype": "Button",
"label": "Get Outstanding Orders"
+ },
+ {
+ "default": "0",
+ "fetch_from": "company.book_advance_payments_in_separate_party_account",
+ "fieldname": "book_advance_payments_in_separate_party_account",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Book Advance Payments in Separate Party Account",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-06-19 11:38:04.387219",
+ "modified": "2023-06-23 18:07:38.023010",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 712023fdf96..dcd7295bae3 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -8,6 +8,7 @@ from functools import reduce
import frappe
from frappe import ValidationError, _, qb, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
+from frappe.utils.data import comma_and, fmt_money
import erpnext
from erpnext.accounts.doctype.bank_account.bank_account import (
@@ -21,7 +22,11 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
-from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map
+from erpnext.accounts.general_ledger import (
+ make_gl_entries,
+ make_reverse_gl_entries,
+ process_gl_map,
+)
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices
from erpnext.controllers.accounts_controller import (
@@ -60,6 +65,7 @@ class PaymentEntry(AccountsController):
def validate(self):
self.setup_party_account_field()
self.set_missing_values()
+ self.set_liability_account()
self.set_missing_ref_details()
self.validate_payment_type()
self.validate_party_details()
@@ -87,11 +93,48 @@ class PaymentEntry(AccountsController):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.make_gl_entries()
+ self.make_advance_gl_entries()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_payment_schedule()
self.set_status()
+ def set_liability_account(self):
+ if not self.book_advance_payments_in_separate_party_account:
+ return
+
+ account_type = frappe.get_value(
+ "Account", {"name": self.party_account, "company": self.company}, "account_type"
+ )
+
+ if (account_type == "Payable" and self.party_type == "Customer") or (
+ account_type == "Receivable" and self.party_type == "Supplier"
+ ):
+ return
+
+ if self.unallocated_amount == 0:
+ for d in self.references:
+ if d.reference_doctype in ["Sales Order", "Purchase Order"]:
+ break
+ else:
+ return
+
+ liability_account = get_party_account(
+ self.party_type, self.party, self.company, include_advance=True
+ )[1]
+
+ self.set(self.party_account_field, liability_account)
+
+ frappe.msgprint(
+ _(
+ "Book Advance Payments as Liability option is chosen. Paid From account changed from {0} to {1}."
+ ).format(
+ frappe.bold(self.party_account),
+ frappe.bold(liability_account),
+ ),
+ alert=True,
+ )
+
def on_cancel(self):
self.ignore_linked_doctypes = (
"GL Entry",
@@ -101,6 +144,7 @@ class PaymentEntry(AccountsController):
"Repost Payment Ledger Items",
)
self.make_gl_entries(cancel=1)
+ self.make_advance_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.update_advance_paid()
self.delink_advance_entry_references()
@@ -174,7 +218,8 @@ class PaymentEntry(AccountsController):
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
- }
+ },
+ validate=True,
)
# Group latest_references by (voucher_type, voucher_no)
@@ -189,17 +234,16 @@ class PaymentEntry(AccountsController):
# The reference has already been fully paid
if not latest:
frappe.throw(
- _("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
+ _("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
- elif (
- latest.outstanding_amount < latest.invoice_amount
- and d.outstanding_amount != latest.outstanding_amount
- ):
+ elif latest.outstanding_amount < latest.invoice_amount and flt(
+ d.outstanding_amount, d.precision("outstanding_amount")
+ ) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
- ).format(d.reference_doctype, d.reference_name)
+ ).format(_(d.reference_doctype), d.reference_name)
)
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
@@ -301,7 +345,7 @@ class PaymentEntry(AccountsController):
def validate_party_details(self):
if self.party:
if not frappe.db.exists(self.party_type, self.party):
- frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
+ frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
@@ -350,7 +394,9 @@ class PaymentEntry(AccountsController):
continue
if d.reference_doctype not in valid_reference_doctypes:
frappe.throw(
- _("Reference Doctype must be one of {0}").format(comma_or(valid_reference_doctypes))
+ _("Reference Doctype must be one of {0}").format(
+ comma_or((_(d) for d in valid_reference_doctypes))
+ )
)
elif d.reference_name:
@@ -363,7 +409,7 @@ class PaymentEntry(AccountsController):
if self.party != ref_doc.get(scrub(self.party_type)):
frappe.throw(
_("{0} {1} is not associated with {2} {3}").format(
- d.reference_doctype, d.reference_name, self.party_type, self.party
+ _(d.reference_doctype), d.reference_name, _(self.party_type), self.party
)
)
else:
@@ -379,21 +425,24 @@ class PaymentEntry(AccountsController):
elif self.party_type == "Employee":
ref_party_account = ref_doc.payable_account
- if ref_party_account != self.party_account:
+ if (
+ ref_party_account != self.party_account
+ and not self.book_advance_payments_in_separate_party_account
+ ):
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
- d.reference_doctype, d.reference_name, ref_party_account, self.party_account
+ _(d.reference_doctype), d.reference_name, ref_party_account, self.party_account
)
)
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
frappe.throw(
- _("{0} {1} is on hold").format(d.reference_doctype, d.reference_name),
- title=_("Invalid Invoice"),
+ _("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
+ title=_("Invalid Purchase Invoice"),
)
if ref_doc.docstatus != 1:
- frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
+ frappe.throw(_("{0} {1} must be submitted").format(_(d.reference_doctype), d.reference_name))
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
@@ -419,14 +468,13 @@ class PaymentEntry(AccountsController):
if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
- for k, v in no_oustanding_refs.items():
+ for reference_doctype, references in no_oustanding_refs.items():
frappe.msgprint(
_(
- "{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
+ "References {0} of type {1} had no outstanding amount left before submitting the Payment Entry. Now they have a negative outstanding amount."
).format(
- _(k),
- frappe.bold(", ".join(d.reference_name for d in v)),
- frappe.bold(_("negative outstanding amount")),
+ frappe.bold(comma_and((d.reference_name for d in references))),
+ _(reference_doctype),
)
+ "
"
+ _("If this is undesirable please cancel the corresponding Payment Entry."),
@@ -461,7 +509,7 @@ class PaymentEntry(AccountsController):
if not valid:
frappe.throw(
_("Against Journal Entry {0} does not have any unmatched {1} entry").format(
- d.reference_name, dr_or_cr
+ d.reference_name, _(dr_or_cr)
)
)
@@ -528,7 +576,7 @@ class PaymentEntry(AccountsController):
if allocated_amount > outstanding:
frappe.throw(
_("Row #{0}: Cannot allocate more than {1} against payment term {2}").format(
- idx, outstanding, key[0]
+ idx, fmt_money(outstanding), key[0]
)
)
@@ -832,7 +880,7 @@ class PaymentEntry(AccountsController):
elif paid_amount - additional_charges > total_negative_outstanding:
frappe.throw(
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
- total_negative_outstanding
+ fmt_money(total_negative_outstanding)
),
InvalidPaymentEntry,
)
@@ -941,24 +989,27 @@ class PaymentEntry(AccountsController):
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
+
gle = party_gl_dict.copy()
- gle.update(
- {
- "against_voucher_type": d.reference_doctype,
- "against_voucher": d.reference_name,
- "cost_center": cost_center,
- }
- )
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
+ if self.book_advance_payments_in_separate_party_account:
+ against_voucher_type = "Payment Entry"
+ against_voucher = self.name
+ else:
+ against_voucher_type = d.reference_doctype
+ against_voucher = d.reference_name
+
gle.update(
{
- dr_or_cr + "_in_account_currency": d.allocated_amount,
dr_or_cr: allocated_amount_in_company_currency,
+ dr_or_cr + "_in_account_currency": d.allocated_amount,
+ "against_voucher_type": against_voucher_type,
+ "against_voucher": against_voucher,
+ "cost_center": cost_center,
}
)
-
gl_entries.append(gle)
if self.unallocated_amount:
@@ -966,7 +1017,6 @@ class PaymentEntry(AccountsController):
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
-
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
@@ -976,6 +1026,80 @@ class PaymentEntry(AccountsController):
gl_entries.append(gle)
+ def make_advance_gl_entries(self, against_voucher_type=None, against_voucher=None, cancel=0):
+ if self.book_advance_payments_in_separate_party_account:
+ gl_entries = []
+ for d in self.get("references"):
+ if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
+ if not (against_voucher_type and against_voucher) or (
+ d.reference_doctype == against_voucher_type and d.reference_name == against_voucher
+ ):
+ self.make_invoice_liability_entry(gl_entries, d)
+
+ if cancel:
+ for entry in gl_entries:
+ frappe.db.set_value(
+ "GL Entry",
+ {
+ "voucher_no": self.name,
+ "voucher_type": self.doctype,
+ "voucher_detail_no": entry.voucher_detail_no,
+ "against_voucher_type": entry.against_voucher_type,
+ "against_voucher": entry.against_voucher,
+ },
+ "is_cancelled",
+ 1,
+ )
+
+ make_reverse_gl_entries(gl_entries=gl_entries, partial_cancel=True)
+ else:
+ make_gl_entries(gl_entries)
+
+ def make_invoice_liability_entry(self, gl_entries, invoice):
+ args_dict = {
+ "party_type": self.party_type,
+ "party": self.party,
+ "account_currency": self.party_account_currency,
+ "cost_center": self.cost_center,
+ "voucher_type": "Payment Entry",
+ "voucher_no": self.name,
+ "voucher_detail_no": invoice.name,
+ }
+
+ dr_or_cr = "credit" if invoice.reference_doctype == "Sales Invoice" else "debit"
+ args_dict["account"] = invoice.account
+ args_dict[dr_or_cr] = invoice.allocated_amount
+ args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
+ args_dict.update(
+ {
+ "against_voucher_type": invoice.reference_doctype,
+ "against_voucher": invoice.reference_name,
+ }
+ )
+ gle = self.get_gl_dict(
+ args_dict,
+ item=self,
+ )
+ gl_entries.append(gle)
+
+ args_dict[dr_or_cr] = 0
+ args_dict[dr_or_cr + "_in_account_currency"] = 0
+ dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
+ args_dict["account"] = self.party_account
+ args_dict[dr_or_cr] = invoice.allocated_amount
+ args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
+ args_dict.update(
+ {
+ "against_voucher_type": "Payment Entry",
+ "against_voucher": self.name,
+ }
+ )
+ gle = self.get_gl_dict(
+ args_dict,
+ item=self,
+ )
+ gl_entries.append(gle)
+
def add_bank_gl_entries(self, gl_entries):
if self.payment_type in ("Pay", "Internal Transfer"):
gl_entries.append(
@@ -1301,13 +1425,16 @@ def validate_inclusive_tax(tax, doc):
@frappe.whitelist()
-def get_outstanding_reference_documents(args):
+def get_outstanding_reference_documents(args, validate=False):
if isinstance(args, str):
args = json.loads(args)
if args.get("party_type") == "Member":
return
+ if not args.get("get_outstanding_invoices") and not args.get("get_orders_to_be_billed"):
+ args["get_outstanding_invoices"] = True
+
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
accounting_dimensions_filter = []
@@ -1365,7 +1492,7 @@ def get_outstanding_reference_documents(args):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
- args.get("party_account"),
+ get_party_account(args.get("party_type"), args.get("party"), args.get("company")),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
@@ -1421,13 +1548,14 @@ def get_outstanding_reference_documents(args):
elif args.get("get_orders_to_be_billed"):
ref_document_type = "orders"
- frappe.msgprint(
- _(
- "No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
- ).format(
- ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
+ if not validate:
+ frappe.msgprint(
+ _(
+ "No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
+ ).format(
+ _(ref_document_type), _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
+ )
)
- )
return data
@@ -1463,6 +1591,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
"outstanding_amount": flt(d.outstanding_amount),
"payment_amount": payment_term.payment_amount,
"payment_term": payment_term.payment_term,
+ "account": d.account,
}
)
)
@@ -1500,60 +1629,59 @@ def get_orders_to_be_billed(
cost_center=None,
filters=None,
):
+ voucher_type = None
if party_type == "Customer":
voucher_type = "Sales Order"
elif party_type == "Supplier":
voucher_type = "Purchase Order"
- elif party_type == "Employee":
- voucher_type = None
+
+ if not voucher_type:
+ return []
# Add cost center condition
- if voucher_type:
- doc = frappe.get_doc({"doctype": voucher_type})
- condition = ""
- if doc and hasattr(doc, "cost_center") and doc.cost_center:
- condition = " and cost_center='%s'" % cost_center
+ doc = frappe.get_doc({"doctype": voucher_type})
+ condition = ""
+ if doc and hasattr(doc, "cost_center") and doc.cost_center:
+ condition = " and cost_center='%s'" % cost_center
- orders = []
- if voucher_type:
- if party_account_currency == company_currency:
- grand_total_field = "base_grand_total"
- rounded_total_field = "base_rounded_total"
- else:
- grand_total_field = "grand_total"
- rounded_total_field = "rounded_total"
+ if party_account_currency == company_currency:
+ grand_total_field = "base_grand_total"
+ rounded_total_field = "base_rounded_total"
+ else:
+ grand_total_field = "grand_total"
+ rounded_total_field = "rounded_total"
- orders = frappe.db.sql(
- """
- select
- name as voucher_no,
- if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
- (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
- transaction_date as posting_date
- from
- `tab{voucher_type}`
- where
- {party_type} = %s
- and docstatus = 1
- and company = %s
- and ifnull(status, "") != "Closed"
- and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
- and abs(100 - per_billed) > 0.01
- {condition}
- order by
- transaction_date, name
- """.format(
- **{
- "rounded_total_field": rounded_total_field,
- "grand_total_field": grand_total_field,
- "voucher_type": voucher_type,
- "party_type": scrub(party_type),
- "condition": condition,
- }
- ),
- (party, company),
- as_dict=True,
- )
+ orders = frappe.db.sql(
+ """
+ select
+ name as voucher_no,
+ if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
+ (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
+ transaction_date as posting_date
+ from
+ `tab{voucher_type}`
+ where
+ {party_type} = %s
+ and docstatus = 1
+ and company = %s
+ and ifnull(status, "") != "Closed"
+ and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
+ and abs(100 - per_billed) > 0.01
+ {condition}
+ order by
+ transaction_date, name
+ """.format(
+ **{
+ "rounded_total_field": rounded_total_field,
+ "grand_total_field": grand_total_field,
+ "voucher_type": voucher_type,
+ "party_type": scrub(party_type),
+ "condition": condition,
+ }
+ ),
+ (party, company),
+ as_dict=True,
+ )
order_list = []
for d in orders:
@@ -1586,7 +1714,10 @@ def get_negative_outstanding_invoices(
cost_center=None,
condition=None,
):
+ if party_type not in ["Customer", "Supplier"]:
+ return []
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
+ account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
supplier_condition = "and (release_date is null or release_date <= CURRENT_DATE)"
@@ -1600,7 +1731,7 @@ def get_negative_outstanding_invoices(
return frappe.db.sql(
"""
select
- "{voucher_type}" as voucher_type, name as voucher_no,
+ "{voucher_type}" as voucher_type, name as voucher_no, {account} as account,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
outstanding_amount, posting_date,
due_date, conversion_rate as exchange_rate
@@ -1623,6 +1754,7 @@ def get_negative_outstanding_invoices(
"party_type": scrub(party_type),
"party_account": "debit_to" if party_type == "Customer" else "credit_to",
"cost_center": cost_center,
+ "account": account,
}
),
(party, party_account),
@@ -1634,10 +1766,9 @@ def get_negative_outstanding_invoices(
def get_party_details(company, party_type, party, date, cost_center=None):
bank_account = ""
if not frappe.db.exists(party_type, party):
- frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
+ frappe.throw(_("{0} {1} does not exist").format(_(party_type), party))
party_account = get_party_account(party_type, party, company)
-
account_currency = get_account_currency(party_account)
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
@@ -1710,7 +1841,7 @@ def get_outstanding_on_journal_entry(name):
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
- total_amount = outstanding_amount = exchange_rate = None
+ total_amount = outstanding_amount = exchange_rate = account = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
@@ -1748,6 +1879,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
+ account = (
+ ref_doc.get("debit_to") if reference_doctype == "Sales Invoice" else ref_doc.get("credit_to")
+ )
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
@@ -1755,7 +1889,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
# Get the exchange rate based on the posting date of the ref doc.
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
- return frappe._dict(
+ res = frappe._dict(
{
"due_date": ref_doc.get("due_date"),
"total_amount": flt(total_amount),
@@ -1764,6 +1898,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"bill_no": ref_doc.get("bill_no"),
}
)
+ if account:
+ res.update({"account": account})
+ return res
@frappe.whitelist()
@@ -1783,7 +1920,7 @@ def get_payment_entry(
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
100.0 + over_billing_allowance
):
- frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
+ frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
if not party_type:
party_type = set_party_type(dt)
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index ae2625b6539..70cc4b3d347 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -932,7 +932,7 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(pe.cost_center, si.cost_center)
self.assertEqual(flt(expected_account_balance), account_balance)
self.assertEqual(flt(expected_party_balance), party_balance)
- self.assertEqual(flt(expected_party_account_balance), party_account_balance)
+ self.assertEqual(flt(expected_party_account_balance, 2), flt(party_account_balance, 2))
def test_multi_currency_payment_entry_with_taxes(self):
payment_entry = create_payment_entry(
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 3003c68196e..12aa0b520ec 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -15,7 +15,8 @@
"outstanding_amount",
"allocated_amount",
"exchange_rate",
- "exchange_gain_loss"
+ "exchange_gain_loss",
+ "account"
],
"fields": [
{
@@ -101,12 +102,18 @@
"label": "Exchange Gain/Loss",
"options": "Company:company:default_currency",
"read_only": 1
+ },
+ {
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "label": "Account",
+ "options": "Account"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-12-12 12:31:44.919895",
+ "modified": "2023-06-08 07:40:38.487874",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
index 22842cec0fe..9cf2ac6c2a4 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
@@ -13,6 +13,7 @@
"party_type",
"party",
"due_date",
+ "voucher_detail_no",
"cost_center",
"finance_book",
"voucher_type",
@@ -142,12 +143,17 @@
"fieldname": "remarks",
"fieldtype": "Text",
"label": "Remarks"
+ },
+ {
+ "fieldname": "voucher_detail_no",
+ "fieldtype": "Data",
+ "label": "Voucher Detail No"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-08-22 15:32:56.629430",
+ "modified": "2023-06-29 12:24:20.500632",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 89fa15172f1..2adc1238b70 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -29,6 +29,17 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
};
});
+ this.frm.set_query('default_advance_account', () => {
+ return {
+ filters: {
+ "company": this.frm.doc.company,
+ "is_group": 0,
+ "account_type": this.frm.doc.party_type == 'Customer' ? "Receivable": "Payable",
+ "root_type": this.frm.doc.party_type == 'Customer' ? "Liability": "Asset"
+ }
+ };
+ });
+
this.frm.set_query('bank_cash_account', () => {
return {
filters:[
@@ -128,19 +139,20 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.frm.trigger("clear_child_tables");
if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) {
- return frappe.call({
+ frappe.call({
method: "erpnext.accounts.party.get_party_account",
args: {
company: this.frm.doc.company,
party_type: this.frm.doc.party_type,
- party: this.frm.doc.party
+ party: this.frm.doc.party,
+ include_advance: 1
},
callback: (r) => {
if (!r.exc && r.message) {
- this.frm.set_value("receivable_payable_account", r.message);
+ this.frm.set_value("receivable_payable_account", r.message[0]);
+ this.frm.set_value("default_advance_account", r.message[1]);
}
this.frm.refresh();
-
}
});
}
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index 18d34850850..5f6c7034ed3 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -10,6 +10,7 @@
"column_break_4",
"party",
"receivable_payable_account",
+ "default_advance_account",
"col_break1",
"from_invoice_date",
"from_payment_date",
@@ -185,13 +186,21 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
+ },
+ {
+ "depends_on": "eval:doc.party",
+ "fieldname": "default_advance_account",
+ "fieldtype": "Link",
+ "label": "Default Advance Account",
+ "mandatory_depends_on": "doc.party_type",
+ "options": "Account"
}
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
"issingle": 1,
"links": [],
- "modified": "2022-04-29 15:37:10.246831",
+ "modified": "2023-06-09 13:02:48.718362",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 216d4eccac7..25d94c55d3a 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -55,12 +55,28 @@ class PaymentReconciliation(Document):
self.add_payment_entries(non_reconciled_payments)
def get_payment_entries(self):
+ if self.default_advance_account:
+ party_account = [self.receivable_payable_account, self.default_advance_account]
+ else:
+ party_account = [self.receivable_payable_account]
+
order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order"
- condition = self.get_conditions(get_payments=True)
+ condition = frappe._dict(
+ {
+ "company": self.get("company"),
+ "get_payments": True,
+ "cost_center": self.get("cost_center"),
+ "from_payment_date": self.get("from_payment_date"),
+ "to_payment_date": self.get("to_payment_date"),
+ "maximum_payment_amount": self.get("maximum_payment_amount"),
+ "minimum_payment_amount": self.get("minimum_payment_amount"),
+ }
+ )
+
payment_entries = get_advance_payment_entries(
self.party_type,
self.party,
- self.receivable_payable_account,
+ party_account,
order_doctype,
against_all_orders=True,
limit=self.payment_limit,
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index cced37589ba..32e267f33c8 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -20,7 +20,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
onload(doc) {
super.onload();
- this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry', 'Serial and Batch Bundle'];
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index bf393c0d29c..4b2fcec7579 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -93,7 +93,7 @@ class POSInvoice(SalesInvoice):
)
def on_cancel(self):
- self.ignore_linked_doctypes = "Payment Ledger Entry"
+ self.ignore_linked_doctypes = ["Payment Ledger Entry", "Serial and Batch Bundle"]
# run on cancel method of selling controller
super(SalesInvoice, self).on_cancel()
if not self.is_return and self.loyalty_program:
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index f842a16b74b..0fce61f1e76 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -767,6 +767,39 @@ class TestPOSInvoice(unittest.TestCase):
)
self.assertEqual(rounded_total, 400)
+ def test_pos_batch_reservation(self):
+ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+ get_auto_batch_nos,
+ )
+ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
+ create_batch_item_with_batch,
+ )
+
+ create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02")
+ make_stock_entry(
+ target="_Test Warehouse - _TC",
+ item_code="_BATCH ITEM Test For Reserve",
+ qty=20,
+ basic_rate=100,
+ batch_no="TestBatch-RS 02",
+ )
+
+ pos_inv1 = create_pos_invoice(
+ item="_BATCH ITEM Test For Reserve", rate=300, qty=15, batch_no="TestBatch-RS 02"
+ )
+ pos_inv1.save()
+ pos_inv1.submit()
+
+ batches = get_auto_batch_nos(
+ frappe._dict(
+ {"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"}
+ )
+ )
+
+ for batch in batches:
+ if batch.batch_no == "TestBatch-RS 02" and batch.warehouse == "_Test Warehouse - _TC":
+ self.assertEqual(batch.qty, 5)
+
def test_pos_batch_item_qty_validation(self):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
BatchNegativeStockError,
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
index 5a0aeb7284a..263621dcf4d 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
@@ -16,8 +16,10 @@ from erpnext.stock.doctype.item.test_item import create_item
class TestProcessDeferredAccounting(unittest.TestCase):
def test_creation_of_ledger_entry_on_submit(self):
"""test creation of gl entries on submission of document"""
+ change_acc_settings(acc_frozen_upto="2023-05-31", book_deferred_entries_based_on="Months")
+
deferred_account = create_account(
- account_name="Deferred Revenue",
+ account_name="Deferred Revenue for Accounts Frozen",
parent_account="Current Liabilities - _TC",
company="_Test Company",
)
@@ -29,11 +31,11 @@ class TestProcessDeferredAccounting(unittest.TestCase):
item.save()
si = create_sales_invoice(
- item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True
+ item=item.name, rate=3000, update_stock=0, posting_date="2023-07-01", do_not_submit=True
)
si.items[0].enable_deferred_revenue = 1
- si.items[0].service_start_date = "2019-01-10"
- si.items[0].service_end_date = "2019-03-15"
+ si.items[0].service_start_date = "2023-05-01"
+ si.items[0].service_end_date = "2023-07-31"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
@@ -41,9 +43,9 @@ class TestProcessDeferredAccounting(unittest.TestCase):
process_deferred_accounting = doc = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
- posting_date="2019-01-01",
- start_date="2019-01-01",
- end_date="2019-01-31",
+ posting_date="2023-07-01",
+ start_date="2023-05-01",
+ end_date="2023-06-30",
type="Income",
)
)
@@ -52,11 +54,16 @@ class TestProcessDeferredAccounting(unittest.TestCase):
process_deferred_accounting.submit()
expected_gle = [
- [deferred_account, 33.85, 0.0, "2019-01-31"],
- ["Sales - _TC", 0.0, 33.85, "2019-01-31"],
+ ["Debtors - _TC", 3000, 0.0, "2023-07-01"],
+ [deferred_account, 0.0, 3000, "2023-07-01"],
+ ["Sales - _TC", 0.0, 1000, "2023-06-30"],
+ [deferred_account, 1000, 0.0, "2023-06-30"],
+ ["Sales - _TC", 0.0, 1000, "2023-06-30"],
+ [deferred_account, 1000, 0.0, "2023-06-30"],
]
- check_gl_entries(self, si.name, expected_gle, "2019-01-10")
+ check_gl_entries(self, si.name, expected_gle, "2023-07-01")
+ change_acc_settings()
def test_pda_submission_and_cancellation(self):
pda = frappe.get_doc(
@@ -70,3 +77,10 @@ class TestProcessDeferredAccounting(unittest.TestCase):
)
pda.submit()
pda.cancel()
+
+
+def change_acc_settings(acc_frozen_upto="", book_deferred_entries_based_on="Days"):
+ acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
+ acc_settings.acc_frozen_upto = acc_frozen_upto
+ acc_settings.book_deferred_entries_based_on = book_deferred_entries_based_on
+ acc_settings.save()
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 0c18f5edb5b..d8759e95b87 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -549,6 +549,7 @@
"depends_on": "update_stock",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -1088,6 +1089,7 @@
"fieldtype": "Button",
"label": "Get Advances Paid",
"oldfieldtype": "Button",
+ "options": "set_advances",
"print_hide": 1
},
{
@@ -1575,7 +1577,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:21:54.637245",
+ "modified": "2023-07-04 17:22:59.145031",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 45bddfc0963..8c964804786 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1664,6 +1664,63 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertTrue(return_pi.docstatus == 1)
+ def test_advance_entries_as_asset(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
+
+ account = create_account(
+ parent_account="Current Assets - _TC",
+ account_name="Advances Paid",
+ company="_Test Company",
+ account_type="Receivable",
+ )
+
+ set_advance_flag(company="_Test Company", flag=1, default_account=account)
+
+ pe = create_payment_entry(
+ company="_Test Company",
+ payment_type="Pay",
+ party_type="Supplier",
+ party="_Test Supplier",
+ paid_from="Cash - _TC",
+ paid_to="Creditors - _TC",
+ paid_amount=500,
+ )
+ pe.submit()
+
+ pi = make_purchase_invoice(
+ company="_Test Company",
+ customer="_Test Supplier",
+ do_not_save=True,
+ do_not_submit=True,
+ rate=1000,
+ price_list_rate=1000,
+ qty=1,
+ )
+ pi.base_grand_total = 1000
+ pi.grand_total = 1000
+ pi.set_advances()
+ for advance in pi.advances:
+ advance.allocated_amount = 500 if advance.reference_name == pe.name else 0
+ pi.save()
+ pi.submit()
+
+ self.assertEqual(pi.advances[0].allocated_amount, 500)
+
+ # Check GL Entry against payment doctype
+ expected_gle = [
+ ["Advances Paid - _TC", 0.0, 500, nowdate()],
+ ["Cash - _TC", 0.0, 500, nowdate()],
+ ["Creditors - _TC", 500, 0.0, nowdate()],
+ ["Creditors - _TC", 500, 0.0, nowdate()],
+ ]
+
+ check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry")
+
+ pi.load_from_db()
+ self.assertEqual(pi.outstanding_amount, 500)
+
+ set_advance_flag(company="_Test Company", flag=0, default_account="")
+
def test_gl_entries_for_standalone_debit_note(self):
make_purchase_invoice(qty=5, rate=500, update_stock=True)
@@ -1680,16 +1737,32 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
-def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
- gl_entries = frappe.db.sql(
- """select account, debit, credit, posting_date
- from `tabGL Entry`
- where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s
- order by posting_date asc, account asc""",
- (voucher_no, posting_date),
- as_dict=1,
+def set_advance_flag(company, flag, default_account):
+ frappe.db.set_value(
+ "Company",
+ company,
+ {
+ "book_advance_payments_in_separate_party_account": flag,
+ "default_advance_paid_account": default_account,
+ },
)
+
+def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Purchase Invoice"):
+ gl = frappe.qb.DocType("GL Entry")
+ q = (
+ frappe.qb.from_(gl)
+ .select(gl.account, gl.debit, gl.credit, gl.posting_date)
+ .where(
+ (gl.voucher_type == voucher_type)
+ & (gl.voucher_no == voucher_no)
+ & (gl.posting_date >= posting_date)
+ & (gl.is_cancelled == 0)
+ )
+ .orderby(gl.posting_date, gl.account, gl.creation)
+ )
+ gl_entries = q.run(as_dict=True)
+
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
doc.assertEqual(expected_gle[i][1], gle.debit)
diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
index 9fcbf5c6339..4db531eac90 100644
--- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
+++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
@@ -117,7 +117,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-09-26 15:47:28.167371",
+ "modified": "2023-06-23 21:13:18.013816",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",
@@ -125,5 +125,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index deb202d1458..4afc4512ff7 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -178,6 +178,7 @@
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty",
+ "no_copy": 1,
"read_only": 1
},
{
@@ -422,6 +423,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"options": "Warehouse"
},
@@ -903,7 +905,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-04-01 20:08:54.545160",
+ "modified": "2023-07-04 17:22:21.501152",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 68407e02210..8753ebc3baf 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -674,19 +674,6 @@ frappe.ui.form.on('Sales Invoice', {
}
}
- // expense account
- frm.fields_dict['items'].grid.get_field('expense_account').get_query = function(doc) {
- if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
- return {
- filters: {
- 'report_type': 'Profit and Loss',
- 'company': doc.company,
- "is_group": 0
- }
- }
- }
- }
-
// discount account
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index 3781f8ccc9b..61e5219c80c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -6,7 +6,7 @@
"cost_center": "_Test Cost Center - _TC",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"items": [
{
@@ -78,7 +78,7 @@
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
@@ -137,7 +137,7 @@
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
@@ -265,7 +265,7 @@
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 784bdf66124..0280c3590c4 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -6,7 +6,6 @@ import unittest
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
-from frappe.model.naming import make_autoname
from frappe.tests.utils import change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
@@ -35,7 +34,6 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle
get_serial_nos_from_bundle,
make_serial_batch_bundle,
)
-from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
get_qty_after_transaction,
make_stock_entry,
@@ -1726,7 +1724,7 @@ class TestSalesInvoice(unittest.TestCase):
# Party Account currency must be in USD, as there is existing GLE with USD
si4 = create_sales_invoice(
customer="_Test Customer USD",
- debit_to="_Test Receivable - _TC",
+ debit_to="Debtors - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
@@ -1739,7 +1737,7 @@ class TestSalesInvoice(unittest.TestCase):
si3.cancel()
si5 = create_sales_invoice(
customer="_Test Customer USD",
- debit_to="_Test Receivable - _TC",
+ debit_to="Debtors - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
@@ -1818,7 +1816,7 @@ class TestSalesInvoice(unittest.TestCase):
"reference_date": nowdate(),
"received_amount": 300,
"paid_amount": 300,
- "paid_from": "_Test Receivable - _TC",
+ "paid_from": "Debtors - _TC",
"paid_to": "_Test Cash - _TC",
}
)
@@ -3252,9 +3250,10 @@ class TestSalesInvoice(unittest.TestCase):
si.submit()
expected_gle = [
- ["_Test Receivable USD - _TC", 7500.0, 500],
- ["Exchange Gain/Loss - _TC", 500.0, 0.0],
- ["Sales - _TC", 0.0, 7500.0],
+ ["_Test Exchange Gain/Loss - _TC", 500.0, 0.0, nowdate()],
+ ["_Test Receivable USD - _TC", 7500.0, 0.0, nowdate()],
+ ["_Test Receivable USD - _TC", 0.0, 500.0, nowdate()],
+ ["Sales - _TC", 0.0, 7500.0, nowdate()],
]
check_gl_entries(self, si.name, expected_gle, nowdate())
@@ -3310,6 +3309,73 @@ class TestSalesInvoice(unittest.TestCase):
)
self.assertRaises(frappe.ValidationError, si.submit)
+ def test_advance_entries_as_liability(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
+
+ account = create_account(
+ parent_account="Current Liabilities - _TC",
+ account_name="Advances Received",
+ company="_Test Company",
+ account_type="Receivable",
+ )
+
+ set_advance_flag(company="_Test Company", flag=1, default_account=account)
+
+ pe = create_payment_entry(
+ company="_Test Company",
+ payment_type="Receive",
+ party_type="Customer",
+ party="_Test Customer",
+ paid_from="Debtors - _TC",
+ paid_to="Cash - _TC",
+ paid_amount=1000,
+ )
+ pe.submit()
+
+ si = create_sales_invoice(
+ company="_Test Company",
+ customer="_Test Customer",
+ do_not_save=True,
+ do_not_submit=True,
+ rate=500,
+ price_list_rate=500,
+ )
+ si.base_grand_total = 500
+ si.grand_total = 500
+ si.set_advances()
+ for advance in si.advances:
+ advance.allocated_amount = 500 if advance.reference_name == pe.name else 0
+ si.save()
+ si.submit()
+
+ self.assertEqual(si.advances[0].allocated_amount, 500)
+
+ # Check GL Entry against payment doctype
+ expected_gle = [
+ ["Advances Received - _TC", 500, 0.0, nowdate()],
+ ["Cash - _TC", 1000, 0.0, nowdate()],
+ ["Debtors - _TC", 0.0, 1000, nowdate()],
+ ["Debtors - _TC", 0.0, 500, nowdate()],
+ ]
+
+ check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry")
+
+ si.load_from_db()
+ self.assertEqual(si.outstanding_amount, 0)
+
+ set_advance_flag(company="_Test Company", flag=0, default_account="")
+
+
+def set_advance_flag(company, flag, default_account):
+ frappe.db.set_value(
+ "Company",
+ company,
+ {
+ "book_advance_payments_in_separate_party_account": flag,
+ "default_advance_received_account": default_account,
+ },
+ )
+
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
@@ -3346,16 +3412,20 @@ def get_sales_invoice_for_e_invoice():
return si
-def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
- gl_entries = frappe.db.sql(
- """select account, debit, credit, posting_date
- from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
- and is_cancelled = 0
- order by posting_date asc, account asc""",
- (voucher_no, posting_date),
- as_dict=1,
+def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Sales Invoice"):
+ gl = frappe.qb.DocType("GL Entry")
+ q = (
+ frappe.qb.from_(gl)
+ .select(gl.account, gl.debit, gl.credit, gl.posting_date)
+ .where(
+ (gl.voucher_type == voucher_type)
+ & (gl.voucher_no == voucher_no)
+ & (gl.posting_date >= posting_date)
+ & (gl.is_cancelled == 0)
+ )
+ .orderby(gl.posting_date, gl.account, gl.creation)
)
+ gl_entries = q.run(as_dict=True)
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
index f92b57a45e1..0ae85d90004 100644
--- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
+++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
@@ -118,7 +118,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-09-26 15:47:46.911595",
+ "modified": "2023-06-23 21:12:57.557731",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",
@@ -126,5 +126,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/shareholder/shareholder.js b/erpnext/accounts/doctype/shareholder/shareholder.js
index c6f101e7f31..544d417a0e5 100644
--- a/erpnext/accounts/doctype/shareholder/shareholder.js
+++ b/erpnext/accounts/doctype/shareholder/shareholder.js
@@ -3,8 +3,6 @@
frappe.ui.form.on('Shareholder', {
refresh: function(frm) {
- frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Shareholder' };
-
frm.toggle_display(['contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) {
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index a929ff17b09..f1dad875fa7 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -223,6 +223,7 @@ def check_if_in_list(gle, gl_map, dimensions=None):
"party_type",
"project",
"finance_book",
+ "voucher_no",
]
if dimensions:
@@ -500,7 +501,12 @@ def get_round_off_account_and_cost_center(
def make_reverse_gl_entries(
- gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes"
+ gl_entries=None,
+ voucher_type=None,
+ voucher_no=None,
+ adv_adj=False,
+ update_outstanding="Yes",
+ partial_cancel=False,
):
"""
Get original gl entries of the voucher
@@ -520,14 +526,19 @@ def make_reverse_gl_entries(
if gl_entries:
create_payment_ledger_entry(
- gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
+ gl_entries,
+ cancel=1,
+ adv_adj=adv_adj,
+ update_outstanding=update_outstanding,
+ partial_cancel=partial_cancel,
)
validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
- set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
+ if not partial_cancel:
+ set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
for entry in gl_entries:
new_gle = copy.deepcopy(entry)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 07b865e66cd..03cf82a2b04 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -367,7 +367,7 @@ def set_account_and_due_date(
@frappe.whitelist()
-def get_party_account(party_type, party=None, company=None):
+def get_party_account(party_type, party=None, company=None, include_advance=False):
"""Returns the account for the given `party`.
Will first search in party (Customer / Supplier) record, if not found,
will search in group (Customer Group / Supplier Group),
@@ -408,6 +408,40 @@ def get_party_account(party_type, party=None, company=None):
if (account and account_currency != existing_gle_currency) or not account:
account = get_party_gle_account(party_type, party, company)
+ if include_advance and party_type in ["Customer", "Supplier"]:
+ advance_account = get_party_advance_account(party_type, party, company)
+ if advance_account:
+ return [account, advance_account]
+ else:
+ return [account]
+
+ return account
+
+
+def get_party_advance_account(party_type, party, company):
+ account = frappe.db.get_value(
+ "Party Account",
+ {"parenttype": party_type, "parent": party, "company": company},
+ "advance_account",
+ )
+
+ if not account:
+ party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group"
+ group = frappe.get_cached_value(party_type, party, scrub(party_group_doctype))
+ account = frappe.db.get_value(
+ "Party Account",
+ {"parenttype": party_group_doctype, "parent": group, "company": company},
+ "advance_account",
+ )
+
+ if not account:
+ account_name = (
+ "default_advance_received_account"
+ if party_type == "Customer"
+ else "default_advance_paid_account"
+ )
+ account = frappe.get_cached_value("Company", company, account_name)
+
return account
@@ -517,7 +551,10 @@ def validate_party_accounts(doc):
)
# validate if account is mapped for same company
- validate_account_head(account.idx, account.account, account.company)
+ if account.account:
+ validate_account_head(account.idx, account.account, account.company)
+ if account.advance_account:
+ validate_account_head(account.idx, account.advance_account, account.company)
@frappe.whitelist()
diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
index e3127b346f1..7e0bdea80c5 100644
--- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
+++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
@@ -73,7 +73,7 @@ def get_entries(filters):
return sorted(
entries,
- key=lambda k: k[2] or getdate(nowdate()),
+ key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()),
)
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
index dd965a9813e..d58fd95a840 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -49,7 +49,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
on_change: () => {
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
@@ -65,7 +65,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
on_change: () => {
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
@@ -139,7 +139,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
return value;
},
onload: function() {
- let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+ let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index a6447549e64..6e39ee9944f 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -650,7 +650,7 @@ def set_gl_entries_by_account(
if filters and filters.get("presentation_currency") != d.default_currency:
currency_info["company"] = d.name
currency_info["company_currency"] = d.default_currency
- convert_to_presentation_currency(gl_entries, currency_info, filters.get("company"))
+ convert_to_presentation_currency(gl_entries, currency_info)
for entry in gl_entries:
if entry.account_number:
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js
index 0056b9e8f56..96e0c844ca5 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js
@@ -48,7 +48,7 @@ function get_filters() {
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1
},
{
@@ -56,7 +56,7 @@ function get_filters() {
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1
},
{
@@ -100,7 +100,7 @@ frappe.query_reports["Deferred Revenue and Expense"] = {
return default_formatter(value, row, column, data);
},
onload: function(report){
- let fiscal_year = frappe.defaults.get_user_default("fiscal_year");
+ let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
index 3e11643776e..cad5325c6e9 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
@@ -4,9 +4,10 @@
import frappe
from frappe import _, qb
from frappe.query_builder import Column, functions
-from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded
+from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, getdate, rounded
from erpnext.accounts.report.financial_statements import get_period_list
+from erpnext.accounts.utils import get_fiscal_year
class Deferred_Item(object):
@@ -226,7 +227,7 @@ class Deferred_Revenue_and_Expense_Report(object):
# If no filters are provided, get user defaults
if not filters:
- fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
+ fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date=getdate()))
self.filters = frappe._dict(
{
"company": frappe.defaults.get_user_default("Company"),
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
index 023ff225eea..c84b843f1fd 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
@@ -10,6 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
Deferred_Revenue_and_Expense_Report,
)
+from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.stock.doctype.item.test_item import create_item
@@ -116,7 +117,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
pda.submit()
# execute report
- fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
+ fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
self.filters = frappe._dict(
{
"company": frappe.defaults.get_user_default("Company"),
@@ -209,7 +210,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
pda.submit()
# execute report
- fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
+ fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
self.filters = frappe._dict(
{
"company": frappe.defaults.get_user_default("Company"),
@@ -297,7 +298,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
pda.submit()
# execute report
- fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
+ fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
self.filters = frappe._dict(
{
"company": frappe.defaults.get_user_default("Company"),
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
index ea05a35b259..9d416db4fdd 100644
--- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
@@ -18,7 +18,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 8a47e1c011b..db9609debe6 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -416,6 +416,7 @@ def set_gl_entries_by_account(
filters,
gl_entries_by_account,
ignore_closing_entries=False,
+ ignore_opening_entries=False,
):
"""Returns a dict like { "account": [gl entries], ... }"""
gl_entries = []
@@ -426,7 +427,6 @@ def set_gl_entries_by_account(
pluck="name",
)
- ignore_opening_entries = False
if accounts_list:
# For balance sheet
if not from_date:
@@ -462,7 +462,7 @@ def set_gl_entries_by_account(
)
if filters and filters.get("presentation_currency"):
- convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get("company"))
+ convert_to_presentation_currency(gl_entries, get_currency(filters))
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry)
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index d47e3da3139..d7af167e381 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -204,7 +204,7 @@ def get_gl_entries(filters, accounting_dimensions):
)
if filters.get("presentation_currency"):
- return convert_to_presentation_currency(gl_entries, currency_map, filters.get("company"))
+ return convert_to_presentation_currency(gl_entries, currency_map)
else:
return gl_entries
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
index 8dc5ab36dd9..92cf36ebc52 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
@@ -12,14 +12,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.financial_statements);
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
- {
- "fieldname": "project",
- "label": __("Project"),
- "fieldtype": "MultiSelectList",
- get_data: function(txt) {
- return frappe.db.get_link_options('Project', txt);
- }
- },
{
"fieldname": "accumulated_values",
"label": __("Accumulated Values"),
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index 6fdb2f337c0..050e6bc5d2f 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -15,20 +15,21 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
get_group_by_conditions,
get_tax_accounts,
)
+from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None):
return _execute(filters)
-def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
+def _execute(filters=None, additional_table_columns=None):
if not filters:
filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = erpnext.get_company_currency(filters.company)
- item_list = get_items(filters, additional_query_columns)
+ item_list = get_items(filters, get_query_columns(additional_table_columns))
aii_account_map = get_aii_accounts()
if item_list:
itemised_tax, tax_columns = get_tax_accounts(
@@ -79,28 +80,20 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
"posting_date": d.posting_date,
"supplier": d.supplier,
"supplier_name": d.supplier_name,
+ **get_values_for_columns(additional_table_columns, d),
+ "credit_to": d.credit_to,
+ "mode_of_payment": d.mode_of_payment,
+ "project": d.project,
+ "company": d.company,
+ "purchase_order": d.purchase_order,
+ "purchase_receipt": purchase_receipt,
+ "expense_account": expense_account,
+ "stock_qty": d.stock_qty,
+ "stock_uom": d.stock_uom,
+ "rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
+ "amount": d.base_net_amount,
}
- if additional_query_columns:
- for col in additional_query_columns:
- row.update({col: d.get(col)})
-
- row.update(
- {
- "credit_to": d.credit_to,
- "mode_of_payment": d.mode_of_payment,
- "project": d.project,
- "company": d.company,
- "purchase_order": d.purchase_order,
- "purchase_receipt": purchase_receipt,
- "expense_account": expense_account,
- "stock_qty": d.stock_qty,
- "stock_uom": d.stock_uom,
- "rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
- "amount": d.base_net_amount,
- }
- )
-
total_tax = 0
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
@@ -317,11 +310,6 @@ def get_conditions(filters):
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
- if additional_query_columns:
- additional_query_columns = ", " + ", ".join(additional_query_columns)
- else:
- additional_query_columns = ""
-
return frappe.db.sql(
"""
select
@@ -340,11 +328,10 @@ def get_items(filters, additional_query_columns):
from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
- `tabPurchase Invoice`.docstatus = 1 %s
+ `tabPurchase Invoice`.docstatus = 1 {1}
""".format(
- additional_query_columns
- )
- % (conditions),
+ additional_query_columns, conditions
+ ),
filters,
as_dict=1,
)
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index bd7d02e0430..4d24dd90762 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -9,6 +9,7 @@ from frappe.utils import cstr, flt
from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
+from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import (
get_customer_details,
)
@@ -18,19 +19,14 @@ def execute(filters=None):
return _execute(filters)
-def _execute(
- filters=None,
- additional_table_columns=None,
- additional_query_columns=None,
- additional_conditions=None,
-):
+def _execute(filters=None, additional_table_columns=None, additional_conditions=None):
if not filters:
filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
- item_list = get_items(filters, additional_query_columns, additional_conditions)
+ item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions)
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
@@ -79,30 +75,22 @@ def _execute(
"customer": d.customer,
"customer_name": customer_record.customer_name,
"customer_group": customer_record.customer_group,
+ **get_values_for_columns(additional_table_columns, d),
+ "debit_to": d.debit_to,
+ "mode_of_payment": ", ".join(mode_of_payments.get(d.parent, [])),
+ "territory": d.territory,
+ "project": d.project,
+ "company": d.company,
+ "sales_order": d.sales_order,
+ "delivery_note": d.delivery_note,
+ "income_account": d.unrealized_profit_loss_account
+ if d.is_internal_customer == 1
+ else d.income_account,
+ "cost_center": d.cost_center,
+ "stock_qty": d.stock_qty,
+ "stock_uom": d.stock_uom,
}
- if additional_query_columns:
- for col in additional_query_columns:
- row.update({col: d.get(col)})
-
- row.update(
- {
- "debit_to": d.debit_to,
- "mode_of_payment": ", ".join(mode_of_payments.get(d.parent, [])),
- "territory": d.territory,
- "project": d.project,
- "company": d.company,
- "sales_order": d.sales_order,
- "delivery_note": d.delivery_note,
- "income_account": d.unrealized_profit_loss_account
- if d.is_internal_customer == 1
- else d.income_account,
- "cost_center": d.cost_center,
- "stock_qty": d.stock_qty,
- "stock_uom": d.stock_uom,
- }
- )
-
if d.stock_uom != d.uom and d.stock_qty:
row.update({"rate": (d.base_net_rate * d.qty) / d.stock_qty, "amount": d.base_net_amount})
else:
@@ -394,11 +382,6 @@ def get_group_by_conditions(filters, doctype):
def get_items(filters, additional_query_columns, additional_conditions=None):
conditions = get_conditions(filters, additional_conditions)
- if additional_query_columns:
- additional_query_columns = ", " + ", ".join(additional_query_columns)
- else:
- additional_query_columns = ""
-
return frappe.db.sql(
"""
select
@@ -424,7 +407,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
`tabItem`.name = `tabSales Invoice Item`.`item_code` and
`tabSales Invoice`.docstatus = 1 {1}
""".format(
- additional_query_columns or "", conditions
+ additional_query_columns, conditions
),
filters,
as_dict=1,
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
index 1c461efbcd3..e794f270c2b 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
@@ -9,14 +9,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
- {
- "fieldname": "project",
- "label": __("Project"),
- "fieldtype": "MultiSelectList",
- get_data: function(txt) {
- return frappe.db.get_link_options('Project', txt);
- }
- },
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
index 889ede5a824..6caebd34a2f 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
@@ -25,7 +25,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py
index a05d581207c..69827aca694 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.py
+++ b/erpnext/accounts/report/purchase_register/purchase_register.py
@@ -10,17 +10,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
+from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None):
return _execute(filters)
-def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
+def _execute(filters=None, additional_table_columns=None):
if not filters:
filters = {}
- invoice_list = get_invoices(filters, additional_query_columns)
+ invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
invoice_list, additional_table_columns
)
@@ -47,13 +48,12 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
- row = [inv.name, inv.posting_date, inv.supplier, inv.supplier_name]
-
- if additional_query_columns:
- for col in additional_query_columns:
- row.append(inv.get(col))
-
- row += [
+ row = [
+ inv.name,
+ inv.posting_date,
+ inv.supplier,
+ inv.supplier_name,
+ *get_values_for_columns(additional_table_columns, inv).values(),
supplier_details.get(inv.supplier), # supplier_group
inv.tax_id,
inv.credit_to,
@@ -244,9 +244,6 @@ def get_conditions(filters):
def get_invoices(filters, additional_query_columns):
- if additional_query_columns:
- additional_query_columns = ", " + ", ".join(additional_query_columns)
-
conditions = get_conditions(filters)
return frappe.db.sql(
"""
@@ -255,11 +252,10 @@ def get_invoices(filters, additional_query_columns):
remarks, base_net_total, base_grand_total, outstanding_amount,
mode_of_payment {0}
from `tabPurchase Invoice`
- where docstatus = 1 %s
+ where docstatus = 1 {1}
order by posting_date desc, name desc""".format(
- additional_query_columns or ""
- )
- % conditions,
+ additional_query_columns, conditions
+ ),
filters,
as_dict=1,
)
diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py
index b333901d7b3..291c7d976e4 100644
--- a/erpnext/accounts/report/sales_register/sales_register.py
+++ b/erpnext/accounts/report/sales_register/sales_register.py
@@ -11,17 +11,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
+from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None):
return _execute(filters)
-def _execute(filters, additional_table_columns=None, additional_query_columns=None):
+def _execute(filters, additional_table_columns=None):
if not filters:
filters = frappe._dict({})
- invoice_list = get_invoices(filters, additional_query_columns)
+ invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
invoice_list, additional_table_columns
)
@@ -54,30 +55,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
"posting_date": inv.posting_date,
"customer": inv.customer,
"customer_name": inv.customer_name,
+ **get_values_for_columns(additional_table_columns, inv),
+ "customer_group": inv.get("customer_group"),
+ "territory": inv.get("territory"),
+ "tax_id": inv.get("tax_id"),
+ "receivable_account": inv.debit_to,
+ "mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
+ "project": inv.project,
+ "owner": inv.owner,
+ "remarks": inv.remarks,
+ "sales_order": ", ".join(sales_order),
+ "delivery_note": ", ".join(delivery_note),
+ "cost_center": ", ".join(cost_center),
+ "warehouse": ", ".join(warehouse),
+ "currency": company_currency,
}
- if additional_query_columns:
- for col in additional_query_columns:
- row.update({col: inv.get(col)})
-
- row.update(
- {
- "customer_group": inv.get("customer_group"),
- "territory": inv.get("territory"),
- "tax_id": inv.get("tax_id"),
- "receivable_account": inv.debit_to,
- "mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
- "project": inv.project,
- "owner": inv.owner,
- "remarks": inv.remarks,
- "sales_order": ", ".join(sales_order),
- "delivery_note": ", ".join(delivery_note),
- "cost_center": ", ".join(cost_center),
- "warehouse": ", ".join(warehouse),
- "currency": company_currency,
- }
- )
-
# map income values
base_net_total = 0
for income_acc in income_accounts:
@@ -402,9 +395,6 @@ def get_conditions(filters):
def get_invoices(filters, additional_query_columns):
- if additional_query_columns:
- additional_query_columns = ", " + ", ".join(additional_query_columns)
-
conditions = get_conditions(filters)
return frappe.db.sql(
"""
@@ -413,10 +403,10 @@ def get_invoices(filters, additional_query_columns):
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
is_internal_customer, represents_company, company {0}
from `tabSales Invoice`
- where docstatus = 1 %s order by posting_date desc, name desc""".format(
- additional_query_columns or ""
- )
- % conditions,
+ where docstatus = 1 {1}
+ order by posting_date desc, name desc""".format(
+ additional_query_columns, conditions
+ ),
filters,
as_dict=1,
)
diff --git a/erpnext/accounts/report/share_ledger/share_ledger.py b/erpnext/accounts/report/share_ledger/share_ledger.py
index d6c3bd059f4..629528e5cc7 100644
--- a/erpnext/accounts/report/share_ledger/share_ledger.py
+++ b/erpnext/accounts/report/share_ledger/share_ledger.py
@@ -67,8 +67,9 @@ def get_all_transfers(date, shareholder):
# condition = 'AND company = %(company)s '
return frappe.db.sql(
"""SELECT * FROM `tabShare Transfer`
- WHERE (DATE(date) <= %(date)s AND from_shareholder = %(shareholder)s {condition})
- OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition})
+ WHERE ((DATE(date) <= %(date)s AND from_shareholder = %(shareholder)s {condition})
+ OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition}))
+ AND docstatus = 1
ORDER BY date""".format(
condition=condition
),
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 078b06519f1..e45c3adcb6d 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -17,7 +17,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 22bebb7d19b..5176c31be71 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -17,6 +17,7 @@ from erpnext.accounts.report.financial_statements import (
filter_out_zero_value_rows,
set_gl_entries_by_account,
)
+from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
value_fields = (
"opening_debit",
@@ -116,6 +117,7 @@ def get_data(filters):
filters,
gl_entries_by_account,
ignore_closing_entries=not flt(filters.with_period_closing_entry),
+ ignore_opening_entries=True,
)
calculate_values(accounts, gl_entries_by_account, opening_balances)
@@ -158,6 +160,8 @@ def get_rootwise_opening_balances(filters, report_type):
accounting_dimensions,
period_closing_voucher=last_period_closing_voucher[0].name,
)
+
+ # Report getting generate from the mid of a fiscal year
if getdate(last_period_closing_voucher[0].posting_date) < getdate(
add_days(filters.from_date, -1)
):
@@ -178,8 +182,8 @@ def get_rootwise_opening_balances(filters, report_type):
"opening_credit": 0.0,
},
)
- opening[d.account]["opening_debit"] += flt(d.opening_debit)
- opening[d.account]["opening_credit"] += flt(d.opening_credit)
+ opening[d.account]["opening_debit"] += flt(d.debit)
+ opening[d.account]["opening_credit"] += flt(d.credit)
return opening
@@ -194,8 +198,11 @@ def get_opening_balance(
frappe.qb.from_(closing_balance)
.select(
closing_balance.account,
- Sum(closing_balance.debit).as_("opening_debit"),
- Sum(closing_balance.credit).as_("opening_credit"),
+ closing_balance.account_currency,
+ Sum(closing_balance.debit).as_("debit"),
+ Sum(closing_balance.credit).as_("credit"),
+ Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
+ Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
)
.where(
(closing_balance.company == filters.company)
@@ -216,7 +223,10 @@ def get_opening_balance(
if start_date:
opening_balance = opening_balance.where(closing_balance.posting_date >= start_date)
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
- opening_balance = opening_balance.where(closing_balance.posting_date < filters.from_date)
+ else:
+ opening_balance = opening_balance.where(
+ (closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
+ )
if (
not filters.show_unclosed_fy_pl_balances
@@ -282,6 +292,9 @@ def get_opening_balance(
gle = opening_balance.run(as_dict=1)
+ if filters and filters.get("presentation_currency"):
+ convert_to_presentation_currency(gle, get_currency(filters))
+
return gle
diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js
index 0e93035a35d..0f7578cdc17 100644
--- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js
+++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js
@@ -16,7 +16,7 @@ frappe.query_reports["Trial Balance for Party"] = {
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index 97cc1c4a130..7ea1fac1056 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -1,5 +1,5 @@
import frappe
-from frappe.utils import flt, formatdate, get_datetime_str
+from frappe.utils import flt, formatdate, get_datetime_str, get_table_name
from erpnext import get_company_currency, get_default_company
from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_date
@@ -78,7 +78,7 @@ def get_rate_as_at(date, from_currency, to_currency):
return rate
-def convert_to_presentation_currency(gl_entries, currency_info, company):
+def convert_to_presentation_currency(gl_entries, currency_info):
"""
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
in `currency_info`.
@@ -93,7 +93,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
for entry in gl_entries:
- account = entry["account"]
debit = flt(entry["debit"])
credit = flt(entry["credit"])
debit_in_account_currency = flt(entry["debit_in_account_currency"])
@@ -151,3 +150,32 @@ def get_invoiced_item_gross_margin(
result = sum(d.gross_profit for d in result)
return result
+
+
+def get_query_columns(report_columns):
+ if not report_columns:
+ return ""
+
+ columns = []
+ for column in report_columns:
+ fieldname = column["fieldname"]
+
+ if doctype := column.get("_doctype"):
+ columns.append(f"`{get_table_name(doctype)}`.`{fieldname}`")
+ else:
+ columns.append(fieldname)
+
+ return ", " + ", ".join(columns)
+
+
+def get_values_for_columns(report_columns, report_row):
+ values = {}
+
+ if not report_columns:
+ return values
+
+ for column in report_columns:
+ fieldname = column["fieldname"]
+ values[fieldname] = report_row.get(fieldname)
+
+ return values
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index a5cb3247627..4b54483bc0c 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -470,6 +470,9 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n
gl_map = doc.build_gl_map()
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
+ if voucher_type == "Payment Entry":
+ doc.make_advance_gl_entries()
+
# Only update outstanding for newly linked vouchers
for entry in entries:
update_voucher_outstanding(
@@ -490,50 +493,53 @@ def check_if_advance_entry_modified(args):
ret = None
if args.voucher_type == "Journal Entry":
- ret = frappe.db.sql(
- """
- select t2.{dr_or_cr} from `tabJournal Entry` t1, `tabJournal Entry Account` t2
- where t1.name = t2.parent and t2.account = %(account)s
- and t2.party_type = %(party_type)s and t2.party = %(party)s
- and (t2.reference_type is null or t2.reference_type in ('', 'Sales Order', 'Purchase Order'))
- and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
- and t1.docstatus=1 """.format(
- dr_or_cr=args.get("dr_or_cr")
- ),
- args,
+ journal_entry = frappe.qb.DocType("Journal Entry")
+ journal_acc = frappe.qb.DocType("Journal Entry Account")
+
+ q = (
+ frappe.qb.from_(journal_entry)
+ .inner_join(journal_acc)
+ .on(journal_entry.name == journal_acc.parent)
+ .select(journal_acc[args.get("dr_or_cr")])
+ .where(
+ (journal_acc.account == args.get("account"))
+ & ((journal_acc.party_type == args.get("party_type")))
+ & ((journal_acc.party == args.get("party")))
+ & (
+ (journal_acc.reference_type.isnull())
+ | (journal_acc.reference_type.isin(["", "Sales Order", "Purchase Order"]))
+ )
+ & ((journal_entry.name == args.get("voucher_no")))
+ & ((journal_acc.name == args.get("voucher_detail_no")))
+ & ((journal_entry.docstatus == 1))
+ )
)
+
else:
- party_account_field = (
- "paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to"
+ payment_entry = frappe.qb.DocType("Payment Entry")
+ payment_ref = frappe.qb.DocType("Payment Entry Reference")
+
+ q = (
+ frappe.qb.from_(payment_entry)
+ .select(payment_entry.name)
+ .where(payment_entry.name == args.get("voucher_no"))
+ .where(payment_entry.docstatus == 1)
+ .where(payment_entry.party_type == args.get("party_type"))
+ .where(payment_entry.party == args.get("party"))
)
if args.voucher_detail_no:
- ret = frappe.db.sql(
- """select t1.name
- from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
- where
- t1.name = t2.parent and t1.docstatus = 1
- and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
- and t1.party_type = %(party_type)s and t1.party = %(party)s and t1.{0} = %(account)s
- and t2.reference_doctype in ('', 'Sales Order', 'Purchase Order')
- and t2.allocated_amount = %(unreconciled_amount)s
- """.format(
- party_account_field
- ),
- args,
+ q = (
+ q.inner_join(payment_ref)
+ .on(payment_entry.name == payment_ref.parent)
+ .where(payment_ref.name == args.get("voucher_detail_no"))
+ .where(payment_ref.reference_doctype.isin(("", "Sales Order", "Purchase Order")))
+ .where(payment_ref.allocated_amount == args.get("unreconciled_amount"))
)
else:
- ret = frappe.db.sql(
- """select name from `tabPayment Entry`
- where
- name = %(voucher_no)s and docstatus = 1
- and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
- and unallocated_amount = %(unreconciled_amount)s
- """.format(
- party_account_field
- ),
- args,
- )
+ q = q.where(payment_entry.unallocated_amount == args.get("unreconciled_amount"))
+
+ ret = q.run(as_dict=True)
if not ret:
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
@@ -612,6 +618,7 @@ def update_reference_in_payment_entry(
if not d.exchange_gain_loss
else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
+ "account": d.account,
}
if d.voucher_detail_no:
@@ -724,6 +731,7 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
try:
pe_doc = frappe.get_doc("Payment Entry", pe)
pe_doc.set_amounts()
+ pe_doc.make_advance_gl_entries(against_voucher_type=ref_type, against_voucher=ref_no, cancel=1)
pe_doc.clear_unallocated_reference_document_rows()
pe_doc.validate_payment_type_with_outstanding()
except Exception as e:
@@ -842,7 +850,7 @@ def get_held_invoices(party_type, party):
if party_type == "Supplier":
held_invoices = frappe.db.sql(
- "select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()",
+ "select name from `tabPurchase Invoice` where on_hold = 1 and release_date IS NOT NULL and release_date > CURDATE()",
as_dict=1,
)
held_invoices = set(d["name"] for d in held_invoices)
@@ -915,6 +923,7 @@ def get_outstanding_invoices(
"outstanding_amount": outstanding_amount,
"due_date": d.due_date,
"currency": d.currency,
+ "account": d.account,
}
)
)
@@ -1101,6 +1110,11 @@ def get_autoname_with_number(number_value, doc_title, company):
return " - ".join(parts)
+def parse_naming_series_variable(doc, variable):
+ if variable == "FY":
+ return get_fiscal_year(date=doc.get("posting_date"), company=doc.get("company"))[0]
+
+
@frappe.whitelist()
def get_coa(doctype, parent, is_root, chart=None):
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
@@ -1399,6 +1413,50 @@ def check_and_delete_linked_reports(report):
frappe.delete_doc("Desktop Icon", icon)
+def create_err_and_its_journals(companies: list = None) -> None:
+ if companies:
+ for company in companies:
+ err = frappe.new_doc("Exchange Rate Revaluation")
+ err.company = company.name
+ err.posting_date = nowdate()
+ err.rounding_loss_allowance = 0.0
+
+ err.fetch_and_calculate_accounts_data()
+ if err.accounts:
+ err.save().submit()
+ response = err.make_jv_entries()
+
+ if company.submit_err_jv:
+ jv = response.get("revaluation_jv", None)
+ jv and frappe.get_doc("Journal Entry", jv).submit()
+ jv = response.get("zero_balance_jv", None)
+ jv and frappe.get_doc("Journal Entry", jv).submit()
+
+
+def auto_create_exchange_rate_revaluation_daily() -> None:
+ """
+ Executed by background job
+ """
+ companies = frappe.db.get_all(
+ "Company",
+ filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"},
+ fields=["name", "submit_err_jv"],
+ )
+ create_err_and_its_journals(companies)
+
+
+def auto_create_exchange_rate_revaluation_weekly() -> None:
+ """
+ Executed by background job
+ """
+ companies = frappe.db.get_all(
+ "Company",
+ filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"},
+ fields=["name", "submit_err_jv"],
+ )
+ create_err_and_its_journals(companies)
+
+
def get_payment_ledger_entries(gl_entries, cancel=0):
ple_map = []
if gl_entries:
@@ -1453,6 +1511,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
due_date=gle.due_date,
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
+ voucher_detail_no=gle.voucher_detail_no,
against_voucher_type=gle.against_voucher_type
if gle.against_voucher_type
else gle.voucher_type,
@@ -1474,7 +1533,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
def create_payment_ledger_entry(
- gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0
+ gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
):
if gl_entries:
ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel)
@@ -1484,7 +1543,7 @@ def create_payment_ledger_entry(
ple = frappe.get_doc(entry)
if cancel:
- delink_original_entry(ple)
+ delink_original_entry(ple, partial_cancel=partial_cancel)
ple.flags.ignore_permissions = 1
ple.flags.adv_adj = adv_adj
@@ -1531,7 +1590,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
ref_doc.set_status(update=True)
-def delink_original_entry(pl_entry):
+def delink_original_entry(pl_entry, partial_cancel=False):
if pl_entry:
ple = qb.DocType("Payment Ledger Entry")
query = (
@@ -1551,6 +1610,10 @@ def delink_original_entry(pl_entry):
& (ple.against_voucher_no == pl_entry.against_voucher_no)
)
)
+
+ if partial_cancel:
+ query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no)
+
query.run()
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index 2260bcad761..c27ede29d1b 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -5,7 +5,7 @@
"label": "Profit and Loss"
}
],
- "content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 15:41:59.515192",
"custom_blocks": [],
"docstatus": 0,
@@ -1061,7 +1061,7 @@
"type": "Link"
}
],
- "modified": "2023-05-30 13:23:29.316711",
+ "modified": "2023-07-04 14:32:15.842044",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@@ -1074,6 +1074,13 @@
"roles": [],
"sequence_id": 2.0,
"shortcuts": [
+ {
+ "color": "Grey",
+ "doc_view": "List",
+ "label": "Learn Accounting",
+ "type": "URL",
+ "url": "https://frappe.school/courses/erpnext-accounting?utm_source=in_app"
+ },
{
"label": "Chart of Accounts",
"link_to": "Account",
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 259568a24b1..e1431eae176 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -40,6 +40,7 @@ def post_depreciation_entries(date=None):
date = today()
failed_asset_names = []
+ error_log_names = []
for asset_name in get_depreciable_assets(date):
asset_doc = frappe.get_doc("Asset", asset_name)
@@ -50,10 +51,12 @@ def post_depreciation_entries(date=None):
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
+ error_log = frappe.log_error(e)
+ error_log_names.append(error_log.name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
- notify_depr_entry_posting_error(failed_asset_names)
+ notify_depr_entry_posting_error(failed_asset_names, error_log_names)
frappe.db.commit()
@@ -239,7 +242,7 @@ def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
-def notify_depr_entry_posting_error(failed_asset_names):
+def notify_depr_entry_posting_error(failed_asset_names, error_log_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
@@ -247,7 +250,8 @@ def notify_depr_entry_posting_error(failed_asset_names):
subject = _("Error while posting depreciation entries")
- asset_links = get_comma_separated_asset_links(failed_asset_names)
+ asset_links = get_comma_separated_links(failed_asset_names, "Asset")
+ error_log_links = get_comma_separated_links(error_log_names, "Error Log")
message = (
_("Hello,")
@@ -257,23 +261,26 @@ def notify_depr_entry_posting_error(failed_asset_names):
)
+ "."
+ "
"
- + _(
- "Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
+ + _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
+ error_log_links
)
+ + "."
+ + "
"
+ + _("Please share this email with your support team so that they can find and fix the issue.")
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
-def get_comma_separated_asset_links(asset_names):
- asset_links = []
+def get_comma_separated_links(names, doctype):
+ links = []
- for asset_name in asset_names:
- asset_links.append(get_link_to_form("Asset", asset_name))
+ for name in names:
+ links.append(get_link_to_form(doctype, name))
- asset_links = ", ".join(asset_links)
+ links = ", ".join(links)
- return asset_links
+ return links
@frappe.whitelist()
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js
index f9c600731b3..4ccc3f8013b 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.js
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.js
@@ -63,7 +63,7 @@ frappe.ui.form.on('Asset Movement', {
fieldnames_to_be_altered = {
target_location: { read_only: 0, reqd: 1 },
source_location: { read_only: 1, reqd: 0 },
- from_employee: { read_only: 0, reqd: 1 },
+ from_employee: { read_only: 0, reqd: 0 },
to_employee: { read_only: 1, reqd: 0 }
};
}
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index b58ca10482b..22055dcb736 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -62,29 +62,20 @@ class AssetMovement(Document):
frappe.throw(_("Source and Target Location cannot be same"))
if self.purpose == "Receipt":
- # only when asset is bought and first entry is made
- if not d.source_location and not (d.target_location or d.to_employee):
+ if not (d.source_location or d.from_employee) and not (d.target_location or d.to_employee):
frappe.throw(
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
)
- elif d.source_location:
- # when asset is received from an employee
- if d.target_location and not d.from_employee:
- frappe.throw(
- _("From employee is required while receiving Asset {0} to a target location").format(
- d.asset
- )
- )
- if d.from_employee and not d.target_location:
- frappe.throw(
- _("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
- )
- if d.to_employee and d.target_location:
- frappe.throw(
- _(
- "Asset {0} cannot be received at a location and given to employee in a single movement"
- ).format(d.asset)
- )
+ elif d.from_employee and not d.target_location:
+ frappe.throw(
+ _("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
+ )
+ elif d.to_employee and d.target_location:
+ frappe.throw(
+ _(
+ "Asset {0} cannot be received at a location and given to an employee in a single movement"
+ ).format(d.asset)
+ )
def validate_employee(self):
for d in self.assets:
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
index b788a32d6ab..48b17f58fb2 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
@@ -82,7 +82,7 @@ frappe.query_reports["Fixed Asset Register"] = {
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
@@ -90,7 +90,7 @@ frappe.query_reports["Fixed Asset Register"] = {
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index a536578b2eb..372ca56b86b 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -8,7 +8,7 @@ frappe.ui.form.on("Supplier", {
frm.set_value("represents_company", "");
}
frm.set_query('account', 'accounts', function (doc, cdt, cdn) {
- var d = locals[cdt][cdn];
+ let d = locals[cdt][cdn];
return {
filters: {
'account_type': 'Payable',
@@ -17,6 +17,19 @@ frappe.ui.form.on("Supplier", {
}
}
});
+
+ frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) {
+ let d = locals[cdt][cdn];
+ return {
+ filters: {
+ "account_type": "Payable",
+ "root_type": "Asset",
+ "company": d.company,
+ "is_group": 0
+ }
+ }
+ });
+
frm.set_query("default_bank_account", function() {
return {
filters: {
@@ -53,8 +66,6 @@ frappe.ui.form.on("Supplier", {
},
refresh: function (frm) {
- frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
-
if (frappe.defaults.get_default("supp_master_name") != "Naming Series") {
frm.toggle_display("naming_series", false);
} else {
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index b3b6185e355..a07af7124e5 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -53,6 +53,7 @@
"primary_address",
"accounting_tab",
"payment_terms",
+ "default_accounts_section",
"accounts",
"settings_tab",
"allow_purchase_invoice_creation_without_purchase_order",
@@ -449,6 +450,11 @@
"fieldname": "column_break_59",
"fieldtype": "Column Break"
},
+ {
+ "fieldname": "default_accounts_section",
+ "fieldtype": "Section Break",
+ "label": "Default Accounts"
+ },
{
"fieldname": "portal_users_tab",
"fieldtype": "Tab Break",
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
index 486bf23e909..58da8512951 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
@@ -329,6 +329,11 @@ def make_default_records():
"variable_label": "Total Shipments",
"path": "get_total_shipments",
},
+ {
+ "param_name": "total_ordered",
+ "variable_label": "Total Ordered",
+ "path": "get_ordered_qty",
+ },
]
install_standing_docs = [
{
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
index fb8819eaf81..4080d1fde09 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
@@ -7,6 +7,7 @@ import sys
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.query_builder.functions import Sum
from frappe.utils import getdate
@@ -422,6 +423,23 @@ def get_total_shipments(scorecard):
return data
+def get_ordered_qty(scorecard):
+ """Returns the total number of ordered quantity (based on Purchase Orders)"""
+
+ po = frappe.qb.DocType("Purchase Order")
+
+ return (
+ frappe.qb.from_(po)
+ .select(Sum(po.total_qty))
+ .where(
+ (po.supplier == scorecard.supplier)
+ & (po.docstatus == 1)
+ & (po.transaction_date >= scorecard.get("start_date"))
+ & (po.transaction_date <= scorecard.get("end_date"))
+ )
+ ).run(as_list=True)[0][0] or 0
+
+
def get_rfq_total_number(scorecard):
"""Gets the total number of RFQs sent to supplier"""
supplier = frappe.get_doc("Supplier", scorecard.supplier)
diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json
index 58c8f747106..1394fc48d5b 100644
--- a/erpnext/buying/workspace/buying/buying.json
+++ b/erpnext/buying/workspace/buying/buying.json
@@ -5,7 +5,7 @@
"label": "Purchase Order Trends"
}
],
- "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Buying\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]",
+ "content": "[{\"id\":\"I3JijHOxil\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Buying\",\"col\":12}},{\"id\":\"j3dJGo8Ok6\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"id\":\"oN7lXSwQji\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"Ivw1PI_wEJ\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"RrWFEi4kCf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"RFIakryyJP\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"id\":\"bM10abFmf6\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order\",\"col\":3}},{\"id\":\"lR0Hw_37Pu\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Analytics\",\"col\":3}},{\"id\":\"_HN0Ljw1lX\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order Analysis\",\"col\":3}},{\"id\":\"kuLuiMRdnX\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"tQFeiKptW2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Procurement\",\"col\":3}},{\"id\":\"0NiuFE_EGS\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"Xe2GVLOq8J\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"QwqyG6XuUt\",\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"id\":\"bTPjOxC_N_\",\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"id\":\"87ht0HIneb\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"EDOsBOmwgw\",\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"id\":\"oWNNIiNb2i\",\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"id\":\"7F_13-ihHB\",\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"id\":\"pfwiLvionl\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"id\":\"8ySDy6s4qn\",\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]",
"creation": "2020-01-28 11:50:26.195467",
"custom_blocks": [],
"docstatus": 0,
@@ -511,7 +511,7 @@
"type": "Link"
}
],
- "modified": "2023-05-24 14:47:20.535772",
+ "modified": "2023-07-04 14:43:30.387683",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
@@ -532,6 +532,13 @@
"stats_filter": "{\n \"disabled\": 0\n}",
"type": "DocType"
},
+ {
+ "color": "Grey",
+ "doc_view": "List",
+ "label": "Learn Procurement",
+ "type": "URL",
+ "url": "https://frappe.school/courses/procurement?utm_source=in_app"
+ },
{
"color": "Yellow",
"format": "{} Pending",
diff --git a/erpnext/communication/doctype/communication_medium/communication_medium.json b/erpnext/communication/doctype/communication_medium/communication_medium.json
index 1e1fe3bf499..b6b9c7e4347 100644
--- a/erpnext/communication/doctype/communication_medium/communication_medium.json
+++ b/erpnext/communication/doctype/communication_medium/communication_medium.json
@@ -61,7 +61,7 @@
"fieldname": "communication_channel",
"fieldtype": "Select",
"label": "Communication Channel",
- "options": "\nExotel"
+ "options": ""
}
],
"links": [],
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index c83e28d78f0..4193b5327d3 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -7,6 +7,7 @@ import json
import frappe
from frappe import _, bold, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
+from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import (
add_days,
@@ -755,6 +756,7 @@ class AccountsController(TransactionBase):
"party": None,
"project": self.get("project"),
"post_net_value": args.get("post_net_value"),
+ "voucher_detail_no": args.get("voucher_detail_no"),
}
)
@@ -858,7 +860,6 @@ class AccountsController(TransactionBase):
amount = self.get("base_rounded_total") or self.base_grand_total
else:
amount = self.get("rounded_total") or self.grand_total
-
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount)
@@ -872,25 +873,31 @@ class AccountsController(TransactionBase):
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
}
+ if d.get("paid_from"):
+ advance_row["account"] = d.paid_from
+ if d.get("paid_to"):
+ advance_row["account"] = d.paid_to
self.append("advances", advance_row)
def get_advance_entries(self, include_unallocated=True):
if self.doctype == "Sales Invoice":
- party_account = self.debit_to
party_type = "Customer"
party = self.customer
amount_field = "credit_in_account_currency"
order_field = "sales_order"
order_doctype = "Sales Order"
else:
- party_account = self.credit_to
party_type = "Supplier"
party = self.supplier
amount_field = "debit_in_account_currency"
order_field = "purchase_order"
order_doctype = "Purchase Order"
+ 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)))
journal_entries = get_advance_journal_entries(
@@ -2140,45 +2147,46 @@ def get_advance_journal_entries(
order_list,
include_unallocated=True,
):
- dr_or_cr = (
- "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
+ journal_entry = frappe.qb.DocType("Journal Entry")
+ journal_acc = frappe.qb.DocType("Journal Entry Account")
+ q = (
+ frappe.qb.from_(journal_entry)
+ .inner_join(journal_acc)
+ .on(journal_entry.name == journal_acc.parent)
+ .select(
+ ConstantColumn("Journal Entry").as_("reference_type"),
+ (journal_entry.name).as_("reference_name"),
+ (journal_entry.remark).as_("remarks"),
+ (journal_acc[amount_field]).as_("amount"),
+ (journal_acc.name).as_("reference_row"),
+ (journal_acc.reference_name).as_("against_order"),
+ (journal_acc.exchange_rate),
+ )
+ .where(
+ journal_acc.account.isin(party_account)
+ & (journal_acc.party_type == party_type)
+ & (journal_acc.party == party)
+ & (journal_acc.is_advance == "Yes")
+ & (journal_entry.docstatus == 1)
+ )
)
+ if party_type == "Customer":
+ q = q.where(journal_acc.credit_in_account_currency > 0)
+
+ else:
+ q = q.where(journal_acc.debit_in_account_currency > 0)
- conditions = []
if include_unallocated:
- conditions.append("ifnull(t2.reference_name, '')=''")
+ q = q.where((journal_acc.reference_name.isnull()) | (journal_acc.reference_name == ""))
if order_list:
- order_condition = ", ".join(["%s"] * len(order_list))
- conditions.append(
- " (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))".format(
- order_doctype, order_condition
- )
+ q = q.where(
+ (journal_acc.reference_type == order_doctype) & ((journal_acc.reference_type).isin(order_list))
)
- reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
-
- # nosemgrep
- journal_entries = frappe.db.sql(
- """
- select
- 'Journal Entry' as reference_type, t1.name as reference_name,
- t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
- t2.reference_name as against_order, t2.exchange_rate
- from
- `tabJournal Entry` t1, `tabJournal Entry Account` t2
- where
- t1.name = t2.parent and t2.account = %s
- and t2.party_type = %s and t2.party = %s
- and t2.is_advance = 'Yes' and t1.docstatus = 1
- and {1} > 0 {2}
- order by t1.posting_date""".format(
- amount_field, dr_or_cr, reference_condition
- ),
- [party_account, party_type, party] + order_list,
- as_dict=1,
- )
+ q = q.orderby(journal_entry.posting_date)
+ journal_entries = q.run(as_dict=True)
return list(journal_entries)
@@ -2193,65 +2201,131 @@ def get_advance_payment_entries(
limit=None,
condition=None,
):
- party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
- currency_field = (
- "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
- )
- payment_type = "Receive" if party_type == "Customer" else "Pay"
- exchange_rate_field = (
- "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
- )
- payment_entries_against_order, unallocated_payment_entries = [], []
- limit_cond = "limit %s" % limit if limit else ""
+ payment_entries = []
+ payment_entry = frappe.qb.DocType("Payment Entry")
if order_list or against_all_orders:
+ q = get_common_query(
+ party_type,
+ party,
+ party_account,
+ limit,
+ condition,
+ )
+ payment_ref = frappe.qb.DocType("Payment Entry Reference")
+
+ q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent)
+ q = q.select(
+ (payment_ref.allocated_amount).as_("amount"),
+ (payment_ref.name).as_("reference_row"),
+ (payment_ref.reference_name).as_("against_order"),
+ )
+
+ q = q.where(payment_ref.reference_doctype == order_doctype)
if order_list:
- reference_condition = " and t2.reference_name in ({0})".format(
- ", ".join(["%s"] * len(order_list))
+ q = q.where(payment_ref.reference_name.isin(order_list))
+
+ allocated = list(q.run(as_dict=True))
+ payment_entries += allocated
+ if include_unallocated:
+ q = get_common_query(
+ party_type,
+ party,
+ party_account,
+ limit,
+ condition,
+ )
+ q = q.select((payment_entry.unallocated_amount).as_("amount"))
+ q = q.where(payment_entry.unallocated_amount > 0)
+
+ unallocated = list(q.run(as_dict=True))
+ payment_entries += unallocated
+ return payment_entries
+
+
+def get_common_query(
+ party_type,
+ party,
+ party_account,
+ limit,
+ condition,
+):
+ payment_type = "Receive" if party_type == "Customer" else "Pay"
+ payment_entry = frappe.qb.DocType("Payment Entry")
+
+ q = (
+ frappe.qb.from_(payment_entry)
+ .select(
+ ConstantColumn("Payment Entry").as_("reference_type"),
+ (payment_entry.name).as_("reference_name"),
+ payment_entry.posting_date,
+ (payment_entry.remarks).as_("remarks"),
+ )
+ .where(payment_entry.payment_type == payment_type)
+ .where(payment_entry.party_type == party_type)
+ .where(payment_entry.party == party)
+ .where(payment_entry.docstatus == 1)
+ )
+
+ if party_type == "Customer":
+ q = q.select((payment_entry.paid_from_account_currency).as_("currency"))
+ q = q.select(payment_entry.paid_from)
+ q = q.where(payment_entry.paid_from.isin(party_account))
+ else:
+ q = q.select((payment_entry.paid_to_account_currency).as_("currency"))
+ q = q.select(payment_entry.paid_to)
+ q = q.where(payment_entry.paid_to.isin(party_account))
+
+ if payment_type == "Receive":
+ q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate"))
+ else:
+ q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate"))
+
+ if condition:
+ q = q.where(payment_entry.company == condition["company"])
+ q = (
+ q.where(payment_entry.posting_date >= condition["from_payment_date"])
+ if condition.get("from_payment_date")
+ else q
+ )
+ q = (
+ q.where(payment_entry.posting_date <= condition["to_payment_date"])
+ if condition.get("to_payment_date")
+ else q
+ )
+ if condition.get("get_payments") == True:
+ q = (
+ q.where(payment_entry.cost_center == condition["cost_center"])
+ if condition.get("cost_center")
+ else q
+ )
+ q = (
+ q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"])
+ if condition.get("minimum_payment_amount")
+ else q
+ )
+ q = (
+ q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"])
+ if condition.get("maximum_payment_amount")
+ else q
)
else:
- reference_condition = ""
- order_list = []
+ q = (
+ q.where(payment_entry.total_debit >= condition["minimum_payment_amount"])
+ if condition.get("minimum_payment_amount")
+ else q
+ )
+ q = (
+ q.where(payment_entry.total_debit <= condition["maximum_payment_amount"])
+ if condition.get("maximum_payment_amount")
+ else q
+ )
- payment_entries_against_order = frappe.db.sql(
- """
- select
- 'Payment Entry' as reference_type, t1.name as reference_name,
- t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
- t2.reference_name as against_order, t1.posting_date,
- t1.{0} as currency, t1.{4} as exchange_rate
- from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
- where
- t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
- and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
- and t2.reference_doctype = %s {2}
- order by t1.posting_date {3}
- """.format(
- currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field
- ),
- [party_account, payment_type, party_type, party, order_doctype] + order_list,
- as_dict=1,
- )
+ q = q.orderby(payment_entry.posting_date)
+ q = q.limit(limit) if limit else q
- if include_unallocated:
- unallocated_payment_entries = frappe.db.sql(
- """
- select 'Payment Entry' as reference_type, name as reference_name, posting_date,
- remarks, unallocated_amount as amount, {2} as exchange_rate, {3} as currency
- from `tabPayment Entry`
- where
- {0} = %s and party_type = %s and party = %s and payment_type = %s
- and docstatus = 1 and unallocated_amount > 0 {condition}
- order by posting_date {1}
- """.format(
- party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or ""
- ),
- (party_account, party_type, party, payment_type),
- as_dict=1,
- )
-
- return list(payment_entries_against_order) + list(unallocated_payment_entries)
+ return q
def update_invoice_status():
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index fec494a84c7..7b7c53ecfe1 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -437,18 +437,23 @@ class BuyingController(SubcontractingController):
# validate rate with ref PR
def validate_rejected_warehouse(self):
- for d in self.get("items"):
- if flt(d.rejected_qty) and not d.rejected_warehouse:
+ for item in self.get("items"):
+ if flt(item.rejected_qty) and not item.rejected_warehouse:
if self.rejected_warehouse:
- d.rejected_warehouse = self.rejected_warehouse
+ item.rejected_warehouse = self.rejected_warehouse
- if not d.rejected_warehouse:
+ if not item.rejected_warehouse:
frappe.throw(
- _("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(
- d.idx, d.item_code
+ _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
+ item.idx, item.item_code
)
)
+ if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
+ frappe.throw(
+ _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
+ )
+
# validate accepted and rejected qty
def validate_accepted_rejected_qty(self):
for d in self.get("items"):
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 954668055e1..173e812dbd0 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -669,7 +669,11 @@ def get_filters(
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
- if item_row and item_row.get("warehouse"):
+ if (
+ voucher_type in ["Purchase Receipt", "Purchase Invoice"]
+ and item_row
+ and item_row.get("warehouse")
+ ):
filters["warehouse"] = item_row.get("warehouse")
return filters
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 5137e030582..caf4b6f18bc 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -201,6 +201,12 @@ class StockController(AccountsController):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
+ if not expense_account:
+ frappe.throw(
+ _(
+ "Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer"
+ ).format(frappe.bold(self.company))
+ )
gl_list.append(
self.get_gl_dict(
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index b98a27ede8e..9ac54183a21 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -30,11 +30,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
var me = this;
let doc = this.frm.doc;
erpnext.toggle_naming_series();
- frappe.dynamic_link = {
- doc: doc,
- fieldname: 'name',
- doctype: 'Lead'
- };
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js
index 495ed291ae9..c1a7ff576c1 100644
--- a/erpnext/crm/doctype/prospect/prospect.js
+++ b/erpnext/crm/doctype/prospect/prospect.js
@@ -3,8 +3,6 @@
frappe.ui.form.on('Prospect', {
refresh (frm) {
- frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
-
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
frm.add_custom_button(__("Customer"), function() {
frappe.model.open_mapped_doc({
diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py b/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json
deleted file mode 100644
index 0d42ca8c85d..00000000000
--- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json
+++ /dev/null
@@ -1,89 +0,0 @@
-{
- "actions": [],
- "creation": "2019-05-21 07:41:53.536536",
- "doctype": "DocType",
- "engine": "InnoDB",
- "field_order": [
- "enabled",
- "section_break_2",
- "account_sid",
- "api_key",
- "api_token",
- "section_break_6",
- "map_custom_field_to_doctype",
- "target_doctype"
- ],
- "fields": [
- {
- "default": "0",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "label": "Enabled"
- },
- {
- "depends_on": "enabled",
- "fieldname": "section_break_2",
- "fieldtype": "Section Break",
- "label": "Credentials"
- },
- {
- "fieldname": "account_sid",
- "fieldtype": "Data",
- "label": "Account SID"
- },
- {
- "fieldname": "api_token",
- "fieldtype": "Data",
- "label": "API Token"
- },
- {
- "fieldname": "api_key",
- "fieldtype": "Data",
- "label": "API Key"
- },
- {
- "depends_on": "enabled",
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "label": "Custom Field"
- },
- {
- "default": "0",
- "fieldname": "map_custom_field_to_doctype",
- "fieldtype": "Check",
- "label": "Map Custom Field to DocType"
- },
- {
- "depends_on": "map_custom_field_to_doctype",
- "fieldname": "target_doctype",
- "fieldtype": "Link",
- "label": "Target DocType",
- "mandatory_depends_on": "map_custom_field_to_doctype",
- "options": "DocType"
- }
- ],
- "issingle": 1,
- "links": [],
- "modified": "2022-12-14 17:24:50.176107",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Exotel Settings",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "print": 1,
- "read": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "sort_field": "modified",
- "sort_order": "ASC",
- "states": [],
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py
deleted file mode 100644
index 4879cb56239..00000000000
--- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-import requests
-from frappe import _
-from frappe.model.document import Document
-
-
-class ExotelSettings(Document):
- def validate(self):
- self.verify_credentials()
-
- def verify_credentials(self):
- if self.enabled:
- response = requests.get(
- "https://api.exotel.com/v1/Accounts/{sid}".format(sid=self.account_sid),
- auth=(self.api_key, self.api_token),
- )
- if response.status_code != 200:
- frappe.throw(_("Invalid credentials"))
diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py
deleted file mode 100644
index 0d40667e32a..00000000000
--- a/erpnext/erpnext_integrations/exotel_integration.py
+++ /dev/null
@@ -1,151 +0,0 @@
-import frappe
-import requests
-
-# api/method/erpnext.erpnext_integrations.exotel_integration.handle_incoming_call
-# api/method/erpnext.erpnext_integrations.exotel_integration.handle_end_call
-# api/method/erpnext.erpnext_integrations.exotel_integration.handle_missed_call
-
-
-@frappe.whitelist(allow_guest=True)
-def handle_incoming_call(**kwargs):
- try:
- exotel_settings = get_exotel_settings()
- if not exotel_settings.enabled:
- return
-
- call_payload = kwargs
- status = call_payload.get("Status")
- if status == "free":
- return
-
- call_log = get_call_log(call_payload)
- if not call_log:
- create_call_log(call_payload)
- else:
- update_call_log(call_payload, call_log=call_log)
- except Exception as e:
- frappe.db.rollback()
- exotel_settings.log_error("Error in Exotel incoming call")
- frappe.db.commit()
-
-
-@frappe.whitelist(allow_guest=True)
-def handle_end_call(**kwargs):
- update_call_log(kwargs, "Completed")
-
-
-@frappe.whitelist(allow_guest=True)
-def handle_missed_call(**kwargs):
- status = ""
- call_type = kwargs.get("CallType")
- dial_call_status = kwargs.get("DialCallStatus")
-
- if call_type == "incomplete" and dial_call_status == "no-answer":
- status = "No Answer"
- elif call_type == "client-hangup" and dial_call_status == "canceled":
- status = "Canceled"
- elif call_type == "incomplete" and dial_call_status == "failed":
- status = "Failed"
-
- update_call_log(kwargs, status)
-
-
-def update_call_log(call_payload, status="Ringing", call_log=None):
- call_log = call_log or get_call_log(call_payload)
-
- # for a new sid, call_log and get_call_log will be empty so create a new log
- if not call_log:
- call_log = create_call_log(call_payload)
- if call_log:
- call_log.status = status
- call_log.to = call_payload.get("DialWhomNumber")
- call_log.duration = call_payload.get("DialCallDuration") or 0
- call_log.recording_url = call_payload.get("RecordingUrl")
- call_log.save(ignore_permissions=True)
- frappe.db.commit()
- return call_log
-
-
-def get_call_log(call_payload):
- call_log_id = call_payload.get("CallSid")
- if frappe.db.exists("Call Log", call_log_id):
- return frappe.get_doc("Call Log", call_log_id)
-
-
-def map_custom_field(call_payload, call_log):
- field_value = call_payload.get("CustomField")
-
- if not field_value:
- return call_log
-
- settings = get_exotel_settings()
- target_doctype = settings.target_doctype
- mapping_enabled = settings.map_custom_field_to_doctype
-
- if not mapping_enabled or not target_doctype:
- return call_log
-
- call_log.append("links", {"link_doctype": target_doctype, "link_name": field_value})
-
- return call_log
-
-
-def create_call_log(call_payload):
- call_log = frappe.new_doc("Call Log")
- call_log.id = call_payload.get("CallSid")
- call_log.to = call_payload.get("DialWhomNumber")
- call_log.medium = call_payload.get("To")
- call_log.status = "Ringing"
- setattr(call_log, "from", call_payload.get("CallFrom"))
- map_custom_field(call_payload, call_log)
- call_log.save(ignore_permissions=True)
- frappe.db.commit()
- return call_log
-
-
-@frappe.whitelist()
-def get_call_status(call_id):
- endpoint = get_exotel_endpoint("Calls/{call_id}.json".format(call_id=call_id))
- response = requests.get(endpoint)
- status = response.json().get("Call", {}).get("Status")
- return status
-
-
-@frappe.whitelist()
-def make_a_call(from_number, to_number, caller_id, **kwargs):
- endpoint = get_exotel_endpoint("Calls/connect.json?details=true")
- response = requests.post(
- endpoint, data={"From": from_number, "To": to_number, "CallerId": caller_id, **kwargs}
- )
-
- return response.json()
-
-
-def get_exotel_settings():
- return frappe.get_single("Exotel Settings")
-
-
-def whitelist_numbers(numbers, caller_id):
- endpoint = get_exotel_endpoint("CustomerWhitelist")
- response = requests.post(
- endpoint,
- data={
- "VirtualNumber": caller_id,
- "Number": numbers,
- },
- )
-
- return response
-
-
-def get_all_exophones():
- endpoint = get_exotel_endpoint("IncomingPhoneNumbers")
- response = requests.post(endpoint)
- return response
-
-
-def get_exotel_endpoint(action):
- settings = get_exotel_settings()
- return "https://{api_key}:{api_token}@api.exotel.com/v1/Accounts/{sid}/{action}".format(
- api_key=settings.api_key, api_token=settings.api_token, sid=settings.account_sid, action=action
- )
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
index ccc46b7a220..5c4be6ffaa2 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
@@ -230,17 +230,6 @@
"onboard": 0,
"type": "Card Break"
},
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Exotel Settings",
- "link_count": 0,
- "link_to": "Exotel Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
{
"hidden": 0,
"is_query_report": 0,
@@ -252,7 +241,7 @@
"type": "Link"
}
],
- "modified": "2023-05-24 14:47:25.984717",
+ "modified": "2023-05-24 14:47:26.984717",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index a6d939e74f8..66f3de459b5 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -354,6 +354,11 @@ doc_events = {
},
}
+# function should expect the variable and doc as arguments
+naming_series_variables = {
+ "FY": "erpnext.accounts.utils.parse_naming_series_variable",
+}
+
# On cancel event Payment Entry will be exempted and all linked submittable doctype will get cancelled.
# to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled.
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
@@ -415,6 +420,10 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
+ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily",
+ ],
+ "weekly": [
+ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly",
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",
@@ -607,3 +616,8 @@ global_search_doctypes = {
additional_timeline_content = {
"*": ["erpnext.telephony.doctype.call_log.call_log.get_linked_call_logs"]
}
+
+
+extend_bootinfo = [
+ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
+]
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 6dc1ff6a49f..a988badd744 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -621,7 +621,7 @@ class ProductionPlan(Document):
def create_work_order(self, item):
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
- if item.get("qty") <= 0:
+ if flt(item.get("qty")) <= 0:
return
wo = frappe.new_doc("Work Order")
@@ -697,10 +697,9 @@ class ProductionPlan(Document):
material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values")
+ material_request.save()
if self.get("submit_material_request"):
material_request.submit()
- else:
- material_request.save()
frappe.flags.mute_messages = False
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 79b1e798ede..7c15bf9234b 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -1026,7 +1026,7 @@ class WorkOrder(Document):
consumed_qty = frappe.db.sql(
"""
SELECT
- SUM(qty)
+ SUM(detail.qty)
FROM
`tabStock Entry` entry,
`tabStock Entry Detail` detail
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
index 782ce8110a8..a874f224820 100644
--- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
@@ -17,7 +17,7 @@ frappe.query_reports["Job Card Summary"] = {
label: __("Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
- default: frappe.defaults.get_user_default("fiscal_year"),
+ default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
reqd: 1,
on_change: function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index d862c349e3d..518ae14659e 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"PIQJYZOMnD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Manufacturing\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 17:11:37.032604",
"custom_blocks": [],
"docstatus": 0,
@@ -316,7 +316,7 @@
"type": "Link"
}
],
- "modified": "2023-05-27 16:41:04.776115",
+ "modified": "2023-07-04 14:40:47.281125",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
@@ -329,6 +329,13 @@
"roles": [],
"sequence_id": 8.0,
"shortcuts": [
+ {
+ "color": "Grey",
+ "doc_view": "List",
+ "label": "Learn Manufacturing",
+ "type": "URL",
+ "url": "https://frappe.school/courses/manufacturing?utm_source=in_app"
+ },
{
"color": "Grey",
"doc_view": "List",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index dc056635ed2..73e0a95da93 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -317,7 +317,7 @@ erpnext.patches.v13_0.update_docs_link
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
-erpnext.patches.v14_0.update_closing_balances #17-05-2023
+erpnext.patches.v14_0.update_closing_balances #14-07-2023
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
# below migration patches should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
@@ -331,6 +331,8 @@ execute:frappe.delete_doc('DocType', 'Cash Flow Mapper', ignore_missing=True)
execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Template', ignore_missing=True)
execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True)
erpnext.patches.v14_0.cleanup_workspaces
-erpnext.patches.v15_0.remove_loan_management_module
+erpnext.patches.v15_0.remove_loan_management_module #2023-07-03
erpnext.patches.v14_0.set_report_in_process_SOA
-erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
\ No newline at end of file
+erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
+execute:frappe.defaults.clear_default("fiscal_year")
+erpnext.patches.v15_0.remove_exotel_integration
diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py
index d66467775c8..2947b98740b 100644
--- a/erpnext/patches/v14_0/update_closing_balances.py
+++ b/erpnext/patches/v14_0/update_closing_balances.py
@@ -13,56 +13,63 @@ from erpnext.accounts.utils import get_fiscal_year
def execute():
frappe.db.truncate("Account Closing Balance")
- i = 0
- company_wise_order = {}
- for pcv in frappe.db.get_all(
- "Period Closing Voucher",
- fields=["company", "posting_date", "name"],
- filters={"docstatus": 1},
- order_by="posting_date",
- ):
+ for company in frappe.get_all("Company", pluck="name"):
+ i = 0
+ company_wise_order = {}
+ for pcv in frappe.db.get_all(
+ "Period Closing Voucher",
+ fields=["company", "posting_date", "name"],
+ filters={"docstatus": 1, "company": company},
+ order_by="posting_date",
+ ):
- company_wise_order.setdefault(pcv.company, [])
- if pcv.posting_date not in company_wise_order[pcv.company]:
- pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name)
- pcv_doc.year_start_date = get_fiscal_year(
- pcv.posting_date, pcv.fiscal_year, company=pcv.company
- )[1]
+ company_wise_order.setdefault(pcv.company, [])
+ if pcv.posting_date not in company_wise_order[pcv.company]:
+ pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name)
+ pcv_doc.year_start_date = get_fiscal_year(
+ pcv.posting_date, pcv.fiscal_year, company=pcv.company
+ )[1]
- # get gl entries against pcv
- gl_entries = frappe.db.get_all(
- "GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"]
- )
- for entry in gl_entries:
- entry["is_period_closing_voucher_entry"] = 1
- entry["closing_date"] = pcv_doc.posting_date
- entry["period_closing_voucher"] = pcv_doc.name
-
- # get all gl entries for the year
- closing_entries = frappe.db.get_all(
- "GL Entry",
- filters={
- "is_cancelled": 0,
- "voucher_no": ["!=", pcv.name],
- "posting_date": ["between", [pcv_doc.year_start_date, pcv.posting_date]],
- "is_opening": "No",
- },
- fields=["*"],
- )
-
- if i == 0:
- # add opening entries only for the first pcv
- closing_entries += frappe.db.get_all(
- "GL Entry",
- filters={"is_cancelled": 0, "is_opening": "Yes"},
- fields=["*"],
+ # get gl entries against pcv
+ gl_entries = frappe.db.get_all(
+ "GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"]
)
+ for entry in gl_entries:
+ entry["is_period_closing_voucher_entry"] = 1
+ entry["closing_date"] = pcv_doc.posting_date
+ entry["period_closing_voucher"] = pcv_doc.name
- for entry in closing_entries:
- entry["closing_date"] = pcv_doc.posting_date
- entry["period_closing_voucher"] = pcv_doc.name
+ closing_entries = []
- make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
- company_wise_order[pcv.company].append(pcv.posting_date)
+ if pcv.posting_date not in company_wise_order[pcv.company]:
+ # get all gl entries for the year
+ closing_entries = frappe.db.get_all(
+ "GL Entry",
+ filters={
+ "is_cancelled": 0,
+ "voucher_no": ["!=", pcv.name],
+ "posting_date": ["between", [pcv_doc.year_start_date, pcv.posting_date]],
+ "is_opening": "No",
+ "company": company,
+ },
+ fields=["*"],
+ )
- i += 1
+ if i == 0:
+ # add opening entries only for the first pcv
+ closing_entries += frappe.db.get_all(
+ "GL Entry",
+ filters={"is_cancelled": 0, "is_opening": "Yes", "company": company},
+ fields=["*"],
+ )
+
+ for entry in closing_entries:
+ entry["closing_date"] = pcv_doc.posting_date
+ entry["period_closing_voucher"] = pcv_doc.name
+
+ entries = gl_entries + closing_entries
+
+ if entries:
+ make_closing_entries(entries, voucher_name=pcv.name)
+ i += 1
+ company_wise_order[pcv.company].append(pcv.posting_date)
diff --git a/erpnext/patches/v15_0/remove_exotel_integration.py b/erpnext/patches/v15_0/remove_exotel_integration.py
new file mode 100644
index 00000000000..a37773f3375
--- /dev/null
+++ b/erpnext/patches/v15_0/remove_exotel_integration.py
@@ -0,0 +1,37 @@
+from contextlib import suppress
+
+import click
+import frappe
+from frappe import _
+from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
+from frappe.utils.user import get_system_managers
+
+SETTINGS_DOCTYPE = "Exotel Settings"
+
+
+def execute():
+ if "exotel_integration" in frappe.get_installed_apps():
+ return
+
+ with suppress(Exception):
+ exotel = frappe.get_doc(SETTINGS_DOCTYPE)
+ if exotel.enabled:
+ notify_existing_users()
+
+ frappe.delete_doc("DocType", SETTINGS_DOCTYPE)
+
+
+def notify_existing_users():
+ click.secho(
+ "Exotel integration is moved to a separate app and will be removed from ERPNext in version-15.\n"
+ "Please install the app to continue using the integration: https://github.com/frappe/exotel_integration",
+ fg="yellow",
+ )
+
+ notification = {
+ "subject": _(
+ "WARNING: Exotel app has been separated from ERPNext, please install the app to continue using Exotel integration."
+ ),
+ "type": "Alert",
+ }
+ make_notification_logs(notification, get_system_managers(only_name=True))
diff --git a/erpnext/patches/v15_0/remove_loan_management_module.py b/erpnext/patches/v15_0/remove_loan_management_module.py
index 6f08c361bac..8242f9cce53 100644
--- a/erpnext/patches/v15_0/remove_loan_management_module.py
+++ b/erpnext/patches/v15_0/remove_loan_management_module.py
@@ -7,7 +7,7 @@ def execute():
frappe.delete_doc("Module Def", "Loan Management", ignore_missing=True, force=True)
- frappe.delete_doc("Workspace", "Loan Management", ignore_missing=True, force=True)
+ frappe.delete_doc("Workspace", "Loans", ignore_missing=True, force=True)
print_formats = frappe.get_all(
"Print Format", {"module": "Loan Management", "standard": "Yes"}, pluck="name"
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index f007430ab37..502ee574159 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -289,7 +289,8 @@
"fieldtype": "Link",
"label": "Company",
"options": "Company",
- "remember_last_selected_value": 1
+ "remember_last_selected_value": 1,
+ "reqd": 1
},
{
"fieldname": "column_break_28",
diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json
index 50730bac0db..94ae9c04a40 100644
--- a/erpnext/projects/workspace/projects/projects.json
+++ b/erpnext/projects/workspace/projects/projects.json
@@ -5,7 +5,7 @@
"label": "Open Projects"
}
],
- "content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Task\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Timesheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project Billing Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Projects\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Time Tracking\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"id\":\"VDMms0hapk\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"id\":\"7Mbx6I5JUf\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"nyuMo9byw7\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"dILbX_r0ve\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Task\",\"col\":3}},{\"id\":\"JT8ntrqRiJ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project\",\"col\":3}},{\"id\":\"RsafDhm1MS\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Timesheet\",\"col\":3}},{\"id\":\"cVJH-gD0CR\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project Billing Summary\",\"col\":3}},{\"id\":\"DbctrdmAy1\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"jx5aPK9aXN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Project Management\",\"col\":3}},{\"id\":\"ncIHWGQQvX\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"oGhjvYjfv-\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"TdsgJyG3EI\",\"type\":\"card\",\"data\":{\"card_name\":\"Projects\",\"col\":4}},{\"id\":\"nIc0iyvf1T\",\"type\":\"card\",\"data\":{\"card_name\":\"Time Tracking\",\"col\":4}},{\"id\":\"8G1if4jsQ7\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"o7qTNRXZI8\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 15:46:04.874669",
"custom_blocks": [],
"docstatus": 0,
@@ -192,7 +192,7 @@
"type": "Link"
}
],
- "modified": "2023-05-24 14:47:23.179860",
+ "modified": "2023-07-04 14:39:08.935853",
"modified_by": "Administrator",
"module": "Projects",
"name": "Projects",
@@ -205,6 +205,13 @@
"roles": [],
"sequence_id": 11.0,
"shortcuts": [
+ {
+ "color": "Grey",
+ "doc_view": "List",
+ "label": "Learn Project Management",
+ "type": "URL",
+ "url": "https://frappe.school/courses/project-management?utm_source=in_app"
+ },
{
"color": "Blue",
"format": "{} Assigned",
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 0d92683f217..543d0e97908 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -193,7 +193,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.frm.set_query("expense_account", "items", function(doc) {
return {
filters: {
- "company": doc.company
+ "company": doc.company,
+ "report_type": "Profit and Loss",
+ "is_group": 0
}
};
});
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index b0082bdb281..959cf507d53 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -56,7 +56,7 @@ erpnext.financial_statements = {
// dropdown for links to other financial statements
erpnext.financial_statements.filters = get_filters()
- let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+ let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
@@ -137,7 +137,7 @@ function get_filters() {
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
},
@@ -146,7 +146,7 @@ function get_filters() {
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
+ "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
},
@@ -182,6 +182,16 @@ function get_filters() {
company: frappe.query_report.get_filter_value("company")
});
}
+ },
+ {
+ "fieldname": "project",
+ "label": __("Project"),
+ "fieldtype": "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Project', txt, {
+ company: frappe.query_report.get_filter_value("company")
+ });
+ },
}
]
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index a859a671b01..8d6097d0a20 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -381,6 +381,23 @@ $.extend(erpnext.utils, {
});
});
});
+ },
+
+ get_fiscal_year: function(date) {
+ let fiscal_year = '';
+ frappe.call({
+ method: "erpnext.accounts.utils.get_fiscal_year",
+ args: {
+ date: date
+ },
+ async: false,
+ callback: function(r) {
+ if (r.message) {
+ fiscal_year = r.message[0];
+ }
+ }
+ });
+ return fiscal_year;
}
});
@@ -632,7 +649,6 @@ erpnext.utils.update_child_items = function(opts) {
fields.splice(3, 0, {
fieldtype: 'Float',
fieldname: "conversion_factor",
- in_list_view: 1,
label: __("Conversion Factor"),
precision: get_precision('conversion_factor')
})
@@ -640,6 +656,7 @@ erpnext.utils.update_child_items = function(opts) {
new frappe.ui.Dialog({
title: __("Update Items"),
+ size: "extra-large",
fields: [
{
fieldname: "trans_items",
@@ -854,95 +871,87 @@ $(document).on('app_ready', function() {
// Show SLA dashboard
$(document).on('app_ready', function() {
- frappe.call({
- method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_sla_doctypes',
- callback: function(r) {
- if (!r.message)
- return;
+ $.each(frappe.boot.service_level_agreement_doctypes, function(_i, d) {
+ frappe.ui.form.on(d, {
+ onload: function(frm) {
+ if (!frm.doc.service_level_agreement)
+ return;
- $.each(r.message, function(_i, d) {
- frappe.ui.form.on(d, {
- onload: function(frm) {
- if (!frm.doc.service_level_agreement)
- return;
-
- frappe.call({
- method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
- args: {
- doctype: frm.doc.doctype,
- name: frm.doc.service_level_agreement,
- customer: frm.doc.customer
- },
- callback: function (r) {
- if (r && r.message) {
- frm.set_query('priority', function() {
- return {
- filters: {
- 'name': ['in', r.message.priority],
- }
- };
- });
- frm.set_query('service_level_agreement', function() {
- return {
- filters: {
- 'name': ['in', r.message.service_level_agreements],
- }
- };
- });
- }
- }
- });
+ frappe.call({
+ method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
+ args: {
+ doctype: frm.doc.doctype,
+ name: frm.doc.service_level_agreement,
+ customer: frm.doc.customer
},
-
- refresh: function(frm) {
- if (frm.doc.status !== 'Closed' && frm.doc.service_level_agreement
- && ['First Response Due', 'Resolution Due'].includes(frm.doc.agreement_status)) {
- frappe.call({
- 'method': 'frappe.client.get',
- args: {
- doctype: 'Service Level Agreement',
- name: frm.doc.service_level_agreement
- },
- callback: function(data) {
- let statuses = data.message.pause_sla_on;
- const hold_statuses = [];
- $.each(statuses, (_i, entry) => {
- hold_statuses.push(entry.status);
- });
- if (hold_statuses.includes(frm.doc.status)) {
- frm.dashboard.clear_headline();
- let message = {'indicator': 'orange', 'msg': __('SLA is on hold since {0}', [moment(frm.doc.on_hold_since).fromNow(true)])};
- frm.dashboard.set_headline_alert(
- '