Merge branch 'develop' into payments-based-dunning

This commit is contained in:
Marica 2023-07-17 12:33:19 +05:30 committed by GitHub
commit 1c1e7380e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
116 changed files with 2604 additions and 2907 deletions

View File

@ -9,7 +9,7 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
release: stable-release:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -30,3 +30,23 @@ jobs:
head: version-${{ matrix.version }}-hotfix head: version-${{ matrix.version }}-hotfix
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 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 }}

38
.github/workflows/release_notes.yml vendored Normal file
View File

@ -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 }}

View File

@ -136,7 +136,7 @@ def convert_deferred_revenue_to_income(
send_mail(deferred_process) 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: if not posting_date:
posting_date = add_days(today(), -1) 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" "deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
) )
prev_gl_entry = frappe.db.sql( 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 select name, posting_date from `tabGL Entry` where company=%s and account=%s and
and is_cancelled = 0 voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
order by posting_date desc limit 1 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, (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
) as_dict=True,
)
prev_gl_via_je = frappe.db.sql( prev_gl_via_je = frappe.db.sql(
""" """
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c 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 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_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1 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), (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True, as_dict=True,
) )
if prev_gl_via_je: if prev_gl_via_je:
if (not prev_gl_entry) or ( 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 and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
): ):
prev_gl_entry = prev_gl_via_je 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: else:
start_date = item.service_start_date start_date = getdate(add_days(prev_posting_date, 1))
end_date = get_last_day(start_date) end_date = get_last_day(start_date)
if end_date >= item.service_end_date: if end_date >= item.service_end_date:
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") accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
def _book_deferred_revenue_or_expense( 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): if not (start_date and end_date):
return return
@ -377,9 +386,12 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if not amount: if not amount:
return return
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate: # check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto): 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: if via_journal_entry:
book_revenue_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, debit_account,
amount, amount,
base_amount, base_amount,
end_date, gl_posting_date,
project, project,
account_currency, account_currency,
item.cost_center, item.cost_center,
@ -404,7 +416,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
against, against,
amount, amount,
base_amount, base_amount,
end_date, gl_posting_date,
project, project,
account_currency, account_currency,
item.cost_center, 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: if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense( _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( via_journal_entry = cint(

View File

@ -2,75 +2,13 @@
"country_code": "nl", "country_code": "nl",
"name": "Netherlands - Grootboekschema", "name": "Netherlands - Grootboekschema",
"tree": { "tree": {
"FABRIKAGEREKENINGEN": {
"is_group": 1,
"root_type": "Expense"
},
"FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": { "FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": {
"Bank": { "Bank": {
"RABO Bank": { "RABO Bank": {
"account_type": "Bank" "account_type": "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": { "LIQUIDE MIDDELEN": {
"ABN-AMRO bank": {}, "ABN-AMRO bank": {},
"Bankbetaalkaarten": {}, "Bankbetaalkaarten": {},
@ -91,6 +29,110 @@
}, },
"account_type": "Cash" "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": { "VORDERINGEN": {
"Debiteuren": { "Debiteuren": {
"account_type": "Receivable" "account_type": "Receivable"
@ -104,278 +146,299 @@
"Voorziening dubieuze debiteuren": {} "Voorziening dubieuze debiteuren": {}
}, },
"root_type": "Asset" "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, "is_group": 1,
"root_type": "Expense" "root_type": "Expense",
}, "INDIRECTE KOSTEN": {
"KOSTENREKENINGEN": { "is_group": 1,
"AFSCHRIJVINGEN": { "root_type": "Expense"
"Aanhangwagens": {}, },
"Aankoopkosten": {}, "KOSTENREKENINGEN": {
"Aanloopkosten": {}, "AFSCHRIJVINGEN": {
"Auteursrechten": {}, "Aanhangwagens": {},
"Bedrijfsgebouwen": {}, "Aankoopkosten": {},
"Bedrijfsinventaris": { "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" "account_type": "Depreciation"
}, },
"Drankvergunningen": {}, "ALGEMENE KOSTEN": {
"Fabrieksinventaris": { "Accountantskosten": {},
"account_type": "Depreciation" "Advieskosten": {},
"Assuranties 1": {},
"Bankkosten": {},
"Juridische kosten": {},
"Overige algemene kosten": {},
"Toev. Ass. eigen risico": {}
}, },
"Gebouwen": {}, "BEDRIJFSKOSTEN": {
"Gereedschappen": {}, "Assuranties 2": {},
"Goodwill": {}, "Energie (krachtstroom)": {},
"Grondverbetering": {}, "Gereedschappen 1": {},
"Heftrucks": {}, "Hulpmaterialen 1": {},
"Kantine-inventaris": {}, "Huur inventaris": {},
"Kantoorinventaris": { "Huur machines": {},
"account_type": "Depreciation" "Leasing invent.operational": {},
"Leasing mach. operational": {},
"Onderhoud inventaris": {},
"Onderhoud machines": {},
"Ophalen/vervoer afval": {},
"Overige bedrijfskosten": {}
}, },
"Kantoormachines": {}, "FINANCIERINGSKOSTEN 1": {
"Licenties": {}, "Overige rentebaten": {},
"Machines 1": {}, "Overige rentelasten": {},
"Magazijninventaris": {}, "Rente bankkrediet": {},
"Octrooien": {}, "Rente huurkoopcontracten": {},
"Ontwikkelingskosten": {}, "Rente hypotheek": {},
"Pachtersinvestering": {}, "Rente leasecontracten": {},
"Parkeerplaats": {}, "Rente lening o/g": {},
"Personenauto's": { "Rente lening u/g": {}
"account_type": "Depreciation"
}, },
"Rijwielen en bromfietsen": {}, "HUISVESTINGSKOSTEN": {
"Tonnagevergunningen": {}, "Assurantie onroerend goed": {},
"Verbouwingen": {}, "Belastingen onr. Goed": {},
"Vergunningen": {}, "Energiekosten": {},
"Voorraadverschillen": {}, "Groot onderhoud onr. Goed": {},
"Vrachtauto's": {}, "Huur": {},
"Winkels": {}, "Huurwaarde woongedeelte": {},
"Woon-winkelhuis": {}, "Onderhoud onroerend goed": {},
"account_type": "Depreciation" "Ontvangen huren": {},
}, "Overige huisvestingskosten": {},
"ALGEMENE KOSTEN": { "Pacht": {},
"Accountantskosten": {}, "Schoonmaakkosten": {},
"Advieskosten": {}, "Toevoeging egalisatieres. Groot onderhoud": {}
"Assuranties 1": {}, },
"Bankkosten": {}, "KANTOORKOSTEN": {
"Juridische kosten": {}, "Administratiekosten": {},
"Overige algemene kosten": {}, "Contributies/abonnementen": {},
"Toev. Ass. eigen risico": {} "Huur kantoorapparatuur": {},
}, "Internetaansluiting": {},
"BEDRIJFSKOSTEN": { "Kantoorbenodigdh./drukw.": {},
"Assuranties 2": {}, "Onderhoud kantoorinvent.": {},
"Energie (krachtstroom)": {}, "Overige kantoorkosten": {},
"Gereedschappen 1": {}, "Porti": {},
"Hulpmaterialen 1": {}, "Telefoon/telefax": {}
"Huur inventaris": {}, },
"Huur machines": {}, "OVERIGE BATEN EN LASTEN": {
"Leasing invent.operational": {}, "Betaalde schadevergoed.": {},
"Leasing mach. operational": {}, "Boekverlies vaste activa": {},
"Onderhoud inventaris": {}, "Boekwinst van vaste activa": {},
"Onderhoud machines": {}, "K.O. regeling OB": {},
"Ophalen/vervoer afval": {}, "Kasverschillen": {},
"Overige bedrijfskosten": {} "Kosten loonbelasting": {},
}, "Kosten omzetbelasting": {},
"FINANCIERINGSKOSTEN 1": { "Nadelige koersverschillen": {},
"Overige rentebaten": {}, "Naheffing bedrijfsver.": {},
"Overige rentelasten": {}, "Ontvangen schadevergoed.": {},
"Rente bankkrediet": {}, "Overige baten": {},
"Rente huurkoopcontracten": {}, "Overige lasten": {},
"Rente hypotheek": {}, "Voordelige koersverschil.": {}
"Rente leasecontracten": {}, },
"Rente lening o/g": {}, "PERSONEELSKOSTEN": {
"Rente lening u/g": {} "Autokostenvergoeding": {},
}, "Bedrijfskleding": {},
"HUISVESTINGSKOSTEN": { "Belastingvrije uitkeringen": {},
"Assurantie onroerend goed": {}, "Bijzondere beloningen": {},
"Belastingen onr. Goed": {}, "Congressen, seminars en symposia": {},
"Energiekosten": {}, "Gereedschapsgeld": {},
"Groot onderhoud onr. Goed": {}, "Geschenken personeel": {},
"Huur": {}, "Gratificaties": {},
"Huurwaarde woongedeelte": {}, "Inhouding pensioenpremies": {},
"Onderhoud onroerend goed": {}, "Inhouding sociale lasten": {},
"Ontvangen huren": {}, "Kantinekosten": {},
"Overige huisvestingskosten": {}, "Lonen en salarissen": {},
"Pacht": {}, "Loonwerk": {},
"Schoonmaakkosten": {}, "Managementvergoedingen": {},
"Toevoeging egalisatieres. Groot onderhoud": {} "Opleidingskosten": {},
}, "Oprenting stamrechtverpl.": {},
"KANTOORKOSTEN": { "Overhevelingstoeslag": {},
"Administratiekosten": {}, "Overige kostenverg.": {},
"Contributies/abonnementen": {}, "Overige personeelskosten": {},
"Huur kantoorapparatuur": {}, "Overige uitkeringen": {},
"Internetaansluiting": {}, "Pensioenpremies": {},
"Kantoorbenodigdh./drukw.": {}, "Provisie 1": {},
"Onderhoud kantoorinvent.": {}, "Reiskosten": {},
"Overige kantoorkosten": {}, "Rijwielvergoeding": {},
"Porti": {}, "Sociale lasten": {},
"Telefoon/telefax": {} "Tanti\u00e8mes": {},
}, "Thuiswerkers": {},
"OVERIGE BATEN EN LASTEN": { "Toev. Backservice pens.verpl.": {},
"Betaalde schadevergoed.": {}, "Toevoeging pensioenverpl.": {},
"Boekverlies vaste activa": {}, "Uitkering ziekengeld": {},
"Boekwinst van vaste activa": {}, "Uitzendkrachten": {},
"K.O. regeling OB": {}, "Vakantiebonnen": {},
"Kasverschillen": {}, "Vakantiegeld": {},
"Kosten loonbelasting": {}, "Vergoeding studiekosten": {},
"Kosten omzetbelasting": {}, "Wervingskosten personeel": {}
"Nadelige koersverschillen": {}, },
"Naheffing bedrijfsver.": {}, "VERKOOPKOSTEN": {
"Ontvangen schadevergoed.": {}, "Advertenties": {},
"Overige baten": {}, "Afschrijving dubieuze deb.": {},
"Overige lasten": {}, "Beurskosten": {},
"Voordelige koersverschil.": {} "Etalagekosten": {},
}, "Exportkosten": {},
"PERSONEELSKOSTEN": { "Kascorrecties": {},
"Autokostenvergoeding": {}, "Overige verkoopkosten": {},
"Bedrijfskleding": {}, "Provisie": {},
"Belastingvrije uitkeringen": {}, "Reclame": {},
"Bijzondere beloningen": {}, "Reis en verblijfkosten": {},
"Congressen, seminars en symposia": {}, "Relatiegeschenken": {},
"Gereedschapsgeld": {}, "Representatiekosten": {},
"Geschenken personeel": {}, "Uitgaande vrachten": {},
"Gratificaties": {}, "Veilingkosten": {},
"Inhouding pensioenpremies": {}, "Verpakkingsmateriaal 1": {},
"Inhouding sociale lasten": {}, "Websitekosten": {}
"Kantinekosten": {}, },
"Lonen en salarissen": {}, "VERVOERSKOSTEN": {
"Loonwerk": {}, "Assuranties auto's": {},
"Managementvergoedingen": {}, "Brandstoffen": {},
"Opleidingskosten": {}, "Leasing auto's": {},
"Oprenting stamrechtverpl.": {}, "Onderhoud personenauto's": {},
"Overhevelingstoeslag": {}, "Onderhoud vrachtauto's": {},
"Overige kostenverg.": {}, "Overige vervoerskosten": {},
"Overige personeelskosten": {}, "Priv\u00e9-gebruik auto's": {},
"Overige uitkeringen": {}, "Wegenbelasting": {}
"Pensioenpremies": {}, },
"Provisie 1": {}, "VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
"Reiskosten": {}, "Betalingskort. crediteuren": {},
"Rijwielvergoeding": {}, "Garantiekosten": {},
"Sociale lasten": {}, "Hulpmaterialen": {},
"Tanti\u00e8mes": {}, "Inkomende vrachten": {
"Thuiswerkers": {}, "account_type": "Expenses Included In Valuation"
"Toev. Backservice pens.verpl.": {}, },
"Toevoeging pensioenverpl.": {}, "Inkoop import buiten EU hoog": {},
"Uitkering ziekengeld": {}, "Inkoop import buiten EU laag": {},
"Uitzendkrachten": {}, "Inkoop import buiten EU overig": {},
"Vakantiebonnen": {}, "Inkoopbonussen": {},
"Vakantiegeld": {}, "Inkoopkosten": {},
"Vergoeding studiekosten": {}, "Inkoopprovisie": {},
"Wervingskosten personeel": {} "Inkopen BTW verlegd": {},
}, "Inkopen EU hoog tarief": {},
"VERKOOPKOSTEN": { "Inkopen EU laag tarief": {},
"Advertenties": {}, "Inkopen EU overig": {},
"Afschrijving dubieuze deb.": {}, "Inkopen hoog": {},
"Beurskosten": {}, "Inkopen laag": {},
"Etalagekosten": {}, "Inkopen nul": {},
"Exportkosten": {}, "Inkopen overig": {},
"Kascorrecties": {}, "Invoerkosten": {},
"Overige verkoopkosten": {}, "Kosten inkoopvereniging": {},
"Provisie": {}, "Kostprijs omzet grondstoffen": {
"Reclame": {}, "account_type": "Cost of Goods Sold"
"Reis en verblijfkosten": {}, },
"Relatiegeschenken": {}, "Kostprijs omzet handelsgoederen": {},
"Representatiekosten": {}, "Onttrekking uitgev.garantie": {},
"Uitgaande vrachten": {}, "Priv\u00e9-gebruik goederen": {},
"Veilingkosten": {}, "Stock aanpassing": {
"Verpakkingsmateriaal 1": {}, "account_type": "Stock Adjustment"
"Websitekosten": {} },
}, "Tegenrekening inkoop": {},
"VERVOERSKOSTEN": { "Toev. Voorz. incour. grondst.": {},
"Assuranties auto's": {}, "Toevoeging garantieverpl.": {},
"Brandstoffen": {}, "Toevoeging voorz. incour. handelsgoed.": {},
"Leasing auto's": {}, "Uitbesteed werk": {},
"Onderhoud personenauto's": {}, "Voorz. Incourourant grondst.": {},
"Onderhoud vrachtauto's": {}, "Voorz.incour. handelsgoed.": {},
"Overige vervoerskosten": {}, "root_type": "Expense"
"Priv\u00e9-gebruik auto's": {}, },
"Wegenbelasting": {} "root_type": "Expense"
}, }
"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"
}, },
"VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": { "VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": {
"EIGEN VERMOGEN": { "EIGEN VERMOGEN": {
@ -602,7 +665,7 @@
"account_type": "Equity" "account_type": "Equity"
} }
}, },
"root_type": "Asset" "root_type": "Equity"
}, },
"VERKOOPRESULTATEN": { "VERKOOPRESULTATEN": {
"Diensten fabric. 0% niet-EU": {}, "Diensten fabric. 0% niet-EU": {},
@ -627,67 +690,6 @@
"Verleende Kredietbep. fabricage": {}, "Verleende Kredietbep. fabricage": {},
"Verleende Kredietbep. handel": {}, "Verleende Kredietbep. handel": {},
"root_type": "Income" "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"
} }
} }
} }

View File

@ -271,6 +271,12 @@ def get_dimensions(with_cost_center_and_project=False):
as_dict=1, 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: if with_cost_center_and_project:
dimension_filters.extend( dimension_filters.extend(
[ [

View File

@ -8,9 +8,6 @@ frappe.ui.form.on('Bank', {
}, },
refresh: function(frm) { refresh: function(frm) {
add_fields_to_mapping_table(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); frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) { if (frm.doc.__islocal) {

View File

@ -19,7 +19,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
onload: function (frm) { onload: function (frm) {
// Set default filter dates // 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_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today; frm.doc.bank_statement_to_date = today;
frm.trigger('bank_account'); frm.trigger('bank_account');

View File

@ -93,6 +93,12 @@ class ExchangeRateRevaluation(Document):
return True 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() @frappe.whitelist()
def get_accounts_data(self): def get_accounts_data(self):
self.validate_mandatory() self.validate_mandatory()
@ -252,8 +258,8 @@ class ExchangeRateRevaluation(Document):
new_balance_in_base_currency = 0 new_balance_in_base_currency = 0
new_balance_in_account_currency = 0 new_balance_in_account_currency = 0
current_exchange_rate = calculate_exchange_rate_using_last_gle( current_exchange_rate = (
company, d.account, d.party_type, d.party calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
) )
gain_loss = new_balance_in_account_currency - ( gain_loss = new_balance_in_account_currency - (

View File

@ -8,17 +8,6 @@ frappe.ui.form.on('Fiscal Year', {
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)); 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) { year_start_date: function(frm) {
if (!frm.doc.is_short_year) { if (!frm.doc.is_short_year) {
let year_end_date = let year_end_date =

View File

@ -4,28 +4,12 @@
import frappe import frappe
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from frappe import _, msgprint from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate from frappe.utils import add_days, add_years, cstr, getdate
class FiscalYear(Document): 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): def validate(self):
self.validate_dates() self.validate_dates()
self.validate_overlap() self.validate_overlap()
@ -68,13 +52,6 @@ class FiscalYear(Document):
frappe.cache().delete_value("fiscal_years") frappe.cache().delete_value("fiscal_years")
def on_trash(self): 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") frappe.cache().delete_value("fiscal_years")
def validate_overlap(self): def validate_overlap(self):

View File

@ -35,6 +35,7 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
@ -56,7 +57,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2022-01-18 21:11:23.105589", "modified": "2023-07-09 18:11:23.105589",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Item Tax Template", "name": "Item Tax Template",
@ -102,4 +103,4 @@
"states": [], "states": [],
"title_field": "title", "title_field": "title",
"track_changes": 1 "track_changes": 1
} }

View File

@ -408,6 +408,15 @@ class JournalEntry(AccountsController):
d.idx, d.account d.idx, d.account
) )
) )
elif (
d.party_type
and frappe.db.get_value("Party Type", d.party_type, "account_type") != account_type
):
frappe.throw(
_("Row {0}: Account {1} and Party Type {2} have different account types").format(
d.idx, d.account, d.party_type
)
)
def check_credit_limit(self): def check_credit_limit(self):
customers = list( customers = list(

View File

@ -8,6 +8,7 @@ from functools import reduce
import frappe import frappe
from frappe import ValidationError, _, qb, scrub, throw from frappe import ValidationError, _, qb, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils.data import comma_and, fmt_money
import erpnext import erpnext
from erpnext.accounts.doctype.bank_account.bank_account import ( from erpnext.accounts.doctype.bank_account.bank_account import (
@ -124,13 +125,16 @@ class PaymentEntry(AccountsController):
self.set(self.party_account_field, liability_account) self.set(self.party_account_field, liability_account)
msg = "Book Advance Payments as Liability option is chosen. Paid From account changed from {0} to {1}.".format( frappe.msgprint(
frappe.bold(self.party_account), _(
frappe.bold(liability_account), "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,
) )
frappe.msgprint(_(msg), alert=True)
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = ( self.ignore_linked_doctypes = (
"GL Entry", "GL Entry",
@ -230,17 +234,16 @@ class PaymentEntry(AccountsController):
# The reference has already been fully paid # The reference has already been fully paid
if not latest: if not latest:
frappe.throw( 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 # The reference has already been partly paid
elif ( elif latest.outstanding_amount < latest.invoice_amount and flt(
latest.outstanding_amount < latest.invoice_amount d.outstanding_amount, d.precision("outstanding_amount")
and d.outstanding_amount != latest.outstanding_amount ) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
):
frappe.throw( 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." "{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.") fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
@ -342,7 +345,7 @@ class PaymentEntry(AccountsController):
def validate_party_details(self): def validate_party_details(self):
if self.party: if self.party:
if not frappe.db.exists(self.party_type, 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): def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc) self.set_source_exchange_rate(ref_doc)
@ -391,7 +394,9 @@ class PaymentEntry(AccountsController):
continue continue
if d.reference_doctype not in valid_reference_doctypes: if d.reference_doctype not in valid_reference_doctypes:
frappe.throw( 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: elif d.reference_name:
@ -404,7 +409,7 @@ class PaymentEntry(AccountsController):
if self.party != ref_doc.get(scrub(self.party_type)): if self.party != ref_doc.get(scrub(self.party_type)):
frappe.throw( frappe.throw(
_("{0} {1} is not associated with {2} {3}").format( _("{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: else:
@ -426,18 +431,18 @@ class PaymentEntry(AccountsController):
): ):
frappe.throw( frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format( _("{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"): if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
frappe.throw( frappe.throw(
_("{0} {1} is on hold").format(d.reference_doctype, d.reference_name), _("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
title=_("Invalid Invoice"), title=_("Invalid Purchase Invoice"),
) )
if ref_doc.docstatus != 1: 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): def get_valid_reference_doctypes(self):
if self.party_type == "Customer": if self.party_type == "Customer":
@ -463,14 +468,13 @@ class PaymentEntry(AccountsController):
if outstanding_amount <= 0 and not is_return: if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d) 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( 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( ).format(
_(k), frappe.bold(comma_and((d.reference_name for d in references))),
frappe.bold(", ".join(d.reference_name for d in v)), _(reference_doctype),
frappe.bold(_("negative outstanding amount")),
) )
+ "<br><br>" + "<br><br>"
+ _("If this is undesirable please cancel the corresponding Payment Entry."), + _("If this is undesirable please cancel the corresponding Payment Entry."),
@ -505,7 +509,7 @@ class PaymentEntry(AccountsController):
if not valid: if not valid:
frappe.throw( frappe.throw(
_("Against Journal Entry {0} does not have any unmatched {1} entry").format( _("Against Journal Entry {0} does not have any unmatched {1} entry").format(
d.reference_name, dr_or_cr d.reference_name, _(dr_or_cr)
) )
) )
@ -572,7 +576,7 @@ class PaymentEntry(AccountsController):
if allocated_amount > outstanding: if allocated_amount > outstanding:
frappe.throw( frappe.throw(
_("Row #{0}: Cannot allocate more than {1} against payment term {2}").format( _("Row #{0}: Cannot allocate more than {1} against payment term {2}").format(
idx, outstanding, key[0] idx, fmt_money(outstanding), key[0]
) )
) )
@ -876,7 +880,7 @@ class PaymentEntry(AccountsController):
elif paid_amount - additional_charges > total_negative_outstanding: elif paid_amount - additional_charges > total_negative_outstanding:
frappe.throw( frappe.throw(
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format( _("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
total_negative_outstanding fmt_money(total_negative_outstanding)
), ),
InvalidPaymentEntry, InvalidPaymentEntry,
) )
@ -1428,6 +1432,9 @@ def get_outstanding_reference_documents(args, validate=False):
if args.get("party_type") == "Member": if args.get("party_type") == "Member":
return 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") ple = qb.DocType("Payment Ledger Entry")
common_filter = [] common_filter = []
accounting_dimensions_filter = [] accounting_dimensions_filter = []
@ -1546,7 +1553,7 @@ def get_outstanding_reference_documents(args, validate=False):
_( _(
"No outstanding {0} found for the {1} {2} which qualify the filters you have specified." "No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
).format( ).format(
ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party")) _(ref_document_type), _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
) )
) )
@ -1622,60 +1629,59 @@ def get_orders_to_be_billed(
cost_center=None, cost_center=None,
filters=None, filters=None,
): ):
voucher_type = None
if party_type == "Customer": if party_type == "Customer":
voucher_type = "Sales Order" voucher_type = "Sales Order"
elif party_type == "Supplier": elif party_type == "Supplier":
voucher_type = "Purchase Order" voucher_type = "Purchase Order"
elif party_type == "Employee":
voucher_type = None if not voucher_type:
return []
# Add cost center condition # Add cost center condition
if voucher_type: doc = frappe.get_doc({"doctype": voucher_type})
doc = frappe.get_doc({"doctype": voucher_type}) condition = ""
condition = "" if doc and hasattr(doc, "cost_center") and doc.cost_center:
if doc and hasattr(doc, "cost_center") and doc.cost_center: condition = " and cost_center='%s'" % cost_center
condition = " and cost_center='%s'" % cost_center
orders = [] if party_account_currency == company_currency:
if voucher_type: grand_total_field = "base_grand_total"
if party_account_currency == company_currency: rounded_total_field = "base_rounded_total"
grand_total_field = "base_grand_total" else:
rounded_total_field = "base_rounded_total" grand_total_field = "grand_total"
else: rounded_total_field = "rounded_total"
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
orders = frappe.db.sql( orders = frappe.db.sql(
""" """
select select
name as voucher_no, 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}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount, (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date transaction_date as posting_date
from from
`tab{voucher_type}` `tab{voucher_type}`
where where
{party_type} = %s {party_type} = %s
and docstatus = 1 and docstatus = 1
and company = %s and company = %s
and ifnull(status, "") != "Closed" and ifnull(status, "") != "Closed"
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01 and abs(100 - per_billed) > 0.01
{condition} {condition}
order by order by
transaction_date, name transaction_date, name
""".format( """.format(
**{ **{
"rounded_total_field": rounded_total_field, "rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field, "grand_total_field": grand_total_field,
"voucher_type": voucher_type, "voucher_type": voucher_type,
"party_type": scrub(party_type), "party_type": scrub(party_type),
"condition": condition, "condition": condition,
} }
), ),
(party, company), (party, company),
as_dict=True, as_dict=True,
) )
order_list = [] order_list = []
for d in orders: for d in orders:
@ -1708,6 +1714,8 @@ def get_negative_outstanding_invoices(
cost_center=None, cost_center=None,
condition=None, condition=None,
): ):
if party_type not in ["Customer", "Supplier"]:
return []
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to" account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to"
supplier_condition = "" supplier_condition = ""
@ -1758,7 +1766,7 @@ def get_negative_outstanding_invoices(
def get_party_details(company, party_type, party, date, cost_center=None): def get_party_details(company, party_type, party, date, cost_center=None):
bank_account = "" bank_account = ""
if not frappe.db.exists(party_type, party): 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) party_account = get_party_account(party_type, party, company)
account_currency = get_account_currency(party_account) account_currency = get_account_currency(party_account)
@ -1912,7 +1920,7 @@ def get_payment_entry(
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= ( if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
100.0 + over_billing_allowance 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: if not party_type:
party_type = set_party_type(dt) party_type = set_party_type(dt)

View File

@ -16,8 +16,10 @@ from erpnext.stock.doctype.item.test_item import create_item
class TestProcessDeferredAccounting(unittest.TestCase): class TestProcessDeferredAccounting(unittest.TestCase):
def test_creation_of_ledger_entry_on_submit(self): def test_creation_of_ledger_entry_on_submit(self):
"""test creation of gl entries on submission of document""" """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( deferred_account = create_account(
account_name="Deferred Revenue", account_name="Deferred Revenue for Accounts Frozen",
parent_account="Current Liabilities - _TC", parent_account="Current Liabilities - _TC",
company="_Test Company", company="_Test Company",
) )
@ -29,21 +31,21 @@ class TestProcessDeferredAccounting(unittest.TestCase):
item.save() item.save()
si = create_sales_invoice( 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].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10" si.items[0].service_start_date = "2023-05-01"
si.items[0].service_end_date = "2019-03-15" si.items[0].service_end_date = "2023-07-31"
si.items[0].deferred_revenue_account = deferred_account si.items[0].deferred_revenue_account = deferred_account
si.save() si.save()
si.submit() si.submit()
process_deferred_accounting = frappe.get_doc( process_deferred_accounting = doc = frappe.get_doc(
dict( dict(
doctype="Process Deferred Accounting", doctype="Process Deferred Accounting",
posting_date="2019-01-01", posting_date="2023-07-01",
start_date="2019-01-01", start_date="2023-05-01",
end_date="2019-01-31", end_date="2023-06-30",
type="Income", type="Income",
) )
) )
@ -52,11 +54,16 @@ class TestProcessDeferredAccounting(unittest.TestCase):
process_deferred_accounting.submit() process_deferred_accounting.submit()
expected_gle = [ expected_gle = [
[deferred_account, 33.85, 0.0, "2019-01-31"], ["Debtors - _TC", 3000, 0.0, "2023-07-01"],
["Sales - _TC", 0.0, 33.85, "2019-01-31"], [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-31") check_gl_entries(self, si.name, expected_gle, "2023-07-01")
change_acc_settings()
def test_pda_submission_and_cancellation(self): def test_pda_submission_and_cancellation(self):
pda = frappe.get_doc( pda = frappe.get_doc(
@ -70,3 +77,10 @@ class TestProcessDeferredAccounting(unittest.TestCase):
) )
pda.submit() pda.submit()
pda.cancel() 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()

View File

@ -549,6 +549,7 @@
"depends_on": "update_stock", "depends_on": "update_stock",
"fieldname": "rejected_warehouse", "fieldname": "rejected_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse", "label": "Rejected Warehouse",
"no_copy": 1, "no_copy": 1,
"options": "Warehouse", "options": "Warehouse",
@ -1576,7 +1577,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-06-03 16:21:54.637245", "modified": "2023-07-04 17:22:59.145031",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -423,6 +423,7 @@
{ {
"fieldname": "rejected_warehouse", "fieldname": "rejected_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse", "label": "Rejected Warehouse",
"options": "Warehouse" "options": "Warehouse"
}, },
@ -904,7 +905,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-07-02 18:39:41.495723", "modified": "2023-07-04 17:22:21.501152",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@ -3,8 +3,6 @@
frappe.ui.form.on('Shareholder', { frappe.ui.form.on('Shareholder', {
refresh: function(frm) { refresh: function(frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Shareholder' };
frm.toggle_display(['contact_html'], !frm.doc.__islocal); frm.toggle_display(['contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) { if (frm.doc.__islocal) {

View File

@ -73,7 +73,7 @@ def get_entries(filters):
return sorted( return sorted(
entries, entries,
key=lambda k: k[2] or getdate(nowdate()), key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()),
) )

View File

@ -49,7 +49,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Start Year"), "label": __("Start Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
on_change: () => { on_change: () => {
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) { 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"), "label": __("End Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
on_change: () => { on_change: () => {
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) { 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; return value;
}, },
onload: function() { 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) { frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);

View File

@ -650,7 +650,7 @@ def set_gl_entries_by_account(
if filters and filters.get("presentation_currency") != d.default_currency: if filters and filters.get("presentation_currency") != d.default_currency:
currency_info["company"] = d.name currency_info["company"] = d.name
currency_info["company_currency"] = d.default_currency 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: for entry in gl_entries:
if entry.account_number: if entry.account_number:

View File

@ -48,7 +48,7 @@ function get_filters() {
"label": __("Start Year"), "label": __("Start Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1 "reqd": 1
}, },
{ {
@ -56,7 +56,7 @@ function get_filters() {
"label": __("End Year"), "label": __("End Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1 "reqd": 1
}, },
{ {
@ -100,7 +100,7 @@ frappe.query_reports["Deferred Revenue and Expense"] = {
return default_formatter(value, row, column, data); return default_formatter(value, row, column, data);
}, },
onload: function(report){ 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) { frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);

View File

@ -4,9 +4,10 @@
import frappe import frappe
from frappe import _, qb from frappe import _, qb
from frappe.query_builder import Column, functions 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.report.financial_statements import get_period_list
from erpnext.accounts.utils import get_fiscal_year
class Deferred_Item(object): class Deferred_Item(object):
@ -226,7 +227,7 @@ class Deferred_Revenue_and_Expense_Report(object):
# If no filters are provided, get user defaults # If no filters are provided, get user defaults
if not filters: 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( self.filters = frappe._dict(
{ {
"company": frappe.defaults.get_user_default("Company"), "company": frappe.defaults.get_user_default("Company"),

View File

@ -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 ( from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
Deferred_Revenue_and_Expense_Report, 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.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
@ -116,7 +117,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
pda.submit() pda.submit()
# execute report # 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( self.filters = frappe._dict(
{ {
"company": frappe.defaults.get_user_default("Company"), "company": frappe.defaults.get_user_default("Company"),
@ -209,7 +210,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
pda.submit() pda.submit()
# execute report # 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( self.filters = frappe._dict(
{ {
"company": frappe.defaults.get_user_default("Company"), "company": frappe.defaults.get_user_default("Company"),
@ -297,7 +298,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
pda.submit() pda.submit()
# execute report # 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( self.filters = frappe._dict(
{ {
"company": frappe.defaults.get_user_default("Company"), "company": frappe.defaults.get_user_default("Company"),

View File

@ -18,7 +18,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Fiscal Year"), "label": __("Fiscal Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
"on_change": function(query_report) { "on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year; var fiscal_year = query_report.get_values().fiscal_year;

View File

@ -416,6 +416,7 @@ def set_gl_entries_by_account(
filters, filters,
gl_entries_by_account, gl_entries_by_account,
ignore_closing_entries=False, ignore_closing_entries=False,
ignore_opening_entries=False,
): ):
"""Returns a dict like { "account": [gl entries], ... }""" """Returns a dict like { "account": [gl entries], ... }"""
gl_entries = [] gl_entries = []
@ -426,7 +427,6 @@ def set_gl_entries_by_account(
pluck="name", pluck="name",
) )
ignore_opening_entries = False
if accounts_list: if accounts_list:
# For balance sheet # For balance sheet
if not from_date: if not from_date:
@ -462,7 +462,7 @@ def set_gl_entries_by_account(
) )
if filters and filters.get("presentation_currency"): 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: for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry) gl_entries_by_account.setdefault(entry.account, []).append(entry)

View File

@ -204,7 +204,7 @@ def get_gl_entries(filters, accounting_dimensions):
) )
if filters.get("presentation_currency"): 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: else:
return gl_entries return gl_entries

View File

@ -12,14 +12,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.financial_statements); erpnext.financial_statements);
frappe.query_reports["Gross and Net Profit Report"]["filters"].push( 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", "fieldname": "accumulated_values",
"label": __("Accumulated Values"), "label": __("Accumulated Values"),

View File

@ -15,20 +15,21 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
get_group_by_conditions, get_group_by_conditions,
get_tax_accounts, get_tax_accounts,
) )
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None): def execute(filters=None):
return _execute(filters) 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: if not filters:
filters = {} filters = {}
columns = get_columns(additional_table_columns, filters) columns = get_columns(additional_table_columns, filters)
company_currency = erpnext.get_company_currency(filters.company) 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() aii_account_map = get_aii_accounts()
if item_list: if item_list:
itemised_tax, tax_columns = get_tax_accounts( 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, "posting_date": d.posting_date,
"supplier": d.supplier, "supplier": d.supplier,
"supplier_name": d.supplier_name, "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 total_tax = 0
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
@ -317,11 +310,6 @@ def get_conditions(filters):
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
conditions = get_conditions(filters) conditions = get_conditions(filters)
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
else:
additional_query_columns = ""
return frappe.db.sql( return frappe.db.sql(
""" """
select select
@ -340,11 +328,10 @@ def get_items(filters, additional_query_columns):
from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem` from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabItem`.name = `tabPurchase Invoice Item`.`item_code` and `tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
`tabPurchase Invoice`.docstatus = 1 %s `tabPurchase Invoice`.docstatus = 1 {1}
""".format( """.format(
additional_query_columns additional_query_columns, conditions
) ),
% (conditions),
filters, filters,
as_dict=1, as_dict=1,
) )

View File

@ -9,6 +9,7 @@ from frappe.utils import cstr, flt
from frappe.utils.xlsxutils import handle_html from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments 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 ( from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import (
get_customer_details, get_customer_details,
) )
@ -18,19 +19,14 @@ def execute(filters=None):
return _execute(filters) return _execute(filters)
def _execute( def _execute(filters=None, additional_table_columns=None, additional_conditions=None):
filters=None,
additional_table_columns=None,
additional_query_columns=None,
additional_conditions=None,
):
if not filters: if not filters:
filters = {} filters = {}
columns = get_columns(additional_table_columns, filters) columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") 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: if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
@ -79,30 +75,22 @@ def _execute(
"customer": d.customer, "customer": d.customer,
"customer_name": customer_record.customer_name, "customer_name": customer_record.customer_name,
"customer_group": customer_record.customer_group, "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: 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}) row.update({"rate": (d.base_net_rate * d.qty) / d.stock_qty, "amount": d.base_net_amount})
else: else:
@ -394,11 +382,6 @@ def get_group_by_conditions(filters, doctype):
def get_items(filters, additional_query_columns, additional_conditions=None): def get_items(filters, additional_query_columns, additional_conditions=None):
conditions = get_conditions(filters, additional_conditions) 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( return frappe.db.sql(
""" """
select select
@ -424,7 +407,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
`tabItem`.name = `tabSales Invoice Item`.`item_code` and `tabItem`.name = `tabSales Invoice Item`.`item_code` and
`tabSales Invoice`.docstatus = 1 {1} `tabSales Invoice`.docstatus = 1 {1}
""".format( """.format(
additional_query_columns or "", conditions additional_query_columns, conditions
), ),
filters, filters,
as_dict=1, as_dict=1,

View File

@ -9,16 +9,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.utils.add_dimensions('Profit and Loss Statement', 10); erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push( 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, {
company: frappe.query_report.get_filter_value("company")
});
},
},
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default Book Entries"),

View File

@ -25,7 +25,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Fiscal Year"), "label": __("Fiscal Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
"on_change": function(query_report) { "on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year; var fiscal_year = query_report.get_values().fiscal_year;

View File

@ -10,17 +10,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions, get_accounting_dimensions,
get_dimension_with_children, get_dimension_with_children,
) )
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None): def execute(filters=None):
return _execute(filters) 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: if not filters:
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( columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
invoice_list, additional_table_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", []))) 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", []))) project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
row = [inv.name, inv.posting_date, inv.supplier, inv.supplier_name] row = [
inv.name,
if additional_query_columns: inv.posting_date,
for col in additional_query_columns: inv.supplier,
row.append(inv.get(col)) inv.supplier_name,
*get_values_for_columns(additional_table_columns, inv).values(),
row += [
supplier_details.get(inv.supplier), # supplier_group supplier_details.get(inv.supplier), # supplier_group
inv.tax_id, inv.tax_id,
inv.credit_to, inv.credit_to,
@ -244,9 +244,6 @@ def get_conditions(filters):
def get_invoices(filters, additional_query_columns): def get_invoices(filters, additional_query_columns):
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
conditions = get_conditions(filters) conditions = get_conditions(filters)
return frappe.db.sql( return frappe.db.sql(
""" """
@ -255,11 +252,10 @@ def get_invoices(filters, additional_query_columns):
remarks, base_net_total, base_grand_total, outstanding_amount, remarks, base_net_total, base_grand_total, outstanding_amount,
mode_of_payment {0} mode_of_payment {0}
from `tabPurchase Invoice` from `tabPurchase Invoice`
where docstatus = 1 %s where docstatus = 1 {1}
order by posting_date desc, name desc""".format( order by posting_date desc, name desc""".format(
additional_query_columns or "" additional_query_columns, conditions
) ),
% conditions,
filters, filters,
as_dict=1, as_dict=1,
) )

View File

@ -11,17 +11,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions, get_accounting_dimensions,
get_dimension_with_children, get_dimension_with_children,
) )
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
def _execute(filters, additional_table_columns=None, additional_query_columns=None): def _execute(filters, additional_table_columns=None):
if not filters: if not filters:
filters = frappe._dict({}) 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( columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
invoice_list, additional_table_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, "posting_date": inv.posting_date,
"customer": inv.customer, "customer": inv.customer,
"customer_name": inv.customer_name, "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 # map income values
base_net_total = 0 base_net_total = 0
for income_acc in income_accounts: for income_acc in income_accounts:
@ -402,9 +395,6 @@ def get_conditions(filters):
def get_invoices(filters, additional_query_columns): def get_invoices(filters, additional_query_columns):
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)
conditions = get_conditions(filters) conditions = get_conditions(filters)
return frappe.db.sql( 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, base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
is_internal_customer, represents_company, company {0} is_internal_customer, represents_company, company {0}
from `tabSales Invoice` from `tabSales Invoice`
where docstatus = 1 %s order by posting_date desc, name desc""".format( where docstatus = 1 {1}
additional_query_columns or "" order by posting_date desc, name desc""".format(
) additional_query_columns, conditions
% conditions, ),
filters, filters,
as_dict=1, as_dict=1,
) )

View File

@ -67,8 +67,9 @@ def get_all_transfers(date, shareholder):
# condition = 'AND company = %(company)s ' # condition = 'AND company = %(company)s '
return frappe.db.sql( return frappe.db.sql(
"""SELECT * FROM `tabShare Transfer` """SELECT * FROM `tabShare Transfer`
WHERE (DATE(date) <= %(date)s AND from_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}) OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition}))
AND docstatus = 1
ORDER BY date""".format( ORDER BY date""".format(
condition=condition condition=condition
), ),

View File

@ -17,7 +17,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Fiscal Year"), "label": __("Fiscal Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
"on_change": function(query_report) { "on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year; var fiscal_year = query_report.get_values().fiscal_year;

View File

@ -17,6 +17,7 @@ from erpnext.accounts.report.financial_statements import (
filter_out_zero_value_rows, filter_out_zero_value_rows,
set_gl_entries_by_account, set_gl_entries_by_account,
) )
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
value_fields = ( value_fields = (
"opening_debit", "opening_debit",
@ -116,6 +117,7 @@ def get_data(filters):
filters, filters,
gl_entries_by_account, gl_entries_by_account,
ignore_closing_entries=not flt(filters.with_period_closing_entry), ignore_closing_entries=not flt(filters.with_period_closing_entry),
ignore_opening_entries=True,
) )
calculate_values(accounts, gl_entries_by_account, opening_balances) calculate_values(accounts, gl_entries_by_account, opening_balances)
@ -158,6 +160,8 @@ def get_rootwise_opening_balances(filters, report_type):
accounting_dimensions, accounting_dimensions,
period_closing_voucher=last_period_closing_voucher[0].name, 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( if getdate(last_period_closing_voucher[0].posting_date) < getdate(
add_days(filters.from_date, -1) add_days(filters.from_date, -1)
): ):
@ -178,8 +182,8 @@ def get_rootwise_opening_balances(filters, report_type):
"opening_credit": 0.0, "opening_credit": 0.0,
}, },
) )
opening[d.account]["opening_debit"] += flt(d.opening_debit) opening[d.account]["opening_debit"] += flt(d.debit)
opening[d.account]["opening_credit"] += flt(d.opening_credit) opening[d.account]["opening_credit"] += flt(d.credit)
return opening return opening
@ -194,8 +198,11 @@ def get_opening_balance(
frappe.qb.from_(closing_balance) frappe.qb.from_(closing_balance)
.select( .select(
closing_balance.account, closing_balance.account,
Sum(closing_balance.debit).as_("opening_debit"), closing_balance.account_currency,
Sum(closing_balance.credit).as_("opening_credit"), 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( .where(
(closing_balance.company == filters.company) (closing_balance.company == filters.company)
@ -216,7 +223,10 @@ def get_opening_balance(
if start_date: if start_date:
opening_balance = opening_balance.where(closing_balance.posting_date >= 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.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 ( if (
not filters.show_unclosed_fy_pl_balances not filters.show_unclosed_fy_pl_balances
@ -282,6 +292,9 @@ def get_opening_balance(
gle = opening_balance.run(as_dict=1) gle = opening_balance.run(as_dict=1)
if filters and filters.get("presentation_currency"):
convert_to_presentation_currency(gle, get_currency(filters))
return gle return gle

View File

@ -16,7 +16,7 @@ frappe.query_reports["Trial Balance for Party"] = {
"label": __("Fiscal Year"), "label": __("Fiscal Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
"on_change": function(query_report) { "on_change": function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year; var fiscal_year = query_report.get_values().fiscal_year;

View File

@ -1,5 +1,5 @@
import frappe 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 import get_company_currency, get_default_company
from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_date 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 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 Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
in `currency_info`. 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)) account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
for entry in gl_entries: for entry in gl_entries:
account = entry["account"]
debit = flt(entry["debit"]) debit = flt(entry["debit"])
credit = flt(entry["credit"]) credit = flt(entry["credit"])
debit_in_account_currency = flt(entry["debit_in_account_currency"]) 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) result = sum(d.gross_profit for d in result)
return 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

View File

@ -850,7 +850,7 @@ def get_held_invoices(party_type, party):
if party_type == "Supplier": if party_type == "Supplier":
held_invoices = frappe.db.sql( 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, as_dict=1,
) )
held_invoices = set(d["name"] for d in held_invoices) held_invoices = set(d["name"] for d in held_invoices)
@ -1110,6 +1110,11 @@ def get_autoname_with_number(number_value, doc_title, company):
return " - ".join(parts) 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() @frappe.whitelist()
def get_coa(doctype, parent, is_root, chart=None): def get_coa(doctype, parent, is_root, chart=None):
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import ( from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
@ -1408,6 +1413,50 @@ def check_and_delete_linked_reports(report):
frappe.delete_doc("Desktop Icon", icon) 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): def get_payment_ledger_entries(gl_entries, cancel=0):
ple_map = [] ple_map = []
if gl_entries: if gl_entries:

View File

@ -5,7 +5,7 @@
"label": "Profit and Loss" "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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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", "creation": "2020-03-02 15:41:59.515192",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@ -1061,7 +1061,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2023-05-30 13:23:29.316711", "modified": "2023-07-04 14:32:15.842044",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",
@ -1074,6 +1074,13 @@
"roles": [], "roles": [],
"sequence_id": 2.0, "sequence_id": 2.0,
"shortcuts": [ "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", "label": "Chart of Accounts",
"link_to": "Account", "link_to": "Account",

View File

@ -40,6 +40,7 @@ def post_depreciation_entries(date=None):
date = today() date = today()
failed_asset_names = [] failed_asset_names = []
error_log_names = []
for asset_name in get_depreciable_assets(date): for asset_name in get_depreciable_assets(date):
asset_doc = frappe.get_doc("Asset", asset_name) asset_doc = frappe.get_doc("Asset", asset_name)
@ -50,10 +51,12 @@ def post_depreciation_entries(date=None):
except Exception as e: except Exception as e:
frappe.db.rollback() frappe.db.rollback()
failed_asset_names.append(asset_name) failed_asset_names.append(asset_name)
error_log = frappe.log_error(e)
error_log_names.append(error_log.name)
if failed_asset_names: if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(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() 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") 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") recipients = get_users_with_role("Accounts Manager")
if not recipients: if not recipients:
@ -247,7 +250,8 @@ def notify_depr_entry_posting_error(failed_asset_names):
subject = _("Error while posting depreciation entries") 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 = ( message = (
_("Hello,") _("Hello,")
@ -257,23 +261,26 @@ def notify_depr_entry_posting_error(failed_asset_names):
) )
+ "." + "."
+ "<br><br>" + "<br><br>"
+ _( + _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
"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." error_log_links
) )
+ "."
+ "<br><br>"
+ _("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) frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names): def get_comma_separated_links(names, doctype):
asset_links = [] links = []
for asset_name in asset_names: for name in names:
asset_links.append(get_link_to_form("Asset", asset_name)) links.append(get_link_to_form(doctype, name))
asset_links = ", ".join(asset_links) links = ", ".join(links)
return asset_links return links
@frappe.whitelist() @frappe.whitelist()

View File

@ -63,7 +63,7 @@ frappe.ui.form.on('Asset Movement', {
fieldnames_to_be_altered = { fieldnames_to_be_altered = {
target_location: { read_only: 0, reqd: 1 }, target_location: { read_only: 0, reqd: 1 },
source_location: { read_only: 1, reqd: 0 }, 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 } to_employee: { read_only: 1, reqd: 0 }
}; };
} }

View File

@ -62,29 +62,20 @@ class AssetMovement(Document):
frappe.throw(_("Source and Target Location cannot be same")) frappe.throw(_("Source and Target Location cannot be same"))
if self.purpose == "Receipt": if self.purpose == "Receipt":
# only when asset is bought and first entry is made if not (d.source_location or d.from_employee) and not (d.target_location or d.to_employee):
if not d.source_location and not (d.target_location or d.to_employee):
frappe.throw( frappe.throw(
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset) _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
) )
elif d.source_location: elif d.from_employee and not d.target_location:
# when asset is received from an employee frappe.throw(
if d.target_location and not d.from_employee: _("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
frappe.throw( )
_("From employee is required while receiving Asset {0} to a target location").format( elif d.to_employee and d.target_location:
d.asset frappe.throw(
) _(
) "Asset {0} cannot be received at a location and given to an employee in a single movement"
if d.from_employee and not d.target_location: ).format(d.asset)
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)
)
def validate_employee(self): def validate_employee(self):
for d in self.assets: for d in self.assets:

View File

@ -82,7 +82,7 @@ frappe.query_reports["Fixed Asset Register"] = {
"label": __("Start Year"), "label": __("Start Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "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'", "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
}, },
{ {
@ -90,7 +90,7 @@ frappe.query_reports["Fixed Asset Register"] = {
"label": __("End Year"), "label": __("End Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "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'", "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
}, },
{ {

View File

@ -66,8 +66,6 @@ frappe.ui.form.on("Supplier", {
}, },
refresh: function (frm) { refresh: function (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
if (frappe.defaults.get_default("supp_master_name") != "Naming Series") { if (frappe.defaults.get_default("supp_master_name") != "Naming Series") {
frm.toggle_display("naming_series", false); frm.toggle_display("naming_series", false);
} else { } else {

View File

@ -5,7 +5,7 @@
"label": "Purchase Order Trends" "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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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", "creation": "2020-01-28 11:50:26.195467",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@ -511,7 +511,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2023-05-24 14:47:20.535772", "modified": "2023-07-04 14:43:30.387683",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying", "name": "Buying",
@ -532,6 +532,13 @@
"stats_filter": "{\n \"disabled\": 0\n}", "stats_filter": "{\n \"disabled\": 0\n}",
"type": "DocType" "type": "DocType"
}, },
{
"color": "Grey",
"doc_view": "List",
"label": "Learn Procurement",
"type": "URL",
"url": "https://frappe.school/courses/procurement?utm_source=in_app"
},
{ {
"color": "Yellow", "color": "Yellow",
"format": "{} Pending", "format": "{} Pending",

View File

@ -61,7 +61,7 @@
"fieldname": "communication_channel", "fieldname": "communication_channel",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Communication Channel", "label": "Communication Channel",
"options": "\nExotel" "options": ""
} }
], ],
"links": [], "links": [],

View File

@ -437,18 +437,23 @@ class BuyingController(SubcontractingController):
# validate rate with ref PR # validate rate with ref PR
def validate_rejected_warehouse(self): def validate_rejected_warehouse(self):
for d in self.get("items"): for item in self.get("items"):
if flt(d.rejected_qty) and not d.rejected_warehouse: if flt(item.rejected_qty) and not item.rejected_warehouse:
if self.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( frappe.throw(
_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format( _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
d.idx, d.item_code 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 # validate accepted and rejected qty
def validate_accepted_rejected_qty(self): def validate_accepted_rejected_qty(self):
for d in self.get("items"): for d in self.get("items"):

View File

@ -669,7 +669,11 @@ def get_filters(
if reference_voucher_detail_no: if reference_voucher_detail_no:
filters["voucher_detail_no"] = 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") filters["warehouse"] = item_row.get("warehouse")
return filters return filters

View File

@ -201,6 +201,12 @@ class StockController(AccountsController):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"] warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_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( gl_list.append(
self.get_gl_dict( self.get_gl_dict(

View File

@ -30,11 +30,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
var me = this; var me = this;
let doc = this.frm.doc; let doc = this.frm.doc;
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
frappe.dynamic_link = {
doc: doc,
fieldname: 'name',
doctype: 'Lead'
};
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) { if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create")); this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));

View File

@ -3,8 +3,6 @@
frappe.ui.form.on('Prospect', { frappe.ui.form.on('Prospect', {
refresh (frm) { refresh (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) { if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
frm.add_custom_button(__("Customer"), function() { frm.add_custom_button(__("Customer"), function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({

View File

@ -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
}

View File

@ -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"))

View File

@ -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
)

View File

@ -230,17 +230,6 @@
"onboard": 0, "onboard": 0,
"type": "Card Break" "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, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -252,7 +241,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2023-05-24 14:47:25.984717", "modified": "2023-05-24 14:47:26.984717",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "ERPNext Integrations", "module": "ERPNext Integrations",
"name": "ERPNext Integrations", "name": "ERPNext Integrations",

View File

@ -83,7 +83,7 @@ update_website_context = [
my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context" my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context"
webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context" webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context"
calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "ToDo"] calendars = ["Task", "Work Order", "Sales Order", "Holiday List", "ToDo"]
website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"] website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"]
@ -355,6 +355,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. # 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. # 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. # if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
@ -416,6 +421,10 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.buying.doctype.supplier_quotation.supplier_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.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": [ "daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.setup.doctype.email_digest.email_digest.send",
@ -608,3 +617,8 @@ global_search_doctypes = {
additional_timeline_content = { additional_timeline_content = {
"*": ["erpnext.telephony.doctype.call_log.call_log.get_linked_call_logs"] "*": ["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",
]

View File

@ -621,7 +621,7 @@ class ProductionPlan(Document):
def create_work_order(self, item): def create_work_order(self, item):
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
if item.get("qty") <= 0: if flt(item.get("qty")) <= 0:
return return
wo = frappe.new_doc("Work Order") wo = frappe.new_doc("Work Order")
@ -697,10 +697,9 @@ class ProductionPlan(Document):
material_request.flags.ignore_permissions = 1 material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values") material_request.run_method("set_missing_values")
material_request.save()
if self.get("submit_material_request"): if self.get("submit_material_request"):
material_request.submit() material_request.submit()
else:
material_request.save()
frappe.flags.mute_messages = False frappe.flags.mute_messages = False

View File

@ -1026,7 +1026,7 @@ class WorkOrder(Document):
consumed_qty = frappe.db.sql( consumed_qty = frappe.db.sql(
""" """
SELECT SELECT
SUM(qty) SUM(detail.qty)
FROM FROM
`tabStock Entry` entry, `tabStock Entry` entry,
`tabStock Entry Detail` detail `tabStock Entry Detail` detail

View File

@ -17,7 +17,7 @@ frappe.query_reports["Job Card Summary"] = {
label: __("Fiscal Year"), label: __("Fiscal Year"),
fieldtype: "Link", fieldtype: "Link",
options: "Fiscal Year", options: "Fiscal Year",
default: frappe.defaults.get_user_default("fiscal_year"), default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
reqd: 1, reqd: 1,
on_change: function(query_report) { on_change: function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year; var fiscal_year = query_report.get_values().fiscal_year;

View File

@ -1,6 +1,6 @@
{ {
"charts": [], "charts": [],
"content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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", "creation": "2020-03-02 17:11:37.032604",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@ -316,7 +316,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2023-05-27 16:41:04.776115", "modified": "2023-07-04 14:40:47.281125",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Manufacturing", "name": "Manufacturing",
@ -329,6 +329,13 @@
"roles": [], "roles": [],
"sequence_id": 8.0, "sequence_id": 8.0,
"shortcuts": [ "shortcuts": [
{
"color": "Grey",
"doc_view": "List",
"label": "Learn Manufacturing",
"type": "URL",
"url": "https://frappe.school/courses/manufacturing?utm_source=in_app"
},
{ {
"color": "Grey", "color": "Grey",
"doc_view": "List", "doc_view": "List",

View File

@ -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_asset_value_for_manual_depr_entries
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance 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) execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
# below migration patches should always run last # below migration patches should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.migrate_gl_to_payment_ledger
@ -334,4 +334,6 @@ erpnext.patches.v14_0.cleanup_workspaces
erpnext.patches.v15_0.remove_loan_management_module #2023-07-03 erpnext.patches.v15_0.remove_loan_management_module #2023-07-03
erpnext.patches.v14_0.set_report_in_process_SOA erpnext.patches.v14_0.set_report_in_process_SOA
erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
execute:frappe.defaults.clear_default("fiscal_year")
erpnext.patches.v15_0.remove_exotel_integration
erpnext.patches.v14_0.single_to_multi_dunning erpnext.patches.v14_0.single_to_multi_dunning

View File

@ -13,56 +13,63 @@ from erpnext.accounts.utils import get_fiscal_year
def execute(): def execute():
frappe.db.truncate("Account Closing Balance") frappe.db.truncate("Account Closing Balance")
i = 0 for company in frappe.get_all("Company", pluck="name"):
company_wise_order = {} i = 0
for pcv in frappe.db.get_all( company_wise_order = {}
"Period Closing Voucher", for pcv in frappe.db.get_all(
fields=["company", "posting_date", "name"], "Period Closing Voucher",
filters={"docstatus": 1}, fields=["company", "posting_date", "name"],
order_by="posting_date", filters={"docstatus": 1, "company": company},
): order_by="posting_date",
):
company_wise_order.setdefault(pcv.company, []) company_wise_order.setdefault(pcv.company, [])
if pcv.posting_date not in company_wise_order[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 = frappe.get_doc("Period Closing Voucher", pcv.name)
pcv_doc.year_start_date = get_fiscal_year( pcv_doc.year_start_date = get_fiscal_year(
pcv.posting_date, pcv.fiscal_year, company=pcv.company pcv.posting_date, pcv.fiscal_year, company=pcv.company
)[1] )[1]
# get gl entries against pcv # get gl entries against pcv
gl_entries = frappe.db.get_all( gl_entries = frappe.db.get_all(
"GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"] "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=["*"],
) )
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: closing_entries = []
entry["closing_date"] = pcv_doc.posting_date
entry["period_closing_voucher"] = pcv_doc.name
make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name) if pcv.posting_date not in company_wise_order[pcv.company]:
company_wise_order[pcv.company].append(pcv.posting_date) # 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)

View File

@ -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))

View File

@ -5,7 +5,7 @@
"label": "Open Projects" "label": "Open Projects"
} }
], ],
"content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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", "creation": "2020-03-02 15:46:04.874669",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@ -192,7 +192,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2023-05-24 14:47:23.179860", "modified": "2023-07-04 14:39:08.935853",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Projects", "name": "Projects",
@ -205,6 +205,13 @@
"roles": [], "roles": [],
"sequence_id": 11.0, "sequence_id": 11.0,
"shortcuts": [ "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", "color": "Blue",
"format": "{} Assigned", "format": "{} Assigned",

View File

@ -358,12 +358,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} }
refresh() { refresh() {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
erpnext.hide_company(); erpnext.hide_company();
this.set_dynamic_labels(); this.set_dynamic_labels();
this.setup_sms(); this.setup_sms();
this.setup_quality_inspection(); this.setup_quality_inspection();
this.validate_has_items(); this.validate_has_items();
erpnext.utils.view_serial_batch_nos(this.frm);
} }
scan_barcode() { scan_barcode() {

View File

@ -56,7 +56,7 @@ erpnext.financial_statements = {
// dropdown for links to other financial statements // dropdown for links to other financial statements
erpnext.financial_statements.filters = get_filters() 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) { frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
@ -137,7 +137,7 @@ function get_filters() {
"label": __("Start Year"), "label": __("Start Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
}, },
@ -146,7 +146,7 @@ function get_filters() {
"label": __("End Year"), "label": __("End Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
}, },
@ -182,6 +182,16 @@ function get_filters() {
company: frappe.query_report.get_filter_value("company") 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")
});
},
} }
] ]

View File

@ -113,6 +113,23 @@ $.extend(erpnext.utils, {
} }
}, },
view_serial_batch_nos: function(frm) {
let bundle_ids = frm.doc.items.filter(d => d.serial_and_batch_bundle);
if (bundle_ids?.length) {
frm.add_custom_button(__('Serial / Batch Nos'), () => {
frappe.route_options = {
"voucher_no": frm.doc.name,
"voucher_type": frm.doc.doctype,
"from_date": frm.doc.posting_date || frm.doc.transaction_date,
"to_date": frm.doc.posting_date || frm.doc.transaction_date,
"company": frm.doc.company,
};
frappe.set_route("query-report", "Serial and Batch Summary");
}, __('View'));
}
},
add_indicator_for_multicompany: function(frm, info) { add_indicator_for_multicompany: function(frm, info) {
frm.dashboard.stats_area.show(); frm.dashboard.stats_area.show();
frm.dashboard.stats_area_row.addClass('flex'); frm.dashboard.stats_area_row.addClass('flex');
@ -381,6 +398,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 +666,6 @@ erpnext.utils.update_child_items = function(opts) {
fields.splice(3, 0, { fields.splice(3, 0, {
fieldtype: 'Float', fieldtype: 'Float',
fieldname: "conversion_factor", fieldname: "conversion_factor",
in_list_view: 1,
label: __("Conversion Factor"), label: __("Conversion Factor"),
precision: get_precision('conversion_factor') precision: get_precision('conversion_factor')
}) })
@ -640,6 +673,7 @@ erpnext.utils.update_child_items = function(opts) {
new frappe.ui.Dialog({ new frappe.ui.Dialog({
title: __("Update Items"), title: __("Update Items"),
size: "extra-large",
fields: [ fields: [
{ {
fieldname: "trans_items", fieldname: "trans_items",
@ -854,95 +888,87 @@ $(document).on('app_ready', function() {
// Show SLA dashboard // Show SLA dashboard
$(document).on('app_ready', function() { $(document).on('app_ready', function() {
frappe.call({ $.each(frappe.boot.service_level_agreement_doctypes, function(_i, d) {
method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_sla_doctypes', frappe.ui.form.on(d, {
callback: function(r) { onload: function(frm) {
if (!r.message) if (!frm.doc.service_level_agreement)
return; return;
$.each(r.message, function(_i, d) { frappe.call({
frappe.ui.form.on(d, { method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
onload: function(frm) { args: {
if (!frm.doc.service_level_agreement) doctype: frm.doc.doctype,
return; name: frm.doc.service_level_agreement,
customer: frm.doc.customer
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],
}
};
});
}
}
});
}, },
callback: function (r) {
refresh: function(frm) { if (r && r.message) {
if (frm.doc.status !== 'Closed' && frm.doc.service_level_agreement frm.set_query('priority', function() {
&& ['First Response Due', 'Resolution Due'].includes(frm.doc.agreement_status)) { return {
frappe.call({ filters: {
'method': 'frappe.client.get', 'name': ['in', r.message.priority],
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(
'<div class="row">' +
'<div class="col-xs-12">' +
'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
'</div>' +
'</div>'
);
} else {
set_time_to_resolve_and_response(frm, data.message.apply_sla_for_resolution);
} }
} };
});
frm.set_query('service_level_agreement', function() {
return {
filters: {
'name': ['in', r.message.service_level_agreements],
}
};
}); });
} else if (frm.doc.service_level_agreement) {
frm.dashboard.clear_headline();
let agreement_status = (frm.doc.agreement_status == 'Fulfilled') ?
{'indicator': 'green', 'msg': 'Service Level Agreement has been fulfilled'} :
{'indicator': 'red', 'msg': 'Service Level Agreement Failed'};
frm.dashboard.set_headline_alert(
'<div class="row">' +
'<div class="col-xs-12">' +
'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
'</div>' +
'</div>'
);
} }
}, }
}); });
}); },
}
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(
'<div class="row">' +
'<div class="col-xs-12">' +
'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
'</div>' +
'</div>'
);
} else {
set_time_to_resolve_and_response(frm, data.message.apply_sla_for_resolution);
}
}
});
} else if (frm.doc.service_level_agreement) {
frm.dashboard.clear_headline();
let agreement_status = (frm.doc.agreement_status == 'Fulfilled') ?
{'indicator': 'green', 'msg': 'Service Level Agreement has been fulfilled'} :
{'indicator': 'red', 'msg': 'Service Level Agreement Failed'};
frm.dashboard.set_headline_alert(
'<div class="row">' +
'<div class="col-xs-12">' +
'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
'</div>' +
'</div>'
);
}
},
});
}); });
}); });
@ -1011,4 +1037,4 @@ function attach_selector_button(inner_text, append_loction, context, grid_row) {
$btn.on("click", function() { $btn.on("click", function() {
context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true); context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
}); });
} }

View File

@ -16,7 +16,7 @@ frappe.query_reports["Fichier des Ecritures Comptables [FEC]"] = {
"label": __("Fiscal Year"), "label": __("Fiscal Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1 "reqd": 1
} }
], ],

View File

@ -17,7 +17,7 @@ frappe.query_reports["IRS 1099"] = {
"label": __("Fiscal Year"), "label": __("Fiscal Year"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"), "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1, "reqd": 1,
"width": 80, "width": 80,
}, },

View File

@ -131,8 +131,6 @@ frappe.ui.form.on("Customer", {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
} }
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'}
if(!frm.doc.__islocal) { if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm); frappe.contacts.render_address_and_contact(frm);

View File

@ -5,7 +5,7 @@
"label": "Sales Order Trends" "label": "Sales Order Trends"
} }
], ],
"content": "[{\"id\":\"ow595dYDrI\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Selling\",\"col\":12}},{\"id\":\"vBSf8Vi9U8\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Sales Order Trends\",\"col\":12}},{\"id\":\"aW2i5R5GRP\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"1it3dCOnm6\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"id\":\"x7pLl-spS4\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"SSGrXWmY-H\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order\",\"col\":3}},{\"id\":\"-5J_yLxDaS\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"id\":\"6YEYpnIBKV\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Point of Sale\",\"col\":3}},{\"id\":\"c_GjZuZ2oN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"oNjjNbnUHp\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"0BcePLg0g1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"uze5dJ1ipL\",\"type\":\"card\",\"data\":{\"card_name\":\"Selling\",\"col\":4}},{\"id\":\"3j2fYwMAkq\",\"type\":\"card\",\"data\":{\"card_name\":\"Point of Sale\",\"col\":4}},{\"id\":\"xImm8NepFt\",\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"id\":\"6MjIe7KCQo\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"lBu2EKgmJF\",\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"id\":\"1ARHrjg4kI\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "content": "[{\"id\":\"ow595dYDrI\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Selling\",\"col\":12}},{\"id\":\"vBSf8Vi9U8\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Sales Order Trends\",\"col\":12}},{\"id\":\"aW2i5R5GRP\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"1it3dCOnm6\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"id\":\"x7pLl-spS4\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"SSGrXWmY-H\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order\",\"col\":3}},{\"id\":\"-5J_yLxDaS\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"id\":\"6YEYpnIBKV\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Point of Sale\",\"col\":3}},{\"id\":\"c_GjZuZ2oN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"mX-9DJSyT2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Sales Management\",\"col\":3}},{\"id\":\"oNjjNbnUHp\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"0BcePLg0g1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"uze5dJ1ipL\",\"type\":\"card\",\"data\":{\"card_name\":\"Selling\",\"col\":4}},{\"id\":\"3j2fYwMAkq\",\"type\":\"card\",\"data\":{\"card_name\":\"Point of Sale\",\"col\":4}},{\"id\":\"xImm8NepFt\",\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"id\":\"6MjIe7KCQo\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"lBu2EKgmJF\",\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"id\":\"1ARHrjg4kI\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-01-28 11:49:12.092882", "creation": "2020-01-28 11:49:12.092882",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@ -621,7 +621,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2023-05-26 16:31:53.634851", "modified": "2023-07-04 14:35:58.204465",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Selling", "name": "Selling",
@ -634,6 +634,13 @@
"roles": [], "roles": [],
"sequence_id": 6.0, "sequence_id": 6.0,
"shortcuts": [ "shortcuts": [
{
"color": "Grey",
"doc_view": "List",
"label": "Learn Sales Management",
"type": "URL",
"url": "https://frappe.school/courses/sales-management-course?utm_source=in_app"
},
{ {
"label": "Point of Sale", "label": "Point of Sale",
"link_to": "point-of-sale", "link_to": "point-of-sale",

View File

@ -81,8 +81,6 @@ frappe.ui.form.on("Company", {
disbale_coa_fields(frm); disbale_coa_fields(frm);
frappe.contacts.render_address_and_contact(frm); frappe.contacts.render_address_and_contact(frm);
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'}
if (frappe.perm.has_perm("Cost Center", 0, 'read')) { if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() { frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});

View File

@ -95,6 +95,10 @@
"depreciation_cost_center", "depreciation_cost_center",
"capital_work_in_progress_account", "capital_work_in_progress_account",
"asset_received_but_not_billed", "asset_received_but_not_billed",
"exchange_rate_revaluation_settings_section",
"auto_exchange_rate_revaluation",
"auto_err_frequency",
"submit_err_jv",
"budget_detail", "budget_detail",
"exception_budget_approver_role", "exception_budget_approver_role",
"registration_info", "registration_info",
@ -731,6 +735,29 @@
"fieldname": "book_advance_payments_in_separate_party_account", "fieldname": "book_advance_payments_in_separate_party_account",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Book Advance Payments in Separate Party Account" "label": "Book Advance Payments in Separate Party Account"
},
{
"fieldname": "exchange_rate_revaluation_settings_section",
"fieldtype": "Section Break",
"label": "Exchange Rate Revaluation Settings"
},
{
"default": "0",
"fieldname": "auto_exchange_rate_revaluation",
"fieldtype": "Check",
"label": "Auto Create Exchange Rate Revaluation"
},
{
"fieldname": "auto_err_frequency",
"fieldtype": "Select",
"label": "Frequency",
"options": "Daily\nWeekly"
},
{
"default": "0",
"fieldname": "submit_err_jv",
"fieldtype": "Check",
"label": "Submit ERR Journals?"
} }
], ],
"icon": "fa fa-building", "icon": "fa fa-building",
@ -738,7 +765,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2023-06-23 18:22:27.219706", "modified": "2023-07-07 05:41:41.537256",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@ -1,352 +1,99 @@
{ {
"allow_copy": 1, "actions": [],
"allow_guest_to_view": 0, "allow_copy": 1,
"allow_import": 0, "creation": "2013-05-02 17:53:24",
"allow_rename": 0, "doctype": "DocType",
"beta": 0, "engine": "InnoDB",
"creation": "2013-05-02 17:53:24", "field_order": [
"custom": 0, "default_company",
"docstatus": 0, "country",
"doctype": "DocType", "default_distance_unit",
"editable_grid": 0, "column_break_8",
"default_currency",
"hide_currency_symbol",
"disable_rounded_total",
"disable_in_words"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "default_company",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "ignore_user_permissions": 1,
"bold": 0, "label": "Default Company",
"collapsible": 0, "options": "Company"
"columns": 0, },
"fieldname": "default_company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "country",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Country",
"bold": 0, "options": "Country"
"collapsible": 0, },
"columns": 0,
"fieldname": "current_fiscal_year",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Fiscal Year",
"length": 0,
"no_copy": 0,
"options": "Fiscal Year",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "default_distance_unit",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Default Distance Unit",
"bold": 0, "options": "UOM"
"collapsible": 0, },
"columns": 0,
"fieldname": "country",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Country",
"length": 0,
"no_copy": 0,
"options": "Country",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_8",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "default_distance_unit",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Distance Unit",
"length": 0,
"no_copy": 0,
"options": "UOM",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "INR",
"allow_in_quick_entry": 0, "fieldname": "default_currency",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "ignore_user_permissions": 1,
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Default Currency",
"fieldname": "column_break_8", "options": "Currency",
"fieldtype": "Column Break", "reqd": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "description": "Do not show any symbol like $ etc next to currencies.",
"allow_in_quick_entry": 0, "fieldname": "hide_currency_symbol",
"allow_on_submit": 0, "fieldtype": "Select",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Hide Currency Symbol",
"columns": 0, "options": "\nNo\nYes"
"default": "INR", },
"fieldname": "default_currency",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default Currency",
"length": 0,
"no_copy": 0,
"options": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "description": "If disable, 'Rounded Total' field will not be visible in any transaction",
"allow_on_submit": 0, "fieldname": "disable_rounded_total",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Disable Rounded Total"
"description": "Do not show any symbol like $ etc next to currencies.", },
"fieldname": "hide_currency_symbol",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Hide Currency Symbol",
"length": 0,
"no_copy": 0,
"options": "\nNo\nYes",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "description": "If disable, 'In Words' field will not be visible in any transaction",
"allow_on_submit": 0, "fieldname": "disable_in_words",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Disable In Words"
"description": "If disable, 'Rounded Total' field will not be visible in any transaction",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disable Rounded Total",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "If disable, 'In Words' field will not be visible in any transaction",
"fieldname": "disable_in_words",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disable In Words",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "icon": "fa fa-cog",
"hide_heading": 0, "idx": 1,
"hide_toolbar": 0, "in_create": 1,
"icon": "fa fa-cog", "issingle": 1,
"idx": 1, "links": [],
"image_view": 0, "modified": "2023-07-01 19:45:00.323953",
"in_create": 1, "modified_by": "Administrator",
"is_submittable": 0, "module": "Setup",
"issingle": 1, "name": "Global Defaults",
"istable": 0, "owner": "Administrator",
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-10-15 03:08:19.886212",
"modified_by": "Administrator",
"module": "Setup",
"name": "Global Defaults",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "read": 1,
"create": 1, "role": "System Manager",
"delete": 0, "share": 1,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "read_only": 1,
"read_only": 1, "sort_field": "modified",
"read_only_onload": 0, "sort_order": "DESC",
"show_name_in_global_search": 0, "states": []
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -10,7 +10,6 @@ from frappe.utils import cint
keydict = { keydict = {
# "key in defaults": "key in Global Defaults" # "key in defaults": "key in Global Defaults"
"fiscal_year": "current_fiscal_year",
"company": "default_company", "company": "default_company",
"currency": "default_currency", "currency": "default_currency",
"country": "country", "country": "country",
@ -29,22 +28,6 @@ class GlobalDefaults(Document):
for key in keydict: for key in keydict:
frappe.db.set_default(key, self.get(keydict[key], "")) frappe.db.set_default(key, self.get(keydict[key], ""))
# update year start date and year end date from fiscal_year
if self.current_fiscal_year:
if fiscal_year := frappe.get_all(
"Fiscal Year",
filters={"name": self.current_fiscal_year},
fields=["year_start_date", "year_end_date"],
limit=1,
order_by=None,
):
ysd = fiscal_year[0].year_start_date or ""
yed = fiscal_year[0].year_end_date or ""
if ysd and yed:
frappe.db.set_default("year_start_date", ysd.strftime("%Y-%m-%d"))
frappe.db.set_default("year_end_date", yed.strftime("%Y-%m-%d"))
# enable default currency # enable default currency
if self.default_currency: if self.default_currency:
frappe.db.set_value("Currency", self.default_currency, "enabled", 1) frappe.db.set_value("Currency", self.default_currency, "enabled", 1)

View File

@ -6,13 +6,41 @@ frappe.ui.form.on("Holiday List", {
if (frm.doc.holidays) { if (frm.doc.holidays) {
frm.set_value("total_holidays", frm.doc.holidays.length); frm.set_value("total_holidays", frm.doc.holidays.length);
} }
frm.call("get_supported_countries").then(r => {
frm.subdivisions_by_country = r.message.subdivisions_by_country;
frm.fields_dict.country.set_data(
r.message.countries.sort((a, b) => a.label.localeCompare(b.label))
);
if (frm.doc.country) {
frm.trigger("set_subdivisions");
}
});
}, },
from_date: function(frm) { from_date: function(frm) {
if (frm.doc.from_date && !frm.doc.to_date) { if (frm.doc.from_date && !frm.doc.to_date) {
var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12); var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
frm.set_value("to_date", frappe.datetime.add_days(a_year_from_start, -1)); frm.set_value("to_date", frappe.datetime.add_days(a_year_from_start, -1));
} }
} },
country: function(frm) {
frm.set_value("subdivision", "");
if (frm.doc.country) {
frm.trigger("set_subdivisions");
}
},
set_subdivisions: function(frm) {
const subdivisions = [...frm.subdivisions_by_country[frm.doc.country]];
if (subdivisions && subdivisions.length > 0) {
frm.fields_dict.subdivision.set_data(subdivisions);
frm.set_df_property("subdivision", "hidden", 0);
} else {
frm.fields_dict.subdivision.set_data([]);
frm.set_df_property("subdivision", "hidden", 1);
}
},
}); });
frappe.tour["Holiday List"] = [ frappe.tour["Holiday List"] = [

View File

@ -1,480 +1,166 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:holiday_list_name", "autoname": "field:holiday_list_name",
"beta": 0,
"creation": "2013-01-10 16:34:14", "creation": "2013-01-10 16:34:14",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"holiday_list_name",
"from_date",
"to_date",
"column_break_4",
"total_holidays",
"add_weekly_holidays",
"weekly_off",
"get_weekly_off_dates",
"add_local_holidays",
"country",
"subdivision",
"get_local_holidays",
"holidays_section",
"holidays",
"clear_table",
"section_break_9",
"color"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "holiday_list_name", "fieldname": "holiday_list_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Holiday List Name", "label": "Holiday List Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "holiday_list_name", "oldfieldname": "holiday_list_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1 "unique": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date", "fieldname": "from_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "From Date", "label": "From Date",
"length": 0, "reqd": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date", "fieldname": "to_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "To Date", "label": "To Date",
"length": 0, "reqd": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_holidays", "fieldname": "total_holidays",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Holidays", "label": "Total Holidays",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1, "collapsible": 1,
"columns": 0, "depends_on": "eval: doc.from_date && doc.to_date",
"fieldname": "add_weekly_holidays", "fieldname": "add_weekly_holidays",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Add Weekly Holidays"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Add Weekly Holidays",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "weekly_off", "fieldname": "weekly_off",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Weekly Off", "label": "Weekly Off",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "\nSunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday", "options": "\nSunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "report_hide": 1
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "get_weekly_off_dates", "fieldname": "get_weekly_off_dates",
"fieldtype": "Button", "fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Add to Holidays", "label": "Add to Holidays",
"length": 0, "options": "get_weekly_off_dates"
"no_copy": 0,
"options": "get_weekly_off_dates",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "holidays_section", "fieldname": "holidays_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Holidays"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Holidays",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "holidays", "fieldname": "holidays",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Holidays", "label": "Holidays",
"length": 0,
"no_copy": 0,
"oldfieldname": "holiday_list_details", "oldfieldname": "holiday_list_details",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Holiday", "options": "Holiday"
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "clear_table", "fieldname": "clear_table",
"fieldtype": "Button", "fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Clear Table", "label": "Clear Table",
"length": 0, "options": "clear_table"
"no_copy": 0,
"options": "clear_table",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9", "fieldname": "section_break_9",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "color", "fieldname": "color",
"fieldtype": "Color", "fieldtype": "Color",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Color", "label": "Color",
"length": 0, "print_hide": 1
"no_copy": 0, },
"permlevel": 0, {
"precision": "", "fieldname": "country",
"print_hide": 1, "fieldtype": "Autocomplete",
"print_hide_if_no_value": 0, "label": "Country"
"read_only": 0, },
"remember_last_selected_value": 0, {
"report_hide": 0, "depends_on": "country",
"reqd": 0, "fieldname": "subdivision",
"search_index": 0, "fieldtype": "Autocomplete",
"set_only_once": 0, "label": "Subdivision"
"translatable": 0, },
"unique": 0 {
"collapsible": 1,
"depends_on": "eval: doc.from_date && doc.to_date",
"fieldname": "add_local_holidays",
"fieldtype": "Section Break",
"label": "Add Local Holidays"
},
{
"fieldname": "get_local_holidays",
"fieldtype": "Button",
"label": "Add to Holidays",
"options": "get_local_holidays"
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-calendar", "icon": "fa fa-calendar",
"idx": 1, "idx": 1,
"image_view": 0, "links": [],
"in_create": 0, "modified": "2023-07-14 13:28:53.156421",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-03 07:22:46.474096",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Holiday List", "name": "Holiday List",
"naming_rule": "By fieldname",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 0, "states": []
"track_seen": 0,
"track_views": 0
} }

View File

@ -3,11 +3,15 @@
import json import json
from datetime import date
import frappe import frappe
from babel import Locale
from frappe import _, throw from frappe import _, throw
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, formatdate, getdate, today from frappe.utils import formatdate, getdate, today
from holidays import country_holidays
from holidays.utils import list_supported_countries
class OverlapError(frappe.ValidationError): class OverlapError(frappe.ValidationError):
@ -21,25 +25,66 @@ class HolidayList(Document):
@frappe.whitelist() @frappe.whitelist()
def get_weekly_off_dates(self): def get_weekly_off_dates(self):
self.validate_values()
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
last_idx = max(
[cint(d.idx) for d in self.get("holidays")]
or [
0,
]
)
for i, d in enumerate(date_list):
ch = self.append("holidays", {})
ch.description = _(self.weekly_off)
ch.holiday_date = d
ch.weekly_off = 1
ch.idx = last_idx + i + 1
def validate_values(self):
if not self.weekly_off: if not self.weekly_off:
throw(_("Please select weekly off day")) throw(_("Please select weekly off day"))
existing_holidays = self.get_holidays()
for d in self.get_weekly_off_date_list(self.from_date, self.to_date):
if d in existing_holidays:
continue
self.append("holidays", {"description": _(self.weekly_off), "holiday_date": d, "weekly_off": 1})
self.sort_holidays()
@frappe.whitelist()
def get_supported_countries(self):
subdivisions_by_country = list_supported_countries()
countries = [
{"value": country, "label": local_country_name(country)}
for country in subdivisions_by_country.keys()
]
return {
"countries": countries,
"subdivisions_by_country": subdivisions_by_country,
}
@frappe.whitelist()
def get_local_holidays(self):
if not self.country:
throw(_("Please select a country"))
existing_holidays = self.get_holidays()
from_date = getdate(self.from_date)
to_date = getdate(self.to_date)
for holiday_date, holiday_name in country_holidays(
self.country,
subdiv=self.subdivision,
years=[from_date.year, to_date.year],
language=frappe.local.lang,
).items():
if holiday_date in existing_holidays:
continue
if holiday_date < from_date or holiday_date > to_date:
continue
self.append(
"holidays", {"description": holiday_name, "holiday_date": holiday_date, "weekly_off": 0}
)
self.sort_holidays()
def sort_holidays(self):
self.holidays.sort(key=lambda x: getdate(x.holiday_date))
for i in range(len(self.holidays)):
self.holidays[i].idx = i + 1
def get_holidays(self) -> list[date]:
return [getdate(holiday.holiday_date) for holiday in self.holidays]
def validate_days(self): def validate_days(self):
if getdate(self.from_date) > getdate(self.to_date): if getdate(self.from_date) > getdate(self.to_date):
throw(_("To Date cannot be before From Date")) throw(_("To Date cannot be before From Date"))
@ -120,3 +165,8 @@ def is_holiday(holiday_list, date=None):
) )
else: else:
return False return False
def local_country_name(country_code: str) -> str:
"""Return the localized country name for the given country code."""
return Locale.parse(frappe.local.lang).territories.get(country_code, country_code)

View File

@ -3,7 +3,7 @@
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
from datetime import timedelta from datetime import date, timedelta
import frappe import frappe
from frappe.utils import getdate from frappe.utils import getdate
@ -23,6 +23,41 @@ class TestHolidayList(unittest.TestCase):
fetched_holiday_list = frappe.get_value("Holiday List", holiday_list.name) fetched_holiday_list = frappe.get_value("Holiday List", holiday_list.name)
self.assertEqual(holiday_list.name, fetched_holiday_list) self.assertEqual(holiday_list.name, fetched_holiday_list)
def test_weekly_off(self):
holiday_list = frappe.new_doc("Holiday List")
holiday_list.from_date = "2023-01-01"
holiday_list.to_date = "2023-02-28"
holiday_list.weekly_off = "Sunday"
holiday_list.get_weekly_off_dates()
holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
self.assertNotIn(date(2022, 12, 25), holidays)
self.assertIn(date(2023, 1, 1), holidays)
self.assertIn(date(2023, 1, 8), holidays)
self.assertIn(date(2023, 1, 15), holidays)
self.assertIn(date(2023, 1, 22), holidays)
self.assertIn(date(2023, 1, 29), holidays)
self.assertIn(date(2023, 2, 5), holidays)
self.assertIn(date(2023, 2, 12), holidays)
self.assertIn(date(2023, 2, 19), holidays)
self.assertIn(date(2023, 2, 26), holidays)
self.assertNotIn(date(2023, 3, 5), holidays)
def test_local_holidays(self):
holiday_list = frappe.new_doc("Holiday List")
holiday_list.from_date = "2023-04-01"
holiday_list.to_date = "2023-04-30"
holiday_list.country = "DE"
holiday_list.subdivision = "SN"
holiday_list.get_local_holidays()
holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
self.assertNotIn(date(2023, 1, 1), holidays)
self.assertIn(date(2023, 4, 7), holidays)
self.assertIn(date(2023, 4, 10), holidays)
self.assertNotIn(date(2023, 5, 1), holidays)
def make_holiday_list( def make_holiday_list(
name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None

View File

@ -3,8 +3,6 @@
frappe.ui.form.on('Sales Partner', { frappe.ui.form.on('Sales Partner', {
refresh: function(frm) { refresh: function(frm) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Sales Partner'}
if(frm.doc.__islocal){ if(frm.doc.__islocal){
hide_field(['address_html', 'contact_html', 'address_contacts']); hide_field(['address_html', 'contact_html', 'address_contacts']);
frappe.contacts.clear_address_and_contact(frm); frappe.contacts.clear_address_and_contact(frm);

View File

@ -169,7 +169,7 @@ def add_standard_navbar_items():
{ {
"item_label": "Documentation", "item_label": "Documentation",
"item_type": "Route", "item_type": "Route",
"route": "https://docs.erpnext.com/docs/v14/user/manual/en/introduction", "route": "https://docs.erpnext.com/",
"is_standard": 1, "is_standard": 1,
}, },
{ {
@ -178,6 +178,12 @@ def add_standard_navbar_items():
"route": "https://discuss.frappe.io", "route": "https://discuss.frappe.io",
"is_standard": 1, "is_standard": 1,
}, },
{
"item_label": "Frappe School",
"item_type": "Route",
"route": "https://frappe.school?utm_source=in_app",
"is_standard": 1,
},
{ {
"item_label": "Report an Issue", "item_label": "Report an Issue",
"item_type": "Route", "item_type": "Route",

View File

@ -462,11 +462,9 @@ def install_defaults(args=None): # nosemgrep
def set_global_defaults(args): def set_global_defaults(args):
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults") global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
current_fiscal_year = frappe.get_all("Fiscal Year")[0]
global_defaults.update( global_defaults.update(
{ {
"current_fiscal_year": current_fiscal_year.name,
"default_currency": args.get("currency"), "default_currency": args.get("currency"),
"default_company": args.get("company_name"), "default_company": args.get("company_name"),
"country": args.get("country"), "country": args.get("country"),

View File

@ -47,8 +47,6 @@ frappe.ui.form.on('Batch', {
return; return;
} }
debugger
const section = frm.dashboard.add_section('', __("Stock Levels")); const section = frm.dashboard.add_section('', __("Stock Levels"));
// sort by qty // sort by qty

View File

@ -59,6 +59,73 @@ class TestBatch(FrappeTestCase):
return receipt return receipt
def test_batch_stock_levels(self, batch_qty=100):
"""Test automated batch creation from Purchase Receipt"""
self.make_batch_item("ITEM-BATCH-1")
receipt = frappe.get_doc(
dict(
doctype="Purchase Receipt",
supplier="_Test Supplier",
company="_Test Company",
items=[dict(item_code="ITEM-BATCH-1", qty=10, rate=10, warehouse="Stores - _TC")],
)
).insert()
receipt.submit()
receipt.load_from_db()
batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
bundle_id = (
SerialBatchCreation(
{
"item_code": "ITEM-BATCH-1",
"warehouse": "_Test Warehouse - _TC",
"actual_qty": 20,
"voucher_type": "Purchase Receipt",
"batches": frappe._dict({batch_no: 20}),
"type_of_transaction": "Inward",
"company": receipt.company,
}
)
.make_serial_and_batch_bundle()
.name
)
receipt2 = frappe.get_doc(
dict(
doctype="Purchase Receipt",
supplier="_Test Supplier",
company="_Test Company",
items=[
dict(
item_code="ITEM-BATCH-1",
qty=20,
rate=10,
warehouse="_Test Warehouse - _TC",
serial_and_batch_bundle=bundle_id,
)
],
)
).insert()
receipt2.submit()
receipt.load_from_db()
receipt2.load_from_db()
self.assertTrue(receipt.items[0].serial_and_batch_bundle)
self.assertTrue(receipt2.items[0].serial_and_batch_bundle)
batchwise_qty = frappe._dict({})
for receipt in [receipt, receipt2]:
batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
key = (batch_no, receipt.items[0].warehouse)
batchwise_qty[key] = receipt.items[0].qty
batches = get_batch_qty(batch_no)
for d in batches:
self.assertEqual(d.qty, batchwise_qty[(d.batch_no, d.warehouse)])
def test_stock_entry_incoming(self): def test_stock_entry_incoming(self):
"""Test batch creation via Stock Entry (Work Order)""" """Test batch creation via Stock Entry (Work Order)"""

View File

@ -318,6 +318,37 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(dn.per_returned, 100) self.assertEqual(dn.per_returned, 100)
self.assertEqual(dn.status, "Return Issued") self.assertEqual(dn.status, "Return Issued")
def test_delivery_note_return_valuation_on_different_warehuose(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
item_code = "Test Return Valuation For DN"
make_item("Test Return Valuation For DN", {"is_stock_item": 1})
return_warehouse = create_warehouse("Returned Test Warehouse", company=company)
make_stock_entry(item_code=item_code, target="Stores - TCP1", qty=5, basic_rate=150)
dn = create_delivery_note(
item_code=item_code,
qty=5,
rate=500,
warehouse="Stores - TCP1",
company=company,
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
)
dn.submit()
self.assertEqual(dn.items[0].incoming_rate, 150)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return_dn = make_return_doc(dn.doctype, dn.name)
return_dn.items[0].warehouse = return_warehouse
return_dn.save().submit()
self.assertEqual(return_dn.items[0].incoming_rate, 150)
def test_return_single_item_from_bundled_items(self): def test_return_single_item_from_bundled_items(self):
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")

View File

@ -194,7 +194,8 @@
"default": "0", "default": "0",
"fieldname": "disabled", "fieldname": "disabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disabled" "label": "Disabled",
"search_index": 1
}, },
{ {
"default": "0", "default": "0",
@ -911,7 +912,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"make_attachments_public": 1, "make_attachments_public": 1,
"modified": "2023-02-14 04:48:26.343620", "modified": "2023-07-14 17:18:18.658942",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -773,7 +773,7 @@ class Item(Document):
rows = "" rows = ""
for docname, attr_list in not_included.items(): for docname, attr_list in not_included.items():
link = "<a href='/app/Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname))) link = f"<a href='/app/item/{docname}'>{frappe.bold(docname)}</a>"
rows += table_row(link, body(attr_list)) rows += table_row(link, body(attr_list))
error_description = _( error_description = _(

View File

@ -1,370 +1,90 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2015-05-19 05:12:30.344797",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "document_type": "Other",
"allow_rename": 0, "editable_grid": 1,
"autoname": "", "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2015-05-19 05:12:30.344797", "variant_of",
"custom": 0, "attribute",
"docstatus": 0, "column_break_2",
"doctype": "DocType", "attribute_value",
"document_type": "Other", "numeric_values",
"editable_grid": 1, "section_break_4",
"from_range",
"increment",
"column_break_8",
"to_range"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "variant_of",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Variant Of",
"bold": 0, "options": "Item",
"collapsible": 0, "search_index": 1
"columns": 0, },
"fieldname": "variant_of",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Variant Of",
"length": 0,
"no_copy": 0,
"options": "Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "attribute",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Attribute",
"collapsible": 0, "options": "Item Attribute",
"columns": 0, "reqd": 1,
"fieldname": "attribute", "search_index": 1
"fieldtype": "Link", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Attribute",
"length": 0,
"no_copy": 0,
"options": "Item Attribute",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_2",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "attribute_value",
"allow_in_quick_entry": 0, "fieldtype": "Data",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Attribute Value"
"collapsible": 0, },
"columns": 0,
"depends_on": "",
"fieldname": "attribute_value",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Attribute Value",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "has_variants",
"allow_on_submit": 0, "fieldname": "numeric_values",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "label": "Numeric Values"
"columns": 0, },
"depends_on": "has_variants",
"fieldname": "numeric_values",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Numeric Values",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "numeric_values",
"allow_in_quick_entry": 0, "fieldname": "section_break_4",
"allow_on_submit": 0, "fieldtype": "Section Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"depends_on": "numeric_values",
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "from_range",
"allow_in_quick_entry": 0, "fieldtype": "Float",
"allow_on_submit": 0, "label": "From Range"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "from_range",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "From Range",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "increment",
"allow_in_quick_entry": 0, "fieldtype": "Float",
"allow_on_submit": 0, "label": "Increment"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "increment",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Increment",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_8",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_8",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "to_range",
"allow_in_quick_entry": 0, "fieldtype": "Float",
"allow_on_submit": 0, "label": "To Range"
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "to_range",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "To Range",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2023-07-14 17:15:19.112119",
"icon": "", "modified_by": "Administrator",
"idx": 0, "module": "Stock",
"image_view": 0, "name": "Item Variant Attribute",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0, "permissions": [],
"issingle": 0, "sort_field": "modified",
"istable": 1, "sort_order": "DESC",
"max_attachments": 0, "states": []
"modified": "2019-01-03 15:36:59.129006",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Variant Attribute",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -3,7 +3,6 @@
frappe.ui.form.on('Manufacturer', { frappe.ui.form.on('Manufacturer', {
refresh: function(frm) { refresh: function(frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Manufacturer' };
if (frm.doc.__islocal) { if (frm.doc.__islocal) {
hide_field(['address_html','contact_html']); hide_field(['address_html','contact_html']);
frappe.contacts.clear_address_and_contact(frm); frappe.contacts.clear_address_and_contact(frm);

View File

@ -438,6 +438,7 @@
{ {
"fieldname": "rejected_warehouse", "fieldname": "rejected_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse", "label": "Rejected Warehouse",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "rejected_warehouse", "oldfieldname": "rejected_warehouse",
@ -1240,7 +1241,7 @@
"idx": 261, "idx": 261,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-06-03 16:23:20.781368", "modified": "2023-07-04 17:23:17.025390",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt", "name": "Purchase Receipt",

View File

@ -350,6 +350,15 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.cancel() pr.cancel()
self.assertFalse(frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse")) self.assertFalse(frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse"))
def test_rejected_warehouse_filter(self):
pr = frappe.copy_doc(test_records[0])
pr.get("items")[0].item_code = "_Test Serialized Item With Series"
pr.get("items")[0].qty = 3
pr.get("items")[0].rejected_qty = 2
pr.get("items")[0].received_qty = 5
pr.get("items")[0].rejected_warehouse = pr.get("items")[0].warehouse
self.assertRaises(frappe.ValidationError, pr.save)
def test_rejected_serial_no(self): def test_rejected_serial_no(self):
pr = frappe.copy_doc(test_records[0]) pr = frappe.copy_doc(test_records[0])
pr.get("items")[0].item_code = "_Test Serialized Item With Series" pr.get("items")[0].item_code = "_Test Serialized Item With Series"
@ -1956,6 +1965,32 @@ class TestPurchaseReceipt(FrappeTestCase):
ste5.reload() ste5.reload()
self.assertEqual(ste5.items[0].valuation_rate, 275.00) self.assertEqual(ste5.items[0].valuation_rate, 275.00)
ste6 = make_stock_entry(
purpose="Material Transfer",
posting_date=add_days(today(), -3),
source=warehouse1,
target=warehouse,
item_code=item_code,
qty=20,
company=pr.company,
)
ste6.reload()
self.assertEqual(ste6.items[0].valuation_rate, 275.00)
ste7 = make_stock_entry(
purpose="Material Transfer",
posting_date=add_days(today(), -3),
source=warehouse,
target=warehouse1,
item_code=item_code,
qty=20,
company=pr.company,
)
ste7.reload()
self.assertEqual(ste7.items[0].valuation_rate, 275.00)
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1) create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1)
pr.reload() pr.reload()
@ -1976,6 +2011,12 @@ class TestPurchaseReceipt(FrappeTestCase):
ste5.reload() ste5.reload()
self.assertEqual(ste5.items[0].valuation_rate, valuation_rate) self.assertEqual(ste5.items[0].valuation_rate, valuation_rate)
ste6.reload()
self.assertEqual(ste6.items[0].valuation_rate, valuation_rate)
ste7.reload()
self.assertEqual(ste7.items[0].valuation_rate, valuation_rate)
def prepare_data_for_internal_transfer(): def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@ -502,6 +502,7 @@
{ {
"fieldname": "rejected_warehouse", "fieldname": "rejected_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Rejected Warehouse", "label": "Rejected Warehouse",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "rejected_warehouse", "oldfieldname": "rejected_warehouse",
@ -1058,7 +1059,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-07-02 18:40:48.152637", "modified": "2023-07-04 17:22:02.830029",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt Item", "name": "Purchase Receipt Item",

View File

@ -193,7 +193,7 @@
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Naming Series", "label": "Naming Series",
"options": "SBB-.####" "options": "SABB-.########"
}, },
{ {
"default": "0", "default": "0",
@ -244,7 +244,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-04-10 20:02:42.964309", "modified": "2023-07-16 10:53:04.045605",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Serial and Batch Bundle", "name": "Serial and Batch Bundle",

View File

@ -889,13 +889,16 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
@frappe.whitelist() @frappe.whitelist()
def get_serial_batch_ledgers(item_code, docstatus=None, voucher_no=None, name=None): def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None):
filters = get_filters_for_bundle(item_code, docstatus=docstatus, voucher_no=voucher_no, name=name) filters = get_filters_for_bundle(
item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name
)
return frappe.get_all( return frappe.get_all(
"Serial and Batch Bundle", "Serial and Batch Bundle",
fields=[ fields=[
"`tabSerial and Batch Bundle`.`name`", "`tabSerial and Batch Bundle`.`name`",
"`tabSerial and Batch Bundle`.`item_code`",
"`tabSerial and Batch Entry`.`qty`", "`tabSerial and Batch Entry`.`qty`",
"`tabSerial and Batch Entry`.`warehouse`", "`tabSerial and Batch Entry`.`warehouse`",
"`tabSerial and Batch Entry`.`batch_no`", "`tabSerial and Batch Entry`.`batch_no`",
@ -906,12 +909,14 @@ def get_serial_batch_ledgers(item_code, docstatus=None, voucher_no=None, name=No
) )
def get_filters_for_bundle(item_code, docstatus=None, voucher_no=None, name=None): def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None):
filters = [ filters = [
["Serial and Batch Bundle", "item_code", "=", item_code],
["Serial and Batch Bundle", "is_cancelled", "=", 0], ["Serial and Batch Bundle", "is_cancelled", "=", 0],
] ]
if item_code:
filters.append(["Serial and Batch Bundle", "item_code", "=", item_code])
if not docstatus: if not docstatus:
docstatus = [0, 1] docstatus = [0, 1]
@ -1272,24 +1277,29 @@ def get_reserved_batches_for_pos(kwargs):
if ids: if ids:
for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids): for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids):
if d.batch_no not in pos_batches: key = (d.batch_no, d.warehouse)
pos_batches[d.batch_no] = frappe._dict( if key not in pos_batches:
pos_batches[key] = frappe._dict(
{ {
"qty": d.qty, "qty": d.qty,
"warehouse": d.warehouse, "warehouse": d.warehouse,
} }
) )
else: else:
pos_batches[d.batch_no].qty += d.qty pos_batches[key].qty += d.qty
for row in pos_invoices: for row in pos_invoices:
if not row.batch_no: if not row.batch_no:
continue continue
if row.batch_no in pos_batches: if kwargs.get("batch_no") and row.batch_no != kwargs.get("batch_no"):
pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty continue
key = (row.batch_no, row.warehouse)
if key in pos_batches:
pos_batches[key] -= row.qty * -1 if row.is_return else row.qty
else: else:
pos_batches[row.batch_no] = frappe._dict( pos_batches[key] = frappe._dict(
{ {
"qty": (row.qty * -1 if row.is_return else row.qty), "qty": (row.qty * -1 if row.is_return else row.qty),
"warehouse": row.warehouse, "warehouse": row.warehouse,
@ -1309,6 +1319,7 @@ def get_auto_batch_nos(kwargs):
update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches) update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches)
available_batches = list(filter(lambda x: x.qty > 0, available_batches)) available_batches = list(filter(lambda x: x.qty > 0, available_batches))
if not qty: if not qty:
return available_batches return available_batches
@ -1351,10 +1362,11 @@ def get_qty_based_available_batches(available_batches, qty):
def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None): def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None):
for batches in [reserved_batches, pos_invoice_batches]: for batches in [reserved_batches, pos_invoice_batches]:
if batches: if batches:
for batch_no, data in batches.items(): for key, data in batches.items():
batch_no, warehouse = key
batch_not_exists = True batch_not_exists = True
for batch in available_batches: for batch in available_batches:
if batch.batch_no == batch_no and batch.warehouse == data.warehouse: if batch.batch_no == batch_no and batch.warehouse == warehouse:
batch.qty += data.qty batch.qty += data.qty
batch_not_exists = False batch_not_exists = False
@ -1563,7 +1575,7 @@ def get_stock_ledgers_batches(kwargs):
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse) .groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
) )
for field in ["warehouse", "item_code"]: for field in ["warehouse", "item_code", "batch_no"]:
if not kwargs.get(field): if not kwargs.get(field):
continue continue
@ -1582,6 +1594,10 @@ def get_stock_ledgers_batches(kwargs):
data = query.run(as_dict=True) data = query.run(as_dict=True)
batches = {} batches = {}
for d in data: for d in data:
batches[d.batch_no] = d key = (d.batch_no, d.warehouse)
if key not in batches:
batches[key] = d
else:
batches[key].qty += d.qty
return batches return batches

View File

@ -925,6 +925,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
this.toggle_related_fields(this.frm.doc); this.toggle_related_fields(this.frm.doc);
this.toggle_enable_bom(); this.toggle_enable_bom();
this.show_stock_ledger(); this.show_stock_ledger();
erpnext.utils.view_serial_batch_nos(this.frm);
if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) { if (this.frm.doc.docstatus===1 && erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
this.show_general_ledger(); this.show_general_ledger();
} }

View File

@ -337,6 +337,7 @@ erpnext.stock.StockReconciliation = class StockReconciliation extends erpnext.st
refresh() { refresh() {
if(this.frm.doc.docstatus > 0) { if(this.frm.doc.docstatus > 0) {
this.show_stock_ledger(); this.show_stock_ledger();
erpnext.utils.view_serial_batch_nos(this.frm);
if (erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) { if (erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) {
this.show_general_ledger(); this.show_general_ledger();
} }

View File

@ -83,12 +83,6 @@ frappe.ui.form.on("Warehouse", {
} }
frm.toggle_enable(["is_group", "company"], false); frm.toggle_enable(["is_group", "company"], false);
frappe.dynamic_link = {
doc: frm.doc,
fieldname: "name",
doctype: "Warehouse",
};
}, },
}); });

View File

@ -9,13 +9,27 @@ frappe.query_reports["Batch Item Expiry Status"] = {
"fieldtype": "Date", "fieldtype": "Date",
"width": "80", "width": "80",
"default": frappe.sys_defaults.year_start_date, "default": frappe.sys_defaults.year_start_date,
"reqd": 1,
}, },
{ {
"fieldname":"to_date", "fieldname":"to_date",
"label": __("To Date"), "label": __("To Date"),
"fieldtype": "Date", "fieldtype": "Date",
"width": "80", "width": "80",
"default": frappe.datetime.get_today() "default": frappe.datetime.get_today(),
"reqd": 1,
},
{
"fieldname":"item",
"label": __("Item"),
"fieldtype": "Link",
"options": "Item",
"width": "100",
"get_query": function () {
return {
filters: {"has_batch_no": 1}
}
}
} }
] ]
} }

View File

@ -4,113 +4,86 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.query_builder.functions import IfNull from frappe.query_builder.functions import Date
from frappe.utils import cint, getdate
def execute(filters=None): def execute(filters=None):
if not filters: validate_filters(filters)
filters = {}
float_precision = cint(frappe.db.get_default("float_precision")) or 3 columns = get_columns()
data = get_data(filters)
columns = get_columns(filters)
item_map = get_item_details(filters)
iwb_map = get_item_warehouse_batch_map(filters, float_precision)
data = []
for item in sorted(iwb_map):
for wh in sorted(iwb_map[item]):
for batch in sorted(iwb_map[item][wh]):
qty_dict = iwb_map[item][wh][batch]
data.append(
[
item,
item_map[item]["item_name"],
item_map[item]["description"],
wh,
batch,
frappe.db.get_value("Batch", batch, "expiry_date"),
qty_dict.expiry_status,
]
)
return columns, data return columns, data
def get_columns(filters): def validate_filters(filters):
"""return columns based on filters""" if not filters:
frappe.throw(_("Please select the required filters"))
columns = (
[_("Item") + ":Link/Item:100"]
+ [_("Item Name") + "::150"]
+ [_("Description") + "::150"]
+ [_("Warehouse") + ":Link/Warehouse:100"]
+ [_("Batch") + ":Link/Batch:100"]
+ [_("Expires On") + ":Date:90"]
+ [_("Expiry (In Days)") + ":Int:120"]
)
return columns
def get_stock_ledger_entries(filters):
if not filters.get("from_date"): if not filters.get("from_date"):
frappe.throw(_("'From Date' is required")) frappe.throw(_("'From Date' is required"))
if not filters.get("to_date"): if not filters.get("to_date"):
frappe.throw(_("'To Date' is required")) frappe.throw(_("'To Date' is required"))
sle = frappe.qb.DocType("Stock Ledger Entry")
query = ( def get_columns():
frappe.qb.from_(sle) return (
.select(sle.item_code, sle.batch_no, sle.warehouse, sle.posting_date, sle.actual_qty) [_("Item") + ":Link/Item:150"]
.where( + [_("Item Name") + "::150"]
(sle.is_cancelled == 0) + [_("Batch") + ":Link/Batch:150"]
& (sle.docstatus < 2) + [_("Stock UOM") + ":Link/UOM:100"]
& (IfNull(sle.batch_no, "") != "") + [_("Quantity") + ":Float:100"]
& (sle.posting_date <= filters["to_date"]) + [_("Expires On") + ":Date:100"]
) + [_("Expiry (In Days)") + ":Int:130"]
.orderby(sle.item_code, sle.warehouse)
) )
return query.run(as_dict=True)
def get_data(filters):
data = []
def get_item_warehouse_batch_map(filters, float_precision): for batch in get_batch_details(filters):
sle = get_stock_ledger_entries(filters) data.append(
iwb_map = {} [
batch.item,
from_date = getdate(filters["from_date"]) batch.item_name,
to_date = getdate(filters["to_date"]) batch.name,
batch.stock_uom,
for d in sle: batch.batch_qty,
iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault( batch.expiry_date,
d.batch_no, frappe._dict({"expires_on": None, "expiry_status": None}) max((batch.expiry_date - frappe.utils.datetime.date.today()).days, 0)
if batch.expiry_date
else None,
]
) )
qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no] return data
expiry_date_unicode = frappe.db.get_value("Batch", d.batch_no, "expiry_date")
qty_dict.expires_on = expiry_date_unicode
exp_date = frappe.utils.data.getdate(expiry_date_unicode)
qty_dict.expires_on = exp_date
expires_in_days = (exp_date - frappe.utils.datetime.date.today()).days
if expires_in_days > 0:
qty_dict.expiry_status = expires_in_days
else:
qty_dict.expiry_status = 0
return iwb_map
def get_item_details(filters): def get_batch_details(filters):
item_map = {} batch = frappe.qb.DocType("Batch")
for d in (frappe.qb.from_("Item").select("name", "item_name", "description")).run(as_dict=True): query = (
item_map.setdefault(d.name, d) frappe.qb.from_(batch)
.select(
batch.name,
batch.creation,
batch.expiry_date,
batch.item,
batch.item_name,
batch.stock_uom,
batch.batch_qty,
)
.where(
(batch.disabled == 0)
& (batch.batch_qty > 0)
& (
(Date(batch.creation) >= filters["from_date"]) & (Date(batch.creation) <= filters["to_date"])
)
)
.orderby(batch.creation)
)
return item_map if filters.get("item"):
query = query.where(batch.item == filters["item"])
return query.run(as_dict=True)

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