diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml
index ef38974ae2..ee60bad104 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 0000000000..e765a66f69
--- /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 45e04ee6b0..fb49ef3a42 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 58b91227f7..9fb47bb02d 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 81ff6a52db..15c84d462f 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 35d606ba3a..6667193a54 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 0647a5ccf3..d961ead642 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 598db642f3..3b5698b118 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 7b2575d2c3..42c0e51238 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 bc77dac1cd..508b2eaf2a 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 9d1b99b29b..0dfe569ec9 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 b42d712d88..87f0ad1048 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 73b1911543..e7aca79d08 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 5077305cf2..dafcf56abd 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 69330577ab..7e345d84ea 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 bac84db231..0701435dfc 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 6224d4038d..d7b6a198df 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 712023fdf9..dcd7295bae 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 ae2625b653..70cc4b3d34 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 3003c68196..12aa0b520e 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 22842cec0f..9cf2ac6c2a 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 89fa15172f..2adc1238b7 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 18d3485085..5f6c7034ed 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 216d4eccac..25d94c55d3 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 cced37589b..32e267f33c 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 bf393c0d29..4b2fcec757 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 f842a16b74..0fce61f1e7 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 5a0aeb7284..263621dcf4 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 0c18f5edb5..d8759e95b8 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 45bddfc096..8c96480478 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 9fcbf5c633..4db531eac9 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 deb202d145..4afc4512ff 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 68407e0221..8753ebc3ba 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 3781f8ccc9..61e5219c80 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 784bdf6612..0280c3590c 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 f92b57a45e..0ae85d9000 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 c6f101e7f3..544d417a0e 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 a929ff17b0..f1dad875fa 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 07b865e66c..03cf82a2b0 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 e3127b346f..7e0bdea80c 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 dd965a9813..d58fd95a84 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 a6447549e6..6e39ee9944 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 0056b9e8f5..96e0c844ca 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 3e11643776..cad5325c6e 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 023ff225ee..c84b843f1f 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 ea05a35b25..9d416db4fd 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 8a47e1c011..db9609debe 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 d47e3da313..d7af167e38 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 8dc5ab36dd..92cf36ebc5 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 6fdb2f337c..050e6bc5d2 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 bd7d02e043..4d24dd9076 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 1c461efbcd..e794f270c2 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 889ede5a82..6caebd34a2 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 a05d581207..69827aca69 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 b333901d7b..291c7d976e 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 d6c3bd059f..629528e5cc 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 078b06519f..e45c3adcb6 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 22bebb7d19..5176c31be7 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 0e93035a35..0f7578cdc1 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 97cc1c4a13..7ea1fac105 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 a5cb324762..4b54483bc0 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 2260bcad76..c27ede29d1 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 259568a24b..e1431eae17 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 f9c600731b..4ccc3f8013 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 b58ca10482..22055dcb73 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 b788a32d6a..48b17f58fb 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 a536578b2e..372ca56b86 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 b3b6185e35..a07af7124e 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 486bf23e90..58da851295 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 fb8819eaf8..4080d1fde0 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 58c8f74710..1394fc48d5 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 1e1fe3bf49..b6b9c7e434 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 c83e28d78f..4193b5327d 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 fec494a84c..7b7c53ecfe 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 954668055e..173e812dbd 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 5137e03058..caf4b6f18b 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 b98a27ede8..9ac54183a2 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 495ed291ae..c1a7ff576c 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 e69de29bb2..0000000000
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 0d42ca8c85..0000000000
--- 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 4879cb5623..0000000000
--- 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 0d40667e32..0000000000
--- 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 ccc46b7a22..5c4be6ffaa 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 a6d939e74f..66f3de459b 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 6dc1ff6a49..a988badd74 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 79b1e798ed..7c15bf9234 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 782ce8110a..a874f22482 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 d862c349e3..518ae14659 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 dc056635ed..73e0a95da9 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 d66467775c..2947b98740 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 0000000000..a37773f337
--- /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 6f08c361ba..8242f9cce5 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 f007430ab3..502ee57415 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 50730bac0d..94ae9c04a4 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 0d92683f21..543d0e9790 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 b0082bdb28..959cf507d5 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 a859a671b0..8d6097d0a2 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(
- '