Merge branch 'develop' into purchase-invoice-performance-issue
This commit is contained in:
commit
47264481ad
@ -32,8 +32,8 @@ repos:
|
|||||||
- id: black
|
- id: black
|
||||||
additional_dependencies: ['click==8.0.4']
|
additional_dependencies: ['click==8.0.4']
|
||||||
|
|
||||||
- repo: https://github.com/timothycrosley/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: 5.9.1
|
rev: 5.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
exclude: ".*setup.py$"
|
exclude: ".*setup.py$"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# the repo. Unless a later match takes precedence,
|
# the repo. Unless a later match takes precedence,
|
||||||
|
|
||||||
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
erpnext/assets/ @anandbaburajan @deepeshgarg007
|
||||||
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
||||||
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
@ -16,6 +16,7 @@ erpnext/maintenance/ @rohitwaghchaure @s-aga-r
|
|||||||
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
|
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
|
||||||
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
|
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
|
||||||
erpnext/stock/ @rohitwaghchaure @s-aga-r
|
erpnext/stock/ @rohitwaghchaure @s-aga-r
|
||||||
|
erpnext/subcontracting @rohitwaghchaure @s-aga-r
|
||||||
|
|
||||||
erpnext/crm/ @NagariaHussain
|
erpnext/crm/ @NagariaHussain
|
||||||
erpnext/education/ @rutwikhdev
|
erpnext/education/ @rutwikhdev
|
||||||
|
@ -56,36 +56,41 @@ frappe.treeview_settings["Account"] = {
|
|||||||
accounts = nodes;
|
accounts = nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const get_balances = frappe.call({
|
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
|
||||||
method: 'erpnext.accounts.utils.get_account_balances',
|
if(value) {
|
||||||
args: {
|
|
||||||
accounts: accounts,
|
|
||||||
company: cur_tree.args.company
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
get_balances.then(r => {
|
const get_balances = frappe.call({
|
||||||
if (!r.message || r.message.length == 0) return;
|
method: 'erpnext.accounts.utils.get_account_balances',
|
||||||
|
args: {
|
||||||
|
accounts: accounts,
|
||||||
|
company: cur_tree.args.company
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
for (let account of r.message) {
|
get_balances.then(r => {
|
||||||
|
if (!r.message || r.message.length == 0) return;
|
||||||
|
|
||||||
const node = cur_tree.nodes && cur_tree.nodes[account.value];
|
for (let account of r.message) {
|
||||||
if (!node || node.is_root) continue;
|
|
||||||
|
|
||||||
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
const node = cur_tree.nodes && cur_tree.nodes[account.value];
|
||||||
const balance = account.balance_in_account_currency || account.balance;
|
if (!node || node.is_root) continue;
|
||||||
const dr_or_cr = balance > 0 ? "Dr": "Cr";
|
|
||||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
|
||||||
|
|
||||||
if (account.balance!==undefined) {
|
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
||||||
node.parent && node.parent.find('.balance-area').remove();
|
const balance = account.balance_in_account_currency || account.balance;
|
||||||
$('<span class="balance-area pull-right">'
|
const dr_or_cr = balance > 0 ? "Dr": "Cr";
|
||||||
+ (account.balance_in_account_currency ?
|
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||||
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
|
||||||
+ format(account.balance, account.company_currency)
|
if (account.balance!==undefined) {
|
||||||
+ " " + dr_or_cr
|
node.parent && node.parent.find('.balance-area').remove();
|
||||||
+ '</span>').insertBefore(node.$ul);
|
$('<span class="balance-area pull-right">'
|
||||||
}
|
+ (account.balance_in_account_currency ?
|
||||||
|
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
||||||
|
+ format(account.balance, account.company_currency)
|
||||||
|
+ " " + dr_or_cr
|
||||||
|
+ '</span>').insertBefore(node.$ul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
{
|
{
|
||||||
"country_code": "de",
|
"country_code": "de",
|
||||||
"name": "SKR03 mit Kontonummern",
|
"name": "SKR03 mit Kontonummern",
|
||||||
"tree": {
|
"tree": {
|
||||||
"Aktiva": {
|
"Aktiva": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"root_type": "Asset",
|
"root_type": "Asset",
|
||||||
"A - Anlagevermögen": {
|
"A - Anlagevermögen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"EDV-Software": {
|
"EDV-Software": {
|
||||||
"account_number": "0027",
|
"account_number": "0027",
|
||||||
"account_type": "Fixed Asset"
|
"account_type": "Fixed Asset"
|
||||||
},
|
},
|
||||||
"Gesch\u00e4ftsausstattung": {
|
"Geschäftsausstattung": {
|
||||||
"account_number": "0410",
|
"account_number": "0410",
|
||||||
"account_type": "Fixed Asset"
|
"account_type": "Fixed Asset"
|
||||||
},
|
},
|
||||||
"B\u00fcroeinrichtung": {
|
"Büroeinrichtung": {
|
||||||
"account_number": "0420",
|
"account_number": "0420",
|
||||||
"account_type": "Fixed Asset"
|
"account_type": "Fixed Asset"
|
||||||
},
|
},
|
||||||
"Darlehen": {
|
"Darlehen": {
|
||||||
"account_number": "0565"
|
"account_number": "0565"
|
||||||
},
|
},
|
||||||
"Maschinen": {
|
"Maschinen": {
|
||||||
"account_number": "0210",
|
"account_number": "0210",
|
||||||
"account_type": "Fixed Asset"
|
"account_type": "Fixed Asset"
|
||||||
},
|
},
|
||||||
"Betriebsausstattung": {
|
"Betriebsausstattung": {
|
||||||
"account_number": "0400",
|
"account_number": "0400",
|
||||||
"account_type": "Fixed Asset"
|
"account_type": "Fixed Asset"
|
||||||
},
|
},
|
||||||
"Ladeneinrichtung": {
|
"Ladeneinrichtung": {
|
||||||
"account_number": "0430",
|
"account_number": "0430",
|
||||||
"account_type": "Fixed Asset"
|
"account_type": "Fixed Asset"
|
||||||
},
|
},
|
||||||
"Accumulated Depreciation": {
|
"Accumulated Depreciation": {
|
||||||
"account_type": "Accumulated Depreciation"
|
"account_type": "Accumulated Depreciation"
|
||||||
@ -60,36 +60,46 @@
|
|||||||
"Durchlaufende Posten": {
|
"Durchlaufende Posten": {
|
||||||
"account_number": "1590"
|
"account_number": "1590"
|
||||||
},
|
},
|
||||||
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
|
"Verrechnungskonto Gewinnermittlung § 4 Abs. 3 EStG, nicht ergebniswirksam": {
|
||||||
"account_number": "1371"
|
"account_number": "1371"
|
||||||
},
|
},
|
||||||
"Abziehbare Vorsteuer": {
|
"Abziehbare Vorsteuer": {
|
||||||
"account_type": "Tax",
|
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Abziehbare Vorsteuer 7%": {
|
"Abziehbare Vorsteuer 7 %": {
|
||||||
"account_number": "1571"
|
"account_number": "1571",
|
||||||
|
"account_type": "Tax",
|
||||||
|
"tax_rate": 7.0
|
||||||
},
|
},
|
||||||
"Abziehbare Vorsteuer 19%": {
|
"Abziehbare Vorsteuer 19 %": {
|
||||||
"account_number": "1576"
|
"account_number": "1576",
|
||||||
|
"account_type": "Tax",
|
||||||
|
"tax_rate": 19.0
|
||||||
},
|
},
|
||||||
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
|
"Abziehbare Vorsteuer nach § 13b UStG 19 %": {
|
||||||
"account_number": "1577"
|
"account_number": "1577",
|
||||||
},
|
"account_type": "Tax",
|
||||||
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
|
"tax_rate": 19.0
|
||||||
"account_number": "3120"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"III. Wertpapiere": {
|
"III. Wertpapiere": {
|
||||||
"is_group": 1
|
"is_group": 1,
|
||||||
|
"Anteile an verbundenen Unternehmen (Umlaufvermögen)": {
|
||||||
|
"account_number": "1340"
|
||||||
|
},
|
||||||
|
"Anteile an herrschender oder mit Mehrheit beteiligter Gesellschaft": {
|
||||||
|
"account_number": "1344"
|
||||||
|
},
|
||||||
|
"Sonstige Wertpapiere": {
|
||||||
|
"account_number": "1348"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
|
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Kasse": {
|
"Kasse": {
|
||||||
"account_type": "Cash",
|
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
"account_type": "Cash",
|
||||||
"Kasse": {
|
"Kasse": {
|
||||||
"is_group": 1,
|
|
||||||
"account_number": "1000",
|
"account_number": "1000",
|
||||||
"account_type": "Cash"
|
"account_type": "Cash"
|
||||||
}
|
}
|
||||||
@ -111,21 +121,21 @@
|
|||||||
"C - Rechnungsabgrenzungsposten": {
|
"C - Rechnungsabgrenzungsposten": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Aktive Rechnungsabgrenzung": {
|
"Aktive Rechnungsabgrenzung": {
|
||||||
"account_number": "0980"
|
"account_number": "0980"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"D - Aktive latente Steuern": {
|
"D - Aktive latente Steuern": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Aktive latente Steuern": {
|
"Aktive latente Steuern": {
|
||||||
"account_number": "0983"
|
"account_number": "0983"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
|
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Passiva": {
|
"Passiva": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"root_type": "Liability",
|
"root_type": "Liability",
|
||||||
"A. Eigenkapital": {
|
"A. Eigenkapital": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
@ -200,26 +210,32 @@
|
|||||||
},
|
},
|
||||||
"Umsatzsteuer": {
|
"Umsatzsteuer": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"account_type": "Tax",
|
"Umsatzsteuer 7 %": {
|
||||||
"Umsatzsteuer 7%": {
|
"account_number": "1771",
|
||||||
"account_number": "1771"
|
"account_type": "Tax",
|
||||||
|
"tax_rate": 7.0
|
||||||
},
|
},
|
||||||
"Umsatzsteuer 19%": {
|
"Umsatzsteuer 19 %": {
|
||||||
"account_number": "1776"
|
"account_number": "1776",
|
||||||
|
"account_type": "Tax",
|
||||||
|
"tax_rate": 19.0
|
||||||
},
|
},
|
||||||
"Umsatzsteuer-Vorauszahlung": {
|
"Umsatzsteuer-Vorauszahlung": {
|
||||||
"account_number": "1780"
|
"account_number": "1780",
|
||||||
|
"account_type": "Tax"
|
||||||
},
|
},
|
||||||
"Umsatzsteuer-Vorauszahlung 1/11": {
|
"Umsatzsteuer-Vorauszahlung 1/11": {
|
||||||
"account_number": "1781"
|
"account_number": "1781"
|
||||||
},
|
},
|
||||||
"Umsatzsteuer \u00a7 13b UStG 19%": {
|
"Umsatzsteuer nach § 13b UStG 19 %": {
|
||||||
"account_number": "1787"
|
"account_number": "1787",
|
||||||
|
"account_type": "Tax",
|
||||||
|
"tax_rate": 19.0
|
||||||
},
|
},
|
||||||
"Umsatzsteuer Vorjahr": {
|
"Umsatzsteuer Vorjahr": {
|
||||||
"account_number": "1790"
|
"account_number": "1790"
|
||||||
},
|
},
|
||||||
"Umsatzsteuer fr\u00fchere Jahre": {
|
"Umsatzsteuer frühere Jahre": {
|
||||||
"account_number": "1791"
|
"account_number": "1791"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,44 +250,56 @@
|
|||||||
"E. Passive latente Steuern": {
|
"E. Passive latente Steuern": {
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Erl\u00f6se u. Ertr\u00e4ge 2/8": {
|
"Erlöse u. Erträge 2/8": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"root_type": "Income",
|
"root_type": "Income",
|
||||||
"Erl\u00f6skonten 8": {
|
"Erlöskonten 8": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Erl\u00f6se": {
|
"Erlöse": {
|
||||||
"account_number": "8200",
|
"account_number": "8200",
|
||||||
"account_type": "Income Account"
|
"account_type": "Income Account"
|
||||||
},
|
},
|
||||||
"Erl\u00f6se USt. 19%": {
|
"Erlöse USt. 19 %": {
|
||||||
"account_number": "8400",
|
"account_number": "8400",
|
||||||
"account_type": "Income Account"
|
"account_type": "Income Account"
|
||||||
},
|
},
|
||||||
"Erl\u00f6se USt. 7%": {
|
"Erlöse USt. 7 %": {
|
||||||
"account_number": "8300",
|
"account_number": "8300",
|
||||||
"account_type": "Income Account"
|
"account_type": "Income Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Ertragskonten 2": {
|
"Ertragskonten 2": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
|
"sonstige Zinsen und ähnliche Erträge": {
|
||||||
"account_number": "2650",
|
"account_number": "2650",
|
||||||
"account_type": "Income Account"
|
"account_type": "Income Account"
|
||||||
},
|
},
|
||||||
"Au\u00dferordentliche Ertr\u00e4ge": {
|
"Außerordentliche Erträge": {
|
||||||
"account_number": "2500",
|
"account_number": "2500",
|
||||||
"account_type": "Income Account"
|
"account_type": "Income Account"
|
||||||
},
|
},
|
||||||
"Sonstige Ertr\u00e4ge": {
|
"Sonstige Erträge": {
|
||||||
"account_number": "2700",
|
"account_number": "2700",
|
||||||
"account_type": "Income Account"
|
"account_type": "Income Account"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Aufwendungen 2/4": {
|
"Aufwendungen 2/4": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"root_type": "Expense",
|
"root_type": "Expense",
|
||||||
|
"Fremdleistungen": {
|
||||||
|
"account_number": "3100",
|
||||||
|
"account_type": "Expense Account"
|
||||||
|
},
|
||||||
|
"Fremdleistungen ohne Vorsteuer": {
|
||||||
|
"account_number": "3109",
|
||||||
|
"account_type": "Expense Account"
|
||||||
|
},
|
||||||
|
"Bauleistungen eines im Inland ansässigen Unternehmers 19 % Vorsteuer und 19 % Umsatzsteuer": {
|
||||||
|
"account_number": "3120",
|
||||||
|
"account_type": "Expense Account"
|
||||||
|
},
|
||||||
"Wareneingang": {
|
"Wareneingang": {
|
||||||
"account_number": "3200"
|
"account_number": "3200"
|
||||||
},
|
},
|
||||||
@ -298,234 +326,234 @@
|
|||||||
"Gegenkonto 4996-4998": {
|
"Gegenkonto 4996-4998": {
|
||||||
"account_number": "4999"
|
"account_number": "4999"
|
||||||
},
|
},
|
||||||
"Abschreibungen": {
|
"Abschreibungen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
|
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
|
||||||
"account_number": "4830",
|
"account_number": "4830",
|
||||||
"account_type": "Accumulated Depreciation"
|
"account_type": "Accumulated Depreciation"
|
||||||
},
|
},
|
||||||
"Abschreibungen auf Gebäude": {
|
"Abschreibungen auf Gebäude": {
|
||||||
"account_number": "4831",
|
"account_number": "4831",
|
||||||
"account_type": "Depreciation"
|
"account_type": "Depreciation"
|
||||||
},
|
},
|
||||||
"Abschreibungen auf Kfz": {
|
"Abschreibungen auf Kfz": {
|
||||||
"account_number": "4832",
|
"account_number": "4832",
|
||||||
"account_type": "Depreciation"
|
"account_type": "Depreciation"
|
||||||
},
|
},
|
||||||
"Sofortabschreibung GWG": {
|
"Sofortabschreibung GWG": {
|
||||||
"account_number": "4855",
|
"account_number": "4855",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Kfz-Kosten": {
|
"Kfz-Kosten": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Kfz-Steuer": {
|
"Kfz-Steuer": {
|
||||||
"account_number": "4510",
|
"account_number": "4510",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Kfz-Versicherungen": {
|
"Kfz-Versicherungen": {
|
||||||
"account_number": "4520",
|
"account_number": "4520",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"laufende Kfz-Betriebskosten": {
|
"laufende Kfz-Betriebskosten": {
|
||||||
"account_number": "4530",
|
"account_number": "4530",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Kfz-Reparaturen": {
|
"Kfz-Reparaturen": {
|
||||||
"account_number": "4540",
|
"account_number": "4540",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Fremdfahrzeuge": {
|
"Fremdfahrzeuge": {
|
||||||
"account_number": "4570",
|
"account_number": "4570",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"sonstige Kfz-Kosten": {
|
"sonstige Kfz-Kosten": {
|
||||||
"account_number": "4580",
|
"account_number": "4580",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Personalkosten": {
|
"Personalkosten": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Geh\u00e4lter": {
|
"Gehälter": {
|
||||||
"account_number": "4120",
|
"account_number": "4120",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"gesetzliche soziale Aufwendungen": {
|
"gesetzliche soziale Aufwendungen": {
|
||||||
"account_number": "4130",
|
"account_number": "4130",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Aufwendungen f\u00fcr Altersvorsorge": {
|
"Aufwendungen für Altersvorsorge": {
|
||||||
"account_number": "4165",
|
"account_number": "4165",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Verm\u00f6genswirksame Leistungen": {
|
"Vermögenswirksame Leistungen": {
|
||||||
"account_number": "4170",
|
"account_number": "4170",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Aushilfsl\u00f6hne": {
|
"Aushilfslöhne": {
|
||||||
"account_number": "4190",
|
"account_number": "4190",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Raumkosten": {
|
"Raumkosten": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Miete und Nebenkosten": {
|
"Miete und Nebenkosten": {
|
||||||
"account_number": "4210",
|
"account_number": "4210",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
|
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
|
||||||
"account_number": "4240",
|
"account_number": "4240",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Reinigung": {
|
"Reinigung": {
|
||||||
"account_number": "4250",
|
"account_number": "4250",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Reparatur/Instandhaltung": {
|
"Reparatur/Instandhaltung": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
|
"Reparaturen und Instandhaltungen von anderen Anlagen und Betriebs- und Geschäftsausstattung": {
|
||||||
"account_number": "4805",
|
"account_number": "4805",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Versicherungsbeitr\u00e4ge": {
|
"Versicherungsbeiträge": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Versicherungen": {
|
"Versicherungen": {
|
||||||
"account_number": "4360",
|
"account_number": "4360",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Beitr\u00e4ge": {
|
"Beiträge": {
|
||||||
"account_number": "4380",
|
"account_number": "4380",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"sonstige Ausgaben": {
|
"sonstige Ausgaben": {
|
||||||
"account_number": "4390",
|
"account_number": "4390",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
|
"steuerlich abzugsfähige Verspätungszuschläge und Zwangsgelder": {
|
||||||
"account_number": "4396",
|
"account_number": "4396",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Werbe-/Reisekosten": {
|
"Werbe-/Reisekosten": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Werbekosten": {
|
"Werbekosten": {
|
||||||
"account_number": "4610",
|
"account_number": "4610",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Aufmerksamkeiten": {
|
"Aufmerksamkeiten": {
|
||||||
"account_number": "4653",
|
"account_number": "4653",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
|
"nicht abzugsfähige Betriebsausg. aus Werbe-, Repräs.- u. Reisekosten": {
|
||||||
"account_number": "4665",
|
"account_number": "4665",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Reisekosten Unternehmer": {
|
"Reisekosten Unternehmer": {
|
||||||
"account_number": "4670",
|
"account_number": "4670",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"verschiedene Kosten": {
|
"verschiedene Kosten": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Porto": {
|
"Porto": {
|
||||||
"account_number": "4910",
|
"account_number": "4910",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Telekom": {
|
"Telekom": {
|
||||||
"account_number": "4920",
|
"account_number": "4920",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Mobilfunk D2": {
|
"Mobilfunk D2": {
|
||||||
"account_number": "4921",
|
"account_number": "4921",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Internet": {
|
"Internet": {
|
||||||
"account_number": "4922",
|
"account_number": "4922",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"B\u00fcrobedarf": {
|
"Bürobedarf": {
|
||||||
"account_number": "4930",
|
"account_number": "4930",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Zeitschriften, B\u00fccher": {
|
"Zeitschriften, Bücher": {
|
||||||
"account_number": "4940",
|
"account_number": "4940",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Fortbildungskosten": {
|
"Fortbildungskosten": {
|
||||||
"account_number": "4945",
|
"account_number": "4945",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Buchf\u00fchrungskosten": {
|
"Buchführungskosten": {
|
||||||
"account_number": "4955",
|
"account_number": "4955",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Abschlu\u00df- u. Pr\u00fcfungskosten": {
|
"Abschluß- u. Prüfungskosten": {
|
||||||
"account_number": "4957",
|
"account_number": "4957",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Nebenkosten des Geldverkehrs": {
|
"Nebenkosten des Geldverkehrs": {
|
||||||
"account_number": "4970",
|
"account_number": "4970",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Werkzeuge und Kleinger\u00e4te": {
|
"Werkzeuge und Kleingeräte": {
|
||||||
"account_number": "4985",
|
"account_number": "4985",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Zinsaufwendungen": {
|
"Zinsaufwendungen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
|
"Zinsaufwendungen für kurzfristige Verbindlichkeiten": {
|
||||||
"account_number": "2110",
|
"account_number": "2110",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
},
|
},
|
||||||
"Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
|
"Zinsaufwendungen für KFZ Finanzierung": {
|
||||||
"account_number": "2121",
|
"account_number": "2121",
|
||||||
"account_type": "Expense Account"
|
"account_type": "Expense Account"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Anfangsbestand 9": {
|
"Anfangsbestand 9": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"root_type": "Equity",
|
"root_type": "Equity",
|
||||||
"Saldenvortragskonten": {
|
"Saldenvortragskonten": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Saldenvortrag Sachkonten": {
|
"Saldenvortrag Sachkonten": {
|
||||||
"account_number": "9000"
|
"account_number": "9000"
|
||||||
},
|
},
|
||||||
"Saldenvortr\u00e4ge Debitoren": {
|
"Saldenvorträge Debitoren": {
|
||||||
"account_number": "9008"
|
"account_number": "9008"
|
||||||
},
|
},
|
||||||
"Saldenvortr\u00e4ge Kreditoren": {
|
"Saldenvorträge Kreditoren": {
|
||||||
"account_number": "9009"
|
"account_number": "9009"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Privatkonten 1": {
|
"Privatkonten 1": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"root_type": "Equity",
|
"root_type": "Equity",
|
||||||
"Privatentnahmen/-einlagen": {
|
"Privatentnahmen/-einlagen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"Privatentnahme allgemein": {
|
"Privatentnahme allgemein": {
|
||||||
"account_number": "1800"
|
"account_number": "1800"
|
||||||
},
|
},
|
||||||
"Privatsteuern": {
|
"Privatsteuern": {
|
||||||
"account_number": "1810"
|
"account_number": "1810"
|
||||||
},
|
},
|
||||||
"Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
|
"Sonderausgaben beschränkt abzugsfähig": {
|
||||||
"account_number": "1820"
|
"account_number": "1820"
|
||||||
},
|
},
|
||||||
"Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
|
"Sonderausgaben unbeschränkt abzugsfähig": {
|
||||||
"account_number": "1830"
|
"account_number": "1830"
|
||||||
},
|
},
|
||||||
"Au\u00dfergew\u00f6hnliche Belastungen": {
|
"Außergewöhnliche Belastungen": {
|
||||||
"account_number": "1850"
|
"account_number": "1850"
|
||||||
},
|
},
|
||||||
"Privateinlagen": {
|
"Privateinlagen": {
|
||||||
"account_number": "1890"
|
"account_number": "1890"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,9 @@
|
|||||||
"acc_frozen_upto",
|
"acc_frozen_upto",
|
||||||
"column_break_25",
|
"column_break_25",
|
||||||
"frozen_accounts_modifier",
|
"frozen_accounts_modifier",
|
||||||
"report_settings_sb"
|
"report_settings_sb",
|
||||||
|
"tab_break_dpet",
|
||||||
|
"show_balance_in_coa"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -347,6 +349,17 @@
|
|||||||
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
|
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow multi-currency invoices against single party account "
|
"label": "Allow multi-currency invoices against single party account "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tab_break_dpet",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Chart Of Accounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_balance_in_coa",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Show Balances in Chart Of Accounts"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -354,7 +367,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-27 21:49:52.538655",
|
"modified": "2023-01-02 12:07:42.434214",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
@ -28,9 +28,14 @@ class InvalidDateError(frappe.ValidationError):
|
|||||||
|
|
||||||
|
|
||||||
class CostCenterAllocation(Document):
|
class CostCenterAllocation(Document):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(CostCenterAllocation, self).__init__(*args, **kwargs)
|
||||||
|
self._skip_from_date_validation = False
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_total_allocation_percentage()
|
self.validate_total_allocation_percentage()
|
||||||
self.validate_from_date_based_on_existing_gle()
|
if not self._skip_from_date_validation:
|
||||||
|
self.validate_from_date_based_on_existing_gle()
|
||||||
self.validate_backdated_allocation()
|
self.validate_backdated_allocation()
|
||||||
self.validate_main_cost_center()
|
self.validate_main_cost_center()
|
||||||
self.validate_child_cost_centers()
|
self.validate_child_cost_centers()
|
||||||
|
@ -40,7 +40,7 @@ class Dunning(AccountsController):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
if self.dunning_amount:
|
if self.dunning_amount:
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||||
|
|
||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
|
@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
|
|||||||
frappe.ui.form.on("Journal Entry", {
|
frappe.ui.form.on("Journal Entry", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.add_fetch("bank_account", "account", "account");
|
frm.add_fetch("bank_account", "account", "account");
|
||||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
|
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry'];
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -81,6 +81,7 @@ class JournalEntry(AccountsController):
|
|||||||
self.check_credit_limit()
|
self.check_credit_limit()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
|
self.update_asset_value()
|
||||||
self.update_inter_company_jv()
|
self.update_inter_company_jv()
|
||||||
self.update_invoice_discounting()
|
self.update_invoice_discounting()
|
||||||
|
|
||||||
@ -225,6 +226,29 @@ class JournalEntry(AccountsController):
|
|||||||
for d in to_remove:
|
for d in to_remove:
|
||||||
self.remove(d)
|
self.remove(d)
|
||||||
|
|
||||||
|
def update_asset_value(self):
|
||||||
|
if self.voucher_type != "Depreciation Entry":
|
||||||
|
return
|
||||||
|
|
||||||
|
processed_assets = []
|
||||||
|
|
||||||
|
for d in self.get("accounts"):
|
||||||
|
if (
|
||||||
|
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
|
||||||
|
):
|
||||||
|
processed_assets.append(d.reference_name)
|
||||||
|
|
||||||
|
asset = frappe.get_doc("Asset", d.reference_name)
|
||||||
|
|
||||||
|
if asset.calculate_depreciation:
|
||||||
|
continue
|
||||||
|
|
||||||
|
depr_value = d.debit or d.credit
|
||||||
|
|
||||||
|
asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
|
||||||
|
|
||||||
|
asset.set_status()
|
||||||
|
|
||||||
def update_inter_company_jv(self):
|
def update_inter_company_jv(self):
|
||||||
if (
|
if (
|
||||||
self.voucher_type == "Inter Company Journal Entry"
|
self.voucher_type == "Inter Company Journal Entry"
|
||||||
@ -283,20 +307,45 @@ class JournalEntry(AccountsController):
|
|||||||
d.db_update()
|
d.db_update()
|
||||||
|
|
||||||
def unlink_asset_reference(self):
|
def unlink_asset_reference(self):
|
||||||
|
if self.voucher_type != "Depreciation Entry":
|
||||||
|
return
|
||||||
|
|
||||||
|
processed_assets = []
|
||||||
|
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if d.reference_type == "Asset" and d.reference_name:
|
if (
|
||||||
|
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
|
||||||
|
):
|
||||||
|
processed_assets.append(d.reference_name)
|
||||||
|
|
||||||
asset = frappe.get_doc("Asset", d.reference_name)
|
asset = frappe.get_doc("Asset", d.reference_name)
|
||||||
for row in asset.get("finance_books"):
|
|
||||||
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
|
|
||||||
|
|
||||||
for s in depr_schedule or []:
|
if asset.calculate_depreciation:
|
||||||
if s.journal_entry == self.name:
|
je_found = False
|
||||||
s.db_set("journal_entry", None)
|
|
||||||
|
|
||||||
row.value_after_depreciation += s.depreciation_amount
|
for row in asset.get("finance_books"):
|
||||||
row.db_update()
|
if je_found:
|
||||||
|
break
|
||||||
|
|
||||||
asset.set_status()
|
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
|
||||||
|
|
||||||
|
for s in depr_schedule or []:
|
||||||
|
if s.journal_entry == self.name:
|
||||||
|
s.db_set("journal_entry", None)
|
||||||
|
|
||||||
|
row.value_after_depreciation += s.depreciation_amount
|
||||||
|
row.db_update()
|
||||||
|
|
||||||
|
asset.set_status()
|
||||||
|
|
||||||
|
je_found = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
depr_value = d.debit or d.credit
|
||||||
|
|
||||||
|
asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
|
||||||
|
|
||||||
|
asset.set_status()
|
||||||
|
|
||||||
def unlink_inter_company_jv(self):
|
def unlink_inter_company_jv(self):
|
||||||
if (
|
if (
|
||||||
|
@ -239,7 +239,7 @@
|
|||||||
"depends_on": "paid_from",
|
"depends_on": "paid_from",
|
||||||
"fieldname": "paid_from_account_currency",
|
"fieldname": "paid_from_account_currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Account Currency",
|
"label": "Account Currency (From)",
|
||||||
"options": "Currency",
|
"options": "Currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
@ -249,7 +249,7 @@
|
|||||||
"depends_on": "paid_from",
|
"depends_on": "paid_from",
|
||||||
"fieldname": "paid_from_account_balance",
|
"fieldname": "paid_from_account_balance",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Account Balance",
|
"label": "Account Balance (From)",
|
||||||
"options": "paid_from_account_currency",
|
"options": "paid_from_account_currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -272,7 +272,7 @@
|
|||||||
"depends_on": "paid_to",
|
"depends_on": "paid_to",
|
||||||
"fieldname": "paid_to_account_currency",
|
"fieldname": "paid_to_account_currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Account Currency",
|
"label": "Account Currency (To)",
|
||||||
"options": "Currency",
|
"options": "Currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
@ -282,7 +282,7 @@
|
|||||||
"depends_on": "paid_to",
|
"depends_on": "paid_to",
|
||||||
"fieldname": "paid_to_account_balance",
|
"fieldname": "paid_to_account_balance",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Account Balance",
|
"label": "Account Balance (To)",
|
||||||
"options": "paid_to_account_currency",
|
"options": "paid_to_account_currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -304,7 +304,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "source_exchange_rate",
|
"fieldname": "source_exchange_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Exchange Rate",
|
"label": "Source Exchange Rate",
|
||||||
"precision": "9",
|
"precision": "9",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
@ -334,7 +334,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "target_exchange_rate",
|
"fieldname": "target_exchange_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Exchange Rate",
|
"label": "Target Exchange Rate",
|
||||||
"precision": "9",
|
"precision": "9",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
@ -633,14 +633,14 @@
|
|||||||
"depends_on": "eval:doc.party_type == 'Supplier'",
|
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||||
"fieldname": "purchase_taxes_and_charges_template",
|
"fieldname": "purchase_taxes_and_charges_template",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Taxes and Charges Template",
|
"label": "Purchase Taxes and Charges Template",
|
||||||
"options": "Purchase Taxes and Charges Template"
|
"options": "Purchase Taxes and Charges Template"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.party_type == 'Customer'",
|
"depends_on": "eval: doc.party_type == 'Customer'",
|
||||||
"fieldname": "sales_taxes_and_charges_template",
|
"fieldname": "sales_taxes_and_charges_template",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Taxes and Charges Template",
|
"label": "Sales Taxes and Charges Template",
|
||||||
"options": "Sales Taxes and Charges Template"
|
"options": "Sales Taxes and Charges Template"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -733,7 +733,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-12-08 16:25:43.824051",
|
"modified": "2023-02-14 04:52:30.478523",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
@ -69,6 +69,10 @@ class PaymentReconciliation(Document):
|
|||||||
|
|
||||||
def get_jv_entries(self):
|
def get_jv_entries(self):
|
||||||
condition = self.get_conditions()
|
condition = self.get_conditions()
|
||||||
|
|
||||||
|
if self.get("cost_center"):
|
||||||
|
condition += f" and t2.cost_center = '{self.cost_center}' "
|
||||||
|
|
||||||
dr_or_cr = (
|
dr_or_cr = (
|
||||||
"credit_in_account_currency"
|
"credit_in_account_currency"
|
||||||
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
||||||
@ -230,7 +234,7 @@ class PaymentReconciliation(Document):
|
|||||||
def allocate_entries(self, args):
|
def allocate_entries(self, args):
|
||||||
self.validate_entries()
|
self.validate_entries()
|
||||||
|
|
||||||
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"))
|
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
|
||||||
default_exchange_gain_loss_account = frappe.get_cached_value(
|
default_exchange_gain_loss_account = frappe.get_cached_value(
|
||||||
"Company", self.company, "exchange_gain_loss_account"
|
"Company", self.company, "exchange_gain_loss_account"
|
||||||
)
|
)
|
||||||
@ -249,6 +253,9 @@ class PaymentReconciliation(Document):
|
|||||||
pay["amount"] = 0
|
pay["amount"] = 0
|
||||||
|
|
||||||
inv["exchange_rate"] = invoice_exchange_map.get(inv.get("invoice_number"))
|
inv["exchange_rate"] = invoice_exchange_map.get(inv.get("invoice_number"))
|
||||||
|
if pay.get("reference_type") in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
|
pay["exchange_rate"] = invoice_exchange_map.get(pay.get("reference_name"))
|
||||||
|
|
||||||
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
||||||
res.difference_account = default_exchange_gain_loss_account
|
res.difference_account = default_exchange_gain_loss_account
|
||||||
res.exchange_rate = inv.get("exchange_rate")
|
res.exchange_rate = inv.get("exchange_rate")
|
||||||
@ -403,13 +410,21 @@ class PaymentReconciliation(Document):
|
|||||||
if not self.get("payments"):
|
if not self.get("payments"):
|
||||||
frappe.throw(_("No records found in the Payments table"))
|
frappe.throw(_("No records found in the Payments table"))
|
||||||
|
|
||||||
def get_invoice_exchange_map(self, invoices):
|
def get_invoice_exchange_map(self, invoices, payments):
|
||||||
sales_invoices = [
|
sales_invoices = [
|
||||||
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Sales Invoice"
|
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Sales Invoice"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
sales_invoices.extend(
|
||||||
|
[d.get("reference_name") for d in payments if d.get("reference_type") == "Sales Invoice"]
|
||||||
|
)
|
||||||
purchase_invoices = [
|
purchase_invoices = [
|
||||||
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Purchase Invoice"
|
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Purchase Invoice"
|
||||||
]
|
]
|
||||||
|
purchase_invoices.extend(
|
||||||
|
[d.get("reference_name") for d in payments if d.get("reference_type") == "Purchase Invoice"]
|
||||||
|
)
|
||||||
|
|
||||||
invoice_exchange_map = frappe._dict()
|
invoice_exchange_map = frappe._dict()
|
||||||
|
|
||||||
if sales_invoices:
|
if sales_invoices:
|
||||||
|
@ -473,6 +473,11 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
invoices = [x.as_dict() for x in pr.get("invoices")]
|
invoices = [x.as_dict() for x in pr.get("invoices")]
|
||||||
payments = [x.as_dict() for x in pr.get("payments")]
|
payments = [x.as_dict() for x in pr.get("payments")]
|
||||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
|
||||||
|
# Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
|
||||||
|
for row in pr.allocation:
|
||||||
|
self.assertEqual(flt(row.get("difference_amount")), 0.0)
|
||||||
|
|
||||||
pr.reconcile()
|
pr.reconcile()
|
||||||
|
|
||||||
pr.get_unreconciled_entries()
|
pr.get_unreconciled_entries()
|
||||||
@ -506,6 +511,11 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
payments = [x.as_dict() for x in pr.get("payments")]
|
payments = [x.as_dict() for x in pr.get("payments")]
|
||||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
pr.allocation[0].allocated_amount = allocated_amount
|
pr.allocation[0].allocated_amount = allocated_amount
|
||||||
|
|
||||||
|
# Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
|
||||||
|
for row in pr.allocation:
|
||||||
|
self.assertEqual(flt(row.get("difference_amount")), 0.0)
|
||||||
|
|
||||||
pr.reconcile()
|
pr.reconcile()
|
||||||
|
|
||||||
# assert outstanding
|
# assert outstanding
|
||||||
@ -747,6 +757,73 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.assertEqual(len(pr.get("invoices")), 0)
|
self.assertEqual(len(pr.get("invoices")), 0)
|
||||||
self.assertEqual(len(pr.get("payments")), 0)
|
self.assertEqual(len(pr.get("payments")), 0)
|
||||||
|
|
||||||
|
def test_cost_center_filter_on_vouchers(self):
|
||||||
|
"""
|
||||||
|
Test Cost Center filter is applied on Invoices, Payment Entries and Journals
|
||||||
|
"""
|
||||||
|
transaction_date = nowdate()
|
||||||
|
rate = 100
|
||||||
|
|
||||||
|
# 'Main - PR' Cost Center
|
||||||
|
si1 = self.create_sales_invoice(
|
||||||
|
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
|
||||||
|
)
|
||||||
|
si1.cost_center = self.main_cc.name
|
||||||
|
si1.submit()
|
||||||
|
|
||||||
|
pe1 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
|
||||||
|
pe1.cost_center = self.main_cc.name
|
||||||
|
pe1 = pe1.save().submit()
|
||||||
|
|
||||||
|
je1 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
|
||||||
|
je1.accounts[0].cost_center = self.main_cc.name
|
||||||
|
je1.accounts[1].cost_center = self.main_cc.name
|
||||||
|
je1.accounts[1].party_type = "Customer"
|
||||||
|
je1.accounts[1].party = self.customer
|
||||||
|
je1 = je1.save().submit()
|
||||||
|
|
||||||
|
# 'Sub - PR' Cost Center
|
||||||
|
si2 = self.create_sales_invoice(
|
||||||
|
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
|
||||||
|
)
|
||||||
|
si2.cost_center = self.sub_cc.name
|
||||||
|
si2.submit()
|
||||||
|
|
||||||
|
pe2 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
|
||||||
|
pe2.cost_center = self.sub_cc.name
|
||||||
|
pe2 = pe2.save().submit()
|
||||||
|
|
||||||
|
je2 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
|
||||||
|
je2.accounts[0].cost_center = self.sub_cc.name
|
||||||
|
je2.accounts[1].cost_center = self.sub_cc.name
|
||||||
|
je2.accounts[1].party_type = "Customer"
|
||||||
|
je2.accounts[1].party = self.customer
|
||||||
|
je2 = je2.save().submit()
|
||||||
|
|
||||||
|
pr = self.create_payment_reconciliation()
|
||||||
|
pr.cost_center = self.main_cc.name
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
# check PR tool output
|
||||||
|
self.assertEqual(len(pr.get("invoices")), 1)
|
||||||
|
self.assertEqual(pr.get("invoices")[0].get("invoice_number"), si1.name)
|
||||||
|
self.assertEqual(len(pr.get("payments")), 2)
|
||||||
|
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
|
||||||
|
self.assertCountEqual(payment_vouchers, [pe1.name, je1.name])
|
||||||
|
|
||||||
|
# Change cost center
|
||||||
|
pr.cost_center = self.sub_cc.name
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
# check PR tool output
|
||||||
|
self.assertEqual(len(pr.get("invoices")), 1)
|
||||||
|
self.assertEqual(pr.get("invoices")[0].get("invoice_number"), si2.name)
|
||||||
|
self.assertEqual(len(pr.get("payments")), 2)
|
||||||
|
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
|
||||||
|
self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
|
||||||
|
|
||||||
|
|
||||||
def make_customer(customer_name, currency=None):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
@ -45,21 +45,20 @@ class PaymentRequest(Document):
|
|||||||
frappe.throw(_("To create a Payment Request reference document is required"))
|
frappe.throw(_("To create a Payment Request reference document is required"))
|
||||||
|
|
||||||
def validate_payment_request_amount(self):
|
def validate_payment_request_amount(self):
|
||||||
existing_payment_request_amount = get_existing_payment_request_amount(
|
existing_payment_request_amount = flt(
|
||||||
self.reference_doctype, self.reference_name
|
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
if existing_payment_request_amount:
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart":
|
||||||
if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") != "Shopping Cart":
|
ref_amount = get_amount(ref_doc, self.payment_account)
|
||||||
ref_amount = get_amount(ref_doc, self.payment_account)
|
|
||||||
|
|
||||||
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
|
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Total Payment Request amount cannot be greater than {0} amount").format(
|
_("Total Payment Request amount cannot be greater than {0} amount").format(
|
||||||
self.reference_doctype
|
self.reference_doctype
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_currency(self):
|
def validate_currency(self):
|
||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
|
@ -472,7 +472,7 @@
|
|||||||
"description": "If rate is zero them item will be treated as \"Free Item\"",
|
"description": "If rate is zero them item will be treated as \"Free Item\"",
|
||||||
"fieldname": "free_item_rate",
|
"fieldname": "free_item_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rate"
|
"label": "Free Item Rate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@ -608,7 +608,7 @@
|
|||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-10-13 19:05:35.056304",
|
"modified": "2023-02-14 04:53:34.887358",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
@ -1426,6 +1426,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "apply_tds",
|
||||||
"fieldname": "tax_withholding_net_total",
|
"fieldname": "tax_withholding_net_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -1435,12 +1436,13 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "apply_tds",
|
||||||
"fieldname": "base_tax_withholding_net_total",
|
"fieldname": "base_tax_withholding_net_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Base Tax Withholding Net Total",
|
"label": "Base Tax Withholding Net Total",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1554,7 +1556,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-12-12 18:37:38.142688",
|
"modified": "2023-01-28 19:18:56.586321",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -1776,6 +1776,8 @@
|
|||||||
"width": "50%"
|
"width": "50%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "sales_partner.commission_rate",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "commission_rate",
|
"fieldname": "commission_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -2141,7 +2143,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-12-12 18:34:33.409895",
|
"modified": "2023-01-28 19:45:47.538163",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -211,7 +211,13 @@ def set_address_details(
|
|||||||
else:
|
else:
|
||||||
party_details.update(get_company_address(company))
|
party_details.update(get_company_address(company))
|
||||||
|
|
||||||
if doctype and doctype in ["Delivery Note", "Sales Invoice", "Sales Order", "Quotation"]:
|
if doctype and doctype in [
|
||||||
|
"Delivery Note",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Sales Order",
|
||||||
|
"Quotation",
|
||||||
|
"POS Invoice",
|
||||||
|
]:
|
||||||
if party_details.company_address:
|
if party_details.company_address:
|
||||||
party_details.update(
|
party_details.update(
|
||||||
get_fetch_values(doctype, "company_address", party_details.company_address)
|
get_fetch_values(doctype, "company_address", party_details.company_address)
|
||||||
@ -544,7 +550,7 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
|
|||||||
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
|
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
|
||||||
due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days))
|
due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days))
|
||||||
else:
|
else:
|
||||||
due_date = max(due_date, add_months(get_last_day(due_date), term.credit_months))
|
due_date = max(due_date, get_last_day(add_months(due_date, term.credit_months)))
|
||||||
return due_date
|
return due_date
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,6 +135,34 @@ def get_assets(filters):
|
|||||||
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and ads.asset = a.name and ads.docstatus=1 and ads.name = ds.parent and ifnull(ds.journal_entry, '') != ''
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and ads.asset = a.name and ads.docstatus=1 and ads.name = ds.parent and ifnull(ds.journal_entry, '') != ''
|
||||||
group by a.asset_category
|
group by a.asset_category
|
||||||
union
|
union
|
||||||
|
SELECT a.asset_category,
|
||||||
|
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
||||||
|
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
|
||||||
|
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_amount_during_the_period
|
||||||
|
from `tabGL Entry` gle
|
||||||
|
join `tabAsset` a on
|
||||||
|
gle.against_voucher = a.name
|
||||||
|
join `tabAsset Category Account` aca on
|
||||||
|
aca.parent = a.asset_category and aca.company_name = %(company)s
|
||||||
|
join `tabCompany` company on
|
||||||
|
company.name = %(company)s
|
||||||
|
where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||||
|
group by a.asset_category
|
||||||
|
union
|
||||||
SELECT a.asset_category,
|
SELECT a.asset_category,
|
||||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
|
||||||
0
|
0
|
||||||
|
@ -526,7 +526,7 @@ def get_columns(filters):
|
|||||||
"options": "GL Entry",
|
"options": "GL Entry",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 90},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
|
||||||
{
|
{
|
||||||
"label": _("Account"),
|
"label": _("Account"),
|
||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
@ -538,13 +538,13 @@ def get_columns(filters):
|
|||||||
"label": _("Debit ({0})").format(currency),
|
"label": _("Debit ({0})").format(currency),
|
||||||
"fieldname": "debit",
|
"fieldname": "debit",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"width": 100,
|
"width": 130,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Credit ({0})").format(currency),
|
"label": _("Credit ({0})").format(currency),
|
||||||
"fieldname": "credit",
|
"fieldname": "credit",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"width": 100,
|
"width": 130,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Balance ({0})").format(currency),
|
"label": _("Balance ({0})").format(currency),
|
||||||
|
@ -50,6 +50,20 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Sales Person"
|
"options": "Sales Person"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"label": __("Warehouse"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Warehouse",
|
||||||
|
"get_query": function () {
|
||||||
|
var company = frappe.query_report.get_filter_value('company');
|
||||||
|
return {
|
||||||
|
filters: [
|
||||||
|
["Warehouse", "company", "=", company]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"tree": true,
|
"tree": true,
|
||||||
"name_field": "parent",
|
"name_field": "parent",
|
||||||
|
@ -655,10 +655,35 @@ class GrossProfitGenerator(object):
|
|||||||
return self.calculate_buying_amount_from_sle(
|
return self.calculate_buying_amount_from_sle(
|
||||||
row, my_sle, parenttype, parent, item_row, item_code
|
row, my_sle, parenttype, parent, item_row, item_code
|
||||||
)
|
)
|
||||||
|
elif row.sales_order and row.so_detail:
|
||||||
|
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
|
||||||
|
if incoming_amount:
|
||||||
|
return incoming_amount
|
||||||
else:
|
else:
|
||||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||||
|
|
||||||
return 0.0
|
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||||
|
|
||||||
|
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
|
|
||||||
|
delivery_note = frappe.qb.DocType("Delivery Note")
|
||||||
|
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(delivery_note)
|
||||||
|
.inner_join(delivery_note_item)
|
||||||
|
.on(delivery_note.name == delivery_note_item.parent)
|
||||||
|
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
|
||||||
|
.where(delivery_note.docstatus == 1)
|
||||||
|
.where(delivery_note_item.item_code == item_code)
|
||||||
|
.where(delivery_note_item.against_sales_order == sales_order)
|
||||||
|
.where(delivery_note_item.so_detail == so_detail)
|
||||||
|
.groupby(delivery_note_item.item_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
incoming_amount = query.run()
|
||||||
|
return flt(incoming_amount[0][0]) if incoming_amount else 0
|
||||||
|
|
||||||
def get_average_buying_rate(self, row, item_code):
|
def get_average_buying_rate(self, row, item_code):
|
||||||
args = row
|
args = row
|
||||||
@ -750,6 +775,13 @@ class GrossProfitGenerator(object):
|
|||||||
if self.filters.get("item_code"):
|
if self.filters.get("item_code"):
|
||||||
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
|
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
|
||||||
|
|
||||||
|
if self.filters.get("warehouse"):
|
||||||
|
warehouse_details = frappe.db.get_value(
|
||||||
|
"Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
||||||
|
)
|
||||||
|
if warehouse_details:
|
||||||
|
conditions += f" and `tabSales Invoice Item`.warehouse in (select name from `tabWarehouse` wh where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
|
||||||
|
|
||||||
self.si_list = frappe.db.sql(
|
self.si_list = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
@ -760,7 +792,8 @@ class GrossProfitGenerator(object):
|
|||||||
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
||||||
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
||||||
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
|
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
|
||||||
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
|
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
|
||||||
|
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
|
||||||
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
||||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||||
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
|
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
|
||||||
|
@ -302,3 +302,82 @@ class TestGrossProfit(FrappeTestCase):
|
|||||||
|
|
||||||
columns, data = execute(filters=filters)
|
columns, data = execute(filters=filters)
|
||||||
self.assertGreater(len(data), 0)
|
self.assertGreater(len(data), 0)
|
||||||
|
|
||||||
|
def test_order_connected_dn_and_inv(self):
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test gp calculation when invoice and delivery note aren't directly connected.
|
||||||
|
SO -- INV
|
||||||
|
|
|
||||||
|
DN
|
||||||
|
"""
|
||||||
|
se = make_stock_entry(
|
||||||
|
company=self.company,
|
||||||
|
item_code=self.item,
|
||||||
|
target=self.warehouse,
|
||||||
|
qty=3,
|
||||||
|
basic_rate=100,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
item = se.items[0]
|
||||||
|
se.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"s_warehouse": item.s_warehouse,
|
||||||
|
"t_warehouse": item.t_warehouse,
|
||||||
|
"qty": 10,
|
||||||
|
"basic_rate": 200,
|
||||||
|
"conversion_factor": item.conversion_factor or 1.0,
|
||||||
|
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
|
||||||
|
"serial_no": item.serial_no,
|
||||||
|
"batch_no": item.batch_no,
|
||||||
|
"cost_center": item.cost_center,
|
||||||
|
"expense_account": item.expense_account,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
se = se.save().submit()
|
||||||
|
|
||||||
|
so = make_sales_order(
|
||||||
|
customer=self.customer,
|
||||||
|
company=self.company,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
item=self.item,
|
||||||
|
qty=4,
|
||||||
|
do_not_save=False,
|
||||||
|
do_not_submit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||||
|
make_delivery_note,
|
||||||
|
make_sales_invoice,
|
||||||
|
)
|
||||||
|
|
||||||
|
make_delivery_note(so.name).submit()
|
||||||
|
sinv = make_sales_invoice(so.name).submit()
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
|
||||||
|
)
|
||||||
|
|
||||||
|
columns, data = execute(filters=filters)
|
||||||
|
expected_entry = {
|
||||||
|
"parent_invoice": sinv.name,
|
||||||
|
"currency": "INR",
|
||||||
|
"sales_invoice": self.item,
|
||||||
|
"customer": self.customer,
|
||||||
|
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
|
||||||
|
"item_code": self.item,
|
||||||
|
"item_name": self.item,
|
||||||
|
"warehouse": "Stores - _GP",
|
||||||
|
"qty": 4.0,
|
||||||
|
"avg._selling_rate": 100.0,
|
||||||
|
"valuation_rate": 125.0,
|
||||||
|
"selling_amount": 400.0,
|
||||||
|
"buying_amount": 500.0,
|
||||||
|
"gross_profit": -100.0,
|
||||||
|
"gross_profit_%": -25.0,
|
||||||
|
}
|
||||||
|
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
||||||
|
self.assertDictContainsSubset(expected_entry, gp_entry[0])
|
||||||
|
@ -1512,9 +1512,12 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
|||||||
ref_doc = frappe.get_doc(voucher_type, voucher_no)
|
ref_doc = frappe.get_doc(voucher_type, voucher_no)
|
||||||
|
|
||||||
# Didn't use db_set for optimisation purpose
|
# Didn't use db_set for optimisation purpose
|
||||||
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"]
|
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"] or 0.0
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
voucher_type, voucher_no, "outstanding_amount", outstanding["outstanding_in_account_currency"]
|
voucher_type,
|
||||||
|
voucher_no,
|
||||||
|
"outstanding_amount",
|
||||||
|
outstanding["outstanding_in_account_currency"] or 0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
ref_doc.set_status(update=True)
|
ref_doc.set_status(update=True)
|
||||||
|
@ -209,23 +209,20 @@ frappe.ui.form.on('Asset', {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var x_intervals = [frm.doc.purchase_date];
|
var x_intervals = [frappe.format(frm.doc.purchase_date, { fieldtype: 'Date' })];
|
||||||
var asset_values = [frm.doc.gross_purchase_amount];
|
var asset_values = [frm.doc.gross_purchase_amount];
|
||||||
var last_depreciation_date = frm.doc.purchase_date;
|
|
||||||
|
|
||||||
if(frm.doc.opening_accumulated_depreciation) {
|
if(frm.doc.calculate_depreciation) {
|
||||||
last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date,
|
if(frm.doc.opening_accumulated_depreciation) {
|
||||||
-1*frm.doc.frequency_of_depreciation);
|
var depreciation_date = frappe.datetime.add_months(
|
||||||
|
frm.doc.finance_books[0].depreciation_start_date,
|
||||||
|
-1 * frm.doc.finance_books[0].frequency_of_depreciation
|
||||||
|
);
|
||||||
|
x_intervals.push(frappe.format(depreciation_date, { fieldtype: 'Date' }));
|
||||||
|
asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
|
||||||
|
}
|
||||||
|
|
||||||
x_intervals.push(last_depreciation_date);
|
let depr_schedule = (await frappe.call(
|
||||||
asset_values.push(flt(frm.doc.gross_purchase_amount) -
|
|
||||||
flt(frm.doc.opening_accumulated_depreciation));
|
|
||||||
}
|
|
||||||
|
|
||||||
let depr_schedule = [];
|
|
||||||
|
|
||||||
if (frm.doc.finance_books.length == 1) {
|
|
||||||
depr_schedule = (await frappe.call(
|
|
||||||
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
|
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
|
||||||
{
|
{
|
||||||
asset_name: frm.doc.name,
|
asset_name: frm.doc.name,
|
||||||
@ -233,27 +230,41 @@ frappe.ui.form.on('Asset', {
|
|||||||
finance_book: frm.doc.finance_books[0].finance_book || null
|
finance_book: frm.doc.finance_books[0].finance_book || null
|
||||||
}
|
}
|
||||||
)).message;
|
)).message;
|
||||||
|
|
||||||
|
$.each(depr_schedule || [], function(i, v) {
|
||||||
|
x_intervals.push(frappe.format(v.schedule_date, { fieldtype: 'Date' }));
|
||||||
|
var asset_value = flt(frm.doc.gross_purchase_amount - v.accumulated_depreciation_amount, precision('gross_purchase_amount'));
|
||||||
|
if(v.journal_entry) {
|
||||||
|
asset_values.push(asset_value);
|
||||||
|
} else {
|
||||||
|
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
|
||||||
|
asset_values.push(null);
|
||||||
|
} else {
|
||||||
|
asset_values.push(asset_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(frm.doc.opening_accumulated_depreciation) {
|
||||||
|
x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
|
||||||
|
asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
|
||||||
|
}
|
||||||
|
|
||||||
|
let depr_entries = (await frappe.call({
|
||||||
|
method: "get_manual_depreciation_entries",
|
||||||
|
doc: frm.doc,
|
||||||
|
})).message;
|
||||||
|
|
||||||
|
$.each(depr_entries || [], function(i, v) {
|
||||||
|
x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
|
||||||
|
let last_asset_value = asset_values[asset_values.length - 1]
|
||||||
|
asset_values.push(flt(last_asset_value - v.value, precision('gross_purchase_amount')));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$.each(depr_schedule || [], function(i, v) {
|
|
||||||
x_intervals.push(v.schedule_date);
|
|
||||||
var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount);
|
|
||||||
if(v.journal_entry) {
|
|
||||||
last_depreciation_date = v.schedule_date;
|
|
||||||
asset_values.push(asset_value);
|
|
||||||
} else {
|
|
||||||
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
|
|
||||||
asset_values.push(null);
|
|
||||||
} else {
|
|
||||||
asset_values.push(asset_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(in_list(["Scrapped", "Sold"], frm.doc.status)) {
|
if(in_list(["Scrapped", "Sold"], frm.doc.status)) {
|
||||||
x_intervals.push(frm.doc.disposal_date);
|
x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: 'Date' }));
|
||||||
asset_values.push(0);
|
asset_values.push(0);
|
||||||
last_depreciation_date = frm.doc.disposal_date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.dashboard.render_graph({
|
frm.dashboard.render_graph({
|
||||||
|
@ -509,9 +509,15 @@
|
|||||||
"group": "Depreciation",
|
"group": "Depreciation",
|
||||||
"link_doctype": "Asset Depreciation Schedule",
|
"link_doctype": "Asset Depreciation Schedule",
|
||||||
"link_fieldname": "asset"
|
"link_fieldname": "asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Journal Entry",
|
||||||
|
"link_doctype": "Journal Entry",
|
||||||
|
"link_fieldname": "reference_name",
|
||||||
|
"table_fieldname": "accounts"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-01-17 00:25:30.387242",
|
"modified": "2023-02-02 00:03:11.706427",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
@ -36,7 +36,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
|||||||
get_depr_schedule,
|
get_depr_schedule,
|
||||||
make_draft_asset_depr_schedules,
|
make_draft_asset_depr_schedules,
|
||||||
make_draft_asset_depr_schedules_if_not_present,
|
make_draft_asset_depr_schedules_if_not_present,
|
||||||
set_draft_asset_depr_schedule_details,
|
|
||||||
update_draft_asset_depr_schedules,
|
update_draft_asset_depr_schedules,
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
@ -240,17 +239,6 @@ class Asset(AccountsController):
|
|||||||
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
|
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_value_after_depreciation(self, finance_book):
|
|
||||||
# value_after_depreciation - current Asset value
|
|
||||||
if self.docstatus == 1 and finance_book.value_after_depreciation:
|
|
||||||
value_after_depreciation = flt(finance_book.value_after_depreciation)
|
|
||||||
else:
|
|
||||||
value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
|
||||||
self.opening_accumulated_depreciation
|
|
||||||
)
|
|
||||||
|
|
||||||
return value_after_depreciation
|
|
||||||
|
|
||||||
# if it returns True, depreciation_amount will not be equal for the first and last rows
|
# if it returns True, depreciation_amount will not be equal for the first and last rows
|
||||||
def check_is_pro_rata(self, row):
|
def check_is_pro_rata(self, row):
|
||||||
has_pro_rata = False
|
has_pro_rata = False
|
||||||
@ -392,18 +380,23 @@ class Asset(AccountsController):
|
|||||||
movement.cancel()
|
movement.cancel()
|
||||||
|
|
||||||
def delete_depreciation_entries(self):
|
def delete_depreciation_entries(self):
|
||||||
for row in self.get("finance_books"):
|
if self.calculate_depreciation:
|
||||||
depr_schedule = get_depr_schedule(self.name, "Active", row.finance_book)
|
for row in self.get("finance_books"):
|
||||||
|
depr_schedule = get_depr_schedule(self.name, "Active", row.finance_book)
|
||||||
|
|
||||||
for d in depr_schedule or []:
|
for d in depr_schedule or []:
|
||||||
if d.journal_entry:
|
if d.journal_entry:
|
||||||
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
|
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
|
||||||
d.db_set("journal_entry", None)
|
else:
|
||||||
|
depr_entries = self.get_manual_depreciation_entries()
|
||||||
|
|
||||||
self.db_set(
|
for depr_entry in depr_entries or []:
|
||||||
"value_after_depreciation",
|
frappe.get_doc("Journal Entry", depr_entry.name).cancel()
|
||||||
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
|
|
||||||
)
|
self.db_set(
|
||||||
|
"value_after_depreciation",
|
||||||
|
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
|
||||||
|
)
|
||||||
|
|
||||||
def set_status(self, status=None):
|
def set_status(self, status=None):
|
||||||
"""Get and update status"""
|
"""Get and update status"""
|
||||||
@ -420,11 +413,14 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
if self.journal_entry_for_scrap:
|
if self.journal_entry_for_scrap:
|
||||||
status = "Scrapped"
|
status = "Scrapped"
|
||||||
elif self.finance_books:
|
else:
|
||||||
idx = self.get_default_finance_book_idx() or 0
|
expected_value_after_useful_life = 0
|
||||||
|
value_after_depreciation = self.value_after_depreciation
|
||||||
|
|
||||||
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
|
if self.calculate_depreciation:
|
||||||
value_after_depreciation = self.finance_books[idx].value_after_depreciation
|
idx = self.get_default_finance_book_idx() or 0
|
||||||
|
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
|
||||||
|
value_after_depreciation = self.finance_books[idx].value_after_depreciation
|
||||||
|
|
||||||
if flt(value_after_depreciation) <= expected_value_after_useful_life:
|
if flt(value_after_depreciation) <= expected_value_after_useful_life:
|
||||||
status = "Fully Depreciated"
|
status = "Fully Depreciated"
|
||||||
@ -434,6 +430,19 @@ class Asset(AccountsController):
|
|||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
def get_value_after_depreciation(self, finance_book=None):
|
||||||
|
if not self.calculate_depreciation:
|
||||||
|
return flt(self.value_after_depreciation, self.precision("gross_purchase_amount"))
|
||||||
|
|
||||||
|
if not finance_book:
|
||||||
|
return flt(
|
||||||
|
self.get("finance_books")[0].value_after_depreciation, self.precision("gross_purchase_amount")
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in self.get("finance_books"):
|
||||||
|
if finance_book == row.finance_book:
|
||||||
|
return flt(row.value_after_depreciation, self.precision("gross_purchase_amount"))
|
||||||
|
|
||||||
def get_default_finance_book_idx(self):
|
def get_default_finance_book_idx(self):
|
||||||
if not self.get("default_finance_book") and self.company:
|
if not self.get("default_finance_book") and self.company:
|
||||||
self.default_finance_book = erpnext.get_default_finance_book(self.company)
|
self.default_finance_book = erpnext.get_default_finance_book(self.company)
|
||||||
@ -443,6 +452,44 @@ class Asset(AccountsController):
|
|||||||
if d.finance_book == self.default_finance_book:
|
if d.finance_book == self.default_finance_book:
|
||||||
return cint(d.idx) - 1
|
return cint(d.idx) - 1
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_manual_depreciation_entries(self):
|
||||||
|
(_, _, depreciation_expense_account) = get_depreciation_accounts(self)
|
||||||
|
|
||||||
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
|
|
||||||
|
records = (
|
||||||
|
frappe.qb.from_(gle)
|
||||||
|
.select(gle.voucher_no.as_("name"), gle.debit.as_("value"), gle.posting_date)
|
||||||
|
.where(gle.against_voucher == self.name)
|
||||||
|
.where(gle.account == depreciation_expense_account)
|
||||||
|
.where(gle.debit != 0)
|
||||||
|
.where(gle.is_cancelled == 0)
|
||||||
|
.orderby(gle.posting_date)
|
||||||
|
.orderby(gle.creation)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def get_depreciation_amount(self, depreciable_value, fb_row):
|
||||||
|
if fb_row.depreciation_method in ("Straight Line", "Manual"):
|
||||||
|
# if the Depreciation Schedule is being prepared for the first time
|
||||||
|
if not self.flags.increase_in_asset_life:
|
||||||
|
depreciation_amount = (
|
||||||
|
flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
|
||||||
|
) / flt(fb_row.total_number_of_depreciations)
|
||||||
|
|
||||||
|
# if the Depreciation Schedule is being modified after Asset Repair
|
||||||
|
else:
|
||||||
|
depreciation_amount = (
|
||||||
|
flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
|
||||||
|
) / (date_diff(self.to_date, self.available_for_use_date) / 365)
|
||||||
|
else:
|
||||||
|
depreciation_amount = flt(depreciable_value * (flt(fb_row.rate_of_depreciation) / 100))
|
||||||
|
|
||||||
|
return depreciation_amount
|
||||||
|
|
||||||
def validate_make_gl_entry(self):
|
def validate_make_gl_entry(self):
|
||||||
purchase_document = self.get_purchase_document()
|
purchase_document = self.get_purchase_document()
|
||||||
if not purchase_document:
|
if not purchase_document:
|
||||||
@ -603,7 +650,6 @@ def update_maintenance_status():
|
|||||||
|
|
||||||
|
|
||||||
def make_post_gl_entry():
|
def make_post_gl_entry():
|
||||||
|
|
||||||
asset_categories = frappe.db.get_all("Asset Category", fields=["name", "enable_cwip_accounting"])
|
asset_categories = frappe.db.get_all("Asset Category", fields=["name", "enable_cwip_accounting"])
|
||||||
|
|
||||||
for asset_category in asset_categories:
|
for asset_category in asset_categories:
|
||||||
@ -756,7 +802,7 @@ def make_journal_entry(asset_name):
|
|||||||
depreciation_expense_account,
|
depreciation_expense_account,
|
||||||
) = get_depreciation_accounts(asset)
|
) = get_depreciation_accounts(asset)
|
||||||
|
|
||||||
depreciation_cost_center, depreciation_series = frappe.db.get_value(
|
depreciation_cost_center, depreciation_series = frappe.get_cached_value(
|
||||||
"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
|
"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
|
||||||
)
|
)
|
||||||
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
||||||
@ -821,6 +867,13 @@ def is_cwip_accounting_enabled(asset_category):
|
|||||||
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
|
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_asset_value_after_depreciation(asset_name, finance_book=None):
|
||||||
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
|
|
||||||
|
return asset.get_value_after_depreciation(finance_book)
|
||||||
|
|
||||||
|
|
||||||
def get_total_days(date, frequency):
|
def get_total_days(date, frequency):
|
||||||
period_start_date = add_months(date, cint(frequency) * -1)
|
period_start_date = add_months(date, cint(frequency) * -1)
|
||||||
|
|
||||||
@ -886,7 +939,7 @@ def update_existing_asset(asset, remaining_qty, new_asset_name):
|
|||||||
)
|
)
|
||||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||||
|
|
||||||
set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, asset, row)
|
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, row)
|
||||||
|
|
||||||
accumulated_depreciation = 0
|
accumulated_depreciation = 0
|
||||||
|
|
||||||
@ -938,7 +991,7 @@ def create_new_asset_after_split(asset, split_qty):
|
|||||||
)
|
)
|
||||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||||
|
|
||||||
set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, new_asset, row)
|
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row)
|
||||||
|
|
||||||
accumulated_depreciation = 0
|
accumulated_depreciation = 0
|
||||||
|
|
||||||
|
@ -4,7 +4,17 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, nowdate, today
|
from frappe.utils import (
|
||||||
|
add_months,
|
||||||
|
cint,
|
||||||
|
flt,
|
||||||
|
get_last_day,
|
||||||
|
get_link_to_form,
|
||||||
|
getdate,
|
||||||
|
is_last_day_of_the_month,
|
||||||
|
nowdate,
|
||||||
|
today,
|
||||||
|
)
|
||||||
from frappe.utils.user import get_users_with_role
|
from frappe.utils.user import get_users_with_role
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
@ -158,7 +168,7 @@ def make_depreciation_entry(asset_depr_schedule_name, date=None):
|
|||||||
row.value_after_depreciation -= d.depreciation_amount
|
row.value_after_depreciation -= d.depreciation_amount
|
||||||
row.db_update()
|
row.db_update()
|
||||||
|
|
||||||
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
|
asset.db_set("depr_entry_posting_status", "Successful")
|
||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
@ -400,6 +410,9 @@ def disposal_was_made_on_original_schedule_date(schedule_idx, row, posting_date_
|
|||||||
row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation)
|
row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if is_last_day_of_the_month(row.depreciation_start_date):
|
||||||
|
orginal_schedule_date = get_last_day(orginal_schedule_date)
|
||||||
|
|
||||||
if orginal_schedule_date == posting_date_of_disposal:
|
if orginal_schedule_date == posting_date_of_disposal:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -520,18 +533,8 @@ def get_asset_details(asset, finance_book=None):
|
|||||||
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
|
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
|
||||||
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
||||||
|
|
||||||
idx = 1
|
value_after_depreciation = asset.get_value_after_depreciation(finance_book)
|
||||||
if finance_book:
|
|
||||||
for d in asset.finance_books:
|
|
||||||
if d.finance_book == finance_book:
|
|
||||||
idx = d.idx
|
|
||||||
break
|
|
||||||
|
|
||||||
value_after_depreciation = (
|
|
||||||
asset.finance_books[idx - 1].value_after_depreciation
|
|
||||||
if asset.calculate_depreciation
|
|
||||||
else asset.value_after_depreciation
|
|
||||||
)
|
|
||||||
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
|
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -16,6 +16,7 @@ from frappe.utils import (
|
|||||||
nowdate,
|
nowdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.assets.doctype.asset.asset import (
|
from erpnext.assets.doctype.asset.asset import (
|
||||||
make_sales_invoice,
|
make_sales_invoice,
|
||||||
@ -28,7 +29,6 @@ from erpnext.assets.doctype.asset.depreciation import (
|
|||||||
scrap_asset,
|
scrap_asset,
|
||||||
)
|
)
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
clear_depr_schedule,
|
|
||||||
get_asset_depr_schedule_doc,
|
get_asset_depr_schedule_doc,
|
||||||
get_depr_schedule,
|
get_depr_schedule,
|
||||||
)
|
)
|
||||||
@ -924,11 +924,6 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
|
|
||||||
def test_get_depreciation_amount(self):
|
def test_get_depreciation_amount(self):
|
||||||
"""Tests if get_depreciation_amount() returns the right value."""
|
"""Tests if get_depreciation_amount() returns the right value."""
|
||||||
|
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
|
||||||
get_depreciation_amount,
|
|
||||||
)
|
|
||||||
|
|
||||||
asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31")
|
asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31")
|
||||||
|
|
||||||
asset.calculate_depreciation = 1
|
asset.calculate_depreciation = 1
|
||||||
@ -943,7 +938,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
|
depreciation_amount = asset.get_depreciation_amount(100000, asset.finance_books[0])
|
||||||
self.assertEqual(depreciation_amount, 30000)
|
self.assertEqual(depreciation_amount, 30000)
|
||||||
|
|
||||||
def test_make_depr_schedule(self):
|
def test_make_depr_schedule(self):
|
||||||
@ -1259,7 +1254,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
|
|
||||||
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
|
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
|
||||||
|
|
||||||
clear_depr_schedule(asset_depr_schedule_doc)
|
asset_depr_schedule_doc.clear_depr_schedule()
|
||||||
|
|
||||||
self.assertEqual(len(asset_depr_schedule_doc.get("depreciation_schedule")), 1)
|
self.assertEqual(len(asset_depr_schedule_doc.get("depreciation_schedule")), 1)
|
||||||
|
|
||||||
@ -1308,19 +1303,19 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
asset_depr_schedule_doc_1 = get_asset_depr_schedule_doc(
|
asset_depr_schedule_doc_1 = get_asset_depr_schedule_doc(
|
||||||
asset.name, "Active", "Test Finance Book 1"
|
asset.name, "Active", "Test Finance Book 1"
|
||||||
)
|
)
|
||||||
clear_depr_schedule(asset_depr_schedule_doc_1)
|
asset_depr_schedule_doc_1.clear_depr_schedule()
|
||||||
self.assertEqual(len(asset_depr_schedule_doc_1.get("depreciation_schedule")), 3)
|
self.assertEqual(len(asset_depr_schedule_doc_1.get("depreciation_schedule")), 3)
|
||||||
|
|
||||||
asset_depr_schedule_doc_2 = get_asset_depr_schedule_doc(
|
asset_depr_schedule_doc_2 = get_asset_depr_schedule_doc(
|
||||||
asset.name, "Active", "Test Finance Book 2"
|
asset.name, "Active", "Test Finance Book 2"
|
||||||
)
|
)
|
||||||
clear_depr_schedule(asset_depr_schedule_doc_2)
|
asset_depr_schedule_doc_2.clear_depr_schedule()
|
||||||
self.assertEqual(len(asset_depr_schedule_doc_2.get("depreciation_schedule")), 3)
|
self.assertEqual(len(asset_depr_schedule_doc_2.get("depreciation_schedule")), 3)
|
||||||
|
|
||||||
asset_depr_schedule_doc_3 = get_asset_depr_schedule_doc(
|
asset_depr_schedule_doc_3 = get_asset_depr_schedule_doc(
|
||||||
asset.name, "Active", "Test Finance Book 3"
|
asset.name, "Active", "Test Finance Book 3"
|
||||||
)
|
)
|
||||||
clear_depr_schedule(asset_depr_schedule_doc_3)
|
asset_depr_schedule_doc_3.clear_depr_schedule()
|
||||||
self.assertEqual(len(asset_depr_schedule_doc_3.get("depreciation_schedule")), 0)
|
self.assertEqual(len(asset_depr_schedule_doc_3.get("depreciation_schedule")), 0)
|
||||||
|
|
||||||
def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self):
|
def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self):
|
||||||
@ -1503,6 +1498,36 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
|
for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
|
||||||
self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date))
|
self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date))
|
||||||
|
|
||||||
|
def test_manual_depreciation_for_existing_asset(self):
|
||||||
|
asset = create_asset(
|
||||||
|
item_code="Macbook Pro",
|
||||||
|
is_existing_asset=1,
|
||||||
|
purchase_date="2020-01-30",
|
||||||
|
available_for_use_date="2020-01-30",
|
||||||
|
submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(asset.status, "Submitted")
|
||||||
|
self.assertEqual(asset.get("value_after_depreciation"), 100000)
|
||||||
|
|
||||||
|
jv = make_journal_entry(
|
||||||
|
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
||||||
|
)
|
||||||
|
for d in jv.accounts:
|
||||||
|
d.reference_type = "Asset"
|
||||||
|
d.reference_name = asset.name
|
||||||
|
jv.voucher_type = "Depreciation Entry"
|
||||||
|
jv.insert()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
asset.reload()
|
||||||
|
self.assertEqual(asset.get("value_after_depreciation"), 99900)
|
||||||
|
|
||||||
|
jv.cancel()
|
||||||
|
|
||||||
|
asset.reload()
|
||||||
|
self.assertEqual(asset.get("value_after_depreciation"), 100000)
|
||||||
|
|
||||||
|
|
||||||
def create_asset_data():
|
def create_asset_data():
|
||||||
if not frappe.db.exists("Asset Category", "Computers"):
|
if not frappe.db.exists("Asset Category", "Computers"):
|
||||||
|
@ -10,6 +10,7 @@ from frappe import _
|
|||||||
from frappe.utils import cint, flt, get_link_to_form
|
from frappe.utils import cint, flt, get_link_to_form
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
|
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
|
||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
depreciate_asset,
|
depreciate_asset,
|
||||||
get_gl_entries_on_asset_disposal,
|
get_gl_entries_on_asset_disposal,
|
||||||
@ -21,9 +22,6 @@ from erpnext.assets.doctype.asset_category.asset_category import get_asset_categ
|
|||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
||||||
)
|
)
|
||||||
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
|
|
||||||
get_current_asset_value,
|
|
||||||
)
|
|
||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController
|
||||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
@ -261,7 +259,9 @@ class AssetCapitalization(StockController):
|
|||||||
for d in self.get("asset_items"):
|
for d in self.get("asset_items"):
|
||||||
if d.asset:
|
if d.asset:
|
||||||
finance_book = d.get("finance_book") or self.get("finance_book")
|
finance_book = d.get("finance_book") or self.get("finance_book")
|
||||||
d.current_asset_value = flt(get_current_asset_value(d.asset, finance_book=finance_book))
|
d.current_asset_value = flt(
|
||||||
|
get_asset_value_after_depreciation(d.asset, finance_book=finance_book)
|
||||||
|
)
|
||||||
d.asset_value = get_value_after_depreciation_on_disposal_date(
|
d.asset_value = get_value_after_depreciation_on_disposal_date(
|
||||||
d.asset, self.posting_date, finance_book=finance_book
|
d.asset, self.posting_date, finance_book=finance_book
|
||||||
)
|
)
|
||||||
@ -713,7 +713,7 @@ def get_consumed_asset_details(args):
|
|||||||
|
|
||||||
if args.asset:
|
if args.asset:
|
||||||
out.current_asset_value = flt(
|
out.current_asset_value = flt(
|
||||||
get_current_asset_value(args.asset, finance_book=args.finance_book)
|
get_asset_value_after_depreciation(args.asset, finance_book=args.finance_book)
|
||||||
)
|
)
|
||||||
out.asset_value = get_value_after_depreciation_on_disposal_date(
|
out.asset_value = get_value_after_depreciation_on_disposal_date(
|
||||||
args.asset, args.posting_date, finance_book=args.finance_book
|
args.asset, args.posting_date, finance_book=args.finance_book
|
||||||
|
@ -4,17 +4,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import (
|
from frappe.utils import add_days, add_months, cint, flt, get_last_day, is_last_day_of_the_month
|
||||||
add_days,
|
|
||||||
add_months,
|
|
||||||
cint,
|
|
||||||
date_diff,
|
|
||||||
flt,
|
|
||||||
get_last_day,
|
|
||||||
is_last_day_of_the_month,
|
|
||||||
)
|
|
||||||
|
|
||||||
import erpnext
|
|
||||||
|
|
||||||
|
|
||||||
class AssetDepreciationSchedule(Document):
|
class AssetDepreciationSchedule(Document):
|
||||||
@ -83,7 +73,267 @@ class AssetDepreciationSchedule(Document):
|
|||||||
)
|
)
|
||||||
asset_finance_book_doc = frappe.get_doc("Asset Finance Book", asset_finance_book_name)
|
asset_finance_book_doc = frappe.get_doc("Asset Finance Book", asset_finance_book_name)
|
||||||
|
|
||||||
prepare_draft_asset_depr_schedule_data(self, asset_doc, asset_finance_book_doc)
|
self.prepare_draft_asset_depr_schedule_data(asset_doc, asset_finance_book_doc)
|
||||||
|
|
||||||
|
def prepare_draft_asset_depr_schedule_data(
|
||||||
|
self,
|
||||||
|
asset_doc,
|
||||||
|
row,
|
||||||
|
date_of_disposal=None,
|
||||||
|
date_of_return=None,
|
||||||
|
update_asset_finance_book_row=True,
|
||||||
|
):
|
||||||
|
self.set_draft_asset_depr_schedule_details(asset_doc, row)
|
||||||
|
self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
|
||||||
|
self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
|
||||||
|
|
||||||
|
def set_draft_asset_depr_schedule_details(self, asset_doc, row):
|
||||||
|
self.asset = asset_doc.name
|
||||||
|
self.finance_book = row.finance_book
|
||||||
|
self.finance_book_id = row.idx
|
||||||
|
self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation
|
||||||
|
self.depreciation_method = row.depreciation_method
|
||||||
|
self.total_number_of_depreciations = row.total_number_of_depreciations
|
||||||
|
self.frequency_of_depreciation = row.frequency_of_depreciation
|
||||||
|
self.rate_of_depreciation = row.rate_of_depreciation
|
||||||
|
self.expected_value_after_useful_life = row.expected_value_after_useful_life
|
||||||
|
self.status = "Draft"
|
||||||
|
|
||||||
|
def make_depr_schedule(
|
||||||
|
self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
|
||||||
|
):
|
||||||
|
if row.depreciation_method != "Manual" and not self.get("depreciation_schedule"):
|
||||||
|
self.depreciation_schedule = []
|
||||||
|
|
||||||
|
if not asset_doc.available_for_use_date:
|
||||||
|
return
|
||||||
|
|
||||||
|
start = self.clear_depr_schedule()
|
||||||
|
|
||||||
|
self._make_depr_schedule(asset_doc, row, start, date_of_disposal, update_asset_finance_book_row)
|
||||||
|
|
||||||
|
def clear_depr_schedule(self):
|
||||||
|
start = 0
|
||||||
|
num_of_depreciations_completed = 0
|
||||||
|
depr_schedule = []
|
||||||
|
|
||||||
|
for schedule in self.get("depreciation_schedule"):
|
||||||
|
if schedule.journal_entry:
|
||||||
|
num_of_depreciations_completed += 1
|
||||||
|
depr_schedule.append(schedule)
|
||||||
|
else:
|
||||||
|
start = num_of_depreciations_completed
|
||||||
|
break
|
||||||
|
|
||||||
|
self.depreciation_schedule = depr_schedule
|
||||||
|
|
||||||
|
return start
|
||||||
|
|
||||||
|
def _make_depr_schedule(
|
||||||
|
self, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
|
||||||
|
):
|
||||||
|
asset_doc.validate_asset_finance_books(row)
|
||||||
|
|
||||||
|
value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
|
||||||
|
row.value_after_depreciation = value_after_depreciation
|
||||||
|
|
||||||
|
if update_asset_finance_book_row:
|
||||||
|
row.db_update()
|
||||||
|
|
||||||
|
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
|
||||||
|
asset_doc.number_of_depreciations_booked
|
||||||
|
)
|
||||||
|
|
||||||
|
has_pro_rata = asset_doc.check_is_pro_rata(row)
|
||||||
|
if has_pro_rata:
|
||||||
|
number_of_pending_depreciations += 1
|
||||||
|
|
||||||
|
skip_row = False
|
||||||
|
should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
|
||||||
|
|
||||||
|
for n in range(start, number_of_pending_depreciations):
|
||||||
|
# If depreciation is already completed (for double declining balance)
|
||||||
|
if skip_row:
|
||||||
|
continue
|
||||||
|
|
||||||
|
depreciation_amount = asset_doc.get_depreciation_amount(value_after_depreciation, row)
|
||||||
|
|
||||||
|
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||||
|
schedule_date = add_months(
|
||||||
|
row.depreciation_start_date, n * cint(row.frequency_of_depreciation)
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_get_last_day:
|
||||||
|
schedule_date = get_last_day(schedule_date)
|
||||||
|
|
||||||
|
# schedule date will be a year later from start date
|
||||||
|
# so monthly schedule date is calculated by removing 11 months from it
|
||||||
|
monthly_schedule_date = add_months(schedule_date, -row.frequency_of_depreciation + 1)
|
||||||
|
|
||||||
|
# if asset is being sold or scrapped
|
||||||
|
if date_of_disposal:
|
||||||
|
from_date = asset_doc.available_for_use_date
|
||||||
|
if self.depreciation_schedule:
|
||||||
|
from_date = self.depreciation_schedule[-1].schedule_date
|
||||||
|
|
||||||
|
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
|
||||||
|
row, depreciation_amount, from_date, date_of_disposal
|
||||||
|
)
|
||||||
|
|
||||||
|
if depreciation_amount > 0:
|
||||||
|
self.add_depr_schedule_row(
|
||||||
|
date_of_disposal,
|
||||||
|
depreciation_amount,
|
||||||
|
row.depreciation_method,
|
||||||
|
)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# For first row
|
||||||
|
if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0:
|
||||||
|
from_date = add_days(
|
||||||
|
asset_doc.available_for_use_date, -1
|
||||||
|
) # needed to calc depr amount for available_for_use_date too
|
||||||
|
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
|
||||||
|
row, depreciation_amount, from_date, row.depreciation_start_date
|
||||||
|
)
|
||||||
|
|
||||||
|
# For first depr schedule date will be the start date
|
||||||
|
# so monthly schedule date is calculated by removing
|
||||||
|
# month difference between use date and start date
|
||||||
|
monthly_schedule_date = add_months(row.depreciation_start_date, -months + 1)
|
||||||
|
|
||||||
|
# For last row
|
||||||
|
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
||||||
|
if not asset_doc.flags.increase_in_asset_life:
|
||||||
|
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
|
||||||
|
asset_doc.to_date = add_months(
|
||||||
|
asset_doc.available_for_use_date,
|
||||||
|
(n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
|
||||||
|
)
|
||||||
|
|
||||||
|
depreciation_amount_without_pro_rata = depreciation_amount
|
||||||
|
|
||||||
|
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
|
||||||
|
row, depreciation_amount, schedule_date, asset_doc.to_date
|
||||||
|
)
|
||||||
|
|
||||||
|
depreciation_amount = self.get_adjusted_depreciation_amount(
|
||||||
|
depreciation_amount_without_pro_rata, depreciation_amount
|
||||||
|
)
|
||||||
|
|
||||||
|
monthly_schedule_date = add_months(schedule_date, 1)
|
||||||
|
schedule_date = add_days(schedule_date, days)
|
||||||
|
last_schedule_date = schedule_date
|
||||||
|
|
||||||
|
if not depreciation_amount:
|
||||||
|
continue
|
||||||
|
value_after_depreciation -= flt(
|
||||||
|
depreciation_amount, asset_doc.precision("gross_purchase_amount")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Adjust depreciation amount in the last period based on the expected value after useful life
|
||||||
|
if row.expected_value_after_useful_life and (
|
||||||
|
(
|
||||||
|
n == cint(number_of_pending_depreciations) - 1
|
||||||
|
and value_after_depreciation != row.expected_value_after_useful_life
|
||||||
|
)
|
||||||
|
or value_after_depreciation < row.expected_value_after_useful_life
|
||||||
|
):
|
||||||
|
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
|
||||||
|
skip_row = True
|
||||||
|
|
||||||
|
if depreciation_amount > 0:
|
||||||
|
self.add_depr_schedule_row(
|
||||||
|
schedule_date,
|
||||||
|
depreciation_amount,
|
||||||
|
row.depreciation_method,
|
||||||
|
)
|
||||||
|
|
||||||
|
# to ensure that final accumulated depreciation amount is accurate
|
||||||
|
def get_adjusted_depreciation_amount(
|
||||||
|
self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row
|
||||||
|
):
|
||||||
|
if not self.opening_accumulated_depreciation:
|
||||||
|
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row()
|
||||||
|
|
||||||
|
if (
|
||||||
|
depreciation_amount_for_first_row + depreciation_amount_for_last_row
|
||||||
|
!= depreciation_amount_without_pro_rata
|
||||||
|
):
|
||||||
|
depreciation_amount_for_last_row = (
|
||||||
|
depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
|
||||||
|
)
|
||||||
|
|
||||||
|
return depreciation_amount_for_last_row
|
||||||
|
|
||||||
|
def get_depreciation_amount_for_first_row(self):
|
||||||
|
return self.get("depreciation_schedule")[0].depreciation_amount
|
||||||
|
|
||||||
|
def add_depr_schedule_row(
|
||||||
|
self,
|
||||||
|
schedule_date,
|
||||||
|
depreciation_amount,
|
||||||
|
depreciation_method,
|
||||||
|
):
|
||||||
|
self.append(
|
||||||
|
"depreciation_schedule",
|
||||||
|
{
|
||||||
|
"schedule_date": schedule_date,
|
||||||
|
"depreciation_amount": depreciation_amount,
|
||||||
|
"depreciation_method": depreciation_method,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_accumulated_depreciation(
|
||||||
|
self,
|
||||||
|
row,
|
||||||
|
date_of_disposal=None,
|
||||||
|
date_of_return=None,
|
||||||
|
ignore_booked_entry=False,
|
||||||
|
):
|
||||||
|
straight_line_idx = [
|
||||||
|
d.idx for d in self.get("depreciation_schedule") if d.depreciation_method == "Straight Line"
|
||||||
|
]
|
||||||
|
|
||||||
|
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||||
|
value_after_depreciation = flt(row.value_after_depreciation)
|
||||||
|
|
||||||
|
for i, d in enumerate(self.get("depreciation_schedule")):
|
||||||
|
if ignore_booked_entry and d.journal_entry:
|
||||||
|
continue
|
||||||
|
|
||||||
|
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
|
||||||
|
value_after_depreciation -= flt(depreciation_amount)
|
||||||
|
|
||||||
|
# for the last row, if depreciation method = Straight Line
|
||||||
|
if (
|
||||||
|
straight_line_idx
|
||||||
|
and i == max(straight_line_idx) - 1
|
||||||
|
and not date_of_disposal
|
||||||
|
and not date_of_return
|
||||||
|
):
|
||||||
|
depreciation_amount += flt(
|
||||||
|
value_after_depreciation - flt(row.expected_value_after_useful_life),
|
||||||
|
d.precision("depreciation_amount"),
|
||||||
|
)
|
||||||
|
|
||||||
|
d.depreciation_amount = depreciation_amount
|
||||||
|
accumulated_depreciation += d.depreciation_amount
|
||||||
|
d.accumulated_depreciation_amount = flt(
|
||||||
|
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row):
|
||||||
|
if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
|
||||||
|
value_after_depreciation = flt(fb_row.value_after_depreciation)
|
||||||
|
else:
|
||||||
|
value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt(
|
||||||
|
asset_doc.opening_accumulated_depreciation
|
||||||
|
)
|
||||||
|
|
||||||
|
return value_after_depreciation
|
||||||
|
|
||||||
|
|
||||||
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
||||||
@ -108,7 +358,7 @@ def make_draft_asset_depr_schedules(asset_doc):
|
|||||||
def make_draft_asset_depr_schedule(asset_doc, row):
|
def make_draft_asset_depr_schedule(asset_doc, row):
|
||||||
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
||||||
|
|
||||||
prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row)
|
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row)
|
||||||
|
|
||||||
asset_depr_schedule_doc.insert()
|
asset_depr_schedule_doc.insert()
|
||||||
|
|
||||||
@ -120,41 +370,11 @@ def update_draft_asset_depr_schedules(asset_doc):
|
|||||||
if not asset_depr_schedule_doc:
|
if not asset_depr_schedule_doc:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row)
|
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row)
|
||||||
|
|
||||||
asset_depr_schedule_doc.save()
|
asset_depr_schedule_doc.save()
|
||||||
|
|
||||||
|
|
||||||
def prepare_draft_asset_depr_schedule_data(
|
|
||||||
asset_depr_schedule_doc,
|
|
||||||
asset_doc,
|
|
||||||
row,
|
|
||||||
date_of_disposal=None,
|
|
||||||
date_of_return=None,
|
|
||||||
update_asset_finance_book_row=True,
|
|
||||||
):
|
|
||||||
set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row)
|
|
||||||
make_depr_schedule(
|
|
||||||
asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row
|
|
||||||
)
|
|
||||||
set_accumulated_depreciation(asset_depr_schedule_doc, row, date_of_disposal, date_of_return)
|
|
||||||
|
|
||||||
|
|
||||||
def set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row):
|
|
||||||
asset_depr_schedule_doc.asset = asset_doc.name
|
|
||||||
asset_depr_schedule_doc.finance_book = row.finance_book
|
|
||||||
asset_depr_schedule_doc.finance_book_id = row.idx
|
|
||||||
asset_depr_schedule_doc.opening_accumulated_depreciation = (
|
|
||||||
asset_doc.opening_accumulated_depreciation
|
|
||||||
)
|
|
||||||
asset_depr_schedule_doc.depreciation_method = row.depreciation_method
|
|
||||||
asset_depr_schedule_doc.total_number_of_depreciations = row.total_number_of_depreciations
|
|
||||||
asset_depr_schedule_doc.frequency_of_depreciation = row.frequency_of_depreciation
|
|
||||||
asset_depr_schedule_doc.rate_of_depreciation = row.rate_of_depreciation
|
|
||||||
asset_depr_schedule_doc.expected_value_after_useful_life = row.expected_value_after_useful_life
|
|
||||||
asset_depr_schedule_doc.status = "Draft"
|
|
||||||
|
|
||||||
|
|
||||||
def convert_draft_asset_depr_schedules_into_active(asset_doc):
|
def convert_draft_asset_depr_schedules_into_active(asset_doc):
|
||||||
for row in asset_doc.get("finance_books"):
|
for row in asset_doc.get("finance_books"):
|
||||||
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book)
|
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book)
|
||||||
@ -192,8 +412,8 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
|||||||
|
|
||||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||||
|
|
||||||
make_depr_schedule(new_asset_depr_schedule_doc, asset_doc, row, date_of_disposal)
|
new_asset_depr_schedule_doc.make_depr_schedule(asset_doc, row, date_of_disposal)
|
||||||
set_accumulated_depreciation(new_asset_depr_schedule_doc, row, date_of_disposal, date_of_return)
|
new_asset_depr_schedule_doc.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
|
||||||
|
|
||||||
new_asset_depr_schedule_doc.notes = notes
|
new_asset_depr_schedule_doc.notes = notes
|
||||||
|
|
||||||
@ -208,8 +428,7 @@ def get_temp_asset_depr_schedule_doc(
|
|||||||
):
|
):
|
||||||
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
||||||
|
|
||||||
prepare_draft_asset_depr_schedule_data(
|
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(
|
||||||
asset_depr_schedule_doc,
|
|
||||||
asset_doc,
|
asset_doc,
|
||||||
row,
|
row,
|
||||||
date_of_disposal,
|
date_of_disposal,
|
||||||
@ -220,21 +439,6 @@ def get_temp_asset_depr_schedule_doc(
|
|||||||
return asset_depr_schedule_doc
|
return asset_depr_schedule_doc
|
||||||
|
|
||||||
|
|
||||||
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
|
|
||||||
finance_book_filter = ["finance_book", "is", "not set"]
|
|
||||||
if finance_book:
|
|
||||||
finance_book_filter = ["finance_book", "=", finance_book]
|
|
||||||
|
|
||||||
return frappe.db.get_value(
|
|
||||||
doctype="Asset Depreciation Schedule",
|
|
||||||
filters=[
|
|
||||||
["asset", "=", asset_name],
|
|
||||||
finance_book_filter,
|
|
||||||
["status", "=", status],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_depr_schedule(asset_name, status, finance_book=None):
|
def get_depr_schedule(asset_name, status, finance_book=None):
|
||||||
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_name, status, finance_book)
|
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_name, status, finance_book)
|
||||||
@ -256,261 +460,16 @@ def get_asset_depr_schedule_doc(asset_name, status, finance_book=None):
|
|||||||
return asset_depr_schedule_doc
|
return asset_depr_schedule_doc
|
||||||
|
|
||||||
|
|
||||||
def make_depr_schedule(
|
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
|
||||||
asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
|
finance_book_filter = ["finance_book", "is", "not set"]
|
||||||
):
|
if finance_book:
|
||||||
if row.depreciation_method != "Manual" and not asset_depr_schedule_doc.get(
|
finance_book_filter = ["finance_book", "=", finance_book]
|
||||||
"depreciation_schedule"
|
|
||||||
):
|
|
||||||
asset_depr_schedule_doc.depreciation_schedule = []
|
|
||||||
|
|
||||||
if not asset_doc.available_for_use_date:
|
return frappe.db.get_value(
|
||||||
return
|
doctype="Asset Depreciation Schedule",
|
||||||
|
filters=[
|
||||||
start = clear_depr_schedule(asset_depr_schedule_doc)
|
["asset", "=", asset_name],
|
||||||
|
finance_book_filter,
|
||||||
_make_depr_schedule(
|
["status", "=", status],
|
||||||
asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def clear_depr_schedule(asset_depr_schedule_doc):
|
|
||||||
start = 0
|
|
||||||
num_of_depreciations_completed = 0
|
|
||||||
depr_schedule = []
|
|
||||||
|
|
||||||
for schedule in asset_depr_schedule_doc.get("depreciation_schedule"):
|
|
||||||
if schedule.journal_entry:
|
|
||||||
num_of_depreciations_completed += 1
|
|
||||||
depr_schedule.append(schedule)
|
|
||||||
else:
|
|
||||||
start = num_of_depreciations_completed
|
|
||||||
break
|
|
||||||
|
|
||||||
asset_depr_schedule_doc.depreciation_schedule = depr_schedule
|
|
||||||
|
|
||||||
return start
|
|
||||||
|
|
||||||
|
|
||||||
def _make_depr_schedule(
|
|
||||||
asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
|
|
||||||
):
|
|
||||||
asset_doc.validate_asset_finance_books(row)
|
|
||||||
|
|
||||||
value_after_depreciation = asset_doc._get_value_after_depreciation(row)
|
|
||||||
row.value_after_depreciation = value_after_depreciation
|
|
||||||
|
|
||||||
if update_asset_finance_book_row:
|
|
||||||
row.db_update()
|
|
||||||
|
|
||||||
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
|
|
||||||
asset_doc.number_of_depreciations_booked
|
|
||||||
)
|
|
||||||
|
|
||||||
has_pro_rata = asset_doc.check_is_pro_rata(row)
|
|
||||||
if has_pro_rata:
|
|
||||||
number_of_pending_depreciations += 1
|
|
||||||
|
|
||||||
skip_row = False
|
|
||||||
should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
|
|
||||||
|
|
||||||
for n in range(start, number_of_pending_depreciations):
|
|
||||||
# If depreciation is already completed (for double declining balance)
|
|
||||||
if skip_row:
|
|
||||||
continue
|
|
||||||
|
|
||||||
depreciation_amount = get_depreciation_amount(asset_doc, value_after_depreciation, row)
|
|
||||||
|
|
||||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
|
||||||
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
|
|
||||||
|
|
||||||
if should_get_last_day:
|
|
||||||
schedule_date = get_last_day(schedule_date)
|
|
||||||
|
|
||||||
# schedule date will be a year later from start date
|
|
||||||
# so monthly schedule date is calculated by removing 11 months from it
|
|
||||||
monthly_schedule_date = add_months(schedule_date, -row.frequency_of_depreciation + 1)
|
|
||||||
|
|
||||||
# if asset is being sold or scrapped
|
|
||||||
if date_of_disposal:
|
|
||||||
from_date = asset_doc.available_for_use_date
|
|
||||||
if asset_depr_schedule_doc.depreciation_schedule:
|
|
||||||
from_date = asset_depr_schedule_doc.depreciation_schedule[-1].schedule_date
|
|
||||||
|
|
||||||
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
|
|
||||||
row, depreciation_amount, from_date, date_of_disposal
|
|
||||||
)
|
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
|
||||||
add_depr_schedule_row(
|
|
||||||
asset_depr_schedule_doc,
|
|
||||||
date_of_disposal,
|
|
||||||
depreciation_amount,
|
|
||||||
row.depreciation_method,
|
|
||||||
)
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
# For first row
|
|
||||||
if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0:
|
|
||||||
from_date = add_days(
|
|
||||||
asset_doc.available_for_use_date, -1
|
|
||||||
) # needed to calc depr amount for available_for_use_date too
|
|
||||||
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
|
|
||||||
row, depreciation_amount, from_date, row.depreciation_start_date
|
|
||||||
)
|
|
||||||
|
|
||||||
# For first depr schedule date will be the start date
|
|
||||||
# so monthly schedule date is calculated by removing
|
|
||||||
# month difference between use date and start date
|
|
||||||
monthly_schedule_date = add_months(row.depreciation_start_date, -months + 1)
|
|
||||||
|
|
||||||
# For last row
|
|
||||||
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
|
||||||
if not asset_doc.flags.increase_in_asset_life:
|
|
||||||
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
|
|
||||||
asset_doc.to_date = add_months(
|
|
||||||
asset_doc.available_for_use_date,
|
|
||||||
(n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
|
|
||||||
)
|
|
||||||
|
|
||||||
depreciation_amount_without_pro_rata = depreciation_amount
|
|
||||||
|
|
||||||
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
|
|
||||||
row, depreciation_amount, schedule_date, asset_doc.to_date
|
|
||||||
)
|
|
||||||
|
|
||||||
depreciation_amount = get_adjusted_depreciation_amount(
|
|
||||||
asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount
|
|
||||||
)
|
|
||||||
|
|
||||||
monthly_schedule_date = add_months(schedule_date, 1)
|
|
||||||
schedule_date = add_days(schedule_date, days)
|
|
||||||
last_schedule_date = schedule_date
|
|
||||||
|
|
||||||
if not depreciation_amount:
|
|
||||||
continue
|
|
||||||
value_after_depreciation -= flt(
|
|
||||||
depreciation_amount, asset_doc.precision("gross_purchase_amount")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Adjust depreciation amount in the last period based on the expected value after useful life
|
|
||||||
if row.expected_value_after_useful_life and (
|
|
||||||
(
|
|
||||||
n == cint(number_of_pending_depreciations) - 1
|
|
||||||
and value_after_depreciation != row.expected_value_after_useful_life
|
|
||||||
)
|
|
||||||
or value_after_depreciation < row.expected_value_after_useful_life
|
|
||||||
):
|
|
||||||
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
|
|
||||||
skip_row = True
|
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
|
||||||
add_depr_schedule_row(
|
|
||||||
asset_depr_schedule_doc,
|
|
||||||
schedule_date,
|
|
||||||
depreciation_amount,
|
|
||||||
row.depreciation_method,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# to ensure that final accumulated depreciation amount is accurate
|
|
||||||
def get_adjusted_depreciation_amount(
|
|
||||||
asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row
|
|
||||||
):
|
|
||||||
if not asset_depr_schedule_doc.opening_accumulated_depreciation:
|
|
||||||
depreciation_amount_for_first_row = get_depreciation_amount_for_first_row(
|
|
||||||
asset_depr_schedule_doc
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
depreciation_amount_for_first_row + depreciation_amount_for_last_row
|
|
||||||
!= depreciation_amount_without_pro_rata
|
|
||||||
):
|
|
||||||
depreciation_amount_for_last_row = (
|
|
||||||
depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
|
|
||||||
)
|
|
||||||
|
|
||||||
return depreciation_amount_for_last_row
|
|
||||||
|
|
||||||
|
|
||||||
def get_depreciation_amount_for_first_row(asset_depr_schedule_doc):
|
|
||||||
return asset_depr_schedule_doc.get("depreciation_schedule")[0].depreciation_amount
|
|
||||||
|
|
||||||
|
|
||||||
@erpnext.allow_regional
|
|
||||||
def get_depreciation_amount(asset_doc, depreciable_value, row):
|
|
||||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
|
||||||
# if the Depreciation Schedule is being prepared for the first time
|
|
||||||
if not asset_doc.flags.increase_in_asset_life:
|
|
||||||
depreciation_amount = (
|
|
||||||
flt(asset_doc.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
|
||||||
) / flt(row.total_number_of_depreciations)
|
|
||||||
|
|
||||||
# if the Depreciation Schedule is being modified after Asset Repair
|
|
||||||
else:
|
|
||||||
depreciation_amount = (
|
|
||||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
|
||||||
) / (date_diff(asset_doc.to_date, asset_doc.available_for_use_date) / 365)
|
|
||||||
else:
|
|
||||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
|
||||||
|
|
||||||
return depreciation_amount
|
|
||||||
|
|
||||||
|
|
||||||
def add_depr_schedule_row(
|
|
||||||
asset_depr_schedule_doc,
|
|
||||||
schedule_date,
|
|
||||||
depreciation_amount,
|
|
||||||
depreciation_method,
|
|
||||||
):
|
|
||||||
asset_depr_schedule_doc.append(
|
|
||||||
"depreciation_schedule",
|
|
||||||
{
|
|
||||||
"schedule_date": schedule_date,
|
|
||||||
"depreciation_amount": depreciation_amount,
|
|
||||||
"depreciation_method": depreciation_method,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def set_accumulated_depreciation(
|
|
||||||
asset_depr_schedule_doc,
|
|
||||||
row,
|
|
||||||
date_of_disposal=None,
|
|
||||||
date_of_return=None,
|
|
||||||
ignore_booked_entry=False,
|
|
||||||
):
|
|
||||||
straight_line_idx = [
|
|
||||||
d.idx
|
|
||||||
for d in asset_depr_schedule_doc.get("depreciation_schedule")
|
|
||||||
if d.depreciation_method == "Straight Line"
|
|
||||||
]
|
|
||||||
|
|
||||||
accumulated_depreciation = flt(asset_depr_schedule_doc.opening_accumulated_depreciation)
|
|
||||||
value_after_depreciation = flt(row.value_after_depreciation)
|
|
||||||
|
|
||||||
for i, d in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
|
|
||||||
if ignore_booked_entry and d.journal_entry:
|
|
||||||
continue
|
|
||||||
|
|
||||||
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
|
|
||||||
value_after_depreciation -= flt(depreciation_amount)
|
|
||||||
|
|
||||||
# for the last row, if depreciation method = Straight Line
|
|
||||||
if (
|
|
||||||
straight_line_idx
|
|
||||||
and i == max(straight_line_idx) - 1
|
|
||||||
and not date_of_disposal
|
|
||||||
and not date_of_return
|
|
||||||
):
|
|
||||||
depreciation_amount += flt(
|
|
||||||
value_after_depreciation - flt(row.expected_value_after_useful_life),
|
|
||||||
d.precision("depreciation_amount"),
|
|
||||||
)
|
|
||||||
|
|
||||||
d.depreciation_amount = depreciation_amount
|
|
||||||
accumulated_depreciation += d.depreciation_amount
|
|
||||||
d.accumulated_depreciation_amount = flt(
|
|
||||||
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
|
|
||||||
)
|
|
||||||
|
@ -91,6 +91,9 @@ class AssetRepair(AccountsController):
|
|||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||||
self.asset_doc.save()
|
self.asset_doc.save()
|
||||||
|
|
||||||
|
def after_delete(self):
|
||||||
|
frappe.get_doc("Asset", self.asset).set_status()
|
||||||
|
|
||||||
def check_repair_status(self):
|
def check_repair_status(self):
|
||||||
if self.repair_status == "Pending":
|
if self.repair_status == "Pending":
|
||||||
frappe.throw(_("Please update Repair Status."))
|
frappe.throw(_("Please update Repair Status."))
|
||||||
|
@ -6,7 +6,10 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
|
|
||||||
from erpnext.assets.doctype.asset.asset import get_asset_account
|
from erpnext.assets.doctype.asset.asset import (
|
||||||
|
get_asset_account,
|
||||||
|
get_asset_value_after_depreciation,
|
||||||
|
)
|
||||||
from erpnext.assets.doctype.asset.test_asset import (
|
from erpnext.assets.doctype.asset.test_asset import (
|
||||||
create_asset,
|
create_asset,
|
||||||
create_asset_data,
|
create_asset_data,
|
||||||
@ -109,20 +112,20 @@ class TestAssetRepair(unittest.TestCase):
|
|||||||
|
|
||||||
def test_increase_in_asset_value_due_to_stock_consumption(self):
|
def test_increase_in_asset_value_due_to_stock_consumption(self):
|
||||||
asset = create_asset(calculate_depreciation=1, submit=1)
|
asset = create_asset(calculate_depreciation=1, submit=1)
|
||||||
initial_asset_value = get_asset_value(asset)
|
initial_asset_value = get_asset_value_after_depreciation(asset.name)
|
||||||
asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1)
|
asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1)
|
||||||
asset.reload()
|
asset.reload()
|
||||||
|
|
||||||
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
|
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
|
||||||
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
|
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
|
||||||
|
|
||||||
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
|
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
|
||||||
asset = create_asset(calculate_depreciation=1, submit=1)
|
asset = create_asset(calculate_depreciation=1, submit=1)
|
||||||
initial_asset_value = get_asset_value(asset)
|
initial_asset_value = get_asset_value_after_depreciation(asset.name)
|
||||||
asset_repair = create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
|
asset_repair = create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
|
||||||
asset.reload()
|
asset.reload()
|
||||||
|
|
||||||
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
|
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
|
||||||
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
|
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
|
||||||
|
|
||||||
def test_purchase_invoice(self):
|
def test_purchase_invoice(self):
|
||||||
@ -256,10 +259,6 @@ class TestAssetRepair(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_asset_value(asset):
|
|
||||||
return asset.finance_books[0].value_after_depreciation
|
|
||||||
|
|
||||||
|
|
||||||
def num_of_depreciations(asset):
|
def num_of_depreciations(asset):
|
||||||
return asset.finance_books[0].total_number_of_depreciations
|
return asset.finance_books[0].total_number_of_depreciations
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ frappe.ui.form.on('Asset Value Adjustment', {
|
|||||||
set_current_asset_value: function(frm) {
|
set_current_asset_value: function(frm) {
|
||||||
if (frm.doc.asset) {
|
if (frm.doc.asset) {
|
||||||
frm.call({
|
frm.call({
|
||||||
method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_current_asset_value",
|
method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation",
|
||||||
args: {
|
args: {
|
||||||
asset: frm.doc.asset,
|
asset: frm.doc.asset,
|
||||||
finance_book: frm.doc.finance_book
|
finance_book: frm.doc.finance_book
|
||||||
|
@ -10,11 +10,10 @@ from frappe.utils import date_diff, flt, formatdate, get_link_to_form, getdate
|
|||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_checks_for_pl_and_bs_accounts,
|
get_checks_for_pl_and_bs_accounts,
|
||||||
)
|
)
|
||||||
|
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
|
||||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
get_asset_depr_schedule_doc,
|
get_asset_depr_schedule_doc,
|
||||||
get_depreciation_amount,
|
|
||||||
set_accumulated_depreciation,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ class AssetValueAdjustment(Document):
|
|||||||
|
|
||||||
def set_current_asset_value(self):
|
def set_current_asset_value(self):
|
||||||
if not self.current_asset_value and self.asset:
|
if not self.current_asset_value and self.asset:
|
||||||
self.current_asset_value = get_current_asset_value(self.asset, self.finance_book)
|
self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book)
|
||||||
|
|
||||||
def make_depreciation_entry(self):
|
def make_depreciation_entry(self):
|
||||||
asset = frappe.get_doc("Asset", self.asset)
|
asset = frappe.get_doc("Asset", self.asset)
|
||||||
@ -163,7 +162,7 @@ class AssetValueAdjustment(Document):
|
|||||||
depreciation_amount = days * rate_per_day
|
depreciation_amount = days * rate_per_day
|
||||||
from_date = data.schedule_date
|
from_date = data.schedule_date
|
||||||
else:
|
else:
|
||||||
depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
|
depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, d)
|
||||||
|
|
||||||
if depreciation_amount:
|
if depreciation_amount:
|
||||||
value_after_depreciation -= flt(depreciation_amount)
|
value_after_depreciation -= flt(depreciation_amount)
|
||||||
@ -171,18 +170,9 @@ class AssetValueAdjustment(Document):
|
|||||||
|
|
||||||
d.db_update()
|
d.db_update()
|
||||||
|
|
||||||
set_accumulated_depreciation(new_asset_depr_schedule_doc, d, ignore_booked_entry=True)
|
new_asset_depr_schedule_doc.set_accumulated_depreciation(d, ignore_booked_entry=True)
|
||||||
for asset_data in depr_schedule:
|
for asset_data in depr_schedule:
|
||||||
if not asset_data.journal_entry:
|
if not asset_data.journal_entry:
|
||||||
asset_data.db_update()
|
asset_data.db_update()
|
||||||
|
|
||||||
new_asset_depr_schedule_doc.submit()
|
new_asset_depr_schedule_doc.submit()
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_current_asset_value(asset, finance_book=None):
|
|
||||||
cond = {"parent": asset, "parenttype": "Asset"}
|
|
||||||
if finance_book:
|
|
||||||
cond.update({"finance_book": finance_book})
|
|
||||||
|
|
||||||
return frappe.db.get_value("Asset Finance Book", cond, "value_after_depreciation")
|
|
||||||
|
@ -6,13 +6,11 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_days, get_last_day, nowdate
|
from frappe.utils import add_days, get_last_day, nowdate
|
||||||
|
|
||||||
|
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
|
||||||
from erpnext.assets.doctype.asset.test_asset import create_asset_data
|
from erpnext.assets.doctype.asset.test_asset import create_asset_data
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
get_asset_depr_schedule_doc,
|
get_asset_depr_schedule_doc,
|
||||||
)
|
)
|
||||||
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
|
|
||||||
get_current_asset_value,
|
|
||||||
)
|
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +44,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
asset_doc.submit()
|
asset_doc.submit()
|
||||||
|
|
||||||
current_value = get_current_asset_value(asset_doc.name)
|
current_value = get_asset_value_after_depreciation(asset_doc.name)
|
||||||
self.assertEqual(current_value, 100000.0)
|
self.assertEqual(current_value, 100000.0)
|
||||||
|
|
||||||
def test_asset_depreciation_value_adjustment(self):
|
def test_asset_depreciation_value_adjustment(self):
|
||||||
@ -79,7 +77,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
|||||||
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||||
self.assertEquals(first_asset_depr_schedule.status, "Active")
|
self.assertEquals(first_asset_depr_schedule.status, "Active")
|
||||||
|
|
||||||
current_value = get_current_asset_value(asset_doc.name)
|
current_value = get_asset_value_after_depreciation(asset_doc.name)
|
||||||
adj_doc = make_asset_value_adjustment(
|
adj_doc = make_asset_value_adjustment(
|
||||||
asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
|
asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
|
||||||
)
|
)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cstr, flt, formatdate, getdate
|
from frappe.utils import cstr, flt, formatdate, getdate
|
||||||
|
|
||||||
from erpnext.accounts.report.financial_statements import (
|
from erpnext.accounts.report.financial_statements import (
|
||||||
@ -11,6 +12,8 @@ from erpnext.accounts.report.financial_statements import (
|
|||||||
get_period_list,
|
get_period_list,
|
||||||
validate_fiscal_year,
|
validate_fiscal_year,
|
||||||
)
|
)
|
||||||
|
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
|
||||||
|
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -85,6 +88,7 @@ def get_data(filters):
|
|||||||
"asset_name",
|
"asset_name",
|
||||||
"status",
|
"status",
|
||||||
"department",
|
"department",
|
||||||
|
"company",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"calculate_depreciation",
|
"calculate_depreciation",
|
||||||
"purchase_receipt",
|
"purchase_receipt",
|
||||||
@ -98,8 +102,21 @@ def get_data(filters):
|
|||||||
]
|
]
|
||||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||||
|
|
||||||
|
assets_linked_to_fb = frappe.db.get_all(
|
||||||
|
doctype="Asset Finance Book",
|
||||||
|
filters={"finance_book": filters.finance_book or ("is", "not set")},
|
||||||
|
pluck="parent",
|
||||||
|
)
|
||||||
|
|
||||||
for asset in assets_record:
|
for asset in assets_record:
|
||||||
asset_value = get_asset_value(asset, filters.finance_book)
|
if filters.finance_book:
|
||||||
|
if asset.asset_id not in assets_linked_to_fb:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
|
||||||
|
continue
|
||||||
|
|
||||||
|
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
|
||||||
row = {
|
row = {
|
||||||
"asset_id": asset.asset_id,
|
"asset_id": asset.asset_id,
|
||||||
"asset_name": asset.asset_name,
|
"asset_name": asset.asset_name,
|
||||||
@ -110,7 +127,7 @@ def get_data(filters):
|
|||||||
or pi_supplier_map.get(asset.purchase_invoice),
|
or pi_supplier_map.get(asset.purchase_invoice),
|
||||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
||||||
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
|
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters),
|
||||||
"available_for_use_date": asset.available_for_use_date,
|
"available_for_use_date": asset.available_for_use_date,
|
||||||
"location": asset.location,
|
"location": asset.location,
|
||||||
"asset_category": asset.asset_category,
|
"asset_category": asset.asset_category,
|
||||||
@ -122,21 +139,6 @@ def get_data(filters):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_asset_value(asset, finance_book=None):
|
|
||||||
if not asset.calculate_depreciation:
|
|
||||||
return flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation)
|
|
||||||
|
|
||||||
finance_book_filter = ["finance_book", "is", "not set"]
|
|
||||||
if finance_book:
|
|
||||||
finance_book_filter = ["finance_book", "=", finance_book]
|
|
||||||
|
|
||||||
return frappe.db.get_value(
|
|
||||||
doctype="Asset Finance Book",
|
|
||||||
filters=[["parent", "=", asset.asset_id], finance_book_filter],
|
|
||||||
fieldname="value_after_depreciation",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_chart_data(data, filters):
|
def prepare_chart_data(data, filters):
|
||||||
labels_values_map = {}
|
labels_values_map = {}
|
||||||
date_field = frappe.scrub(filters.date_based_on)
|
date_field = frappe.scrub(filters.date_based_on)
|
||||||
@ -182,6 +184,15 @@ def prepare_chart_data(data, filters):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
|
||||||
|
if asset.calculate_depreciation:
|
||||||
|
depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
|
||||||
|
else:
|
||||||
|
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
|
||||||
|
|
||||||
|
return flt(depr_amount, 2)
|
||||||
|
|
||||||
|
|
||||||
def get_finance_book_value_map(filters):
|
def get_finance_book_value_map(filters):
|
||||||
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
|
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
|
||||||
|
|
||||||
@ -203,6 +214,31 @@ def get_finance_book_value_map(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_manual_depreciation_amount_of_asset(asset, filters):
|
||||||
|
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
|
||||||
|
|
||||||
|
(_, _, depreciation_expense_account) = get_depreciation_accounts(asset)
|
||||||
|
|
||||||
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
|
|
||||||
|
result = (
|
||||||
|
frappe.qb.from_(gle)
|
||||||
|
.select(Sum(gle.debit))
|
||||||
|
.where(gle.against_voucher == asset.asset_id)
|
||||||
|
.where(gle.account == depreciation_expense_account)
|
||||||
|
.where(gle.debit != 0)
|
||||||
|
.where(gle.is_cancelled == 0)
|
||||||
|
.where(gle.posting_date <= date)
|
||||||
|
).run()
|
||||||
|
|
||||||
|
if result and result[0] and result[0][0]:
|
||||||
|
depr_amount = result[0][0]
|
||||||
|
else:
|
||||||
|
depr_amount = 0
|
||||||
|
|
||||||
|
return depr_amount
|
||||||
|
|
||||||
|
|
||||||
def get_purchase_receipt_supplier_map():
|
def get_purchase_receipt_supplier_map():
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
|
@ -15,17 +15,6 @@ class TestBulkTransactionLog(unittest.TestCase):
|
|||||||
create_customer()
|
create_customer()
|
||||||
create_item()
|
create_item()
|
||||||
|
|
||||||
def test_for_single_record(self):
|
|
||||||
so_name = create_so()
|
|
||||||
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
|
|
||||||
data = frappe.db.get_list(
|
|
||||||
"Sales Invoice",
|
|
||||||
filters={"posting_date": date.today(), "customer": "Bulk Customer"},
|
|
||||||
fields=["*"],
|
|
||||||
)
|
|
||||||
if not data:
|
|
||||||
self.fail("No Sales Invoice Created !")
|
|
||||||
|
|
||||||
def test_entry_in_log(self):
|
def test_entry_in_log(self):
|
||||||
so_name = create_so()
|
so_name = create_so()
|
||||||
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
|
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
|
||||||
|
@ -1221,6 +1221,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "apply_tds",
|
||||||
"fieldname": "tax_withholding_net_total",
|
"fieldname": "tax_withholding_net_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -1230,12 +1231,13 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "apply_tds",
|
||||||
"fieldname": "base_tax_withholding_net_total",
|
"fieldname": "base_tax_withholding_net_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Base Tax Withholding Net Total",
|
"label": "Base Tax Withholding Net Total",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "currency",
|
"options": "Company:company:default_currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1269,7 +1271,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-12-25 18:08:59.074182",
|
"modified": "2023-01-28 18:59:16.322824",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -10,6 +10,7 @@ from frappe.utils import add_days, flt, getdate, nowdate
|
|||||||
from frappe.utils.data import today
|
from frappe.utils.data import today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
from erpnext.accounts.party import get_due_date_from_template
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order
|
from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
||||||
make_purchase_invoice as make_pi_from_po,
|
make_purchase_invoice as make_pi_from_po,
|
||||||
@ -685,6 +686,12 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
else:
|
else:
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
|
def test_default_payment_terms(self):
|
||||||
|
due_date = get_due_date_from_template(
|
||||||
|
"_Test Payment Term Template 1", "2023-02-03", None
|
||||||
|
).strftime("%Y-%m-%d")
|
||||||
|
self.assertEqual(due_date, "2023-03-31")
|
||||||
|
|
||||||
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
|
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
|
||||||
po = create_purchase_order(do_not_save=1)
|
po = create_purchase_order(do_not_save=1)
|
||||||
po.payment_terms_template = "_Test Payment Term Template"
|
po.payment_terms_template = "_Test Payment Term Template"
|
||||||
|
@ -124,12 +124,11 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
frappe.urllib.get_full_url(
|
frappe.urllib.get_full_url(
|
||||||
"/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" +
|
"/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" +
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
doctype: frm.doc.doctype,
|
|
||||||
name: frm.doc.name,
|
name: frm.doc.name,
|
||||||
supplier: data.supplier,
|
supplier: data.supplier,
|
||||||
print_format: data.print_format || "Standard",
|
print_format: data.print_format || "Standard",
|
||||||
language: data.language || frappe.boot.lang,
|
language: data.language || frappe.boot.lang,
|
||||||
letter_head: data.letter_head || frm.doc.letter_head || "",
|
letterhead: data.letter_head || frm.doc.letter_head || "",
|
||||||
}).toString()
|
}).toString()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"message_for_supplier",
|
"message_for_supplier",
|
||||||
"terms_section_break",
|
"terms_section_break",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"tc_name",
|
"tc_name",
|
||||||
"terms",
|
"terms",
|
||||||
"printing_settings",
|
"printing_settings",
|
||||||
@ -278,13 +279,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-17 17:26:33.770993",
|
"modified": "2023-01-31 23:22:06.684694",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation",
|
"name": "Request for Quotation",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@ -388,24 +389,26 @@ def create_rfq_items(sq_doc, supplier, data):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pdf(doctype, name, supplier, print_format=None, language=None, letter_head=None):
|
def get_pdf(
|
||||||
# permissions get checked in `download_pdf`
|
name: str,
|
||||||
if doc := get_rfq_doc(doctype, name, supplier):
|
supplier: str,
|
||||||
download_pdf(
|
print_format: Optional[str] = None,
|
||||||
doctype,
|
language: Optional[str] = None,
|
||||||
name,
|
letterhead: Optional[str] = None,
|
||||||
print_format,
|
):
|
||||||
doc=doc,
|
doc = frappe.get_doc("Request for Quotation", name)
|
||||||
language=language,
|
|
||||||
letter_head=letter_head or None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_rfq_doc(doctype, name, supplier):
|
|
||||||
if supplier:
|
if supplier:
|
||||||
doc = frappe.get_doc(doctype, name)
|
|
||||||
doc.update_supplier_part_no(supplier)
|
doc.update_supplier_part_no(supplier)
|
||||||
return doc
|
|
||||||
|
# permissions get checked in `download_pdf`
|
||||||
|
download_pdf(
|
||||||
|
doc.doctype,
|
||||||
|
doc.name,
|
||||||
|
print_format,
|
||||||
|
doc=doc,
|
||||||
|
language=language,
|
||||||
|
letterhead=letterhead or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -8,6 +8,7 @@ from frappe.utils import nowdate
|
|||||||
|
|
||||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
|
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
|
||||||
create_supplier_quotation,
|
create_supplier_quotation,
|
||||||
|
get_pdf,
|
||||||
make_supplier_quotation_from_rfq,
|
make_supplier_quotation_from_rfq,
|
||||||
)
|
)
|
||||||
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
|
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
|
||||||
@ -124,6 +125,11 @@ class TestRequestforQuotation(FrappeTestCase):
|
|||||||
rfq.status = "Draft"
|
rfq.status = "Draft"
|
||||||
rfq.submit()
|
rfq.submit()
|
||||||
|
|
||||||
|
def test_get_pdf(self):
|
||||||
|
rfq = make_request_for_quotation()
|
||||||
|
get_pdf(rfq.name, rfq.get("suppliers")[0].supplier)
|
||||||
|
self.assertEqual(frappe.local.response.type, "pdf")
|
||||||
|
|
||||||
|
|
||||||
def make_request_for_quotation(**args):
|
def make_request_for_quotation(**args):
|
||||||
"""
|
"""
|
||||||
|
@ -15,60 +15,4 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
|||||||
|
|
||||||
|
|
||||||
class TestProcurementTracker(FrappeTestCase):
|
class TestProcurementTracker(FrappeTestCase):
|
||||||
def test_result_for_procurement_tracker(self):
|
pass
|
||||||
filters = {"company": "_Test Procurement Company", "cost_center": "Main - _TPC"}
|
|
||||||
expected_data = self.generate_expected_data()
|
|
||||||
report = execute(filters)
|
|
||||||
|
|
||||||
length = len(report[1])
|
|
||||||
self.assertEqual(expected_data, report[1][length - 1])
|
|
||||||
|
|
||||||
def generate_expected_data(self):
|
|
||||||
if not frappe.db.exists("Company", "_Test Procurement Company"):
|
|
||||||
frappe.get_doc(
|
|
||||||
dict(
|
|
||||||
doctype="Company",
|
|
||||||
company_name="_Test Procurement Company",
|
|
||||||
abbr="_TPC",
|
|
||||||
default_currency="INR",
|
|
||||||
country="Pakistan",
|
|
||||||
)
|
|
||||||
).insert()
|
|
||||||
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
|
|
||||||
mr = make_material_request(
|
|
||||||
company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC"
|
|
||||||
)
|
|
||||||
po = make_purchase_order(mr.name)
|
|
||||||
po.supplier = "_Test Supplier"
|
|
||||||
po.get("items")[0].cost_center = "Main - _TPC"
|
|
||||||
po.submit()
|
|
||||||
pr = make_purchase_receipt(po.name)
|
|
||||||
pr.get("items")[0].cost_center = "Main - _TPC"
|
|
||||||
pr.submit()
|
|
||||||
date_obj = datetime.date(datetime.now())
|
|
||||||
|
|
||||||
po.load_from_db()
|
|
||||||
|
|
||||||
expected_data = {
|
|
||||||
"material_request_date": date_obj,
|
|
||||||
"cost_center": "Main - _TPC",
|
|
||||||
"project": None,
|
|
||||||
"requesting_site": "_Test Procurement Warehouse - _TPC",
|
|
||||||
"requestor": "Administrator",
|
|
||||||
"material_request_no": mr.name,
|
|
||||||
"item_code": "_Test Item",
|
|
||||||
"quantity": 10.0,
|
|
||||||
"unit_of_measurement": "_Test UOM",
|
|
||||||
"status": "To Bill",
|
|
||||||
"purchase_order_date": date_obj,
|
|
||||||
"purchase_order": po.name,
|
|
||||||
"supplier": "_Test Supplier",
|
|
||||||
"estimated_cost": 0.0,
|
|
||||||
"actual_cost": 0.0,
|
|
||||||
"purchase_order_amt": po.net_total,
|
|
||||||
"purchase_order_amt_in_company_currency": po.base_net_total,
|
|
||||||
"expected_delivery_date": date_obj,
|
|
||||||
"actual_delivery_date": date_obj,
|
|
||||||
}
|
|
||||||
|
|
||||||
return expected_data
|
|
||||||
|
@ -712,6 +712,8 @@ class BuyingController(SubcontractingController):
|
|||||||
asset.purchase_date = self.posting_date
|
asset.purchase_date = self.posting_date
|
||||||
asset.supplier = self.supplier
|
asset.supplier = self.supplier
|
||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
|
if asset.docstatus == 2:
|
||||||
|
continue
|
||||||
if asset.docstatus == 0:
|
if asset.docstatus == 0:
|
||||||
asset.set(field, None)
|
asset.set(field, None)
|
||||||
asset.supplier = None
|
asset.supplier = None
|
||||||
|
@ -252,6 +252,7 @@ def get_already_returned_items(doc):
|
|||||||
child.parent = par.name and par.docstatus = 1
|
child.parent = par.name and par.docstatus = 1
|
||||||
and par.is_return = 1 and par.return_against = %s
|
and par.is_return = 1 and par.return_against = %s
|
||||||
group by item_code
|
group by item_code
|
||||||
|
for update
|
||||||
""".format(
|
""".format(
|
||||||
column, doc.doctype, doc.doctype
|
column, doc.doctype, doc.doctype
|
||||||
),
|
),
|
||||||
@ -305,7 +306,7 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
|
|||||||
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
|
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
|
||||||
|
|
||||||
# Used retrun against and supplier and is_retrun because there is an index added for it
|
# Used retrun against and supplier and is_retrun because there is an index added for it
|
||||||
data = frappe.db.get_list(
|
data = frappe.get_all(
|
||||||
doctype,
|
doctype,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
filters=[
|
filters=[
|
||||||
|
@ -409,7 +409,14 @@ class SubcontractingController(StockController):
|
|||||||
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
||||||
new_rm_obj = None
|
new_rm_obj = None
|
||||||
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
||||||
if batch_qty >= qty:
|
if batch_qty >= qty or (
|
||||||
|
rm_obj.consumed_qty == 0
|
||||||
|
and self.backflush_based_on == "BOM"
|
||||||
|
and len(self.available_materials[key]["batch_no"]) == 1
|
||||||
|
):
|
||||||
|
if rm_obj.consumed_qty == 0:
|
||||||
|
self.__set_consumed_qty(rm_obj, qty)
|
||||||
|
|
||||||
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
||||||
self.available_materials[key]["batch_no"][batch_no] -= qty
|
self.available_materials[key]["batch_no"][batch_no] -= qty
|
||||||
return
|
return
|
||||||
|
@ -312,7 +312,8 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "language",
|
"fieldname": "language",
|
||||||
@ -514,11 +515,10 @@
|
|||||||
"idx": 5,
|
"idx": 5,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-10-13 12:42:04.277879",
|
"modified": "2023-01-24 18:20:05.044791",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Lead",
|
"name": "Lead",
|
||||||
"name_case": "Title Case",
|
|
||||||
"naming_rule": "By \"Naming Series\" field",
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
@ -282,6 +282,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
"contact_no": "phone_1",
|
"contact_no": "phone_1",
|
||||||
"fax": "fax_1",
|
"fax": "fax_1",
|
||||||
},
|
},
|
||||||
|
"field_no_map": ["disabled"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
target_doc,
|
target_doc,
|
||||||
@ -390,7 +391,7 @@ def get_lead_details(lead, posting_date=None, company=None):
|
|||||||
{
|
{
|
||||||
"territory": lead.territory,
|
"territory": lead.territory,
|
||||||
"customer_name": lead.company_name or lead.lead_name,
|
"customer_name": lead.company_name or lead.lead_name,
|
||||||
"contact_display": " ".join(filter(None, [lead.salutation, lead.lead_name])),
|
"contact_display": " ".join(filter(None, [lead.lead_name])),
|
||||||
"contact_email": lead.email_id,
|
"contact_email": lead.email_id,
|
||||||
"contact_mobile": lead.mobile_no,
|
"contact_mobile": lead.mobile_no,
|
||||||
"contact_phone": lead.phone,
|
"contact_phone": lead.phone,
|
||||||
|
@ -26,10 +26,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-08 12:51:48.971517",
|
"modified": "2023-02-10 00:51:44.973957",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Lead Source",
|
"name": "Lead Source",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -58,5 +59,7 @@
|
|||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"translated_doctype": 1
|
||||||
}
|
}
|
@ -18,10 +18,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-20 12:22:01.866472",
|
"modified": "2023-02-10 01:40:23.713390",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Sales Stage",
|
"name": "Sales Stage",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -40,5 +41,7 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"states": [],
|
||||||
|
"track_changes": 1,
|
||||||
|
"translated_doctype": 1
|
||||||
}
|
}
|
@ -11,6 +11,40 @@ frappe.query_reports["Loan Interest Report"] = {
|
|||||||
"options": "Company",
|
"options": "Company",
|
||||||
"default": frappe.defaults.get_user_default("Company"),
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"applicant_type",
|
||||||
|
"label": __("Applicant Type"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["Customer", "Employee"],
|
||||||
|
"reqd": 1,
|
||||||
|
"default": "Customer",
|
||||||
|
on_change: function() {
|
||||||
|
frappe.query_report.set_filter_value('applicant', "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "applicant",
|
||||||
|
"label": __("Applicant"),
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"get_options": function() {
|
||||||
|
var applicant_type = frappe.query_report.get_filter_value('applicant_type');
|
||||||
|
var applicant = frappe.query_report.get_filter_value('applicant');
|
||||||
|
if(applicant && !applicant_type) {
|
||||||
|
frappe.throw(__("Please select Applicant Type first"));
|
||||||
|
}
|
||||||
|
return applicant_type;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"from_date",
|
||||||
|
"label": __("From Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"to_date",
|
||||||
|
"label": __("From Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -13,12 +13,12 @@ from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applic
|
|||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
columns = get_columns(filters)
|
columns = get_columns()
|
||||||
data = get_active_loan_details(filters)
|
data = get_active_loan_details(filters)
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns():
|
||||||
columns = [
|
columns = [
|
||||||
{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
|
{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
|
||||||
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
|
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
|
||||||
@ -70,6 +70,13 @@ def get_columns(filters):
|
|||||||
"options": "currency",
|
"options": "currency",
|
||||||
"width": 120,
|
"width": 120,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Accrued Principal"),
|
||||||
|
"fieldname": "accrued_principal",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": _("Total Repayment"),
|
"label": _("Total Repayment"),
|
||||||
"fieldname": "total_repayment",
|
"fieldname": "total_repayment",
|
||||||
@ -137,11 +144,16 @@ def get_columns(filters):
|
|||||||
|
|
||||||
|
|
||||||
def get_active_loan_details(filters):
|
def get_active_loan_details(filters):
|
||||||
|
filter_obj = {
|
||||||
filter_obj = {"status": ("!=", "Closed")}
|
"status": ("!=", "Closed"),
|
||||||
|
"docstatus": 1,
|
||||||
|
}
|
||||||
if filters.get("company"):
|
if filters.get("company"):
|
||||||
filter_obj.update({"company": filters.get("company")})
|
filter_obj.update({"company": filters.get("company")})
|
||||||
|
|
||||||
|
if filters.get("applicant"):
|
||||||
|
filter_obj.update({"applicant": filters.get("applicant")})
|
||||||
|
|
||||||
loan_details = frappe.get_all(
|
loan_details = frappe.get_all(
|
||||||
"Loan",
|
"Loan",
|
||||||
fields=[
|
fields=[
|
||||||
@ -167,8 +179,8 @@ def get_active_loan_details(filters):
|
|||||||
|
|
||||||
sanctioned_amount_map = get_sanctioned_amount_map()
|
sanctioned_amount_map = get_sanctioned_amount_map()
|
||||||
penal_interest_rate_map = get_penal_interest_rate_map()
|
penal_interest_rate_map = get_penal_interest_rate_map()
|
||||||
payments = get_payments(loan_list)
|
payments = get_payments(loan_list, filters)
|
||||||
accrual_map = get_interest_accruals(loan_list)
|
accrual_map = get_interest_accruals(loan_list, filters)
|
||||||
currency = erpnext.get_company_currency(filters.get("company"))
|
currency = erpnext.get_company_currency(filters.get("company"))
|
||||||
|
|
||||||
for loan in loan_details:
|
for loan in loan_details:
|
||||||
@ -183,6 +195,7 @@ def get_active_loan_details(filters):
|
|||||||
- flt(loan.written_off_amount),
|
- flt(loan.written_off_amount),
|
||||||
"total_repayment": flt(payments.get(loan.loan)),
|
"total_repayment": flt(payments.get(loan.loan)),
|
||||||
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
|
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
|
||||||
|
"accrued_principal": flt(accrual_map.get(loan.loan, {}).get("accrued_principal")),
|
||||||
"interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
|
"interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
|
||||||
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
|
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
|
||||||
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
|
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
|
||||||
@ -212,20 +225,35 @@ def get_sanctioned_amount_map():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_payments(loans):
|
def get_payments(loans, filters):
|
||||||
|
query_filters = {"against_loan": ("in", loans)}
|
||||||
|
|
||||||
|
if filters.get("from_date"):
|
||||||
|
query_filters.update({"posting_date": (">=", filters.get("from_date"))})
|
||||||
|
|
||||||
|
if filters.get("to_date"):
|
||||||
|
query_filters.update({"posting_date": ("<=", filters.get("to_date"))})
|
||||||
|
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
frappe.get_all(
|
frappe.get_all(
|
||||||
"Loan Repayment",
|
"Loan Repayment",
|
||||||
fields=["against_loan", "sum(amount_paid)"],
|
fields=["against_loan", "sum(amount_paid)"],
|
||||||
filters={"against_loan": ("in", loans)},
|
filters=query_filters,
|
||||||
group_by="against_loan",
|
group_by="against_loan",
|
||||||
as_list=1,
|
as_list=1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_interest_accruals(loans):
|
def get_interest_accruals(loans, filters):
|
||||||
accrual_map = {}
|
accrual_map = {}
|
||||||
|
query_filters = {"loan": ("in", loans)}
|
||||||
|
|
||||||
|
if filters.get("from_date"):
|
||||||
|
query_filters.update({"posting_date": (">=", filters.get("from_date"))})
|
||||||
|
|
||||||
|
if filters.get("to_date"):
|
||||||
|
query_filters.update({"posting_date": ("<=", filters.get("to_date"))})
|
||||||
|
|
||||||
interest_accruals = frappe.get_all(
|
interest_accruals = frappe.get_all(
|
||||||
"Loan Interest Accrual",
|
"Loan Interest Accrual",
|
||||||
@ -236,8 +264,9 @@ def get_interest_accruals(loans):
|
|||||||
"penalty_amount",
|
"penalty_amount",
|
||||||
"paid_interest_amount",
|
"paid_interest_amount",
|
||||||
"accrual_type",
|
"accrual_type",
|
||||||
|
"payable_principal_amount",
|
||||||
],
|
],
|
||||||
filters={"loan": ("in", loans)},
|
filters=query_filters,
|
||||||
order_by="posting_date desc",
|
order_by="posting_date desc",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -246,6 +275,7 @@ def get_interest_accruals(loans):
|
|||||||
entry.loan,
|
entry.loan,
|
||||||
{
|
{
|
||||||
"accrued_interest": 0.0,
|
"accrued_interest": 0.0,
|
||||||
|
"accrued_principal": 0.0,
|
||||||
"undue_interest": 0.0,
|
"undue_interest": 0.0,
|
||||||
"interest_outstanding": 0.0,
|
"interest_outstanding": 0.0,
|
||||||
"last_accrual_date": "",
|
"last_accrual_date": "",
|
||||||
@ -270,6 +300,7 @@ def get_interest_accruals(loans):
|
|||||||
accrual_map[entry.loan]["undue_interest"] += entry.interest_amount - entry.paid_interest_amount
|
accrual_map[entry.loan]["undue_interest"] += entry.interest_amount - entry.paid_interest_amount
|
||||||
|
|
||||||
accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
|
accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
|
||||||
|
accrual_map[entry.loan]["accrued_principal"] += entry.payable_principal_amount
|
||||||
|
|
||||||
if last_accrual_date and getdate(entry.posting_date) == last_accrual_date:
|
if last_accrual_date and getdate(entry.posting_date) == last_accrual_date:
|
||||||
accrual_map[entry.loan]["penalty"] = entry.penalty_amount
|
accrual_map[entry.loan]["penalty"] = entry.penalty_amount
|
||||||
|
315
erpnext/loan_management/workspace/loans/loans.json
Normal file
315
erpnext/loan_management/workspace/loans/loans.json
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
{
|
||||||
|
"charts": [],
|
||||||
|
"content": "[{\"id\":\"_38WStznya\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"t7o_K__1jB\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan Application\",\"col\":3}},{\"id\":\"IRiNDC6w1p\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan\",\"col\":3}},{\"id\":\"xbbo0FYbq0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"7ZL4Bro-Vi\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yhyioTViZ3\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"id\":\"oYFn4b1kSw\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan\",\"col\":4}},{\"id\":\"vZepJF5tl9\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Processes\",\"col\":4}},{\"id\":\"k-393Mjhqe\",\"type\":\"card\",\"data\":{\"card_name\":\"Disbursement and Repayment\",\"col\":4}},{\"id\":\"6crJ0DBiBJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Security\",\"col\":4}},{\"id\":\"Um5YwxVLRJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
|
||||||
|
"creation": "2020-03-12 16:35:55.299820",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Workspace",
|
||||||
|
"for_user": "",
|
||||||
|
"hide_custom": 0,
|
||||||
|
"icon": "loan",
|
||||||
|
"idx": 0,
|
||||||
|
"is_hidden": 0,
|
||||||
|
"label": "Loans",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan",
|
||||||
|
"link_count": 0,
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Type",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Type",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Application",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Application",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Processes",
|
||||||
|
"link_count": 0,
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Process Loan Security Shortfall",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Process Loan Security Shortfall",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Process Loan Interest Accrual",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Process Loan Interest Accrual",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Disbursement and Repayment",
|
||||||
|
"link_count": 0,
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Disbursement",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Disbursement",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Repayment",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Repayment",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Write Off",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Write Off",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Interest Accrual",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Interest Accrual",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Security",
|
||||||
|
"link_count": 0,
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Security Type",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Type",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Security Price",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Price",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Security",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Security Pledge",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Pledge",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Security Unpledge",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Unpledge",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Loan Security Shortfall",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Shortfall",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Reports",
|
||||||
|
"link_count": 6,
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Loan Repayment and Closure",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Repayment and Closure",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Loan Security Status",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Status",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Loan Interest Report",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Interest Report",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Loan Security Exposure",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Exposure",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Applicant-Wise Loan Security Exposure",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Applicant-Wise Loan Security Exposure",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Loan Security Status",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Loan Security Status",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2023-01-31 19:47:13.114415",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Loan Management",
|
||||||
|
"name": "Loans",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"parent_page": "",
|
||||||
|
"public": 1,
|
||||||
|
"quick_lists": [],
|
||||||
|
"restrict_to_domain": "",
|
||||||
|
"roles": [],
|
||||||
|
"sequence_id": 16.0,
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"color": "Green",
|
||||||
|
"format": "{} Open",
|
||||||
|
"label": "Loan Application",
|
||||||
|
"link_to": "Loan Application",
|
||||||
|
"stats_filter": "{ \"status\": \"Open\" }",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Loan",
|
||||||
|
"link_to": "Loan",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doc_view": "",
|
||||||
|
"label": "Dashboard",
|
||||||
|
"link_to": "Loan Dashboard",
|
||||||
|
"type": "Dashboard"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Loans"
|
||||||
|
}
|
@ -289,7 +289,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "scrap_items",
|
"fieldname": "scrap_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Items",
|
"label": "Scrap Items",
|
||||||
"options": "BOM Scrap Item"
|
"options": "BOM Scrap Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -605,7 +605,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-01-10 07:47:08.652616",
|
"modified": "2023-02-13 17:31:37.504565",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM",
|
"name": "BOM",
|
||||||
|
@ -306,7 +306,6 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
|||||||
execute:frappe.delete_doc("DocType", "Naming Series")
|
execute:frappe.delete_doc("DocType", "Naming Series")
|
||||||
erpnext.patches.v13_0.job_card_status_on_hold
|
erpnext.patches.v13_0.job_card_status_on_hold
|
||||||
erpnext.patches.v14_0.copy_is_subcontracted_value_to_is_old_subcontracting_flow
|
erpnext.patches.v14_0.copy_is_subcontracted_value_to_is_old_subcontracting_flow
|
||||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
|
||||||
erpnext.patches.v14_0.crm_ux_cleanup
|
erpnext.patches.v14_0.crm_ux_cleanup
|
||||||
erpnext.patches.v14_0.migrate_existing_lead_notes_as_per_the_new_format
|
erpnext.patches.v14_0.migrate_existing_lead_notes_as_per_the_new_format
|
||||||
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
|
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
|
||||||
@ -315,7 +314,6 @@ erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
|
|||||||
erpnext.patches.v14_0.fix_crm_no_of_employees
|
erpnext.patches.v14_0.fix_crm_no_of_employees
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
||||||
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
|
||||||
erpnext.patches.v13_0.update_schedule_type_in_loans
|
erpnext.patches.v13_0.update_schedule_type_in_loans
|
||||||
erpnext.patches.v13_0.drop_unused_sle_index_parts
|
erpnext.patches.v13_0.drop_unused_sle_index_parts
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
||||||
@ -325,3 +323,8 @@ erpnext.patches.v14_0.setup_clear_repost_logs
|
|||||||
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
|
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
|
||||||
erpnext.patches.v14_0.update_entry_type_for_journal_entry
|
erpnext.patches.v14_0.update_entry_type_for_journal_entry
|
||||||
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
|
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
|
||||||
|
erpnext.patches.v14_0.set_pick_list_status
|
||||||
|
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
||||||
|
# below 2 migration patches should always run last
|
||||||
|
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||||
|
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
from erpnext.setup.setup_wizard.operations.install_fixtures import default_sales_partner_type
|
from erpnext.setup.setup_wizard.operations.install_fixtures import read_lines
|
||||||
|
|
||||||
frappe.reload_doc("selling", "doctype", "sales_partner_type")
|
frappe.reload_doc("selling", "doctype", "sales_partner_type")
|
||||||
|
|
||||||
frappe.local.lang = frappe.db.get_default("lang") or "en"
|
frappe.local.lang = frappe.db.get_default("lang") or "en"
|
||||||
|
|
||||||
|
default_sales_partner_type = read_lines("sales_partner_type.txt")
|
||||||
|
|
||||||
for s in default_sales_partner_type:
|
for s in default_sales_partner_type:
|
||||||
insert_sales_partner_type(_(s))
|
insert_sales_partner_type(s)
|
||||||
|
|
||||||
# get partner type in existing forms (customized)
|
# get partner type in existing forms (customized)
|
||||||
# and create a document if not created
|
# and create a document if not created
|
||||||
|
@ -18,9 +18,11 @@ def create_new_cost_center_allocation_records(cc_allocations):
|
|||||||
cca = frappe.new_doc("Cost Center Allocation")
|
cca = frappe.new_doc("Cost Center Allocation")
|
||||||
cca.main_cost_center = main_cc
|
cca.main_cost_center = main_cc
|
||||||
cca.valid_from = today()
|
cca.valid_from = today()
|
||||||
|
cca._skip_from_date_validation = True
|
||||||
|
|
||||||
for child_cc, percentage in allocations.items():
|
for child_cc, percentage in allocations.items():
|
||||||
cca.append("allocation_percentages", ({"cost_center": child_cc, "percentage": percentage}))
|
cca.append("allocation_percentages", ({"cost_center": child_cc, "percentage": percentage}))
|
||||||
|
|
||||||
cca.save()
|
cca.save()
|
||||||
cca.submit()
|
cca.submit()
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@ import frappe
|
|||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.query_builder import Case, CustomFunction
|
from frappe.query_builder import Case, CustomFunction
|
||||||
from frappe.query_builder.custom import ConstantColumn
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
from frappe.query_builder.functions import IfNull
|
from frappe.query_builder.functions import Count, IfNull
|
||||||
|
from frappe.utils import flt
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_dimensions,
|
get_dimensions,
|
||||||
@ -17,9 +18,9 @@ def create_accounting_dimension_fields():
|
|||||||
make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
|
make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
|
||||||
|
|
||||||
|
|
||||||
def generate_name_for_payment_ledger_entries(gl_entries):
|
def generate_name_for_payment_ledger_entries(gl_entries, start):
|
||||||
for index, entry in enumerate(gl_entries, 1):
|
for index, entry in enumerate(gl_entries, 0):
|
||||||
entry.name = index
|
entry.name = start + index
|
||||||
|
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
@ -81,6 +82,14 @@ def insert_chunk_into_payment_ledger(insert_query, gl_entries):
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
|
"""
|
||||||
|
Description:
|
||||||
|
Migrate records from `tabGL Entry` to `tabPayment Ledger Entry`.
|
||||||
|
Patch is non-resumable. if patch failed or is terminatted abnormally, clear 'tabPayment Ledger Entry' table manually before re-running. Re-running is safe only during V13->V14 update.
|
||||||
|
|
||||||
|
Note: Post successful migration to V14, re-running is NOT-SAFE and SHOULD NOT be attempted.
|
||||||
|
"""
|
||||||
|
|
||||||
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
|
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
|
||||||
# create accounting dimension fields in Payment Ledger
|
# create accounting dimension fields in Payment Ledger
|
||||||
create_accounting_dimension_fields()
|
create_accounting_dimension_fields()
|
||||||
@ -89,52 +98,90 @@ def execute():
|
|||||||
account = qb.DocType("Account")
|
account = qb.DocType("Account")
|
||||||
ifelse = CustomFunction("IF", ["condition", "then", "else"])
|
ifelse = CustomFunction("IF", ["condition", "then", "else"])
|
||||||
|
|
||||||
gl_entries = (
|
# Get Records Count
|
||||||
qb.from_(gl)
|
accounts = (
|
||||||
.inner_join(account)
|
qb.from_(account)
|
||||||
.on((gl.account == account.name) & (account.account_type.isin(["Receivable", "Payable"])))
|
.select(account.name)
|
||||||
.select(
|
.where((account.account_type == "Receivable") | (account.account_type == "Payable"))
|
||||||
gl.star,
|
.orderby(account.name)
|
||||||
ConstantColumn(1).as_("docstatus"),
|
|
||||||
account.account_type.as_("account_type"),
|
|
||||||
IfNull(
|
|
||||||
ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
|
|
||||||
).as_("against_voucher_type"),
|
|
||||||
IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
|
|
||||||
"against_voucher_no"
|
|
||||||
),
|
|
||||||
# convert debit/credit to amount
|
|
||||||
Case()
|
|
||||||
.when(account.account_type == "Receivable", gl.debit - gl.credit)
|
|
||||||
.else_(gl.credit - gl.debit)
|
|
||||||
.as_("amount"),
|
|
||||||
# convert debit/credit in account currency to amount in account currency
|
|
||||||
Case()
|
|
||||||
.when(
|
|
||||||
account.account_type == "Receivable",
|
|
||||||
gl.debit_in_account_currency - gl.credit_in_account_currency,
|
|
||||||
)
|
|
||||||
.else_(gl.credit_in_account_currency - gl.debit_in_account_currency)
|
|
||||||
.as_("amount_in_account_currency"),
|
|
||||||
)
|
|
||||||
.where(gl.is_cancelled == 0)
|
|
||||||
.orderby(gl.creation)
|
|
||||||
.run(as_dict=True)
|
|
||||||
)
|
)
|
||||||
|
un_processed = (
|
||||||
|
qb.from_(gl)
|
||||||
|
.select(Count(gl.name))
|
||||||
|
.where((gl.is_cancelled == 0) & (gl.account.isin(accounts)))
|
||||||
|
.run()
|
||||||
|
)[0][0]
|
||||||
|
|
||||||
# primary key(name) for payment ledger records
|
if un_processed:
|
||||||
generate_name_for_payment_ledger_entries(gl_entries)
|
print(f"Migrating {un_processed} GL Entries to Payment Ledger")
|
||||||
|
|
||||||
# split data into chunks
|
processed = 0
|
||||||
chunk_size = 1000
|
last_update_percent = 0
|
||||||
try:
|
batch_size = 5000
|
||||||
for i in range(0, len(gl_entries), chunk_size):
|
last_name = None
|
||||||
insert_query = build_insert_query()
|
|
||||||
insert_chunk_into_payment_ledger(insert_query, gl_entries[i : i + chunk_size])
|
while True:
|
||||||
frappe.db.commit()
|
if last_name:
|
||||||
except Exception as err:
|
where_clause = gl.name.gt(last_name) & (gl.is_cancelled == 0)
|
||||||
frappe.db.rollback()
|
else:
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
where_clause = gl.is_cancelled == 0
|
||||||
qb.from_(ple).delete().where(ple.docstatus >= 0).run()
|
|
||||||
frappe.db.commit()
|
gl_entries = (
|
||||||
raise err
|
qb.from_(gl)
|
||||||
|
.inner_join(account)
|
||||||
|
.on((gl.account == account.name) & (account.account_type.isin(["Receivable", "Payable"])))
|
||||||
|
.select(
|
||||||
|
gl.star,
|
||||||
|
ConstantColumn(1).as_("docstatus"),
|
||||||
|
account.account_type.as_("account_type"),
|
||||||
|
IfNull(
|
||||||
|
ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
|
||||||
|
).as_("against_voucher_type"),
|
||||||
|
IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
|
||||||
|
"against_voucher_no"
|
||||||
|
),
|
||||||
|
# convert debit/credit to amount
|
||||||
|
Case()
|
||||||
|
.when(account.account_type == "Receivable", gl.debit - gl.credit)
|
||||||
|
.else_(gl.credit - gl.debit)
|
||||||
|
.as_("amount"),
|
||||||
|
# convert debit/credit in account currency to amount in account currency
|
||||||
|
Case()
|
||||||
|
.when(
|
||||||
|
account.account_type == "Receivable",
|
||||||
|
gl.debit_in_account_currency - gl.credit_in_account_currency,
|
||||||
|
)
|
||||||
|
.else_(gl.credit_in_account_currency - gl.debit_in_account_currency)
|
||||||
|
.as_("amount_in_account_currency"),
|
||||||
|
)
|
||||||
|
.where(where_clause)
|
||||||
|
.orderby(gl.name)
|
||||||
|
.limit(batch_size)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
if gl_entries:
|
||||||
|
last_name = gl_entries[-1].name
|
||||||
|
|
||||||
|
# primary key(name) for payment ledger records
|
||||||
|
generate_name_for_payment_ledger_entries(gl_entries, processed)
|
||||||
|
|
||||||
|
try:
|
||||||
|
insert_query = build_insert_query()
|
||||||
|
insert_chunk_into_payment_ledger(insert_query, gl_entries)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
processed += len(gl_entries)
|
||||||
|
|
||||||
|
# Progress message
|
||||||
|
percent = flt((processed / un_processed) * 100, 2)
|
||||||
|
if percent - last_update_percent > 1:
|
||||||
|
print(f"{percent}% ({processed}) records processed")
|
||||||
|
last_update_percent = percent
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
print("Migration Failed. Clear `tabPayment Ledger Entry` table before re-running")
|
||||||
|
raise err
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
print(f"{processed} records have been sucessfully migrated")
|
||||||
|
@ -1,81 +1,98 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.utils import create_batch
|
from frappe.query_builder import CustomFunction
|
||||||
|
from frappe.query_builder.functions import Count, IfNull
|
||||||
|
from frappe.utils import flt
|
||||||
def remove_duplicate_entries(pl_entries):
|
|
||||||
unique_vouchers = set()
|
|
||||||
for x in pl_entries:
|
|
||||||
unique_vouchers.add(
|
|
||||||
(x.company, x.account, x.party_type, x.party, x.voucher_type, x.voucher_no, x.gle_remarks)
|
|
||||||
)
|
|
||||||
|
|
||||||
entries = []
|
|
||||||
for x in unique_vouchers:
|
|
||||||
entries.append(
|
|
||||||
frappe._dict(
|
|
||||||
company=x[0],
|
|
||||||
account=x[1],
|
|
||||||
party_type=x[2],
|
|
||||||
party=x[3],
|
|
||||||
voucher_type=x[4],
|
|
||||||
voucher_no=x[5],
|
|
||||||
gle_remarks=x[6],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return entries
|
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
|
"""
|
||||||
|
Migrate 'remarks' field from 'tabGL Entry' to 'tabPayment Ledger Entry'
|
||||||
|
"""
|
||||||
|
|
||||||
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
|
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
|
||||||
|
|
||||||
gle = qb.DocType("GL Entry")
|
gle = qb.DocType("GL Entry")
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
|
|
||||||
# get ple and their remarks from GL Entry
|
# Get empty PLE records
|
||||||
pl_entries = (
|
un_processed = (
|
||||||
qb.from_(ple)
|
qb.from_(ple).select(Count(ple.name)).where((ple.remarks.isnull()) & (ple.delinked == 0)).run()
|
||||||
.left_join(gle)
|
)[0][0]
|
||||||
.on(
|
|
||||||
(ple.account == gle.account)
|
|
||||||
& (ple.party_type == gle.party_type)
|
|
||||||
& (ple.party == gle.party)
|
|
||||||
& (ple.voucher_type == gle.voucher_type)
|
|
||||||
& (ple.voucher_no == gle.voucher_no)
|
|
||||||
& (ple.company == gle.company)
|
|
||||||
)
|
|
||||||
.select(
|
|
||||||
ple.company,
|
|
||||||
ple.account,
|
|
||||||
ple.party_type,
|
|
||||||
ple.party,
|
|
||||||
ple.voucher_type,
|
|
||||||
ple.voucher_no,
|
|
||||||
gle.remarks.as_("gle_remarks"),
|
|
||||||
)
|
|
||||||
.where((ple.delinked == 0) & (gle.is_cancelled == 0))
|
|
||||||
.run(as_dict=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
pl_entries = remove_duplicate_entries(pl_entries)
|
if un_processed:
|
||||||
|
print(f"Remarks for {un_processed} Payment Ledger records will be updated from GL Entry")
|
||||||
|
|
||||||
if pl_entries:
|
ifelse = CustomFunction("IF", ["condition", "then", "else"])
|
||||||
# split into multiple batches, update and commit for each batch
|
|
||||||
|
processed = 0
|
||||||
|
last_percent_update = 0
|
||||||
batch_size = 1000
|
batch_size = 1000
|
||||||
for batch in create_batch(pl_entries, batch_size):
|
last_name = None
|
||||||
for entry in batch:
|
|
||||||
query = (
|
|
||||||
qb.update(ple)
|
|
||||||
.set(ple.remarks, entry.gle_remarks)
|
|
||||||
.where(
|
|
||||||
(ple.company == entry.company)
|
|
||||||
& (ple.account == entry.account)
|
|
||||||
& (ple.party_type == entry.party_type)
|
|
||||||
& (ple.party == entry.party)
|
|
||||||
& (ple.voucher_type == entry.voucher_type)
|
|
||||||
& (ple.voucher_no == entry.voucher_no)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.run()
|
|
||||||
|
|
||||||
frappe.db.commit()
|
while True:
|
||||||
|
if last_name:
|
||||||
|
where_clause = (ple.name.gt(last_name)) & (ple.remarks.isnull()) & (ple.delinked == 0)
|
||||||
|
else:
|
||||||
|
where_clause = (ple.remarks.isnull()) & (ple.delinked == 0)
|
||||||
|
|
||||||
|
# results are deterministic
|
||||||
|
names = (
|
||||||
|
qb.from_(ple).select(ple.name).where(where_clause).orderby(ple.name).limit(batch_size).run()
|
||||||
|
)
|
||||||
|
|
||||||
|
if names:
|
||||||
|
last_name = names[-1][0]
|
||||||
|
|
||||||
|
pl_entries = (
|
||||||
|
qb.from_(ple)
|
||||||
|
.left_join(gle)
|
||||||
|
.on(
|
||||||
|
(ple.account == gle.account)
|
||||||
|
& (ple.party_type == gle.party_type)
|
||||||
|
& (ple.party == gle.party)
|
||||||
|
& (ple.voucher_type == gle.voucher_type)
|
||||||
|
& (ple.voucher_no == gle.voucher_no)
|
||||||
|
& (
|
||||||
|
ple.against_voucher_type
|
||||||
|
== IfNull(
|
||||||
|
ifelse(gle.against_voucher_type == "", None, gle.against_voucher_type), gle.voucher_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
& (
|
||||||
|
ple.against_voucher_no
|
||||||
|
== IfNull(ifelse(gle.against_voucher == "", None, gle.against_voucher), gle.voucher_no)
|
||||||
|
)
|
||||||
|
& (ple.company == gle.company)
|
||||||
|
& (
|
||||||
|
((ple.account_type == "Receivable") & (ple.amount == (gle.debit - gle.credit)))
|
||||||
|
| (ple.account_type == "Payable") & (ple.amount == (gle.credit - gle.debit))
|
||||||
|
)
|
||||||
|
& (gle.remarks.notnull())
|
||||||
|
& (gle.is_cancelled == 0)
|
||||||
|
)
|
||||||
|
.select(ple.name)
|
||||||
|
.distinct()
|
||||||
|
.select(
|
||||||
|
gle.remarks.as_("gle_remarks"),
|
||||||
|
)
|
||||||
|
.where(ple.name.isin(names))
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
if pl_entries:
|
||||||
|
for entry in pl_entries:
|
||||||
|
query = qb.update(ple).set(ple.remarks, entry.gle_remarks).where((ple.name == entry.name))
|
||||||
|
query.run()
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
processed += len(pl_entries)
|
||||||
|
percentage = flt((processed / un_processed) * 100, 2)
|
||||||
|
if percentage - last_percent_update > 1:
|
||||||
|
print(f"{percentage}% ({processed}) PLE records updated")
|
||||||
|
last_percent_update = percentage
|
||||||
|
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
print("Remarks succesfully migrated")
|
||||||
|
40
erpnext/patches/v14_0/set_pick_list_status.py
Normal file
40
erpnext/patches/v14_0/set_pick_list_status.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
pl = frappe.qb.DocType("Pick List")
|
||||||
|
se = frappe.qb.DocType("Stock Entry")
|
||||||
|
dn = frappe.qb.DocType("Delivery Note")
|
||||||
|
|
||||||
|
(
|
||||||
|
frappe.qb.update(pl).set(
|
||||||
|
pl.status,
|
||||||
|
(
|
||||||
|
frappe.qb.terms.Case()
|
||||||
|
.when(pl.docstatus == 0, "Draft")
|
||||||
|
.when(pl.docstatus == 2, "Cancelled")
|
||||||
|
.else_("Completed")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
).run()
|
||||||
|
|
||||||
|
(
|
||||||
|
frappe.qb.update(pl)
|
||||||
|
.set(pl.status, "Open")
|
||||||
|
.where(
|
||||||
|
(
|
||||||
|
ExistsCriterion(
|
||||||
|
frappe.qb.from_(se).select(se.name).where((se.docstatus == 1) & (se.pick_list == pl.name))
|
||||||
|
)
|
||||||
|
| ExistsCriterion(
|
||||||
|
frappe.qb.from_(dn).select(dn.name).where((dn.docstatus == 1) & (dn.pick_list == pl.name))
|
||||||
|
)
|
||||||
|
).negate()
|
||||||
|
& (pl.docstatus == 1)
|
||||||
|
)
|
||||||
|
).run()
|
@ -1,9 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
|
||||||
set_draft_asset_depr_schedule_details,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule")
|
frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule")
|
||||||
@ -16,7 +12,7 @@ def execute():
|
|||||||
for fb_row in finance_book_rows:
|
for fb_row in finance_book_rows:
|
||||||
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
||||||
|
|
||||||
set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset, fb_row)
|
asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, fb_row)
|
||||||
|
|
||||||
asset_depr_schedule_doc.insert()
|
asset_depr_schedule_doc.insert()
|
||||||
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.query_builder.functions import IfNull, Sum
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
asset = frappe.qb.DocType("Asset")
|
||||||
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
|
aca = frappe.qb.DocType("Asset Category Account")
|
||||||
|
company = frappe.qb.DocType("Company")
|
||||||
|
|
||||||
|
asset_total_depr_value_map = (
|
||||||
|
frappe.qb.from_(gle)
|
||||||
|
.join(asset)
|
||||||
|
.on(gle.against_voucher == asset.name)
|
||||||
|
.join(aca)
|
||||||
|
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
|
||||||
|
.join(company)
|
||||||
|
.on(company.name == asset.company)
|
||||||
|
.select(Sum(gle.debit).as_("value"), asset.name.as_("asset_name"))
|
||||||
|
.where(
|
||||||
|
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||||
|
)
|
||||||
|
.where(gle.debit != 0)
|
||||||
|
.where(gle.is_cancelled == 0)
|
||||||
|
.where(asset.docstatus == 1)
|
||||||
|
.where(asset.calculate_depreciation == 0)
|
||||||
|
.groupby(asset.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.qb.update(asset).join(asset_total_depr_value_map).on(
|
||||||
|
asset_total_depr_value_map.asset_name == asset.name
|
||||||
|
).set(
|
||||||
|
asset.value_after_depreciation, asset.value_after_depreciation - asset_total_depr_value_map.value
|
||||||
|
).where(
|
||||||
|
asset.docstatus == 1
|
||||||
|
).where(
|
||||||
|
asset.calculate_depreciation == 0
|
||||||
|
).run()
|
@ -10,62 +10,6 @@ from frappe.website.serve import get_response
|
|||||||
|
|
||||||
|
|
||||||
class TestHomepageSection(unittest.TestCase):
|
class TestHomepageSection(unittest.TestCase):
|
||||||
def test_homepage_section_card(self):
|
|
||||||
try:
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Homepage Section",
|
|
||||||
"name": "Card Section",
|
|
||||||
"section_based_on": "Cards",
|
|
||||||
"section_cards": [
|
|
||||||
{
|
|
||||||
"title": "Card 1",
|
|
||||||
"subtitle": "Subtitle 1",
|
|
||||||
"content": "This is test card 1",
|
|
||||||
"route": "/card-1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Card 2",
|
|
||||||
"subtitle": "Subtitle 2",
|
|
||||||
"content": "This is test card 2",
|
|
||||||
"image": "test.jpg",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no_of_columns": 3,
|
|
||||||
}
|
|
||||||
).insert(ignore_if_duplicate=True)
|
|
||||||
except frappe.DuplicateEntryError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
set_request(method="GET", path="home")
|
|
||||||
response = get_response()
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
html = frappe.safe_decode(response.get_data())
|
|
||||||
|
|
||||||
soup = BeautifulSoup(html, "html.parser")
|
|
||||||
sections = soup.find("main").find_all("section")
|
|
||||||
self.assertEqual(len(sections), 3)
|
|
||||||
|
|
||||||
homepage_section = sections[2]
|
|
||||||
self.assertEqual(homepage_section.h3.text, "Card Section")
|
|
||||||
|
|
||||||
cards = homepage_section.find_all(class_="card")
|
|
||||||
|
|
||||||
self.assertEqual(len(cards), 2)
|
|
||||||
self.assertEqual(cards[0].h5.text, "Card 1")
|
|
||||||
self.assertEqual(cards[0].a["href"], "/card-1")
|
|
||||||
self.assertEqual(cards[1].p.text, "Subtitle 2")
|
|
||||||
|
|
||||||
img = cards[1].find(class_="card-img-top")
|
|
||||||
|
|
||||||
self.assertEqual(img["src"], "test.jpg")
|
|
||||||
self.assertEqual(img["loading"], "lazy")
|
|
||||||
|
|
||||||
# cleanup
|
|
||||||
frappe.db.rollback()
|
|
||||||
|
|
||||||
def test_homepage_section_custom_html(self):
|
def test_homepage_section_custom_html(self):
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
|
@ -408,7 +408,7 @@
|
|||||||
"depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)",
|
"depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)",
|
||||||
"fieldname": "daily_time_to_send",
|
"fieldname": "daily_time_to_send",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"label": "Time to send"
|
"label": "Daily Time to send"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
|
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
|
||||||
@ -421,7 +421,7 @@
|
|||||||
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
|
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
|
||||||
"fieldname": "weekly_time_to_send",
|
"fieldname": "weekly_time_to_send",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"label": "Time to send"
|
"label": "Weekly Time to send"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_45",
|
"fieldname": "column_break_45",
|
||||||
@ -451,7 +451,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 4,
|
"max_attachments": 4,
|
||||||
"modified": "2022-06-23 16:45:06.108499",
|
"modified": "2023-02-14 04:54:25.819620",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Project",
|
"name": "Project",
|
||||||
|
@ -161,6 +161,37 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
to_time = timesheet.time_logs[0].to_time
|
to_time = timesheet.time_logs[0].to_time
|
||||||
self.assertEqual(to_time, add_to_date(from_time, hours=2, as_datetime=True))
|
self.assertEqual(to_time, add_to_date(from_time, hours=2, as_datetime=True))
|
||||||
|
|
||||||
|
def test_per_billed_hours(self):
|
||||||
|
"""If amounts are 0, per_billed should be calculated based on hours."""
|
||||||
|
ts = frappe.new_doc("Timesheet")
|
||||||
|
ts.total_billable_amount = 0
|
||||||
|
ts.total_billed_amount = 0
|
||||||
|
ts.total_billable_hours = 2
|
||||||
|
|
||||||
|
ts.total_billed_hours = 0.5
|
||||||
|
ts.calculate_percentage_billed()
|
||||||
|
self.assertEqual(ts.per_billed, 25)
|
||||||
|
|
||||||
|
ts.total_billed_hours = 2
|
||||||
|
ts.calculate_percentage_billed()
|
||||||
|
self.assertEqual(ts.per_billed, 100)
|
||||||
|
|
||||||
|
def test_per_billed_amount(self):
|
||||||
|
"""If amounts are > 0, per_billed should be calculated based on amounts, regardless of hours."""
|
||||||
|
ts = frappe.new_doc("Timesheet")
|
||||||
|
ts.total_billable_hours = 2
|
||||||
|
ts.total_billed_hours = 1
|
||||||
|
ts.total_billable_amount = 200
|
||||||
|
ts.total_billed_amount = 50
|
||||||
|
ts.calculate_percentage_billed()
|
||||||
|
self.assertEqual(ts.per_billed, 25)
|
||||||
|
|
||||||
|
ts.total_billed_hours = 3
|
||||||
|
ts.total_billable_amount = 200
|
||||||
|
ts.total_billed_amount = 200
|
||||||
|
ts.calculate_percentage_billed()
|
||||||
|
self.assertEqual(ts.per_billed, 100)
|
||||||
|
|
||||||
|
|
||||||
def make_timesheet(
|
def make_timesheet(
|
||||||
employee,
|
employee,
|
||||||
|
@ -282,21 +282,21 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "base_total_costing_amount",
|
"fieldname": "base_total_costing_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Costing Amount",
|
"label": "Base Total Costing Amount",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "base_total_billable_amount",
|
"fieldname": "base_total_billable_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Billable Amount",
|
"label": "Base Total Billable Amount",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "base_total_billed_amount",
|
"fieldname": "base_total_billed_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Billed Amount",
|
"label": "Base Total Billed Amount",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -311,10 +311,11 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-15 22:08:53.930200",
|
"modified": "2023-02-14 04:55:41.735991",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Timesheet",
|
"name": "Timesheet",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -388,5 +389,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
|
"states": [],
|
||||||
"title_field": "title"
|
"title_field": "title"
|
||||||
}
|
}
|
@ -64,6 +64,8 @@ class Timesheet(Document):
|
|||||||
self.per_billed = 0
|
self.per_billed = 0
|
||||||
if self.total_billed_amount > 0 and self.total_billable_amount > 0:
|
if self.total_billed_amount > 0 and self.total_billable_amount > 0:
|
||||||
self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount
|
self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount
|
||||||
|
elif self.total_billed_hours > 0 and self.total_billable_hours > 0:
|
||||||
|
self.per_billed = (self.total_billed_hours * 100) / self.total_billable_hours
|
||||||
|
|
||||||
def update_billing_hours(self, args):
|
def update_billing_hours(self, args):
|
||||||
if args.is_billable:
|
if args.is_billable:
|
||||||
|
@ -126,7 +126,16 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
frappe.model.round_floats_in(item);
|
frappe.model.round_floats_in(item);
|
||||||
item.net_rate = item.rate;
|
item.net_rate = item.rate;
|
||||||
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
|
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
|
||||||
item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
|
|
||||||
|
if (!(me.frm.doc.is_return || me.frm.doc.is_debit_note)) {
|
||||||
|
item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let qty = item.qty || 1;
|
||||||
|
qty = me.frm.doc.is_return ? -1 * qty : qty;
|
||||||
|
item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
|
||||||
|
}
|
||||||
|
|
||||||
item.item_tax_amount = 0.0;
|
item.item_tax_amount = 0.0;
|
||||||
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
|
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
|
||||||
|
|
||||||
|
@ -1691,6 +1691,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
var me = this;
|
var me = this;
|
||||||
var valid = true;
|
var valid = true;
|
||||||
|
|
||||||
|
if (frappe.flags.ignore_company_party_validation) {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
$.each(["company", "customer"], function(i, fieldname) {
|
$.each(["company", "customer"], function(i, fieldname) {
|
||||||
if(frappe.meta.has_field(me.frm.doc.doctype, fieldname) && !["Purchase Order","Purchase Invoice"].includes(me.frm.doc.doctype)) {
|
if(frappe.meta.has_field(me.frm.doc.doctype, fieldname) && !["Purchase Order","Purchase Invoice"].includes(me.frm.doc.doctype)) {
|
||||||
if (!me.frm.doc[fieldname]) {
|
if (!me.frm.doc[fieldname]) {
|
||||||
|
@ -13,19 +13,11 @@ frappe.setup.on("before_load", function () {
|
|||||||
|
|
||||||
erpnext.setup.slides_settings = [
|
erpnext.setup.slides_settings = [
|
||||||
{
|
{
|
||||||
// Brand
|
// Organization
|
||||||
name: 'brand',
|
name: 'organization',
|
||||||
icon: "fa fa-bookmark",
|
title: __("Setup your organization"),
|
||||||
title: __("The Brand"),
|
icon: "fa fa-building",
|
||||||
// help: __('Upload your letter head and logo. (you can edit them later).'),
|
|
||||||
fields: [
|
fields: [
|
||||||
{
|
|
||||||
fieldtype: "Attach Image", fieldname: "attach_logo",
|
|
||||||
label: __("Attach Logo"),
|
|
||||||
description: __("100px by 100px"),
|
|
||||||
is_private: 0,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
fieldname: 'company_name',
|
fieldname: 'company_name',
|
||||||
label: __('Company Name'),
|
label: __('Company Name'),
|
||||||
@ -35,54 +27,9 @@ erpnext.setup.slides_settings = [
|
|||||||
{
|
{
|
||||||
fieldname: 'company_abbr',
|
fieldname: 'company_abbr',
|
||||||
label: __('Company Abbreviation'),
|
label: __('Company Abbreviation'),
|
||||||
fieldtype: 'Data'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
onload: function(slide) {
|
|
||||||
this.bind_events(slide);
|
|
||||||
},
|
|
||||||
bind_events: function (slide) {
|
|
||||||
slide.get_input("company_name").on("change", function () {
|
|
||||||
var parts = slide.get_input("company_name").val().split(" ");
|
|
||||||
var abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join("");
|
|
||||||
slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase());
|
|
||||||
}).val(frappe.boot.sysdefaults.company_name || "").trigger("change");
|
|
||||||
|
|
||||||
slide.get_input("company_abbr").on("change", function () {
|
|
||||||
if (slide.get_input("company_abbr").val().length > 10) {
|
|
||||||
frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters"));
|
|
||||||
slide.get_field("company_abbr").set_value("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
validate: function() {
|
|
||||||
if ((this.values.company_name || "").toLowerCase() == "company") {
|
|
||||||
frappe.msgprint(__("Company Name cannot be Company"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!this.values.company_abbr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.values.company_abbr.length > 10) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Organisation
|
|
||||||
name: 'organisation',
|
|
||||||
title: __("Your Organization"),
|
|
||||||
icon: "fa fa-building",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
fieldname: 'company_tagline',
|
|
||||||
label: __('What does it do?'),
|
|
||||||
fieldtype: 'Data',
|
fieldtype: 'Data',
|
||||||
placeholder: __('e.g. "Build tools for builders"'),
|
hidden: 1
|
||||||
reqd: 1
|
|
||||||
},
|
},
|
||||||
{ fieldname: 'bank_account', label: __('Bank Name'), fieldtype: 'Data', reqd: 1 },
|
|
||||||
{
|
{
|
||||||
fieldname: 'chart_of_accounts', label: __('Chart of Accounts'),
|
fieldname: 'chart_of_accounts', label: __('Chart of Accounts'),
|
||||||
options: "", fieldtype: 'Select'
|
options: "", fieldtype: 'Select'
|
||||||
@ -94,40 +41,24 @@ erpnext.setup.slides_settings = [
|
|||||||
],
|
],
|
||||||
|
|
||||||
onload: function (slide) {
|
onload: function (slide) {
|
||||||
this.load_chart_of_accounts(slide);
|
|
||||||
this.bind_events(slide);
|
this.bind_events(slide);
|
||||||
|
this.load_chart_of_accounts(slide);
|
||||||
this.set_fy_dates(slide);
|
this.set_fy_dates(slide);
|
||||||
},
|
},
|
||||||
|
|
||||||
validate: function () {
|
validate: function () {
|
||||||
let me = this;
|
|
||||||
let exist;
|
|
||||||
|
|
||||||
if (!this.validate_fy_dates()) {
|
if (!this.validate_fy_dates()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate bank name
|
if ((this.values.company_name || "").toLowerCase() == "company") {
|
||||||
if(me.values.bank_account) {
|
frappe.msgprint(__("Company Name cannot be Company"));
|
||||||
frappe.call({
|
return false;
|
||||||
async: false,
|
}
|
||||||
method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account",
|
if (!this.values.company_abbr) {
|
||||||
args: {
|
return false;
|
||||||
"coa": me.values.chart_of_accounts,
|
}
|
||||||
"bank_account": me.values.bank_account
|
if (this.values.company_abbr.length > 10) {
|
||||||
},
|
return false;
|
||||||
callback: function (r) {
|
|
||||||
if(r.message){
|
|
||||||
exist = r.message;
|
|
||||||
me.get_field("bank_account").set_value("");
|
|
||||||
let message = __('Account {0} already exists. Please enter a different name for your bank account.',
|
|
||||||
[me.values.bank_account]
|
|
||||||
);
|
|
||||||
frappe.msgprint(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return !exist; // Return False if exist = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -151,15 +82,15 @@ erpnext.setup.slides_settings = [
|
|||||||
var country = frappe.wizard.values.country;
|
var country = frappe.wizard.values.country;
|
||||||
|
|
||||||
if (country) {
|
if (country) {
|
||||||
var fy = erpnext.setup.fiscal_years[country];
|
let fy = erpnext.setup.fiscal_years[country];
|
||||||
var current_year = moment(new Date()).year();
|
let current_year = moment(new Date()).year();
|
||||||
var next_year = current_year + 1;
|
let next_year = current_year + 1;
|
||||||
if (!fy) {
|
if (!fy) {
|
||||||
fy = ["01-01", "12-31"];
|
fy = ["01-01", "12-31"];
|
||||||
next_year = current_year;
|
next_year = current_year;
|
||||||
}
|
}
|
||||||
|
|
||||||
var year_start_date = current_year + "-" + fy[0];
|
let year_start_date = current_year + "-" + fy[0];
|
||||||
if (year_start_date > frappe.datetime.get_today()) {
|
if (year_start_date > frappe.datetime.get_today()) {
|
||||||
next_year = current_year;
|
next_year = current_year;
|
||||||
current_year -= 1;
|
current_year -= 1;
|
||||||
@ -171,7 +102,7 @@ erpnext.setup.slides_settings = [
|
|||||||
|
|
||||||
|
|
||||||
load_chart_of_accounts: function (slide) {
|
load_chart_of_accounts: function (slide) {
|
||||||
var country = frappe.wizard.values.country;
|
let country = frappe.wizard.values.country;
|
||||||
|
|
||||||
if (country) {
|
if (country) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
@ -202,12 +133,25 @@ erpnext.setup.slides_settings = [
|
|||||||
|
|
||||||
me.charts_modal(slide, chart_template);
|
me.charts_modal(slide, chart_template);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
slide.get_input("company_name").on("change", function () {
|
||||||
|
let parts = slide.get_input("company_name").val().split(" ");
|
||||||
|
let abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join("");
|
||||||
|
slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase());
|
||||||
|
}).val(frappe.boot.sysdefaults.company_name || "").trigger("change");
|
||||||
|
|
||||||
|
slide.get_input("company_abbr").on("change", function () {
|
||||||
|
if (slide.get_input("company_abbr").val().length > 10) {
|
||||||
|
frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters"));
|
||||||
|
slide.get_field("company_abbr").set_value("");
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
charts_modal: function(slide, chart_template) {
|
charts_modal: function(slide, chart_template) {
|
||||||
let parent = __('All Accounts');
|
let parent = __('All Accounts');
|
||||||
|
|
||||||
var dialog = new frappe.ui.Dialog({
|
let dialog = new frappe.ui.Dialog({
|
||||||
title: chart_template,
|
title: chart_template,
|
||||||
fields: [
|
fields: [
|
||||||
{'fieldname': 'expand_all', 'label': __('Expand All'), 'fieldtype': 'Button',
|
{'fieldname': 'expand_all', 'label': __('Expand All'), 'fieldtype': 'Button',
|
||||||
|
@ -491,7 +491,20 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`);
|
const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`);
|
||||||
const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision;
|
const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision;
|
||||||
|
|
||||||
this.data = [];
|
this.data = frm.doc[opts.child_docname].map((d) => {
|
||||||
|
return {
|
||||||
|
"docname": d.name,
|
||||||
|
"name": d.name,
|
||||||
|
"item_code": d.item_code,
|
||||||
|
"delivery_date": d.delivery_date,
|
||||||
|
"schedule_date": d.schedule_date,
|
||||||
|
"conversion_factor": d.conversion_factor,
|
||||||
|
"qty": d.qty,
|
||||||
|
"rate": d.rate,
|
||||||
|
"uom": d.uom
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const fields = [{
|
const fields = [{
|
||||||
fieldtype:'Data',
|
fieldtype:'Data',
|
||||||
fieldname:"docname",
|
fieldname:"docname",
|
||||||
@ -588,7 +601,7 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialog = new frappe.ui.Dialog({
|
new frappe.ui.Dialog({
|
||||||
title: __("Update Items"),
|
title: __("Update Items"),
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
@ -624,24 +637,7 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
},
|
},
|
||||||
primary_action_label: __('Update')
|
primary_action_label: __('Update')
|
||||||
});
|
}).show();
|
||||||
|
|
||||||
frm.doc[opts.child_docname].forEach(d => {
|
|
||||||
dialog.fields_dict.trans_items.df.data.push({
|
|
||||||
"docname": d.name,
|
|
||||||
"name": d.name,
|
|
||||||
"item_code": d.item_code,
|
|
||||||
"delivery_date": d.delivery_date,
|
|
||||||
"schedule_date": d.schedule_date,
|
|
||||||
"conversion_factor": d.conversion_factor,
|
|
||||||
"qty": d.qty,
|
|
||||||
"rate": d.rate,
|
|
||||||
"uom": d.uom
|
|
||||||
});
|
|
||||||
this.data = dialog.fields_dict.trans_items.df.data;
|
|
||||||
dialog.fields_dict.trans_items.grid.refresh();
|
|
||||||
})
|
|
||||||
dialog.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.map_current_doc = function(opts) {
|
erpnext.utils.map_current_doc = function(opts) {
|
||||||
|
@ -1,123 +1,68 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:industry",
|
"autoname": "field:industry",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2012-03-27 14:36:09",
|
"creation": "2012-03-27 14:36:09",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 0,
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"industry"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"fieldname": "industry",
|
"fieldname": "industry",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"in_list_view": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Industry",
|
"label": "Industry",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "industry",
|
"oldfieldname": "industry",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"unique": 1
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-flag",
|
"icon": "fa fa-flag",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"links": [],
|
||||||
"in_create": 0,
|
"modified": "2023-02-10 03:14:40.735763",
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Industry Type",
|
"name": "Industry Type",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 0,
|
|
||||||
"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": "Sales Manager",
|
"role": "Sales Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"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": "Sales User",
|
"role": "Sales User"
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 0,
|
|
||||||
"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": "Sales Master Manager",
|
"role": "Sales Master Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC",
|
||||||
"track_seen": 0
|
"states": [],
|
||||||
|
"translated_doctype": 1
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
"creation": "2021-08-27 19:28:07.559978",
|
"creation": "2021-08-27 19:28:07.559978",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -51,7 +52,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-14 13:27:58.612334",
|
"modified": "2023-02-15 13:00:50.379713",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Party Specific Item",
|
"name": "Party Specific Item",
|
||||||
@ -72,6 +73,7 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "party",
|
"title_field": "party",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -85,11 +85,15 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
|
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
|
||||||
this.frm.add_custom_button(
|
if (frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation
|
||||||
__("Sales Order"),
|
|| (!doc.valid_till)
|
||||||
this.frm.cscript["Make Sales Order"],
|
|| frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
|
||||||
__("Create")
|
this.frm.add_custom_button(
|
||||||
);
|
__("Sales Order"),
|
||||||
|
this.frm.cscript["Make Sales Order"],
|
||||||
|
__("Create")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if(doc.status!=="Ordered") {
|
if(doc.status!=="Ordered") {
|
||||||
this.frm.add_custom_button(__('Set as Lost'), () => {
|
this.frm.add_custom_button(__('Set as Lost'), () => {
|
||||||
|
@ -195,6 +195,17 @@ def get_list_context(context=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_sales_order(source_name: str, target_doc=None):
|
def make_sales_order(source_name: str, target_doc=None):
|
||||||
|
if not frappe.db.get_singles_value(
|
||||||
|
"Selling Settings", "allow_sales_order_creation_for_expired_quotation"
|
||||||
|
):
|
||||||
|
quotation = frappe.db.get_value(
|
||||||
|
"Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1
|
||||||
|
)
|
||||||
|
if quotation.valid_till and (
|
||||||
|
quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate())
|
||||||
|
):
|
||||||
|
frappe.throw(_("Validity period of this quotation has ended."))
|
||||||
|
|
||||||
return _make_sales_order(source_name, target_doc)
|
return _make_sales_order(source_name, target_doc)
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,11 +144,21 @@ class TestQuotation(FrappeTestCase):
|
|||||||
def test_so_from_expired_quotation(self):
|
def test_so_from_expired_quotation(self):
|
||||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Selling Settings", "allow_sales_order_creation_for_expired_quotation", 0
|
||||||
|
)
|
||||||
|
|
||||||
quotation = frappe.copy_doc(test_records[0])
|
quotation = frappe.copy_doc(test_records[0])
|
||||||
quotation.valid_till = add_days(nowdate(), -1)
|
quotation.valid_till = add_days(nowdate(), -1)
|
||||||
quotation.insert()
|
quotation.insert()
|
||||||
quotation.submit()
|
quotation.submit()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Selling Settings", "allow_sales_order_creation_for_expired_quotation", 1
|
||||||
|
)
|
||||||
|
|
||||||
make_sales_order(quotation.name)
|
make_sales_order(quotation.name)
|
||||||
|
|
||||||
def test_shopping_cart_without_website_item(self):
|
def test_shopping_cart_without_website_item(self):
|
||||||
|
@ -1,94 +1,47 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "field:sales_partner_type",
|
"autoname": "field:sales_partner_type",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-06-11 13:15:57.404716",
|
"creation": "2018-06-11 13:15:57.404716",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"sales_partner_type"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "sales_partner_type",
|
"fieldname": "sales_partner_type",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"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": "Sales Partner Type",
|
"label": "Sales Partner Type",
|
||||||
"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": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"unique": 1
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"links": [],
|
||||||
"hide_heading": 0,
|
"modified": "2023-02-10 01:00:20.110800",
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-06-11 13:45:13.554307",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Partner Type",
|
"name": "Sales Partner Type",
|
||||||
"name_case": "",
|
"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": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"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
|
"translated_doctype": 1
|
||||||
}
|
}
|
@ -27,6 +27,7 @@
|
|||||||
"column_break_5",
|
"column_break_5",
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
"allow_against_multiple_purchase_orders",
|
"allow_against_multiple_purchase_orders",
|
||||||
|
"allow_sales_order_creation_for_expired_quotation",
|
||||||
"hide_tax_id",
|
"hide_tax_id",
|
||||||
"enable_discount_accounting"
|
"enable_discount_accounting"
|
||||||
],
|
],
|
||||||
@ -172,6 +173,12 @@
|
|||||||
"fieldname": "enable_discount_accounting",
|
"fieldname": "enable_discount_accounting",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Discount Accounting for Selling"
|
"label": "Enable Discount Accounting for Selling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_sales_order_creation_for_expired_quotation",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow Sales Order Creation For Expired Quotation"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@ -179,7 +186,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-05-31 19:39:48.398738",
|
"modified": "2023-02-04 12:37:53.380857",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
|
@ -17,45 +17,79 @@ from erpnext.stock.utils import scan_barcode
|
|||||||
def search_by_term(search_term, warehouse, price_list):
|
def search_by_term(search_term, warehouse, price_list):
|
||||||
result = search_for_serial_or_batch_or_barcode_number(search_term) or {}
|
result = search_for_serial_or_batch_or_barcode_number(search_term) or {}
|
||||||
|
|
||||||
item_code = result.get("item_code") or search_term
|
item_code = result.get("item_code", search_term)
|
||||||
serial_no = result.get("serial_no") or ""
|
serial_no = result.get("serial_no", "")
|
||||||
batch_no = result.get("batch_no") or ""
|
batch_no = result.get("batch_no", "")
|
||||||
barcode = result.get("barcode") or ""
|
barcode = result.get("barcode", "")
|
||||||
|
|
||||||
if result:
|
if not result:
|
||||||
item_info = frappe.db.get_value(
|
return
|
||||||
"Item",
|
|
||||||
item_code,
|
|
||||||
[
|
|
||||||
"name as item_code",
|
|
||||||
"item_name",
|
|
||||||
"description",
|
|
||||||
"stock_uom",
|
|
||||||
"image as item_image",
|
|
||||||
"is_stock_item",
|
|
||||||
],
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
|
item_doc = frappe.get_doc("Item", item_code)
|
||||||
price_list_rate, currency = frappe.db.get_value(
|
|
||||||
"Item Price",
|
|
||||||
{"price_list": price_list, "item_code": item_code},
|
|
||||||
["price_list_rate", "currency"],
|
|
||||||
) or [None, None]
|
|
||||||
|
|
||||||
item_info.update(
|
if not item_doc:
|
||||||
|
return
|
||||||
|
|
||||||
|
item = {
|
||||||
|
"barcode": barcode,
|
||||||
|
"batch_no": batch_no,
|
||||||
|
"description": item_doc.description,
|
||||||
|
"is_stock_item": item_doc.is_stock_item,
|
||||||
|
"item_code": item_doc.name,
|
||||||
|
"item_image": item_doc.image,
|
||||||
|
"item_name": item_doc.item_name,
|
||||||
|
"serial_no": serial_no,
|
||||||
|
"stock_uom": item_doc.stock_uom,
|
||||||
|
"uom": item_doc.stock_uom,
|
||||||
|
}
|
||||||
|
|
||||||
|
if barcode:
|
||||||
|
barcode_info = next(filter(lambda x: x.barcode == barcode, item_doc.get("barcodes", [])), None)
|
||||||
|
if barcode_info and barcode_info.uom:
|
||||||
|
uom = next(filter(lambda x: x.uom == barcode_info.uom, item_doc.uoms), {})
|
||||||
|
item.update(
|
||||||
|
{
|
||||||
|
"uom": barcode_info.uom,
|
||||||
|
"conversion_factor": uom.get("conversion_factor", 1),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
|
||||||
|
item_stock_qty = item_stock_qty // item.get("conversion_factor")
|
||||||
|
item.update({"actual_qty": item_stock_qty})
|
||||||
|
|
||||||
|
price = frappe.get_list(
|
||||||
|
doctype="Item Price",
|
||||||
|
filters={
|
||||||
|
"price_list": price_list,
|
||||||
|
"item_code": item_code,
|
||||||
|
},
|
||||||
|
fields=["uom", "stock_uom", "currency", "price_list_rate"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def __sort(p):
|
||||||
|
p_uom = p.get("uom")
|
||||||
|
|
||||||
|
if p_uom == item.get("uom"):
|
||||||
|
return 0
|
||||||
|
elif p_uom == item.get("stock_uom"):
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 2
|
||||||
|
|
||||||
|
# sort by fallback preference. always pick exact uom match if available
|
||||||
|
price = sorted(price, key=__sort)
|
||||||
|
|
||||||
|
if len(price) > 0:
|
||||||
|
p = price.pop(0)
|
||||||
|
item.update(
|
||||||
{
|
{
|
||||||
"serial_no": serial_no,
|
"currency": p.get("currency"),
|
||||||
"batch_no": batch_no,
|
"price_list_rate": p.get("price_list_rate"),
|
||||||
"barcode": barcode,
|
|
||||||
"price_list_rate": price_list_rate,
|
|
||||||
"currency": currency,
|
|
||||||
"actual_qty": item_stock_qty,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"items": [item_info]}
|
return {"items": [item]}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -121,33 +155,43 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te
|
|||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
if items_data:
|
# return (empty) list if there are no results
|
||||||
items = [d.item_code for d in items_data]
|
if not items_data:
|
||||||
item_prices_data = frappe.get_all(
|
return result
|
||||||
|
|
||||||
|
for item in items_data:
|
||||||
|
uoms = frappe.get_doc("Item", item.item_code).get("uoms", [])
|
||||||
|
|
||||||
|
item.actual_qty, _ = get_stock_availability(item.item_code, warehouse)
|
||||||
|
item.uom = item.stock_uom
|
||||||
|
|
||||||
|
item_price = frappe.get_all(
|
||||||
"Item Price",
|
"Item Price",
|
||||||
fields=["item_code", "price_list_rate", "currency"],
|
fields=["price_list_rate", "currency", "uom"],
|
||||||
filters={"price_list": price_list, "item_code": ["in", items]},
|
filters={
|
||||||
|
"price_list": price_list,
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"selling": True,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
item_prices = {}
|
if not item_price:
|
||||||
for d in item_prices_data:
|
result.append(item)
|
||||||
item_prices[d.item_code] = d
|
|
||||||
|
|
||||||
for item in items_data:
|
for price in item_price:
|
||||||
item_code = item.item_code
|
uom = next(filter(lambda x: x.uom == price.uom, uoms), {})
|
||||||
item_price = item_prices.get(item_code) or {}
|
|
||||||
item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
|
|
||||||
|
|
||||||
row = {}
|
if price.uom != item.stock_uom and uom and uom.conversion_factor:
|
||||||
row.update(item)
|
item.actual_qty = item.actual_qty // uom.conversion_factor
|
||||||
row.update(
|
|
||||||
|
result.append(
|
||||||
{
|
{
|
||||||
"price_list_rate": item_price.get("price_list_rate"),
|
**item,
|
||||||
"currency": item_price.get("currency"),
|
"price_list_rate": price.get("price_list_rate"),
|
||||||
"actual_qty": item_stock_qty,
|
"currency": price.get("currency"),
|
||||||
|
"uom": price.uom or item.uom,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
result.append(row)
|
|
||||||
|
|
||||||
return {"items": result}
|
return {"items": result}
|
||||||
|
|
||||||
|
@ -542,12 +542,12 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
if (!this.frm.doc.customer)
|
if (!this.frm.doc.customer)
|
||||||
return this.raise_customer_selection_alert();
|
return this.raise_customer_selection_alert();
|
||||||
|
|
||||||
const { item_code, batch_no, serial_no, rate } = item;
|
const { item_code, batch_no, serial_no, rate, uom } = item;
|
||||||
|
|
||||||
if (!item_code)
|
if (!item_code)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const new_item = { item_code, batch_no, rate, [field]: value };
|
const new_item = { item_code, batch_no, rate, uom, [field]: value };
|
||||||
|
|
||||||
if (serial_no) {
|
if (serial_no) {
|
||||||
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
|
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
|
||||||
@ -649,6 +649,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
const is_stock_item = resp[1];
|
const is_stock_item = resp[1];
|
||||||
|
|
||||||
frappe.dom.unfreeze();
|
frappe.dom.unfreeze();
|
||||||
|
const bold_uom = item_row.stock_uom.bold();
|
||||||
const bold_item_code = item_row.item_code.bold();
|
const bold_item_code = item_row.item_code.bold();
|
||||||
const bold_warehouse = warehouse.bold();
|
const bold_warehouse = warehouse.bold();
|
||||||
const bold_available_qty = available_qty.toString().bold()
|
const bold_available_qty = available_qty.toString().bold()
|
||||||
@ -664,7 +665,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
}
|
}
|
||||||
} else if (is_stock_item && available_qty < qty_needed) {
|
} else if (is_stock_item && available_qty < qty_needed) {
|
||||||
frappe.throw({
|
frappe.throw({
|
||||||
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
|
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2} {3}.', [bold_item_code, bold_warehouse, bold_available_qty, bold_uom]),
|
||||||
indicator: 'orange'
|
indicator: 'orange'
|
||||||
});
|
});
|
||||||
frappe.utils.play_sound("error");
|
frappe.utils.play_sound("error");
|
||||||
|
@ -609,7 +609,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) {
|
if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) {
|
||||||
return `
|
return `
|
||||||
<div class="item-qty-rate">
|
<div class="item-qty-rate">
|
||||||
<div class="item-qty"><span>${item_data.qty || 0}</span></div>
|
<div class="item-qty"><span>${item_data.qty || 0} ${item_data.uom}</span></div>
|
||||||
<div class="item-rate-amount">
|
<div class="item-rate-amount">
|
||||||
<div class="item-rate">${format_currency(item_data.amount, currency)}</div>
|
<div class="item-rate">${format_currency(item_data.amount, currency)}</div>
|
||||||
<div class="item-amount">${format_currency(item_data.rate, currency)}</div>
|
<div class="item-amount">${format_currency(item_data.rate, currency)}</div>
|
||||||
@ -618,7 +618,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
} else {
|
} else {
|
||||||
return `
|
return `
|
||||||
<div class="item-qty-rate">
|
<div class="item-qty-rate">
|
||||||
<div class="item-qty"><span>${item_data.qty || 0}</span></div>
|
<div class="item-qty"><span>${item_data.qty || 0} ${item_data.uom}</span></div>
|
||||||
<div class="item-rate-amount">
|
<div class="item-rate-amount">
|
||||||
<div class="item-rate">${format_currency(item_data.rate, currency)}</div>
|
<div class="item-rate">${format_currency(item_data.rate, currency)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,7 +78,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
get_item_html(item) {
|
get_item_html(item) {
|
||||||
const me = this;
|
const me = this;
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item;
|
const { item_image, serial_no, batch_no, barcode, actual_qty, uom, price_list_rate } = item;
|
||||||
const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0;
|
const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0;
|
||||||
let indicator_color;
|
let indicator_color;
|
||||||
let qty_to_display = actual_qty;
|
let qty_to_display = actual_qty;
|
||||||
@ -118,7 +118,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
return (
|
return (
|
||||||
`<div class="item-wrapper"
|
`<div class="item-wrapper"
|
||||||
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
|
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
|
||||||
data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
|
data-batch-no="${escape(batch_no)}" data-uom="${escape(uom)}"
|
||||||
data-rate="${escape(price_list_rate || 0)}"
|
data-rate="${escape(price_list_rate || 0)}"
|
||||||
title="${item.item_name}">
|
title="${item.item_name}">
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
|||||||
<div class="item-name">
|
<div class="item-name">
|
||||||
${frappe.ellipsis(item.item_name, 18)}
|
${frappe.ellipsis(item.item_name, 18)}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-rate">${format_currency(price_list_rate, item.currency, precision) || 0}</div>
|
<div class="item-rate">${format_currency(price_list_rate, item.currency, precision) || 0} / ${uom}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
|
@ -94,7 +94,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
get_item_html(doc, item_data) {
|
get_item_html(doc, item_data) {
|
||||||
return `<div class="item-row-wrapper">
|
return `<div class="item-row-wrapper">
|
||||||
<div class="item-name">${item_data.item_name}</div>
|
<div class="item-name">${item_data.item_name}</div>
|
||||||
<div class="item-qty">${item_data.qty || 0}</div>
|
<div class="item-qty">${item_data.qty || 0} ${item_data.uom}</div>
|
||||||
<div class="item-rate-disc">${get_rate_discount_html()}</div>
|
<div class="item-rate-disc">${get_rate_discount_html()}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
@ -322,6 +322,11 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
this.focus_on_default_mop();
|
this.focus_on_default_mop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
after_render() {
|
||||||
|
const frm = this.events.get_frm();
|
||||||
|
frm.script_manager.trigger("after_payment_render", frm.doc.doctype, frm.doc.docname);
|
||||||
|
}
|
||||||
|
|
||||||
edit_cart() {
|
edit_cart() {
|
||||||
this.events.toggle_other_sections(false);
|
this.events.toggle_other_sections(false);
|
||||||
this.toggle_component(false);
|
this.toggle_component(false);
|
||||||
@ -332,6 +337,7 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
this.toggle_component(true);
|
this.toggle_component(true);
|
||||||
|
|
||||||
this.render_payment_section();
|
this.render_payment_section();
|
||||||
|
this.after_render();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_remarks_control() {
|
toggle_remarks_control() {
|
||||||
|
@ -41,8 +41,20 @@ def get_columns(filters):
|
|||||||
{"label": _("Description"), "fieldtype": "Data", "fieldname": "description", "width": 150},
|
{"label": _("Description"), "fieldtype": "Data", "fieldname": "description", "width": 150},
|
||||||
{"label": _("Quantity"), "fieldtype": "Float", "fieldname": "quantity", "width": 150},
|
{"label": _("Quantity"), "fieldtype": "Float", "fieldname": "quantity", "width": 150},
|
||||||
{"label": _("UOM"), "fieldtype": "Link", "fieldname": "uom", "options": "UOM", "width": 100},
|
{"label": _("UOM"), "fieldtype": "Link", "fieldname": "uom", "options": "UOM", "width": 100},
|
||||||
{"label": _("Rate"), "fieldname": "rate", "options": "Currency", "width": 120},
|
{
|
||||||
{"label": _("Amount"), "fieldname": "amount", "options": "Currency", "width": 120},
|
"label": _("Rate"),
|
||||||
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Amount"),
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": _("Sales Order"),
|
"label": _("Sales Order"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -93,8 +105,9 @@ def get_columns(filters):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Billed Amount"),
|
"label": _("Billed Amount"),
|
||||||
"fieldtype": "currency",
|
"fieldtype": "Currency",
|
||||||
"fieldname": "billed_amount",
|
"fieldname": "billed_amount",
|
||||||
|
"options": "currency",
|
||||||
"width": 120,
|
"width": 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -104,6 +117,13 @@ def get_columns(filters):
|
|||||||
"options": "Company",
|
"options": "Company",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Currency"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -141,31 +161,12 @@ def get_data(filters):
|
|||||||
"billed_amount": flt(record.get("billed_amt")),
|
"billed_amount": flt(record.get("billed_amt")),
|
||||||
"company": record.get("company"),
|
"company": record.get("company"),
|
||||||
}
|
}
|
||||||
|
row["currency"] = frappe.get_cached_value("Company", row["company"], "default_currency")
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_conditions(filters):
|
|
||||||
conditions = ""
|
|
||||||
if filters.get("item_group"):
|
|
||||||
conditions += "AND so_item.item_group = %s" % frappe.db.escape(filters.item_group)
|
|
||||||
|
|
||||||
if filters.get("from_date"):
|
|
||||||
conditions += "AND so.transaction_date >= '%s'" % filters.from_date
|
|
||||||
|
|
||||||
if filters.get("to_date"):
|
|
||||||
conditions += "AND so.transaction_date <= '%s'" % filters.to_date
|
|
||||||
|
|
||||||
if filters.get("item_code"):
|
|
||||||
conditions += "AND so_item.item_code = %s" % frappe.db.escape(filters.item_code)
|
|
||||||
|
|
||||||
if filters.get("customer"):
|
|
||||||
conditions += "AND so.customer = %s" % frappe.db.escape(filters.customer)
|
|
||||||
|
|
||||||
return conditions
|
|
||||||
|
|
||||||
|
|
||||||
def get_customer_details():
|
def get_customer_details():
|
||||||
details = frappe.get_all("Customer", fields=["name", "customer_name", "customer_group"])
|
details = frappe.get_all("Customer", fields=["name", "customer_name", "customer_group"])
|
||||||
customer_details = {}
|
customer_details = {}
|
||||||
@ -187,29 +188,50 @@ def get_item_details():
|
|||||||
|
|
||||||
|
|
||||||
def get_sales_order_details(company_list, filters):
|
def get_sales_order_details(company_list, filters):
|
||||||
conditions = get_conditions(filters)
|
db_so = frappe.qb.DocType("Sales Order")
|
||||||
|
db_so_item = frappe.qb.DocType("Sales Order Item")
|
||||||
|
|
||||||
return frappe.db.sql(
|
query = (
|
||||||
"""
|
frappe.qb.from_(db_so)
|
||||||
SELECT
|
.inner_join(db_so_item)
|
||||||
so_item.item_code, so_item.description, so_item.qty,
|
.on(db_so_item.parent == db_so.name)
|
||||||
so_item.uom, so_item.base_rate, so_item.base_amount,
|
.select(
|
||||||
so.name, so.transaction_date, so.customer,so.territory,
|
db_so.name,
|
||||||
so.project, so_item.delivered_qty,
|
db_so.customer,
|
||||||
so_item.billed_amt, so.company
|
db_so.transaction_date,
|
||||||
FROM
|
db_so.territory,
|
||||||
`tabSales Order` so, `tabSales Order Item` so_item
|
db_so.project,
|
||||||
WHERE
|
db_so.company,
|
||||||
so.name = so_item.parent
|
db_so_item.item_code,
|
||||||
AND so.company in ({0})
|
db_so_item.description,
|
||||||
AND so.docstatus = 1 {1}
|
db_so_item.qty,
|
||||||
""".format(
|
db_so_item.uom,
|
||||||
",".join(["%s"] * len(company_list)), conditions
|
db_so_item.base_rate,
|
||||||
),
|
db_so_item.base_amount,
|
||||||
tuple(company_list),
|
db_so_item.delivered_qty,
|
||||||
as_dict=1,
|
(db_so_item.billed_amt * db_so.conversion_rate).as_("billed_amt"),
|
||||||
|
)
|
||||||
|
.where(db_so.docstatus == 1)
|
||||||
|
.where(db_so.company.isin(tuple(company_list)))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if filters.get("item_group"):
|
||||||
|
query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_group))
|
||||||
|
|
||||||
|
if filters.get("from_date"):
|
||||||
|
query = query.where(db_so.transaction_date >= filters.from_date)
|
||||||
|
|
||||||
|
if filters.get("to_date"):
|
||||||
|
query = query.where(db_so.transaction_date <= filters.to_date)
|
||||||
|
|
||||||
|
if filters.get("item_code"):
|
||||||
|
query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_code))
|
||||||
|
|
||||||
|
if filters.get("customer"):
|
||||||
|
query = query.where(db_so.customer == filters.customer)
|
||||||
|
|
||||||
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def get_chart_data(data):
|
def get_chart_data(data):
|
||||||
item_wise_sales_map = {}
|
item_wise_sales_map = {}
|
||||||
|
@ -103,6 +103,11 @@ function get_filters() {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"only_immediate_upcoming_term",
|
||||||
|
"label": __("Show only the Immediate Upcoming Term"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb, query_builder
|
from frappe import _, qb, query_builder
|
||||||
from frappe.query_builder import Criterion, functions
|
from frappe.query_builder import Criterion, functions
|
||||||
|
from frappe.utils.dateutils import getdate
|
||||||
|
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
@ -208,6 +209,7 @@ def get_so_with_invoices(filters):
|
|||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(so.docstatus == 1)
|
(so.docstatus == 1)
|
||||||
|
& (so.status.isin(["To Deliver and Bill", "To Bill"]))
|
||||||
& (so.payment_terms_template != "NULL")
|
& (so.payment_terms_template != "NULL")
|
||||||
& (so.company == conditions.company)
|
& (so.company == conditions.company)
|
||||||
& (so.transaction_date[conditions.start_date : conditions.end_date])
|
& (so.transaction_date[conditions.start_date : conditions.end_date])
|
||||||
@ -291,6 +293,18 @@ def filter_on_calculated_status(filters, sales_orders):
|
|||||||
return sales_orders
|
return sales_orders
|
||||||
|
|
||||||
|
|
||||||
|
def filter_for_immediate_upcoming_term(filters, sales_orders):
|
||||||
|
if filters.only_immediate_upcoming_term and sales_orders:
|
||||||
|
immediate_term_found = set()
|
||||||
|
filtered_data = []
|
||||||
|
for order in sales_orders:
|
||||||
|
if order.name not in immediate_term_found and order.due_date > getdate():
|
||||||
|
filtered_data.append(order)
|
||||||
|
immediate_term_found.add(order.name)
|
||||||
|
return filtered_data
|
||||||
|
return sales_orders
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
sales_orders, so_invoices = get_so_with_invoices(filters)
|
sales_orders, so_invoices = get_so_with_invoices(filters)
|
||||||
@ -298,6 +312,8 @@ def execute(filters=None):
|
|||||||
|
|
||||||
sales_orders = filter_on_calculated_status(filters, sales_orders)
|
sales_orders = filter_on_calculated_status(filters, sales_orders)
|
||||||
|
|
||||||
|
sales_orders = filter_for_immediate_upcoming_term(filters, sales_orders)
|
||||||
|
|
||||||
prepare_chart(sales_orders)
|
prepare_chart(sales_orders)
|
||||||
|
|
||||||
data = sales_orders
|
data = sales_orders
|
||||||
|
@ -175,7 +175,9 @@ def prepare_data(data, so_elapsed_time, filters):
|
|||||||
# update existing entry
|
# update existing entry
|
||||||
so_row = sales_order_map[so_name]
|
so_row = sales_order_map[so_name]
|
||||||
so_row["required_date"] = max(getdate(so_row["delivery_date"]), getdate(row["delivery_date"]))
|
so_row["required_date"] = max(getdate(so_row["delivery_date"]), getdate(row["delivery_date"]))
|
||||||
so_row["delay"] = min(so_row["delay"], row["delay"])
|
so_row["delay"] = (
|
||||||
|
min(so_row["delay"], row["delay"]) if row["delay"] and so_row["delay"] else so_row["delay"]
|
||||||
|
)
|
||||||
|
|
||||||
# sum numeric columns
|
# sum numeric columns
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"icon": "fa fa-bookmark",
|
"icon": "fa fa-bookmark",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-28 17:10:26.853753",
|
"modified": "2023-02-10 01:53:41.319386",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Designation",
|
"name": "Designation",
|
||||||
@ -58,5 +58,6 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"states": []
|
"states": [],
|
||||||
|
"translated_doctype": 1
|
||||||
}
|
}
|
@ -1,13 +1,6 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Terms and Conditions", {
|
||||||
|
// refresh(frm) {}
|
||||||
//--------- ONLOAD -------------
|
// });
|
||||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "disabled",
|
"fieldname": "disabled",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Disabled"
|
"label": "Disabled"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -60,12 +59,14 @@
|
|||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "selling",
|
"fieldname": "selling",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Selling"
|
"label": "Selling"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "buying",
|
"fieldname": "buying",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Buying"
|
"label": "Buying"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -76,10 +77,11 @@
|
|||||||
"icon": "icon-legal",
|
"icon": "icon-legal",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-16 15:07:38.094844",
|
"modified": "2023-02-01 14:33:39.246532",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Terms and Conditions",
|
"name": "Terms and Conditions",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -133,5 +135,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC"
|
"sort_order": "ASC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
31
erpnext/setup/setup_wizard/data/designation.txt
Normal file
31
erpnext/setup/setup_wizard/data/designation.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Accountant
|
||||||
|
Administrative Assistant
|
||||||
|
Administrative Officer
|
||||||
|
Analyst
|
||||||
|
Associate
|
||||||
|
Business Analyst
|
||||||
|
Business Development Manager
|
||||||
|
Consultant
|
||||||
|
Chief Executive Officer
|
||||||
|
Chief Financial Officer
|
||||||
|
Chief Operating Officer
|
||||||
|
Chief Technology Officer
|
||||||
|
Customer Service Representative
|
||||||
|
Designer
|
||||||
|
Engineer
|
||||||
|
Executive Assistant
|
||||||
|
Finance Manager
|
||||||
|
HR Manager
|
||||||
|
Head of Marketing and Sales
|
||||||
|
Manager
|
||||||
|
Managing Director
|
||||||
|
Marketing Manager
|
||||||
|
Marketing Specialist
|
||||||
|
President
|
||||||
|
Product Manager
|
||||||
|
Project Manager
|
||||||
|
Researcher
|
||||||
|
Sales Representative
|
||||||
|
Secretary
|
||||||
|
Software Developer
|
||||||
|
Vice President
|
@ -1,57 +0,0 @@
|
|||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def get_industry_types():
|
|
||||||
return [
|
|
||||||
_("Accounting"),
|
|
||||||
_("Advertising"),
|
|
||||||
_("Aerospace"),
|
|
||||||
_("Agriculture"),
|
|
||||||
_("Airline"),
|
|
||||||
_("Apparel & Accessories"),
|
|
||||||
_("Automotive"),
|
|
||||||
_("Banking"),
|
|
||||||
_("Biotechnology"),
|
|
||||||
_("Broadcasting"),
|
|
||||||
_("Brokerage"),
|
|
||||||
_("Chemical"),
|
|
||||||
_("Computer"),
|
|
||||||
_("Consulting"),
|
|
||||||
_("Consumer Products"),
|
|
||||||
_("Cosmetics"),
|
|
||||||
_("Defense"),
|
|
||||||
_("Department Stores"),
|
|
||||||
_("Education"),
|
|
||||||
_("Electronics"),
|
|
||||||
_("Energy"),
|
|
||||||
_("Entertainment & Leisure"),
|
|
||||||
_("Executive Search"),
|
|
||||||
_("Financial Services"),
|
|
||||||
_("Food, Beverage & Tobacco"),
|
|
||||||
_("Grocery"),
|
|
||||||
_("Health Care"),
|
|
||||||
_("Internet Publishing"),
|
|
||||||
_("Investment Banking"),
|
|
||||||
_("Legal"),
|
|
||||||
_("Manufacturing"),
|
|
||||||
_("Motion Picture & Video"),
|
|
||||||
_("Music"),
|
|
||||||
_("Newspaper Publishers"),
|
|
||||||
_("Online Auctions"),
|
|
||||||
_("Pension Funds"),
|
|
||||||
_("Pharmaceuticals"),
|
|
||||||
_("Private Equity"),
|
|
||||||
_("Publishing"),
|
|
||||||
_("Real Estate"),
|
|
||||||
_("Retail & Wholesale"),
|
|
||||||
_("Securities & Commodity Exchanges"),
|
|
||||||
_("Service"),
|
|
||||||
_("Soap & Detergent"),
|
|
||||||
_("Software"),
|
|
||||||
_("Sports"),
|
|
||||||
_("Technology"),
|
|
||||||
_("Telecommunications"),
|
|
||||||
_("Television"),
|
|
||||||
_("Transportation"),
|
|
||||||
_("Venture Capital"),
|
|
||||||
]
|
|
51
erpnext/setup/setup_wizard/data/industry_type.txt
Normal file
51
erpnext/setup/setup_wizard/data/industry_type.txt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
Accounting
|
||||||
|
Advertising
|
||||||
|
Aerospace
|
||||||
|
Agriculture
|
||||||
|
Airline
|
||||||
|
Apparel & Accessories
|
||||||
|
Automotive
|
||||||
|
Banking
|
||||||
|
Biotechnology
|
||||||
|
Broadcasting
|
||||||
|
Brokerage
|
||||||
|
Chemical
|
||||||
|
Computer
|
||||||
|
Consulting
|
||||||
|
Consumer Products
|
||||||
|
Cosmetics
|
||||||
|
Defense
|
||||||
|
Department Stores
|
||||||
|
Education
|
||||||
|
Electronics
|
||||||
|
Energy
|
||||||
|
Entertainment & Leisure
|
||||||
|
Executive Search
|
||||||
|
Financial Services
|
||||||
|
Food, Beverage & Tobacco
|
||||||
|
Grocery
|
||||||
|
Health Care
|
||||||
|
Internet Publishing
|
||||||
|
Investment Banking
|
||||||
|
Legal
|
||||||
|
Manufacturing
|
||||||
|
Motion Picture & Video
|
||||||
|
Music
|
||||||
|
Newspaper Publishers
|
||||||
|
Online Auctions
|
||||||
|
Pension Funds
|
||||||
|
Pharmaceuticals
|
||||||
|
Private Equity
|
||||||
|
Publishing
|
||||||
|
Real Estate
|
||||||
|
Retail & Wholesale
|
||||||
|
Securities & Commodity Exchanges
|
||||||
|
Service
|
||||||
|
Soap & Detergent
|
||||||
|
Software
|
||||||
|
Sports
|
||||||
|
Technology
|
||||||
|
Telecommunications
|
||||||
|
Television
|
||||||
|
Transportation
|
||||||
|
Venture Capital
|
10
erpnext/setup/setup_wizard/data/lead_source.txt
Normal file
10
erpnext/setup/setup_wizard/data/lead_source.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Existing Customer
|
||||||
|
Reference
|
||||||
|
Advertisement
|
||||||
|
Cold Calling
|
||||||
|
Exhibition
|
||||||
|
Supplier Reference
|
||||||
|
Mass Mailing
|
||||||
|
Customer's Vendor
|
||||||
|
Campaign
|
||||||
|
Walk In
|
7
erpnext/setup/setup_wizard/data/sales_partner_type.txt
Normal file
7
erpnext/setup/setup_wizard/data/sales_partner_type.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Channel Partner
|
||||||
|
Distributor
|
||||||
|
Dealer
|
||||||
|
Agent
|
||||||
|
Retailer
|
||||||
|
Implementation Partner
|
||||||
|
Reseller
|
8
erpnext/setup/setup_wizard/data/sales_stage.txt
Normal file
8
erpnext/setup/setup_wizard/data/sales_stage.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Prospecting
|
||||||
|
Qualification
|
||||||
|
Needs Analysis
|
||||||
|
Value Proposition
|
||||||
|
Identifying Decision Makers
|
||||||
|
Perception Analysis
|
||||||
|
Proposal/Price Quote
|
||||||
|
Negotiation/Review
|
@ -4,7 +4,6 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cstr, getdate
|
from frappe.utils import cstr, getdate
|
||||||
from .default_website import website_maker
|
|
||||||
|
|
||||||
|
|
||||||
def create_fiscal_year_and_company(args):
|
def create_fiscal_year_and_company(args):
|
||||||
@ -48,83 +47,6 @@ def enable_shopping_cart(args): # nosemgrep
|
|||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
|
|
||||||
def create_email_digest():
|
|
||||||
from frappe.utils.user import get_system_managers
|
|
||||||
|
|
||||||
system_managers = get_system_managers(only_name=True)
|
|
||||||
|
|
||||||
if not system_managers:
|
|
||||||
return
|
|
||||||
|
|
||||||
recipients = []
|
|
||||||
for d in system_managers:
|
|
||||||
recipients.append({"recipient": d})
|
|
||||||
|
|
||||||
companies = frappe.db.sql_list("select name FROM `tabCompany`")
|
|
||||||
for company in companies:
|
|
||||||
if not frappe.db.exists("Email Digest", "Default Weekly Digest - " + company):
|
|
||||||
edigest = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Email Digest",
|
|
||||||
"name": "Default Weekly Digest - " + company,
|
|
||||||
"company": company,
|
|
||||||
"frequency": "Weekly",
|
|
||||||
"recipients": recipients,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
for df in edigest.meta.get("fields", {"fieldtype": "Check"}):
|
|
||||||
if df.fieldname != "scheduler_errors":
|
|
||||||
edigest.set(df.fieldname, 1)
|
|
||||||
|
|
||||||
edigest.insert()
|
|
||||||
|
|
||||||
# scheduler errors digest
|
|
||||||
if companies:
|
|
||||||
edigest = frappe.new_doc("Email Digest")
|
|
||||||
edigest.update(
|
|
||||||
{
|
|
||||||
"name": "Scheduler Errors",
|
|
||||||
"company": companies[0],
|
|
||||||
"frequency": "Daily",
|
|
||||||
"recipients": recipients,
|
|
||||||
"scheduler_errors": 1,
|
|
||||||
"enabled": 1,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
edigest.insert()
|
|
||||||
|
|
||||||
|
|
||||||
def create_logo(args):
|
|
||||||
if args.get("attach_logo"):
|
|
||||||
attach_logo = args.get("attach_logo").split(",")
|
|
||||||
if len(attach_logo) == 3:
|
|
||||||
filename, filetype, content = attach_logo
|
|
||||||
_file = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "File",
|
|
||||||
"file_name": filename,
|
|
||||||
"attached_to_doctype": "Website Settings",
|
|
||||||
"attached_to_name": "Website Settings",
|
|
||||||
"decode": True,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
_file.save()
|
|
||||||
fileurl = _file.file_url
|
|
||||||
frappe.db.set_value(
|
|
||||||
"Website Settings",
|
|
||||||
"Website Settings",
|
|
||||||
"brand_html",
|
|
||||||
"<img src='{0}' style='max-width: 40px; max-height: 25px;'> {1}".format(
|
|
||||||
fileurl, args.get("company_name")
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_website(args):
|
|
||||||
website_maker(args)
|
|
||||||
|
|
||||||
|
|
||||||
def get_fy_details(fy_start_date, fy_end_date):
|
def get_fy_details(fy_start_date, fy_end_date):
|
||||||
start_year = getdate(fy_start_date).year
|
start_year = getdate(fy_start_date).year
|
||||||
if start_year == getdate(fy_end_date).year:
|
if start_year == getdate(fy_end_date).year:
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.utils import nowdate
|
|
||||||
|
|
||||||
|
|
||||||
class website_maker(object):
|
|
||||||
def __init__(self, args):
|
|
||||||
self.args = args
|
|
||||||
self.company = args.company_name
|
|
||||||
self.tagline = args.company_tagline
|
|
||||||
self.user = args.get("email")
|
|
||||||
self.make_web_page()
|
|
||||||
self.make_website_settings()
|
|
||||||
self.make_blog()
|
|
||||||
|
|
||||||
def make_web_page(self):
|
|
||||||
# home page
|
|
||||||
homepage = frappe.get_doc("Homepage", "Homepage")
|
|
||||||
homepage.company = self.company
|
|
||||||
homepage.tag_line = self.tagline
|
|
||||||
homepage.setup_items()
|
|
||||||
homepage.save()
|
|
||||||
|
|
||||||
def make_website_settings(self):
|
|
||||||
# update in home page in settings
|
|
||||||
website_settings = frappe.get_doc("Website Settings", "Website Settings")
|
|
||||||
website_settings.home_page = "home"
|
|
||||||
website_settings.brand_html = self.company
|
|
||||||
website_settings.copyright = self.company
|
|
||||||
website_settings.top_bar_items = []
|
|
||||||
website_settings.append(
|
|
||||||
"top_bar_items", {"doctype": "Top Bar Item", "label": "Contact", "url": "/contact"}
|
|
||||||
)
|
|
||||||
website_settings.append(
|
|
||||||
"top_bar_items", {"doctype": "Top Bar Item", "label": "Blog", "url": "/blog"}
|
|
||||||
)
|
|
||||||
website_settings.append(
|
|
||||||
"top_bar_items", {"doctype": "Top Bar Item", "label": _("Products"), "url": "/all-products"}
|
|
||||||
)
|
|
||||||
website_settings.save()
|
|
||||||
|
|
||||||
def make_blog(self):
|
|
||||||
blog_category = frappe.get_doc(
|
|
||||||
{"doctype": "Blog Category", "category_name": "general", "published": 1, "title": _("General")}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
if not self.user:
|
|
||||||
# Admin setup
|
|
||||||
return
|
|
||||||
|
|
||||||
blogger = frappe.new_doc("Blogger")
|
|
||||||
user = frappe.get_doc("User", self.user)
|
|
||||||
blogger.user = self.user
|
|
||||||
blogger.full_name = user.first_name + (" " + user.last_name if user.last_name else "")
|
|
||||||
blogger.short_name = user.first_name.lower()
|
|
||||||
blogger.avatar = user.user_image
|
|
||||||
blogger.insert()
|
|
||||||
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Blog Post",
|
|
||||||
"title": "Welcome",
|
|
||||||
"published": 1,
|
|
||||||
"published_on": nowdate(),
|
|
||||||
"blogger": blogger.name,
|
|
||||||
"blog_category": blog_category.name,
|
|
||||||
"blog_intro": "My First Blog",
|
|
||||||
"content": frappe.get_template("setup/setup_wizard/data/sample_blog_post.html").render(),
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
|
|
||||||
def test():
|
|
||||||
frappe.delete_doc("Web Page", "test-company")
|
|
||||||
frappe.delete_doc("Blog Post", "welcome")
|
|
||||||
frappe.delete_doc("Blogger", "administrator")
|
|
||||||
frappe.delete_doc("Blog Category", "general")
|
|
||||||
website_maker(
|
|
||||||
{
|
|
||||||
"company": "Test Company",
|
|
||||||
"company_tagline": "Better Tools for Everyone",
|
|
||||||
"name": "Administrator",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
frappe.db.commit()
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user