mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-27 21:08:33 +00:00
Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07ff956fd8 | ||
|
|
c575942acf | ||
|
|
6841e22ffe | ||
|
|
56a422deed | ||
|
|
0ec34e5880 | ||
|
|
ba58c7ed59 | ||
|
|
3a1475a90b | ||
|
|
65c0189c4d | ||
|
|
1b2c4bf868 | ||
|
|
88ed6e6cb4 | ||
|
|
8c5322c1cb | ||
|
|
82a8f2b1b2 | ||
|
|
3908b510bd | ||
|
|
14547d94b3 | ||
|
|
9bea2fcdfc | ||
|
|
bb55210f49 | ||
|
|
178be42369 | ||
|
|
b4e775b264 | ||
|
|
e6945508f1 | ||
|
|
5354169f31 | ||
|
|
ba66a6714c | ||
|
|
573cd3c33b | ||
|
|
b6edadb3cb | ||
|
|
d65df443fc | ||
|
|
f6607a6050 | ||
|
|
75d98ef205 | ||
|
|
fd1d2cd203 | ||
|
|
c66dc5658f | ||
|
|
1ebf2dd2bf | ||
|
|
de631e65cc | ||
|
|
02f2844db2 | ||
|
|
c4d9576f9f | ||
|
|
74303b65cf | ||
|
|
a34aff6f49 | ||
|
|
f105c1bd5e | ||
|
|
264c314416 | ||
|
|
a71a336e59 | ||
|
|
7db3645298 | ||
|
|
b1ecca3a16 | ||
|
|
cbfa188d3d | ||
|
|
4f7344c278 | ||
|
|
45645c1064 | ||
|
|
d44da6c820 | ||
|
|
95ea28f14d | ||
|
|
7971c149ed | ||
|
|
3d7b2b1a6d | ||
|
|
9942a9d40a | ||
|
|
9a607b9bd0 | ||
|
|
304e6bb996 | ||
|
|
4a557b47d7 | ||
|
|
71767994a7 | ||
|
|
2039bd066d | ||
|
|
b599b93ae8 | ||
|
|
59d579764d | ||
|
|
5f25cea322 | ||
|
|
6a0c24e7b3 | ||
|
|
8eb6053c97 | ||
|
|
c8ec365594 | ||
|
|
0fbd29b16d | ||
|
|
806f7e5eef | ||
|
|
59c6eb591b | ||
|
|
0490e3bfe6 | ||
|
|
0aeef34944 | ||
|
|
69f1247fab | ||
|
|
2d01b72b04 | ||
|
|
bb4c968d95 | ||
|
|
c40aa580c5 | ||
|
|
642692a040 | ||
|
|
fd24d52d86 | ||
|
|
91a95adcb6 | ||
|
|
fe04b5a2b9 | ||
|
|
d1b611d37f | ||
|
|
e98b34617f | ||
|
|
6391ccd56a | ||
|
|
b848b77815 | ||
|
|
d82ba4e86f | ||
|
|
aea9d82672 | ||
|
|
a3a9cd5174 | ||
|
|
9766827a08 | ||
|
|
eeaa8b2479 | ||
|
|
c7093b6e96 | ||
|
|
92da1ed3c2 | ||
|
|
9826245d8a | ||
|
|
26ed460a4f | ||
|
|
7bed6cddc7 | ||
|
|
1fb3a28128 | ||
|
|
c49c621e43 | ||
|
|
8add12d568 | ||
|
|
acdf7fd8df | ||
|
|
ccd25684f9 | ||
|
|
e34f5c9cf7 | ||
|
|
bdefd700af | ||
|
|
495d1b2548 | ||
|
|
2408966090 | ||
|
|
58a006ff64 | ||
|
|
3585b90ce5 | ||
|
|
922b30a566 | ||
|
|
5fc68a3dfe | ||
|
|
4a95c9d642 | ||
|
|
f6707b2b92 | ||
|
|
f6d8adc921 | ||
|
|
48f2bd9add | ||
|
|
62dc68bb57 | ||
|
|
f80fb97c71 | ||
|
|
6308fca587 | ||
|
|
fb3a411d1f | ||
|
|
6227c16374 | ||
|
|
ff2e617c0c | ||
|
|
4f10f48f7c | ||
|
|
612ceb59c7 | ||
|
|
117dbe38c4 | ||
|
|
1223e31e7d | ||
|
|
bc59ea0d55 | ||
|
|
6ae1cc020a | ||
|
|
4278bfe7b3 | ||
|
|
574791f2c9 | ||
|
|
2391c37238 | ||
|
|
ab71a7bba8 | ||
|
|
958a3320e8 | ||
|
|
60d2bf939b | ||
|
|
a8ea3efae2 | ||
|
|
c1de4e4420 | ||
|
|
f04542eac9 | ||
|
|
de87786db4 | ||
|
|
f6410393ce | ||
|
|
5be4c6ffbc | ||
|
|
124d7dea1b | ||
|
|
bbcdd1e2e2 | ||
|
|
b6bc29ac92 | ||
|
|
e9a453c430 | ||
|
|
7e1d5e3595 | ||
|
|
a9f5be3f98 | ||
|
|
d6b0e622ea | ||
|
|
fbeaabffc9 | ||
|
|
c7c611d929 | ||
|
|
8106c64c91 | ||
|
|
bd1191783b | ||
|
|
2f4ffe137e | ||
|
|
6735b09dd9 | ||
|
|
745bef8ebc | ||
|
|
5a9673ae1f | ||
|
|
8d0b45b835 | ||
|
|
6a9660de65 | ||
|
|
edbbb2469f | ||
|
|
e3ad0b1655 | ||
|
|
7738ca1ce0 | ||
|
|
8f42833fba | ||
|
|
b2a3e014e9 | ||
|
|
64018c29f3 | ||
|
|
813e8bb664 |
@@ -4,7 +4,7 @@
|
||||
# the repo. Unless a later match takes precedence,
|
||||
|
||||
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/assets/ @anandbaburajan @deepeshgarg007
|
||||
erpnext/erpnext_integrations/ @nextchamp-saqib
|
||||
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
||||
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||
|
||||
@@ -4,7 +4,7 @@ import frappe
|
||||
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = "13.45.0"
|
||||
__version__ = "13.49.3"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
{
|
||||
"country_code": "de",
|
||||
"name": "SKR03 mit Kontonummern",
|
||||
"tree": {
|
||||
"Aktiva": {
|
||||
"is_group": 1,
|
||||
"country_code": "de",
|
||||
"name": "SKR03 mit Kontonummern",
|
||||
"tree": {
|
||||
"Aktiva": {
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"A - Anlagevermögen": {
|
||||
"is_group": 1,
|
||||
"EDV-Software": {
|
||||
"account_number": "0027",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Gesch\u00e4ftsausstattung": {
|
||||
"account_number": "0410",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"B\u00fcroeinrichtung": {
|
||||
"account_number": "0420",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Darlehen": {
|
||||
"account_number": "0565"
|
||||
},
|
||||
"Maschinen": {
|
||||
"account_number": "0210",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Betriebsausstattung": {
|
||||
"account_number": "0400",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Ladeneinrichtung": {
|
||||
"account_number": "0430",
|
||||
"account_type": "Fixed Asset"
|
||||
"A - Anlagevermögen": {
|
||||
"is_group": 1,
|
||||
"EDV-Software": {
|
||||
"account_number": "0027",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Geschäftsausstattung": {
|
||||
"account_number": "0410",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Büroeinrichtung": {
|
||||
"account_number": "0420",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Darlehen": {
|
||||
"account_number": "0565"
|
||||
},
|
||||
"Maschinen": {
|
||||
"account_number": "0210",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Betriebsausstattung": {
|
||||
"account_number": "0400",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Ladeneinrichtung": {
|
||||
"account_number": "0430",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Accumulated Depreciation": {
|
||||
"account_type": "Accumulated Depreciation"
|
||||
@@ -60,36 +60,46 @@
|
||||
"Durchlaufende Posten": {
|
||||
"account_number": "1590"
|
||||
},
|
||||
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
|
||||
"Verrechnungskonto Gewinnermittlung § 4 Abs. 3 EStG, nicht ergebniswirksam": {
|
||||
"account_number": "1371"
|
||||
},
|
||||
"Abziehbare Vorsteuer": {
|
||||
"account_type": "Tax",
|
||||
"is_group": 1,
|
||||
"Abziehbare Vorsteuer 7%": {
|
||||
"account_number": "1571"
|
||||
"Abziehbare Vorsteuer 7 %": {
|
||||
"account_number": "1571",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": 7.0
|
||||
},
|
||||
"Abziehbare Vorsteuer 19%": {
|
||||
"account_number": "1576"
|
||||
"Abziehbare Vorsteuer 19 %": {
|
||||
"account_number": "1576",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": 19.0
|
||||
},
|
||||
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
|
||||
"account_number": "1577"
|
||||
},
|
||||
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
|
||||
"account_number": "3120"
|
||||
"Abziehbare Vorsteuer nach § 13b UStG 19 %": {
|
||||
"account_number": "1577",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": 19.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.": {
|
||||
"is_group": 1,
|
||||
"Kasse": {
|
||||
"account_type": "Cash",
|
||||
"is_group": 1,
|
||||
"account_type": "Cash",
|
||||
"Kasse": {
|
||||
"is_group": 1,
|
||||
"account_number": "1000",
|
||||
"account_type": "Cash"
|
||||
}
|
||||
@@ -111,21 +121,21 @@
|
||||
"C - Rechnungsabgrenzungsposten": {
|
||||
"is_group": 1,
|
||||
"Aktive Rechnungsabgrenzung": {
|
||||
"account_number": "0980"
|
||||
"account_number": "0980"
|
||||
}
|
||||
},
|
||||
"D - Aktive latente Steuern": {
|
||||
"is_group": 1,
|
||||
"Aktive latente Steuern": {
|
||||
"account_number": "0983"
|
||||
"account_number": "0983"
|
||||
}
|
||||
},
|
||||
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
|
||||
"is_group": 1
|
||||
}
|
||||
},
|
||||
"Passiva": {
|
||||
"is_group": 1,
|
||||
},
|
||||
"Passiva": {
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"A. Eigenkapital": {
|
||||
"is_group": 1,
|
||||
@@ -200,26 +210,32 @@
|
||||
},
|
||||
"Umsatzsteuer": {
|
||||
"is_group": 1,
|
||||
"account_type": "Tax",
|
||||
"Umsatzsteuer 7%": {
|
||||
"account_number": "1771"
|
||||
"Umsatzsteuer 7 %": {
|
||||
"account_number": "1771",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": 7.0
|
||||
},
|
||||
"Umsatzsteuer 19%": {
|
||||
"account_number": "1776"
|
||||
"Umsatzsteuer 19 %": {
|
||||
"account_number": "1776",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": 19.0
|
||||
},
|
||||
"Umsatzsteuer-Vorauszahlung": {
|
||||
"account_number": "1780"
|
||||
"account_number": "1780",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Umsatzsteuer-Vorauszahlung 1/11": {
|
||||
"account_number": "1781"
|
||||
},
|
||||
"Umsatzsteuer \u00a7 13b UStG 19%": {
|
||||
"account_number": "1787"
|
||||
"Umsatzsteuer nach § 13b UStG 19 %": {
|
||||
"account_number": "1787",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": 19.0
|
||||
},
|
||||
"Umsatzsteuer Vorjahr": {
|
||||
"account_number": "1790"
|
||||
},
|
||||
"Umsatzsteuer fr\u00fchere Jahre": {
|
||||
"Umsatzsteuer frühere Jahre": {
|
||||
"account_number": "1791"
|
||||
}
|
||||
}
|
||||
@@ -234,44 +250,56 @@
|
||||
"E. Passive latente Steuern": {
|
||||
"is_group": 1
|
||||
}
|
||||
},
|
||||
"Erl\u00f6se u. Ertr\u00e4ge 2/8": {
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Erl\u00f6skonten 8": {
|
||||
},
|
||||
"Erlöse u. Erträge 2/8": {
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Erlöskonten 8": {
|
||||
"is_group": 1,
|
||||
"Erl\u00f6se": {
|
||||
"account_number": "8200",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Erl\u00f6se USt. 19%": {
|
||||
"account_number": "8400",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Erl\u00f6se USt. 7%": {
|
||||
"account_number": "8300",
|
||||
"account_type": "Income Account"
|
||||
}
|
||||
},
|
||||
"Ertragskonten 2": {
|
||||
"is_group": 1,
|
||||
"sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
|
||||
"account_number": "2650",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Au\u00dferordentliche Ertr\u00e4ge": {
|
||||
"account_number": "2500",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Sonstige Ertr\u00e4ge": {
|
||||
"account_number": "2700",
|
||||
"account_type": "Income Account"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Aufwendungen 2/4": {
|
||||
"is_group": 1,
|
||||
"Erlöse": {
|
||||
"account_number": "8200",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Erlöse USt. 19 %": {
|
||||
"account_number": "8400",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Erlöse USt. 7 %": {
|
||||
"account_number": "8300",
|
||||
"account_type": "Income Account"
|
||||
}
|
||||
},
|
||||
"Ertragskonten 2": {
|
||||
"is_group": 1,
|
||||
"sonstige Zinsen und ähnliche Erträge": {
|
||||
"account_number": "2650",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Außerordentliche Erträge": {
|
||||
"account_number": "2500",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Sonstige Erträge": {
|
||||
"account_number": "2700",
|
||||
"account_type": "Income Account"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Aufwendungen 2/4": {
|
||||
"is_group": 1,
|
||||
"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": {
|
||||
"account_number": "3200"
|
||||
},
|
||||
@@ -298,234 +326,234 @@
|
||||
"Gegenkonto 4996-4998": {
|
||||
"account_number": "4999"
|
||||
},
|
||||
"Abschreibungen": {
|
||||
"is_group": 1,
|
||||
"Abschreibungen": {
|
||||
"is_group": 1,
|
||||
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
|
||||
"account_number": "4830",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
"account_number": "4830",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Abschreibungen auf Gebäude": {
|
||||
"account_number": "4831",
|
||||
"account_type": "Depreciation"
|
||||
"account_number": "4831",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Abschreibungen auf Kfz": {
|
||||
"account_number": "4832",
|
||||
"account_type": "Depreciation"
|
||||
"account_number": "4832",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Sofortabschreibung GWG": {
|
||||
"account_number": "4855",
|
||||
"account_type": "Expense Account"
|
||||
"account_number": "4855",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Kfz-Kosten": {
|
||||
"is_group": 1,
|
||||
"Kfz-Steuer": {
|
||||
"account_number": "4510",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Kfz-Versicherungen": {
|
||||
"account_number": "4520",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"laufende Kfz-Betriebskosten": {
|
||||
"account_number": "4530",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Kfz-Reparaturen": {
|
||||
"account_number": "4540",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Fremdfahrzeuge": {
|
||||
"account_number": "4570",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"sonstige Kfz-Kosten": {
|
||||
"account_number": "4580",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Personalkosten": {
|
||||
"is_group": 1,
|
||||
"Geh\u00e4lter": {
|
||||
"account_number": "4120",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"gesetzliche soziale Aufwendungen": {
|
||||
"account_number": "4130",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Aufwendungen f\u00fcr Altersvorsorge": {
|
||||
"account_number": "4165",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verm\u00f6genswirksame Leistungen": {
|
||||
"account_number": "4170",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Aushilfsl\u00f6hne": {
|
||||
"account_number": "4190",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Raumkosten": {
|
||||
"is_group": 1,
|
||||
"Miete und Nebenkosten": {
|
||||
"account_number": "4210",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
|
||||
"account_number": "4240",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Reinigung": {
|
||||
"account_number": "4250",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Reparatur/Instandhaltung": {
|
||||
"is_group": 1,
|
||||
"Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
|
||||
"account_number": "4805",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Versicherungsbeitr\u00e4ge": {
|
||||
"is_group": 1,
|
||||
"Versicherungen": {
|
||||
"account_number": "4360",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Beitr\u00e4ge": {
|
||||
"account_number": "4380",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"sonstige Ausgaben": {
|
||||
"account_number": "4390",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
|
||||
"account_number": "4396",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Werbe-/Reisekosten": {
|
||||
"is_group": 1,
|
||||
"Werbekosten": {
|
||||
"account_number": "4610",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Aufmerksamkeiten": {
|
||||
"account_number": "4653",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
|
||||
"account_number": "4665",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Reisekosten Unternehmer": {
|
||||
"account_number": "4670",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"verschiedene Kosten": {
|
||||
"is_group": 1,
|
||||
"Porto": {
|
||||
"account_number": "4910",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Telekom": {
|
||||
"account_number": "4920",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Mobilfunk D2": {
|
||||
"account_number": "4921",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Internet": {
|
||||
"account_number": "4922",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"B\u00fcrobedarf": {
|
||||
"account_number": "4930",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Zeitschriften, B\u00fccher": {
|
||||
"account_number": "4940",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Fortbildungskosten": {
|
||||
"account_number": "4945",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Buchf\u00fchrungskosten": {
|
||||
"account_number": "4955",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Abschlu\u00df- u. Pr\u00fcfungskosten": {
|
||||
"account_number": "4957",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Nebenkosten des Geldverkehrs": {
|
||||
"account_number": "4970",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Werkzeuge und Kleinger\u00e4te": {
|
||||
"account_number": "4985",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Zinsaufwendungen": {
|
||||
"is_group": 1,
|
||||
"Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
|
||||
"account_number": "2110",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
|
||||
"account_number": "2121",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Anfangsbestand 9": {
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Saldenvortragskonten": {
|
||||
"is_group": 1,
|
||||
"Saldenvortrag Sachkonten": {
|
||||
"account_number": "9000"
|
||||
},
|
||||
"Saldenvortr\u00e4ge Debitoren": {
|
||||
"account_number": "9008"
|
||||
},
|
||||
"Saldenvortr\u00e4ge Kreditoren": {
|
||||
"account_number": "9009"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Privatkonten 1": {
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Privatentnahmen/-einlagen": {
|
||||
"is_group": 1,
|
||||
"Privatentnahme allgemein": {
|
||||
"account_number": "1800"
|
||||
},
|
||||
"Privatsteuern": {
|
||||
"account_number": "1810"
|
||||
},
|
||||
"Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
|
||||
"account_number": "1820"
|
||||
},
|
||||
"Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
|
||||
"account_number": "1830"
|
||||
},
|
||||
"Au\u00dfergew\u00f6hnliche Belastungen": {
|
||||
"account_number": "1850"
|
||||
},
|
||||
"Privateinlagen": {
|
||||
"account_number": "1890"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Kfz-Kosten": {
|
||||
"is_group": 1,
|
||||
"Kfz-Steuer": {
|
||||
"account_number": "4510",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Kfz-Versicherungen": {
|
||||
"account_number": "4520",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"laufende Kfz-Betriebskosten": {
|
||||
"account_number": "4530",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Kfz-Reparaturen": {
|
||||
"account_number": "4540",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Fremdfahrzeuge": {
|
||||
"account_number": "4570",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"sonstige Kfz-Kosten": {
|
||||
"account_number": "4580",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Personalkosten": {
|
||||
"is_group": 1,
|
||||
"Gehälter": {
|
||||
"account_number": "4120",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"gesetzliche soziale Aufwendungen": {
|
||||
"account_number": "4130",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Aufwendungen für Altersvorsorge": {
|
||||
"account_number": "4165",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Vermögenswirksame Leistungen": {
|
||||
"account_number": "4170",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Aushilfslöhne": {
|
||||
"account_number": "4190",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Raumkosten": {
|
||||
"is_group": 1,
|
||||
"Miete und Nebenkosten": {
|
||||
"account_number": "4210",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
|
||||
"account_number": "4240",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Reinigung": {
|
||||
"account_number": "4250",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Reparatur/Instandhaltung": {
|
||||
"is_group": 1,
|
||||
"Reparaturen und Instandhaltungen von anderen Anlagen und Betriebs- und Geschäftsausstattung": {
|
||||
"account_number": "4805",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Versicherungsbeiträge": {
|
||||
"is_group": 1,
|
||||
"Versicherungen": {
|
||||
"account_number": "4360",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Beiträge": {
|
||||
"account_number": "4380",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"sonstige Ausgaben": {
|
||||
"account_number": "4390",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"steuerlich abzugsfähige Verspätungszuschläge und Zwangsgelder": {
|
||||
"account_number": "4396",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Werbe-/Reisekosten": {
|
||||
"is_group": 1,
|
||||
"Werbekosten": {
|
||||
"account_number": "4610",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Aufmerksamkeiten": {
|
||||
"account_number": "4653",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"nicht abzugsfähige Betriebsausg. aus Werbe-, Repräs.- u. Reisekosten": {
|
||||
"account_number": "4665",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Reisekosten Unternehmer": {
|
||||
"account_number": "4670",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"verschiedene Kosten": {
|
||||
"is_group": 1,
|
||||
"Porto": {
|
||||
"account_number": "4910",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Telekom": {
|
||||
"account_number": "4920",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Mobilfunk D2": {
|
||||
"account_number": "4921",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Internet": {
|
||||
"account_number": "4922",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Bürobedarf": {
|
||||
"account_number": "4930",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Zeitschriften, Bücher": {
|
||||
"account_number": "4940",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Fortbildungskosten": {
|
||||
"account_number": "4945",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Buchführungskosten": {
|
||||
"account_number": "4955",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Abschluß- u. Prüfungskosten": {
|
||||
"account_number": "4957",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Nebenkosten des Geldverkehrs": {
|
||||
"account_number": "4970",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Werkzeuge und Kleingeräte": {
|
||||
"account_number": "4985",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
},
|
||||
"Zinsaufwendungen": {
|
||||
"is_group": 1,
|
||||
"Zinsaufwendungen für kurzfristige Verbindlichkeiten": {
|
||||
"account_number": "2110",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Zinsaufwendungen für KFZ Finanzierung": {
|
||||
"account_number": "2121",
|
||||
"account_type": "Expense Account"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Anfangsbestand 9": {
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Saldenvortragskonten": {
|
||||
"is_group": 1,
|
||||
"Saldenvortrag Sachkonten": {
|
||||
"account_number": "9000"
|
||||
},
|
||||
"Saldenvorträge Debitoren": {
|
||||
"account_number": "9008"
|
||||
},
|
||||
"Saldenvorträge Kreditoren": {
|
||||
"account_number": "9009"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Privatkonten 1": {
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Privatentnahmen/-einlagen": {
|
||||
"is_group": 1,
|
||||
"Privatentnahme allgemein": {
|
||||
"account_number": "1800"
|
||||
},
|
||||
"Privatsteuern": {
|
||||
"account_number": "1810"
|
||||
},
|
||||
"Sonderausgaben beschränkt abzugsfähig": {
|
||||
"account_number": "1820"
|
||||
},
|
||||
"Sonderausgaben unbeschränkt abzugsfähig": {
|
||||
"account_number": "1830"
|
||||
},
|
||||
"Außergewöhnliche Belastungen": {
|
||||
"account_number": "1850"
|
||||
},
|
||||
"Privateinlagen": {
|
||||
"account_number": "1890"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,21 +248,16 @@ class JournalEntry(AccountsController):
|
||||
):
|
||||
processed_assets.append(d.reference_name)
|
||||
|
||||
asset = frappe.db.get_value(
|
||||
"Asset", d.reference_name, ["calculate_depreciation", "value_after_depreciation"], as_dict=1
|
||||
)
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
continue
|
||||
|
||||
depr_value = d.debit or d.credit
|
||||
|
||||
frappe.db.set_value(
|
||||
"Asset",
|
||||
d.reference_name,
|
||||
"value_after_depreciation",
|
||||
asset.value_after_depreciation - depr_value,
|
||||
)
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
|
||||
|
||||
asset.set_status()
|
||||
|
||||
def update_inter_company_jv(self):
|
||||
if (
|
||||
@@ -346,15 +341,14 @@ class JournalEntry(AccountsController):
|
||||
finance_books.db_update()
|
||||
|
||||
asset.set_status()
|
||||
|
||||
break
|
||||
else:
|
||||
depr_value = d.debit or d.credit
|
||||
|
||||
frappe.db.set_value(
|
||||
"Asset",
|
||||
d.reference_name,
|
||||
"value_after_depreciation",
|
||||
asset.value_after_depreciation + depr_value,
|
||||
)
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
|
||||
|
||||
asset.set_status()
|
||||
|
||||
def unlink_inter_company_jv(self):
|
||||
if (
|
||||
|
||||
@@ -21,8 +21,24 @@ class POSClosingEntry(StatusUpdater):
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
self.validate_duplicate_pos_invoices()
|
||||
self.validate_pos_invoices()
|
||||
|
||||
def validate_duplicate_pos_invoices(self):
|
||||
pos_occurences = {}
|
||||
for idx, inv in enumerate(self.pos_transactions, 1):
|
||||
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
|
||||
|
||||
error_list = []
|
||||
for key, value in pos_occurences.items():
|
||||
if len(value) > 1:
|
||||
error_list.append(
|
||||
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
|
||||
)
|
||||
|
||||
if error_list:
|
||||
frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True)
|
||||
|
||||
def validate_pos_invoices(self):
|
||||
invalid_rows = []
|
||||
for d in self.pos_transactions:
|
||||
|
||||
@@ -161,7 +161,7 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
bold_item_name = frappe.bold(item.item_name)
|
||||
bold_extra_batch_qty_needed = frappe.bold(
|
||||
abs(available_batch_qty - reserved_batch_qty - item.qty)
|
||||
abs(available_batch_qty - reserved_batch_qty - item.stock_qty)
|
||||
)
|
||||
bold_invalid_batch_no = frappe.bold(item.batch_no)
|
||||
|
||||
@@ -172,7 +172,7 @@ class POSInvoice(SalesInvoice):
|
||||
).format(item.idx, bold_invalid_batch_no, bold_item_name),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
elif (available_batch_qty - reserved_batch_qty - item.qty) < 0:
|
||||
elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
|
||||
@@ -246,7 +246,7 @@ class POSInvoice(SalesInvoice):
|
||||
),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
elif is_stock_item and flt(available_stock) < flt(d.qty):
|
||||
elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
|
||||
@@ -652,7 +652,7 @@ def get_bundle_availability(bundle_item_code, warehouse):
|
||||
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
|
||||
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||
|
||||
max_available_bundles = available_qty / item.qty
|
||||
max_available_bundles = available_qty / item.stock_qty
|
||||
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
||||
"Item", item.item_code, "is_stock_item"
|
||||
):
|
||||
|
||||
@@ -18,6 +18,22 @@ class POSInvoiceMergeLog(Document):
|
||||
def validate(self):
|
||||
self.validate_customer()
|
||||
self.validate_pos_invoice_status()
|
||||
self.validate_duplicate_pos_invoices()
|
||||
|
||||
def validate_duplicate_pos_invoices(self):
|
||||
pos_occurences = {}
|
||||
for idx, inv in enumerate(self.pos_invoices, 1):
|
||||
pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
|
||||
|
||||
error_list = []
|
||||
for key, value in pos_occurences.items():
|
||||
if len(value) > 1:
|
||||
error_list.append(
|
||||
_("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value)))
|
||||
)
|
||||
|
||||
if error_list:
|
||||
frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True)
|
||||
|
||||
def validate_customer(self):
|
||||
if self.merge_invoices_based_on == "Customer Group":
|
||||
@@ -427,6 +443,8 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status="Failed")
|
||||
if type(error_message) == list:
|
||||
error_message = frappe.json.dumps(error_message)
|
||||
closing_entry.db_set("error_message", error_message)
|
||||
raise
|
||||
|
||||
|
||||
@@ -598,7 +598,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def make_supplier_gl_entry(self, gl_entries):
|
||||
# Checked both rounding_adjustment and rounded_total
|
||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||
# because rounded_total had value even before introduction of posting GLE based on rounded total
|
||||
grand_total = (
|
||||
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
)
|
||||
@@ -799,10 +799,7 @@ class PurchaseInvoice(BuyingController):
|
||||
else item.deferred_expense_account
|
||||
)
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
dummy, amount = self.get_amount_and_base_amount(item, None)
|
||||
else:
|
||||
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
|
||||
dummy, amount = self.get_amount_and_base_amount(item, None)
|
||||
|
||||
if provisional_accounting_for_non_stock_items:
|
||||
if item.purchase_receipt:
|
||||
|
||||
@@ -1078,7 +1078,7 @@ var select_loyalty_program = function(frm, loyalty_programs) {
|
||||
]
|
||||
});
|
||||
|
||||
dialog.set_primary_action(__("Set"), function() {
|
||||
dialog.set_primary_action(__("Set Loyalty Program"), function() {
|
||||
dialog.hide();
|
||||
return frappe.call({
|
||||
method: "frappe.client.set_value",
|
||||
|
||||
@@ -256,7 +256,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers)
|
||||
|
||||
if cint(tax_details.round_off_tax_amount):
|
||||
tax_amount = round(tax_amount)
|
||||
tax_amount = normal_round(tax_amount)
|
||||
|
||||
return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount
|
||||
|
||||
@@ -555,3 +555,20 @@ def is_valid_certificate(
|
||||
valid = True
|
||||
|
||||
return valid
|
||||
|
||||
|
||||
def normal_round(number):
|
||||
"""
|
||||
Rounds a number to the nearest integer.
|
||||
:param number: The number to round.
|
||||
"""
|
||||
decimal_part = number - int(number)
|
||||
|
||||
if decimal_part >= 0.5:
|
||||
decimal_part = 1
|
||||
else:
|
||||
decimal_part = 0
|
||||
|
||||
number = int(number) + decimal_part
|
||||
|
||||
return number
|
||||
|
||||
@@ -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 a.name = ds.parent and ifnull(ds.journal_entry, '') != ''
|
||||
group by a.asset_category
|
||||
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,
|
||||
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
|
||||
|
||||
@@ -38,8 +38,11 @@
|
||||
{% if(data[i].posting_date) { %}
|
||||
<td>{%= frappe.datetime.str_to_user(data[i].posting_date) %}</td>
|
||||
<td>{%= data[i].voucher_type %}
|
||||
<br>{%= data[i].voucher_no %}</td>
|
||||
<td>
|
||||
<br>{%= data[i].voucher_no %}
|
||||
</td>
|
||||
{% var longest_word = cstr(data[i].remarks).split(" ").reduce((longest, word) => word.length > longest.length ? word : longest, ""); %}
|
||||
<td {% if longest_word.length > 45 %} class="overflow-wrap-anywhere" {% endif %}>
|
||||
<span>
|
||||
{% if(!(filters.party || filters.account)) { %}
|
||||
{%= data[i].party || data[i].account %}
|
||||
<br>
|
||||
@@ -49,11 +52,14 @@
|
||||
{% if(data[i].bill_no) { %}
|
||||
<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
|
||||
{% } %}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i].debit, filters.presentation_currency) %}</td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i].credit, filters.presentation_currency) %}</td>
|
||||
</span>
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i].debit, filters.presentation_currency) %}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i].credit, filters.presentation_currency) %}
|
||||
</td>
|
||||
{% } else { %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
|
||||
@@ -364,6 +364,7 @@ def get_column_names():
|
||||
|
||||
class GrossProfitGenerator(object):
|
||||
def __init__(self, filters=None):
|
||||
self.sle = {}
|
||||
self.data = []
|
||||
self.average_buying_rate = {}
|
||||
self.filters = frappe._dict(filters)
|
||||
@@ -373,7 +374,6 @@ class GrossProfitGenerator(object):
|
||||
if filters.group_by == "Invoice":
|
||||
self.group_items_by_invoice()
|
||||
|
||||
self.load_stock_ledger_entries()
|
||||
self.load_product_bundle()
|
||||
self.load_non_stock_items()
|
||||
self.get_returned_invoice_items()
|
||||
@@ -563,7 +563,7 @@ class GrossProfitGenerator(object):
|
||||
return flt(row.qty) * item_rate
|
||||
|
||||
else:
|
||||
my_sle = self.sle.get((item_code, row.warehouse))
|
||||
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
|
||||
if (row.update_stock or row.dn_detail) and my_sle:
|
||||
parenttype, parent = row.parenttype, row.parent
|
||||
if row.dn_detail:
|
||||
@@ -581,7 +581,7 @@ class GrossProfitGenerator(object):
|
||||
dn["item_row"],
|
||||
dn["warehouse"],
|
||||
)
|
||||
my_sle = self.sle.get((item_code, warehouse))
|
||||
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
|
||||
return self.calculate_buying_amount_from_sle(
|
||||
row, my_sle, parenttype, parent, item_row, item_code
|
||||
)
|
||||
@@ -597,15 +597,12 @@ class GrossProfitGenerator(object):
|
||||
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)
|
||||
frappe.qb.from_(delivery_note_item)
|
||||
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
|
||||
.where(delivery_note.docstatus == 1)
|
||||
.where(delivery_note_item.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)
|
||||
@@ -840,24 +837,36 @@ class GrossProfitGenerator(object):
|
||||
"Item", item_code, ["item_name", "description", "item_group", "brand"]
|
||||
)
|
||||
|
||||
def load_stock_ledger_entries(self):
|
||||
res = frappe.db.sql(
|
||||
"""select item_code, voucher_type, voucher_no,
|
||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where company=%(company)s and is_cancelled = 0
|
||||
order by
|
||||
item_code desc, warehouse desc, posting_date desc,
|
||||
posting_time desc, creation desc""",
|
||||
self.filters,
|
||||
as_dict=True,
|
||||
)
|
||||
self.sle = {}
|
||||
for r in res:
|
||||
if (r.item_code, r.warehouse) not in self.sle:
|
||||
self.sle[(r.item_code, r.warehouse)] = []
|
||||
def get_stock_ledger_entries(self, item_code, warehouse):
|
||||
if item_code and warehouse:
|
||||
if (item_code, warehouse) not in self.sle:
|
||||
sle = qb.DocType("Stock Ledger Entry")
|
||||
res = (
|
||||
qb.from_(sle)
|
||||
.select(
|
||||
sle.item_code,
|
||||
sle.voucher_type,
|
||||
sle.voucher_no,
|
||||
sle.voucher_detail_no,
|
||||
sle.stock_value,
|
||||
sle.warehouse,
|
||||
sle.actual_qty.as_("qty"),
|
||||
)
|
||||
.where(
|
||||
(sle.company == self.filters.company)
|
||||
& (sle.item_code == item_code)
|
||||
& (sle.warehouse == warehouse)
|
||||
& (sle.is_cancelled == 0)
|
||||
)
|
||||
.orderby(sle.item_code)
|
||||
.orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
self.sle[(r.item_code, r.warehouse)].append(r)
|
||||
self.sle[(item_code, warehouse)] = res
|
||||
|
||||
return self.sle[(item_code, warehouse)]
|
||||
return []
|
||||
|
||||
def load_product_bundle(self):
|
||||
self.product_bundles = {}
|
||||
|
||||
@@ -206,52 +206,53 @@ frappe.ui.form.on('Asset', {
|
||||
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 last_depreciation_date = frm.doc.purchase_date;
|
||||
|
||||
if(frm.doc.opening_accumulated_depreciation) {
|
||||
last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date,
|
||||
-1*frm.doc.frequency_of_depreciation);
|
||||
|
||||
x_intervals.push(last_depreciation_date);
|
||||
asset_values.push(flt(frm.doc.gross_purchase_amount) -
|
||||
flt(frm.doc.opening_accumulated_depreciation));
|
||||
}
|
||||
|
||||
if(frm.doc.calculate_depreciation) {
|
||||
if(frm.doc.opening_accumulated_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')));
|
||||
}
|
||||
|
||||
$.each(frm.doc.schedules || [], function(i, v) {
|
||||
x_intervals.push(v.schedule_date);
|
||||
var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount);
|
||||
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) {
|
||||
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)
|
||||
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(v.posting_date);
|
||||
last_depreciation_date = v.posting_date;
|
||||
x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
|
||||
let last_asset_value = asset_values[asset_values.length - 1]
|
||||
asset_values.push(last_asset_value - v.value);
|
||||
asset_values.push(flt(last_asset_value - v.value, precision('gross_purchase_amount')));
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
last_depreciation_date = frm.doc.disposal_date;
|
||||
}
|
||||
|
||||
frm.dashboard.render_graph({
|
||||
@@ -295,10 +296,6 @@ frappe.ui.form.on('Asset', {
|
||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||
},
|
||||
|
||||
opening_accumulated_depreciation: function(frm) {
|
||||
erpnext.asset.set_accumulated_depreciation(frm);
|
||||
},
|
||||
|
||||
make_schedules_editable: function(frm) {
|
||||
if (frm.doc.finance_books) {
|
||||
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
||||
@@ -518,19 +515,23 @@ frappe.ui.form.on('Depreciation Schedule', {
|
||||
},
|
||||
|
||||
depreciation_amount: function(frm, cdt, cdn) {
|
||||
erpnext.asset.set_accumulated_depreciation(frm);
|
||||
erpnext.asset.set_accumulated_depreciation(frm, locals[cdt][cdn].finance_book_id);
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
erpnext.asset.set_accumulated_depreciation = function(frm) {
|
||||
if(frm.doc.depreciation_method != "Manual") return;
|
||||
erpnext.asset.set_accumulated_depreciation = function(frm, finance_book_id) {
|
||||
var depreciation_method = frm.doc.finance_books[Number(finance_book_id) - 1].depreciation_method;
|
||||
|
||||
if(depreciation_method != "Manual") return;
|
||||
|
||||
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
|
||||
|
||||
$.each(frm.doc.schedules || [], function(i, row) {
|
||||
accumulated_depreciation += flt(row.depreciation_amount);
|
||||
frappe.model.set_value(row.doctype, row.name,
|
||||
"accumulated_depreciation_amount", accumulated_depreciation);
|
||||
if (row.finance_book_id === finance_book_id) {
|
||||
accumulated_depreciation += flt(row.depreciation_amount);
|
||||
frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation);
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -84,14 +84,55 @@ class Asset(AccountsController):
|
||||
if self.calculate_depreciation:
|
||||
self.value_after_depreciation = 0
|
||||
self.set_depreciation_rate()
|
||||
self.make_depreciation_schedule(date_of_disposal)
|
||||
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
|
||||
if self.should_prepare_depreciation_schedule():
|
||||
self.make_depreciation_schedule(date_of_disposal)
|
||||
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
|
||||
else:
|
||||
self.finance_books = []
|
||||
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
||||
self.opening_accumulated_depreciation
|
||||
)
|
||||
|
||||
def should_prepare_depreciation_schedule(self):
|
||||
if not self.get("schedules"):
|
||||
return True
|
||||
|
||||
old_asset_doc = self.get_doc_before_save()
|
||||
|
||||
if not old_asset_doc:
|
||||
return True
|
||||
|
||||
have_asset_details_been_modified = (
|
||||
old_asset_doc.gross_purchase_amount != self.gross_purchase_amount
|
||||
or old_asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
|
||||
or old_asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
|
||||
)
|
||||
|
||||
if have_asset_details_been_modified:
|
||||
return True
|
||||
|
||||
manual_fb_idx = -1
|
||||
for d in self.finance_books:
|
||||
if d.depreciation_method == "Manual":
|
||||
manual_fb_idx = d.idx - 1
|
||||
|
||||
no_manual_depr_or_have_manual_depr_details_been_modified = (
|
||||
manual_fb_idx == -1
|
||||
or old_asset_doc.finance_books[manual_fb_idx].total_number_of_depreciations
|
||||
!= self.finance_books[manual_fb_idx].total_number_of_depreciations
|
||||
or old_asset_doc.finance_books[manual_fb_idx].frequency_of_depreciation
|
||||
!= self.finance_books[manual_fb_idx].frequency_of_depreciation
|
||||
or old_asset_doc.finance_books[manual_fb_idx].depreciation_start_date
|
||||
!= getdate(self.finance_books[manual_fb_idx].depreciation_start_date)
|
||||
or old_asset_doc.finance_books[manual_fb_idx].expected_value_after_useful_life
|
||||
!= self.finance_books[manual_fb_idx].expected_value_after_useful_life
|
||||
)
|
||||
|
||||
if no_manual_depr_or_have_manual_depr_details_been_modified:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def validate_item(self):
|
||||
item = frappe.get_cached_value(
|
||||
"Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1
|
||||
@@ -225,9 +266,7 @@ class Asset(AccountsController):
|
||||
)
|
||||
|
||||
def make_depreciation_schedule(self, date_of_disposal):
|
||||
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
|
||||
"schedules"
|
||||
):
|
||||
if not self.get("schedules"):
|
||||
self.schedules = []
|
||||
|
||||
if not self.available_for_use_date:
|
||||
@@ -545,9 +584,7 @@ class Asset(AccountsController):
|
||||
def set_accumulated_depreciation(
|
||||
self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False
|
||||
):
|
||||
straight_line_idx = [
|
||||
d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
|
||||
]
|
||||
straight_line_idx = []
|
||||
finance_books = []
|
||||
|
||||
for i, d in enumerate(self.get("schedules")):
|
||||
@@ -555,6 +592,12 @@ class Asset(AccountsController):
|
||||
continue
|
||||
|
||||
if int(d.finance_book_id) not in finance_books:
|
||||
straight_line_idx = [
|
||||
s.idx
|
||||
for s in self.get("schedules")
|
||||
if s.finance_book_id == d.finance_book_id
|
||||
and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual")
|
||||
]
|
||||
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||
value_after_depreciation = flt(
|
||||
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
|
||||
@@ -667,11 +710,15 @@ class Asset(AccountsController):
|
||||
|
||||
if self.journal_entry_for_scrap:
|
||||
status = "Scrapped"
|
||||
elif self.finance_books:
|
||||
idx = self.get_default_finance_book_idx() or 0
|
||||
else:
|
||||
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
|
||||
value_after_depreciation = self.finance_books[idx].value_after_depreciation
|
||||
if self.calculate_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:
|
||||
status = "Fully Depreciated"
|
||||
@@ -683,14 +730,16 @@ class Asset(AccountsController):
|
||||
|
||||
def get_value_after_depreciation(self, finance_book=None):
|
||||
if not self.calculate_depreciation:
|
||||
return self.value_after_depreciation
|
||||
return flt(self.value_after_depreciation, self.precision("gross_purchase_amount"))
|
||||
|
||||
if not finance_book:
|
||||
return self.get("finance_books")[0].value_after_depreciation
|
||||
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 row.value_after_depreciation
|
||||
return flt(row.value_after_depreciation, self.precision("gross_purchase_amount"))
|
||||
|
||||
def get_default_finance_book_idx(self):
|
||||
if not self.get("default_finance_book") and self.company:
|
||||
@@ -701,24 +750,6 @@ class Asset(AccountsController):
|
||||
if d.finance_book == self.default_finance_book:
|
||||
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)
|
||||
).run(as_dict=True)
|
||||
|
||||
return records
|
||||
|
||||
def validate_make_gl_entry(self):
|
||||
purchase_document = self.get_purchase_document()
|
||||
if not purchase_document:
|
||||
@@ -835,6 +866,25 @@ class Asset(AccountsController):
|
||||
make_gl_entries(gl_entries)
|
||||
self.db_set("booked_fixed_asset", 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
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_depreciation_rate(self, args, on_validate=False):
|
||||
if isinstance(args, string_types):
|
||||
|
||||
@@ -144,7 +144,7 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
finance_books.value_after_depreciation -= d.depreciation_amount
|
||||
finance_books.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()
|
||||
|
||||
|
||||
@@ -82,6 +82,9 @@ class AssetRepair(AccountsController):
|
||||
self.asset_doc.prepare_depreciation_data()
|
||||
self.asset_doc.save()
|
||||
|
||||
def after_delete(self):
|
||||
frappe.get_doc("Asset", self.asset).set_status()
|
||||
|
||||
def check_repair_status(self):
|
||||
if self.repair_status == "Pending":
|
||||
frappe.throw(_("Please update Repair Status."))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cstr, formatdate, getdate
|
||||
from frappe.utils import cstr, flt, formatdate, getdate
|
||||
|
||||
from erpnext.accounts.report.financial_statements import (
|
||||
get_fiscal_year_data,
|
||||
@@ -102,13 +102,9 @@ def get_data(filters):
|
||||
]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||
|
||||
finance_book_filter = ("is", "not set")
|
||||
if filters.finance_book:
|
||||
finance_book_filter = ("=", filters.finance_book)
|
||||
|
||||
assets_linked_to_fb = frappe.db.get_all(
|
||||
doctype="Asset Finance Book",
|
||||
filters={"finance_book": finance_book_filter},
|
||||
filters={"finance_book": filters.finance_book or ("is", "not set")},
|
||||
pluck="parent",
|
||||
)
|
||||
|
||||
@@ -155,6 +151,7 @@ def prepare_chart_data(data, filters):
|
||||
filters.filter_based_on,
|
||||
"Monthly",
|
||||
company=filters.company,
|
||||
ignore_fiscal_year=True,
|
||||
)
|
||||
|
||||
for d in period_list:
|
||||
@@ -194,7 +191,7 @@ def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
|
||||
else:
|
||||
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
|
||||
|
||||
return depr_amount
|
||||
return flt(depr_amount, 2)
|
||||
|
||||
|
||||
def get_finance_book_value_map(filters):
|
||||
|
||||
@@ -755,6 +755,8 @@ class BuyingController(StockController, Subcontracting):
|
||||
asset.purchase_date = self.posting_date
|
||||
asset.supplier = self.supplier
|
||||
elif self.docstatus == 2:
|
||||
if asset.docstatus == 2:
|
||||
continue
|
||||
if asset.docstatus == 0:
|
||||
asset.set(field, None)
|
||||
asset.supplier = None
|
||||
|
||||
@@ -131,7 +131,7 @@ def validate_returned_items(doc):
|
||||
)
|
||||
|
||||
elif ref.serial_no:
|
||||
if not d.serial_no:
|
||||
if d.qty and not d.serial_no:
|
||||
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
|
||||
else:
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
@@ -393,6 +393,16 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
if serial_nos:
|
||||
target_doc.serial_no = "\n".join(serial_nos)
|
||||
|
||||
if source_doc.get("rejected_serial_no"):
|
||||
returned_serial_nos = get_returned_serial_nos(
|
||||
source_doc, source_parent, serial_no_field="rejected_serial_no"
|
||||
)
|
||||
rejected_serial_nos = list(
|
||||
set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos)
|
||||
)
|
||||
if rejected_serial_nos:
|
||||
target_doc.rejected_serial_no = "\n".join(rejected_serial_nos)
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
||||
@@ -587,7 +597,7 @@ def get_filters(
|
||||
return filters
|
||||
|
||||
|
||||
def get_returned_serial_nos(child_doc, parent_doc):
|
||||
def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
return_ref_field = frappe.scrub(child_doc.doctype)
|
||||
@@ -596,7 +606,7 @@ def get_returned_serial_nos(child_doc, parent_doc):
|
||||
|
||||
serial_nos = []
|
||||
|
||||
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
|
||||
fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
|
||||
|
||||
filters = [
|
||||
[parent_doc.doctype, "return_against", "=", parent_doc.name],
|
||||
@@ -606,6 +616,6 @@ def get_returned_serial_nos(child_doc, parent_doc):
|
||||
]
|
||||
|
||||
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
|
||||
serial_nos.extend(get_serial_nos(row.serial_no))
|
||||
serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
|
||||
|
||||
return serial_nos
|
||||
|
||||
@@ -86,6 +86,9 @@ class SellingController(StockController):
|
||||
)
|
||||
if not self.meta.get_field("sales_team"):
|
||||
party_details.pop("sales_team")
|
||||
else:
|
||||
self.set("sales_team", party_details.get("sales_team"))
|
||||
|
||||
self.update_if_missing(party_details)
|
||||
|
||||
elif lead:
|
||||
|
||||
@@ -345,7 +345,8 @@
|
||||
"image_field": "website_image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-28 17:10:30.613251",
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2022-09-13 04:05:11.614087",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "Website Item",
|
||||
|
||||
@@ -817,7 +817,9 @@ def get_leave_balance_on(
|
||||
allocation = allocation_records.get(leave_type, frappe._dict())
|
||||
|
||||
end_date = allocation.to_date if cint(consider_all_leaves_in_the_allocation_period) else date
|
||||
cf_expiry = get_allocation_expiry_for_cf_leaves(employee, leave_type, to_date, date)
|
||||
cf_expiry = get_allocation_expiry_for_cf_leaves(
|
||||
employee, leave_type, to_date, allocation.from_date
|
||||
)
|
||||
|
||||
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date)
|
||||
|
||||
@@ -832,6 +834,7 @@ def get_leave_balance_on(
|
||||
def get_leave_allocation_records(employee, date, leave_type=None):
|
||||
"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
|
||||
Ledger = frappe.qb.DocType("Leave Ledger Entry")
|
||||
LeaveAllocation = frappe.qb.DocType("Leave Allocation")
|
||||
|
||||
cf_leave_case = (
|
||||
frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
|
||||
@@ -845,6 +848,8 @@ def get_leave_allocation_records(employee, date, leave_type=None):
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(Ledger)
|
||||
.inner_join(LeaveAllocation)
|
||||
.on(Ledger.transaction_name == LeaveAllocation.name)
|
||||
.select(
|
||||
sum_cf_leaves,
|
||||
sum_new_leaves,
|
||||
@@ -854,12 +859,21 @@ def get_leave_allocation_records(employee, date, leave_type=None):
|
||||
)
|
||||
.where(
|
||||
(Ledger.from_date <= date)
|
||||
& (Ledger.to_date >= date)
|
||||
& (Ledger.docstatus == 1)
|
||||
& (Ledger.transaction_type == "Leave Allocation")
|
||||
& (Ledger.employee == employee)
|
||||
& (Ledger.is_expired == 0)
|
||||
& (Ledger.is_lwp == 0)
|
||||
& (
|
||||
# newly allocated leave's end date is same as the leave allocation's to date
|
||||
((Ledger.is_carry_forward == 0) & (Ledger.to_date >= date))
|
||||
# carry forwarded leave's end date won't be same as the leave allocation's to date
|
||||
# it's between the leave allocation's from and to date
|
||||
| (
|
||||
(Ledger.is_carry_forward == 1)
|
||||
& (Ledger.to_date.between(LeaveAllocation.from_date, LeaveAllocation.to_date))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -925,8 +939,12 @@ def get_remaining_leaves(
|
||||
|
||||
# balance for carry forwarded leaves
|
||||
if cf_expiry and allocation.unused_leaves:
|
||||
cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken)
|
||||
remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry)
|
||||
if getdate(date) > getdate(cf_expiry):
|
||||
# carry forwarded leave expiry date passed
|
||||
cf_leaves = remaining_cf_leaves = 0
|
||||
else:
|
||||
cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken)
|
||||
remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry)
|
||||
|
||||
leave_balance = flt(allocation.new_leaves_allocated) + flt(cf_leaves)
|
||||
leave_balance_for_consumption = flt(allocation.new_leaves_allocated) + flt(remaining_cf_leaves)
|
||||
|
||||
@@ -698,8 +698,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=90,
|
||||
)
|
||||
leave_type.insert()
|
||||
).insert()
|
||||
|
||||
create_carry_forwarded_allocation(employee, leave_type)
|
||||
details = get_leave_balance_on(
|
||||
@@ -992,17 +991,51 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
self.assertEqual(leave_allocation, expected)
|
||||
|
||||
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
|
||||
def test_get_leave_allocation_records(self):
|
||||
def test_leave_details_with_expired_cf_leaves(self):
|
||||
employee = get_employee()
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=90,
|
||||
)
|
||||
leave_type.insert()
|
||||
).insert()
|
||||
|
||||
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
|
||||
details = get_leave_allocation_records(employee.name, getdate(), leave_type.name)
|
||||
cf_expiry = frappe.db.get_value(
|
||||
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
|
||||
)
|
||||
|
||||
# all leaves available before cf leave expiry
|
||||
leave_details = get_leave_details(employee.name, add_days(cf_expiry, -1))
|
||||
self.assertEqual(leave_details["leave_allocation"][leave_type.name]["remaining_leaves"], 30.0)
|
||||
|
||||
# cf leaves expired
|
||||
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 1))
|
||||
expected_data = {
|
||||
"total_leaves": 30.0,
|
||||
"expired_leaves": 15.0,
|
||||
"leaves_taken": 0.0,
|
||||
"leaves_pending_approval": 0.0,
|
||||
"remaining_leaves": 15.0,
|
||||
}
|
||||
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
|
||||
|
||||
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
|
||||
def test_get_leave_allocation_records(self):
|
||||
"""Tests if total leaves allocated before and after carry forwarded leave expiry is same"""
|
||||
employee = get_employee()
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=90,
|
||||
).insert()
|
||||
|
||||
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
|
||||
cf_expiry = frappe.db.get_value(
|
||||
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
|
||||
)
|
||||
|
||||
# test total leaves allocated before cf leave expiry
|
||||
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, -1), leave_type.name)
|
||||
expected_data = {
|
||||
"from_date": getdate(leave_alloc.from_date),
|
||||
"to_date": getdate(leave_alloc.to_date),
|
||||
@@ -1013,6 +1046,11 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
}
|
||||
self.assertEqual(details.get(leave_type.name), expected_data)
|
||||
|
||||
# test leaves allocated after carry forwarded leaves expiry, should be same thoroughout allocation period
|
||||
# cf leaves should show up under expired or taken leaves later
|
||||
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, 1), leave_type.name)
|
||||
self.assertEqual(details.get(leave_type.name), expected_data)
|
||||
|
||||
|
||||
def create_carry_forwarded_allocation(employee, leave_type):
|
||||
# initial leave allocation
|
||||
|
||||
@@ -2,53 +2,60 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.query_reports["Employee Leave Balance"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_default("year_start_date")
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_default("year_start_date")
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_default("year_end_date")
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_default("year_end_date")
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
label: __("Company"),
|
||||
fieldname: "company",
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname": "department",
|
||||
"label": __("Department"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Department",
|
||||
fieldname: "department",
|
||||
label: __("Department"),
|
||||
fieldtype: "Link",
|
||||
options: "Department",
|
||||
},
|
||||
{
|
||||
"fieldname": "employee",
|
||||
"label": __("Employee"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee",
|
||||
fieldname: "employee",
|
||||
label: __("Employee"),
|
||||
fieldtype: "Link",
|
||||
options: "Employee",
|
||||
},
|
||||
{
|
||||
"fieldname": "employee_status",
|
||||
"label": __("Employee Status"),
|
||||
"fieldtype": "Select",
|
||||
"options": [
|
||||
fieldname: "employee_status",
|
||||
label: __("Employee Status"),
|
||||
fieldtype: "Select",
|
||||
options: [
|
||||
"",
|
||||
{ "value": "Active", "label": __("Active") },
|
||||
{ "value": "Inactive", "label": __("Inactive") },
|
||||
{ "value": "Suspended", "label": __("Suspended") },
|
||||
{ "value": "Left", "label": __("Left") },
|
||||
],
|
||||
"default": "Active",
|
||||
default: "Active",
|
||||
},
|
||||
{
|
||||
fieldname: "consolidate_leave_types",
|
||||
label: __("Consolidate Leave Types"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
depends_on: "eval: !doc.employee",
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_days, getdate
|
||||
from frappe.utils import add_days, cint, getdate
|
||||
|
||||
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_previous_allocation
|
||||
from erpnext.hr.doctype.leave_application.leave_application import (
|
||||
@@ -24,7 +24,7 @@ def execute(filters: Optional[Filters] = None) -> Tuple:
|
||||
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
charts = get_chart_data(data)
|
||||
charts = get_chart_data(data, filters)
|
||||
return columns, data, None, charts
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ def get_data(filters: Filters) -> List:
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
user = frappe.session.user
|
||||
department_approver_map = get_department_leave_approver_map(filters.get("department"))
|
||||
department_approver_map = get_department_leave_approver_map(filters.department)
|
||||
|
||||
active_employees = frappe.get_list(
|
||||
"Employee",
|
||||
@@ -97,22 +97,27 @@ def get_data(filters: Filters) -> List:
|
||||
fields=["name", "employee_name", "department", "user_id", "leave_approver"],
|
||||
)
|
||||
|
||||
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
|
||||
consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types
|
||||
row = None
|
||||
|
||||
data = []
|
||||
|
||||
for leave_type in leave_types:
|
||||
if len(active_employees) > 1:
|
||||
if consolidate_leave_types:
|
||||
data.append({"leave_type": leave_type})
|
||||
else:
|
||||
row = frappe._dict({"leave_type": leave_type})
|
||||
|
||||
for employee in active_employees:
|
||||
|
||||
leave_approvers = department_approver_map.get(employee.department_name, []).append(
|
||||
employee.leave_approver
|
||||
)
|
||||
|
||||
if len(active_employees) > 1:
|
||||
if consolidate_leave_types:
|
||||
row = frappe._dict()
|
||||
else:
|
||||
row = frappe._dict({"leave_type": leave_type})
|
||||
|
||||
row.employee = employee.name
|
||||
row.employee_name = employee.employee_name
|
||||
@@ -166,17 +171,17 @@ def get_opening_balance(
|
||||
def get_conditions(filters: Filters) -> Dict:
|
||||
conditions = {}
|
||||
|
||||
if filters.get("employee"):
|
||||
conditions["name"] = filters.get("employee")
|
||||
if filters.employee:
|
||||
conditions["name"] = filters.employee
|
||||
|
||||
if filters.get("company"):
|
||||
conditions["company"] = filters.get("company")
|
||||
if filters.company:
|
||||
conditions["company"] = filters.company
|
||||
|
||||
if filters.get("department"):
|
||||
conditions["department"] = filters.get("department")
|
||||
if filters.department:
|
||||
conditions["department"] = filters.department
|
||||
|
||||
if filters.get("employee_status"):
|
||||
conditions["status"] = filters.get("employee_status")
|
||||
if filters.employee_status:
|
||||
conditions["status"] = filters.employee_status
|
||||
|
||||
return conditions
|
||||
|
||||
@@ -268,12 +273,15 @@ def get_leave_ledger_entries(
|
||||
return records
|
||||
|
||||
|
||||
def get_chart_data(data: List) -> Dict:
|
||||
def get_chart_data(data: List, filters: Filters) -> Dict:
|
||||
labels = []
|
||||
datasets = []
|
||||
employee_data = data
|
||||
|
||||
if data and data[0].get("employee_name"):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
if data and filters.employee:
|
||||
get_dataset_for_chart(employee_data, datasets, labels)
|
||||
|
||||
chart = {
|
||||
|
||||
@@ -64,8 +64,6 @@
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "prevdoc_detail_docname.sales_person",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "service_person",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@@ -110,13 +108,15 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-27 17:47:21.474282",
|
||||
"modified": "2023-02-27 11:09:33.114458",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Maintenance",
|
||||
"name": "Maintenance Visit Purpose",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -536,7 +536,34 @@ class JobCard(Document):
|
||||
)
|
||||
|
||||
def set_transferred_qty_in_job_card_item(self, ste_doc):
|
||||
from frappe.query_builder.functions import Sum
|
||||
def _get_job_card_items_transferred_qty(ste_doc):
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
job_card_items_transferred_qty = {}
|
||||
job_card_items = [
|
||||
x.get("job_card_item") for x in ste_doc.get("items") if x.get("job_card_item")
|
||||
]
|
||||
|
||||
if job_card_items:
|
||||
se = frappe.qb.DocType("Stock Entry")
|
||||
sed = frappe.qb.DocType("Stock Entry Detail")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(sed)
|
||||
.join(se)
|
||||
.on(sed.parent == se.name)
|
||||
.select(sed.job_card_item, Sum(sed.qty))
|
||||
.where(
|
||||
(sed.job_card_item.isin(job_card_items))
|
||||
& (se.docstatus == 1)
|
||||
& (se.purpose == "Material Transfer for Manufacture")
|
||||
)
|
||||
.groupby(sed.job_card_item)
|
||||
)
|
||||
|
||||
job_card_items_transferred_qty = frappe._dict(query.run(as_list=True))
|
||||
|
||||
return job_card_items_transferred_qty
|
||||
|
||||
def _validate_over_transfer(row, transferred_qty):
|
||||
"Block over transfer of items if not allowed in settings."
|
||||
@@ -553,29 +580,23 @@ class JobCard(Document):
|
||||
exc=JobCardOverTransferError,
|
||||
)
|
||||
|
||||
for row in ste_doc.items:
|
||||
if not row.job_card_item:
|
||||
continue
|
||||
|
||||
sed = frappe.qb.DocType("Stock Entry Detail")
|
||||
se = frappe.qb.DocType("Stock Entry")
|
||||
transferred_qty = (
|
||||
frappe.qb.from_(sed)
|
||||
.join(se)
|
||||
.on(sed.parent == se.name)
|
||||
.select(Sum(sed.qty))
|
||||
.where(
|
||||
(sed.job_card_item == row.job_card_item)
|
||||
& (se.docstatus == 1)
|
||||
& (se.purpose == "Material Transfer for Manufacture")
|
||||
)
|
||||
).run()[0][0]
|
||||
job_card_items_transferred_qty = _get_job_card_items_transferred_qty(ste_doc)
|
||||
|
||||
if job_card_items_transferred_qty:
|
||||
allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
|
||||
if not allow_excess:
|
||||
_validate_over_transfer(row, transferred_qty)
|
||||
|
||||
frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
|
||||
for row in ste_doc.items:
|
||||
if not row.job_card_item:
|
||||
continue
|
||||
|
||||
transferred_qty = flt(job_card_items_transferred_qty.get(row.job_card_item))
|
||||
|
||||
if not allow_excess:
|
||||
_validate_over_transfer(row, transferred_qty)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)
|
||||
)
|
||||
|
||||
def set_transferred_qty(self, update_status=False):
|
||||
"Set total FG Qty in Job Card for which RM was transferred."
|
||||
|
||||
@@ -25,8 +25,9 @@ frappe.query_reports["BOM Stock Report"] = {
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (column.id == "item") {
|
||||
if (data["enough_parts_to_build"] > 0) {
|
||||
if (data["in_stock_qty"] >= data["required_qty"]) {
|
||||
value = `<a style='color:green' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
|
||||
} else {
|
||||
value = `<a style='color:red' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
|
||||
|
||||
@@ -1,16 +1,61 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.india.setup import make_custom_fields
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.get_all("Company", filters={"country": "India"}):
|
||||
frappe.reload_doc("accounts", "doctype", "POS Invoice")
|
||||
frappe.reload_doc("accounts", "doctype", "POS Invoice Item")
|
||||
|
||||
make_custom_fields()
|
||||
custom_fields = get_non_profit_custom_fields()
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
|
||||
if not frappe.db.exists("Party Type", "Donor"):
|
||||
frappe.get_doc(
|
||||
{"doctype": "Party Type", "party_type": "Donor", "account_type": "Receivable"}
|
||||
).insert(ignore_permissions=True)
|
||||
).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||
|
||||
|
||||
def get_non_profit_custom_fields():
|
||||
return {
|
||||
"Company": [
|
||||
{
|
||||
"fieldname": "non_profit_section",
|
||||
"label": "Non Profit Settings",
|
||||
"fieldtype": "Section Break",
|
||||
"insert_after": "asset_received_but_not_billed",
|
||||
"collapsible": 1,
|
||||
},
|
||||
{
|
||||
"fieldname": "company_80g_number",
|
||||
"label": "80G Number",
|
||||
"fieldtype": "Data",
|
||||
"insert_after": "non_profit_section",
|
||||
},
|
||||
{
|
||||
"fieldname": "with_effect_from",
|
||||
"label": "80G With Effect From",
|
||||
"fieldtype": "Date",
|
||||
"insert_after": "company_80g_number",
|
||||
},
|
||||
{
|
||||
"fieldname": "pan_details",
|
||||
"label": "PAN Number",
|
||||
"fieldtype": "Data",
|
||||
"insert_after": "with_effect_from",
|
||||
},
|
||||
],
|
||||
"Member": [
|
||||
{
|
||||
"fieldname": "pan_number",
|
||||
"label": "PAN Details",
|
||||
"fieldtype": "Data",
|
||||
"insert_after": "email_id",
|
||||
},
|
||||
],
|
||||
"Donor": [
|
||||
{
|
||||
"fieldname": "pan_number",
|
||||
"label": "PAN Details",
|
||||
"fieldtype": "Data",
|
||||
"insert_after": "email",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import datetime
|
||||
import math
|
||||
|
||||
@@ -316,6 +315,8 @@ class SalarySlip(TransactionBase):
|
||||
)
|
||||
|
||||
working_days = date_diff(self.end_date, self.start_date) + 1
|
||||
working_days_list = [add_days(self.start_date, i) for i in range(working_days)]
|
||||
|
||||
if for_preview:
|
||||
self.total_working_days = working_days
|
||||
self.payment_days = working_days
|
||||
@@ -325,6 +326,8 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
if not cint(include_holidays_in_total_working_days):
|
||||
working_days -= len(holidays)
|
||||
working_days_list = [cstr(day) for day in working_days_list if cstr(day) not in holidays]
|
||||
|
||||
if working_days < 0:
|
||||
frappe.throw(_("There are more holidays than working days this month."))
|
||||
|
||||
@@ -335,7 +338,7 @@ class SalarySlip(TransactionBase):
|
||||
actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(holidays)
|
||||
self.absent_days = absent
|
||||
else:
|
||||
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days)
|
||||
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days_list)
|
||||
|
||||
if not lwp:
|
||||
lwp = actual_lwp
|
||||
@@ -458,16 +461,15 @@ class SalarySlip(TransactionBase):
|
||||
def get_holidays_for_employee(self, start_date, end_date):
|
||||
return get_holiday_dates_for_employee(self.employee, start_date, end_date)
|
||||
|
||||
def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days):
|
||||
def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days_list):
|
||||
lwp = 0
|
||||
holidays = "','".join(holidays)
|
||||
|
||||
daily_wages_fraction_for_half_day = (
|
||||
flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
|
||||
)
|
||||
|
||||
for d in range(working_days):
|
||||
date = add_days(cstr(getdate(self.start_date)), d)
|
||||
leave = get_lwp_or_ppl_for_date(date, self.employee, holidays)
|
||||
for d in working_days_list:
|
||||
leave = get_lwp_or_ppl_for_date(d, self.employee, holidays)
|
||||
|
||||
if leave:
|
||||
equivalent_lwp_count = 0
|
||||
|
||||
@@ -119,7 +119,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
frappe.model.round_floats_in(item);
|
||||
item.net_rate = item.rate;
|
||||
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 {
|
||||
// allow for '0' qty on Credit/Debit notes
|
||||
let qty = item.qty || -1
|
||||
item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
|
||||
}
|
||||
|
||||
item.item_tax_amount = 0.0;
|
||||
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
|
||||
|
||||
|
||||
@@ -1975,11 +1975,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
|
||||
get_advances: function() {
|
||||
if(!this.frm.is_return) {
|
||||
var me = this;
|
||||
return this.frm.call({
|
||||
method: "set_advances",
|
||||
doc: this.frm.doc,
|
||||
callback: function(r, rt) {
|
||||
refresh_field("advances");
|
||||
me.frm.dirty();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,11 +84,15 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
|
||||
}
|
||||
|
||||
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Order"),
|
||||
this.frm.cscript["Make Sales Order"],
|
||||
__("Create")
|
||||
);
|
||||
if (frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation
|
||||
|| (!doc.valid_till)
|
||||
|| frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Order"),
|
||||
this.frm.cscript["Make Sales Order"],
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
if(doc.status!=="Ordered") {
|
||||
this.frm.add_custom_button(__('Set as Lost'), () => {
|
||||
|
||||
@@ -192,6 +192,17 @@ def get_list_context(context=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -126,11 +126,21 @@ class TestQuotation(FrappeTestCase):
|
||||
def test_so_from_expired_quotation(self):
|
||||
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.valid_till = add_days(nowdate(), -1)
|
||||
quotation.insert()
|
||||
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)
|
||||
|
||||
def test_shopping_cart_without_website_item(self):
|
||||
|
||||
@@ -280,9 +280,12 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
|
||||
make_work_order() {
|
||||
var me = this;
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: 'get_work_order_items',
|
||||
me.frm.call({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
|
||||
args: {
|
||||
sales_order: this.frm.docname,
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if(!r.message) {
|
||||
frappe.msgprint({
|
||||
@@ -292,14 +295,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
});
|
||||
return;
|
||||
}
|
||||
else if(!r.message) {
|
||||
frappe.msgprint({
|
||||
title: __('Work Order not created'),
|
||||
message: __('Work Order already created for all items with BOM'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
else {
|
||||
const fields = [{
|
||||
label: 'Items',
|
||||
fieldtype: 'Table',
|
||||
@@ -400,9 +396,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
make_raw_material_request: function() {
|
||||
var me = this;
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: 'get_work_order_items',
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
|
||||
args: {
|
||||
sales_order: this.frm.docname,
|
||||
for_raw_material_request: 1
|
||||
},
|
||||
callback: function(r) {
|
||||
@@ -421,6 +417,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
},
|
||||
|
||||
make_raw_material_request_dialog: function(r) {
|
||||
var me = this;
|
||||
var fields = [
|
||||
{fieldtype:'Check', fieldname:'include_exploded_items',
|
||||
label: __('Include Exploded Items')},
|
||||
|
||||
@@ -6,11 +6,12 @@ import json
|
||||
|
||||
import frappe
|
||||
import frappe.utils
|
||||
from frappe import _
|
||||
from frappe import _, qb
|
||||
from frappe.contacts.doctype.address.address import get_company_address
|
||||
from frappe.desk.notifications import clear_doctype_notifications
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html
|
||||
from six import string_types
|
||||
|
||||
@@ -481,51 +482,6 @@ class SalesOrder(SellingController):
|
||||
self.indicator_color = "green"
|
||||
self.indicator_title = _("Paid")
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_work_order_items(self, for_raw_material_request=0):
|
||||
"""Returns items with BOM that already do not have a linked work order"""
|
||||
items = []
|
||||
item_codes = [i.item_code for i in self.items]
|
||||
product_bundle_parents = [
|
||||
pb.new_item_code
|
||||
for pb in frappe.get_all(
|
||||
"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
|
||||
)
|
||||
]
|
||||
|
||||
for table in [self.items, self.packed_items]:
|
||||
for i in table:
|
||||
bom = get_default_bom(i.item_code)
|
||||
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
|
||||
|
||||
if not for_raw_material_request:
|
||||
total_work_order_qty = flt(
|
||||
frappe.db.sql(
|
||||
"""select sum(qty) from `tabWork Order`
|
||||
where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2""",
|
||||
(i.item_code, self.name, i.name),
|
||||
)[0][0]
|
||||
)
|
||||
pending_qty = stock_qty - total_work_order_qty
|
||||
else:
|
||||
pending_qty = stock_qty
|
||||
|
||||
if pending_qty and i.item_code not in product_bundle_parents:
|
||||
items.append(
|
||||
dict(
|
||||
name=i.name,
|
||||
item_code=i.item_code,
|
||||
description=i.description,
|
||||
bom=bom or "",
|
||||
warehouse=i.warehouse,
|
||||
pending_qty=pending_qty,
|
||||
required_qty=pending_qty if for_raw_material_request else 0,
|
||||
sales_order_item=i.name,
|
||||
)
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
|
||||
delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
|
||||
@@ -1399,3 +1355,57 @@ def update_produced_qty_in_so_item(sales_order, sales_order_item):
|
||||
return
|
||||
|
||||
frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_work_order_items(sales_order, for_raw_material_request=0):
|
||||
"""Returns items with BOM that already do not have a linked work order"""
|
||||
if sales_order:
|
||||
so = frappe.get_doc("Sales Order", sales_order)
|
||||
|
||||
wo = qb.DocType("Work Order")
|
||||
|
||||
items = []
|
||||
item_codes = [i.item_code for i in so.items]
|
||||
product_bundle_parents = [
|
||||
pb.new_item_code
|
||||
for pb in frappe.get_all(
|
||||
"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
|
||||
)
|
||||
]
|
||||
|
||||
for table in [so.items, so.packed_items]:
|
||||
for i in table:
|
||||
bom = get_default_bom(i.item_code)
|
||||
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
|
||||
|
||||
if not for_raw_material_request:
|
||||
total_work_order_qty = flt(
|
||||
qb.from_(wo)
|
||||
.select(Sum(wo.qty))
|
||||
.where(
|
||||
(wo.production_item == i.item_code)
|
||||
& (wo.sales_order == so.name) * (wo.sales_order_item == i.name)
|
||||
& (wo.docstatus.lte(2))
|
||||
)
|
||||
.run()[0][0]
|
||||
)
|
||||
pending_qty = stock_qty - total_work_order_qty
|
||||
else:
|
||||
pending_qty = stock_qty
|
||||
|
||||
if pending_qty and i.item_code not in product_bundle_parents:
|
||||
items.append(
|
||||
dict(
|
||||
name=i.name,
|
||||
item_code=i.item_code,
|
||||
description=i.description,
|
||||
bom=bom or "",
|
||||
warehouse=i.warehouse,
|
||||
pending_qty=pending_qty,
|
||||
required_qty=pending_qty if for_raw_material_request else 0,
|
||||
sales_order_item=i.name,
|
||||
)
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
@@ -1211,6 +1211,8 @@ class TestSalesOrder(FrappeTestCase):
|
||||
self.assertTrue(si.get("payment_schedule"))
|
||||
|
||||
def test_make_work_order(self):
|
||||
from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
|
||||
|
||||
# Make a new Sales Order
|
||||
so = make_sales_order(
|
||||
**{
|
||||
@@ -1224,7 +1226,7 @@ class TestSalesOrder(FrappeTestCase):
|
||||
# Raise Work Orders
|
||||
po_items = []
|
||||
so_item_name = {}
|
||||
for item in so.get_work_order_items():
|
||||
for item in get_work_order_items(so.name):
|
||||
po_items.append(
|
||||
{
|
||||
"warehouse": item.get("warehouse"),
|
||||
@@ -1415,6 +1417,7 @@ class TestSalesOrder(FrappeTestCase):
|
||||
|
||||
from erpnext.controllers.item_variant import create_variant
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
|
||||
|
||||
make_item( # template item
|
||||
"Test-WO-Tshirt",
|
||||
@@ -1454,7 +1457,7 @@ class TestSalesOrder(FrappeTestCase):
|
||||
]
|
||||
}
|
||||
)
|
||||
wo_items = so.get_work_order_items()
|
||||
wo_items = get_work_order_items(so.name)
|
||||
|
||||
self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
|
||||
self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
|
||||
@@ -1464,6 +1467,8 @@ class TestSalesOrder(FrappeTestCase):
|
||||
self.assertEqual(wo_items[1].get("bom"), template_bom.name)
|
||||
|
||||
def test_request_for_raw_materials(self):
|
||||
from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
|
||||
|
||||
item = make_item(
|
||||
"_Test Finished Item",
|
||||
{
|
||||
@@ -1496,7 +1501,7 @@ class TestSalesOrder(FrappeTestCase):
|
||||
so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]})
|
||||
so.submit()
|
||||
mr_dict = frappe._dict()
|
||||
items = so.get_work_order_items(1)
|
||||
items = get_work_order_items(so.name, 1)
|
||||
mr_dict["items"] = items
|
||||
mr_dict["include_exploded_items"] = 0
|
||||
mr_dict["ignore_existing_ordered_qty"] = 1
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"sales_update_frequency",
|
||||
"allow_multiple_items",
|
||||
"allow_against_multiple_purchase_orders",
|
||||
"hide_tax_id"
|
||||
"hide_tax_id",
|
||||
"allow_sales_order_creation_for_expired_quotation"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -199,6 +200,12 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Contract Naming By",
|
||||
"options": "Party Name\nNaming Series"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_sales_order_creation_for_expired_quotation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Sales Order Creation For Expired Quotation"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -206,7 +213,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-28 12:18:06.768403",
|
||||
"modified": "2023-02-04 12:37:53.380857",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
@@ -522,7 +522,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
|
||||
const from_selector = field === 'qty' && value === "+1";
|
||||
if (from_selector)
|
||||
value = flt(item_row.qty) + flt(value);
|
||||
value = flt(item_row.stock_qty) + flt(value);
|
||||
|
||||
if (item_row_exists) {
|
||||
if (field === 'qty')
|
||||
|
||||
@@ -41,8 +41,20 @@ def get_columns(filters):
|
||||
{"label": _("Description"), "fieldtype": "Data", "fieldname": "description", "width": 150},
|
||||
{"label": _("Quantity"), "fieldtype": "Float", "fieldname": "quantity", "width": 150},
|
||||
{"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"),
|
||||
"fieldtype": "Link",
|
||||
@@ -93,8 +105,9 @@ def get_columns(filters):
|
||||
},
|
||||
{
|
||||
"label": _("Billed Amount"),
|
||||
"fieldtype": "currency",
|
||||
"fieldtype": "Currency",
|
||||
"fieldname": "billed_amount",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
@@ -104,6 +117,13 @@ def get_columns(filters):
|
||||
"options": "Company",
|
||||
"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")),
|
||||
"company": record.get("company"),
|
||||
}
|
||||
row["currency"] = frappe.get_cached_value("Company", row["company"], "default_currency")
|
||||
data.append(row)
|
||||
|
||||
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():
|
||||
details = frappe.get_all("Customer", fields=["name", "customer_name", "customer_group"])
|
||||
customer_details = {}
|
||||
@@ -187,29 +188,50 @@ def get_item_details():
|
||||
|
||||
|
||||
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(
|
||||
"""
|
||||
SELECT
|
||||
so_item.item_code, so_item.description, so_item.qty,
|
||||
so_item.uom, so_item.base_rate, so_item.base_amount,
|
||||
so.name, so.transaction_date, so.customer,so.territory,
|
||||
so.project, so_item.delivered_qty,
|
||||
so_item.billed_amt, so.company
|
||||
FROM
|
||||
`tabSales Order` so, `tabSales Order Item` so_item
|
||||
WHERE
|
||||
so.name = so_item.parent
|
||||
AND so.company in ({0})
|
||||
AND so.docstatus = 1 {1}
|
||||
""".format(
|
||||
",".join(["%s"] * len(company_list)), conditions
|
||||
),
|
||||
tuple(company_list),
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(db_so)
|
||||
.inner_join(db_so_item)
|
||||
.on(db_so_item.parent == db_so.name)
|
||||
.select(
|
||||
db_so.name,
|
||||
db_so.customer,
|
||||
db_so.transaction_date,
|
||||
db_so.territory,
|
||||
db_so.project,
|
||||
db_so.company,
|
||||
db_so_item.item_code,
|
||||
db_so_item.description,
|
||||
db_so_item.qty,
|
||||
db_so_item.uom,
|
||||
db_so_item.base_rate,
|
||||
db_so_item.base_amount,
|
||||
db_so_item.delivered_qty,
|
||||
(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 == 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_code == 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):
|
||||
item_wise_sales_map = {}
|
||||
|
||||
@@ -417,9 +417,14 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
|
||||
} else {
|
||||
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
|
||||
if (r.message.batch_no != null) {
|
||||
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message.batch_no);
|
||||
} else if (r.message.msg_print) {
|
||||
frappe.show_alert({
|
||||
message: r.message.msg_print,
|
||||
indicator:'orange'
|
||||
}, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,6 +26,12 @@ def boot_session(bootinfo):
|
||||
frappe.db.get_single_value("Selling Settings", "default_valid_till")
|
||||
)
|
||||
|
||||
bootinfo.sysdefaults.allow_sales_order_creation_for_expired_quotation = cint(
|
||||
frappe.db.get_single_value(
|
||||
"Selling Settings", "allow_sales_order_creation_for_expired_quotation"
|
||||
)
|
||||
)
|
||||
|
||||
# if no company, show a dialog box to create a new company
|
||||
bootinfo.customer_count = frappe.db.sql("""SELECT count(*) FROM `tabCustomer`""")[0][0]
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
let warehouse = unescape(element.attr('data-warehouse'));
|
||||
let actual_qty = unescape(element.attr('data-actual_qty'));
|
||||
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
|
||||
let entry_type = action === "Move" ? "Material Transfer" : null;
|
||||
let entry_type = action === "Move" ? "Material Transfer" : "Material Receipt";
|
||||
|
||||
if (disable_quick_entry) {
|
||||
open_stock_entry(item, warehouse, entry_type);
|
||||
@@ -63,11 +63,19 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
function open_stock_entry(item, warehouse, entry_type) {
|
||||
frappe.model.with_doctype('Stock Entry', function () {
|
||||
var doc = frappe.model.get_new_doc('Stock Entry');
|
||||
if (entry_type) doc.stock_entry_type = entry_type;
|
||||
if (entry_type) {
|
||||
doc.stock_entry_type = entry_type;
|
||||
}
|
||||
|
||||
var row = frappe.model.add_child(doc, 'items');
|
||||
row.item_code = item;
|
||||
row.s_warehouse = warehouse;
|
||||
|
||||
if (entry_type === "Material Transfer") {
|
||||
row.s_warehouse = warehouse;
|
||||
}
|
||||
else {
|
||||
row.t_warehouse = warehouse;
|
||||
}
|
||||
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
});
|
||||
|
||||
@@ -260,7 +260,9 @@ def set_batch_nos(doc, warehouse_field, throw=False, child_table="items"):
|
||||
warehouse = d.get(warehouse_field, None)
|
||||
if warehouse and qty > 0 and frappe.db.get_value("Item", d.item_code, "has_batch_no"):
|
||||
if not d.batch_no:
|
||||
d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw, d.serial_no)
|
||||
d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw, d.serial_no).get(
|
||||
"batch_no", None
|
||||
)
|
||||
else:
|
||||
batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse)
|
||||
if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")):
|
||||
@@ -282,6 +284,7 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
|
||||
"""
|
||||
|
||||
batch_no = None
|
||||
message = None
|
||||
batches = get_batches(item_code, warehouse, qty, throw, serial_no)
|
||||
|
||||
for batch in batches:
|
||||
@@ -290,15 +293,18 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
|
||||
break
|
||||
|
||||
if not batch_no:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement"
|
||||
).format(frappe.bold(item_code))
|
||||
)
|
||||
message = _(
|
||||
"Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement"
|
||||
).format(frappe.bold(item_code))
|
||||
if throw:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement"
|
||||
).format(frappe.bold(item_code))
|
||||
)
|
||||
raise UnableToSelectBatchError
|
||||
|
||||
return batch_no
|
||||
return {"batch_no": batch_no, "msg_print": message}
|
||||
|
||||
|
||||
def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
|
||||
|
||||
@@ -99,7 +99,8 @@ class TestBatch(FrappeTestCase):
|
||||
|
||||
# shipped from FEFO batch
|
||||
self.assertEqual(
|
||||
delivery_note.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
|
||||
delivery_note.items[0].batch_no,
|
||||
get_batch_no(item_code, receipt.items[0].warehouse, batch_qty).get("batch_no", None),
|
||||
)
|
||||
|
||||
def test_delivery_note_fail(self):
|
||||
@@ -145,7 +146,8 @@ class TestBatch(FrappeTestCase):
|
||||
|
||||
# assert same batch is selected
|
||||
self.assertEqual(
|
||||
stock_entry.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
|
||||
stock_entry.items[0].batch_no,
|
||||
get_batch_no(item_code, receipt.items[0].warehouse, batch_qty).get("batch_no", None),
|
||||
)
|
||||
|
||||
def test_batch_split(self):
|
||||
|
||||
@@ -150,12 +150,17 @@ def update_qty(bin_name, args):
|
||||
last_sle_qty = (
|
||||
frappe.qb.from_(sle)
|
||||
.select(sle.qty_after_transaction)
|
||||
.where((sle.item_code == args.get("item_code")) & (sle.warehouse == args.get("warehouse")))
|
||||
.where(
|
||||
(sle.item_code == args.get("item_code"))
|
||||
& (sle.warehouse == args.get("warehouse"))
|
||||
& (sle.is_cancelled == 0)
|
||||
)
|
||||
.orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=Order.desc)
|
||||
.orderby(sle.creation, order=Order.desc)
|
||||
.run()
|
||||
)
|
||||
|
||||
actual_qty = 0.0
|
||||
if last_sle_qty:
|
||||
actual_qty = last_sle_qty[0][0]
|
||||
|
||||
|
||||
@@ -97,12 +97,12 @@ frappe.ui.form.on("Delivery Note", {
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) {
|
||||
let internal = me.frm.doc.is_internal_customer;
|
||||
let internal = frm.doc.is_internal_customer;
|
||||
if (internal) {
|
||||
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Receipt" :
|
||||
let button_label = (frm.doc.company === frm.doc.represents_company) ? "Internal Purchase Receipt" :
|
||||
"Inter Company Purchase Receipt";
|
||||
|
||||
me.frm.add_custom_button(button_label, function() {
|
||||
frm.add_custom_button(__(button_label), function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt',
|
||||
frm: frm,
|
||||
|
||||
@@ -33,6 +33,9 @@ frappe.ui.form.on("Item", {
|
||||
'Material Request': () => {
|
||||
open_form(frm, "Material Request", "Material Request Item", "items");
|
||||
},
|
||||
'Stock Entry': () => {
|
||||
open_form(frm, "Stock Entry", "Stock Entry Detail", "items");
|
||||
},
|
||||
};
|
||||
|
||||
},
|
||||
@@ -848,6 +851,9 @@ function open_form(frm, doctype, child_doctype, parentfield) {
|
||||
new_child_doc.item_name = frm.doc.item_name;
|
||||
new_child_doc.uom = frm.doc.stock_uom;
|
||||
new_child_doc.description = frm.doc.description;
|
||||
if (!new_child_doc.qty) {
|
||||
new_child_doc.qty = 1.0;
|
||||
}
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc),
|
||||
|
||||
@@ -955,7 +955,8 @@
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-28 04:52:10.272256",
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2022-09-13 04:08:17.431731",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -2,7 +2,18 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Item Price", {
|
||||
onload: function (frm) {
|
||||
setup(frm) {
|
||||
frm.set_query("item_code", function() {
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0,
|
||||
"has_variants": 0
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onload(frm) {
|
||||
// Fetch price list details
|
||||
frm.add_fetch("price_list", "buying", "buying");
|
||||
frm.add_fetch("price_list", "selling", "selling");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
|
||||
@@ -19,6 +19,7 @@ class ItemPrice(Document):
|
||||
self.update_price_list_details()
|
||||
self.update_item_details()
|
||||
self.check_duplicates()
|
||||
self.validate_item_template()
|
||||
|
||||
def validate_item(self):
|
||||
if not frappe.db.exists("Item", self.item_code):
|
||||
@@ -47,6 +48,12 @@ class ItemPrice(Document):
|
||||
"Item", self.item_code, ["item_name", "description"]
|
||||
)
|
||||
|
||||
def validate_item_template(self):
|
||||
if frappe.get_cached_value("Item", self.item_code, "has_variants"):
|
||||
msg = f"Item Price cannot be created for the template item {bold(self.item_code)}"
|
||||
|
||||
frappe.throw(_(msg))
|
||||
|
||||
def check_duplicates(self):
|
||||
conditions = (
|
||||
"""where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
|
||||
|
||||
@@ -16,6 +16,28 @@ class TestItemPrice(FrappeTestCase):
|
||||
frappe.db.sql("delete from `tabItem Price`")
|
||||
make_test_records_for_doctype("Item Price", force=True)
|
||||
|
||||
def test_template_item_price(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
item = make_item(
|
||||
"Test Template Item 1",
|
||||
{
|
||||
"has_variants": 1,
|
||||
"variant_based_on": "Manufacturer",
|
||||
},
|
||||
)
|
||||
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"price_list": "_Test Price List",
|
||||
"item_code": item.name,
|
||||
"price_list_rate": 100,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
|
||||
def test_duplicate_item(self):
|
||||
doc = frappe.copy_doc(test_records[0])
|
||||
self.assertRaises(ItemPriceDuplicateItem, doc.save)
|
||||
|
||||
@@ -38,5 +38,19 @@
|
||||
"price_list_rate": 1000,
|
||||
"valid_from": "2017-04-10",
|
||||
"valid_upto": "2017-04-17"
|
||||
},
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"item_code": "_Test Item",
|
||||
"price_list": "_Test Buying Price List",
|
||||
"price_list_rate": 100,
|
||||
"supplier": "_Test Supplier"
|
||||
},
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"item_code": "_Test Item",
|
||||
"price_list": "_Test Selling Price List",
|
||||
"price_list_rate": 200,
|
||||
"customer": "_Test Customer"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -55,7 +55,6 @@ class LandedCostVoucher(Document):
|
||||
self.get_items_from_purchase_receipts()
|
||||
|
||||
self.set_applicable_charges_on_item()
|
||||
self.validate_applicable_charges_for_item()
|
||||
|
||||
def check_mandatory(self):
|
||||
if not self.get("purchase_receipts"):
|
||||
@@ -115,6 +114,13 @@ class LandedCostVoucher(Document):
|
||||
total_item_cost += item.get(based_on_field)
|
||||
|
||||
for item in self.get("items"):
|
||||
if not total_item_cost and not item.get(based_on_field):
|
||||
frappe.throw(
|
||||
_(
|
||||
"It's not possible to distribute charges equally when total amount is zero, please set 'Distribute Charges Based On' as 'Quantity'"
|
||||
)
|
||||
)
|
||||
|
||||
item.applicable_charges = flt(
|
||||
flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
|
||||
item.precision("applicable_charges"),
|
||||
@@ -162,6 +168,7 @@ class LandedCostVoucher(Document):
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_applicable_charges_for_item()
|
||||
self.update_landed_cost()
|
||||
|
||||
def on_cancel(self):
|
||||
|
||||
@@ -175,6 +175,59 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
)
|
||||
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
|
||||
|
||||
def test_landed_cost_voucher_for_zero_purchase_rate(self):
|
||||
"Test impact of LCV on future stock balances."
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
item = make_item("LCV Stock Item", {"is_stock_item": 1})
|
||||
warehouse = "Stores - _TC"
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item.name,
|
||||
warehouse=warehouse,
|
||||
qty=10,
|
||||
rate=0,
|
||||
posting_date=add_days(frappe.utils.nowdate(), -2),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
|
||||
"stock_value_difference",
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
lcv = make_landed_cost_voucher(
|
||||
company=pr.company,
|
||||
receipt_document_type="Purchase Receipt",
|
||||
receipt_document=pr.name,
|
||||
charges=100,
|
||||
distribute_charges_based_on="Distribute Manually",
|
||||
do_not_save=True,
|
||||
)
|
||||
|
||||
lcv.get_items_from_purchase_receipts()
|
||||
lcv.items[0].applicable_charges = 100
|
||||
lcv.save()
|
||||
lcv.submit()
|
||||
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
|
||||
"stock_value_difference",
|
||||
),
|
||||
100,
|
||||
)
|
||||
|
||||
def test_landed_cost_voucher_against_purchase_invoice(self):
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
@@ -516,7 +569,7 @@ def make_landed_cost_voucher(**args):
|
||||
|
||||
lcv = frappe.new_doc("Landed Cost Voucher")
|
||||
lcv.company = args.company or "_Test Company"
|
||||
lcv.distribute_charges_based_on = "Amount"
|
||||
lcv.distribute_charges_based_on = args.distribute_charges_based_on or "Amount"
|
||||
|
||||
lcv.set(
|
||||
"purchase_receipts",
|
||||
|
||||
@@ -366,10 +366,11 @@ frappe.ui.form.on('Material Request', {
|
||||
|
||||
frappe.ui.form.on("Material Request Item", {
|
||||
qty: function (frm, doctype, name) {
|
||||
var d = locals[doctype][name];
|
||||
if (flt(d.qty) < flt(d.min_order_qty)) {
|
||||
const item = locals[doctype][name];
|
||||
if (flt(item.qty) < flt(item.min_order_qty)) {
|
||||
frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
|
||||
}
|
||||
frm.events.get_item_data(frm, item, false);
|
||||
},
|
||||
|
||||
from_warehouse: function(frm, doctype, name) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import json
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
|
||||
from six import string_types
|
||||
|
||||
@@ -185,6 +186,34 @@ class MaterialRequest(BuyingController):
|
||||
self.update_requested_qty()
|
||||
self.update_requested_qty_in_production_plan()
|
||||
|
||||
def get_mr_items_ordered_qty(self, mr_items):
|
||||
mr_items_ordered_qty = {}
|
||||
mr_items = [d.name for d in self.get("items") if d.name in mr_items]
|
||||
|
||||
doctype = qty_field = None
|
||||
if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
|
||||
doctype = frappe.qb.DocType("Stock Entry Detail")
|
||||
qty_field = doctype.transfer_qty
|
||||
elif self.material_request_type == "Manufacture":
|
||||
doctype = frappe.qb.DocType("Work Order")
|
||||
qty_field = doctype.qty
|
||||
|
||||
if doctype and qty_field:
|
||||
query = (
|
||||
frappe.qb.from_(doctype)
|
||||
.select(doctype.material_request_item, Sum(qty_field))
|
||||
.where(
|
||||
(doctype.material_request == self.name)
|
||||
& (doctype.material_request_item.isin(mr_items))
|
||||
& (doctype.docstatus == 1)
|
||||
)
|
||||
.groupby(doctype.material_request_item)
|
||||
)
|
||||
|
||||
mr_items_ordered_qty = frappe._dict(query.run())
|
||||
|
||||
return mr_items_ordered_qty
|
||||
|
||||
def update_completed_qty(self, mr_items=None, update_modified=True):
|
||||
if self.material_request_type == "Purchase":
|
||||
return
|
||||
@@ -192,18 +221,13 @@ class MaterialRequest(BuyingController):
|
||||
if not mr_items:
|
||||
mr_items = [d.name for d in self.get("items")]
|
||||
|
||||
mr_items_ordered_qty = self.get_mr_items_ordered_qty(mr_items)
|
||||
mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.name in mr_items:
|
||||
if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
|
||||
d.ordered_qty = flt(
|
||||
frappe.db.sql(
|
||||
"""select sum(transfer_qty)
|
||||
from `tabStock Entry Detail` where material_request = %s
|
||||
and material_request_item = %s and docstatus = 1""",
|
||||
(self.name, d.name),
|
||||
)[0][0]
|
||||
)
|
||||
mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
|
||||
d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
|
||||
|
||||
if mr_qty_allowance:
|
||||
allowed_qty = d.qty + (d.qty * (mr_qty_allowance / 100))
|
||||
@@ -224,14 +248,7 @@ class MaterialRequest(BuyingController):
|
||||
)
|
||||
|
||||
elif self.material_request_type == "Manufacture":
|
||||
d.ordered_qty = flt(
|
||||
frappe.db.sql(
|
||||
"""select sum(qty)
|
||||
from `tabWork Order` where material_request = %s
|
||||
and material_request_item = %s and docstatus = 1""",
|
||||
(self.name, d.name),
|
||||
)[0][0]
|
||||
)
|
||||
d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
|
||||
|
||||
frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
|
||||
|
||||
@@ -594,6 +611,9 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.purpose = source.material_request_type
|
||||
target.from_warehouse = source.set_from_warehouse
|
||||
target.to_warehouse = source.set_warehouse
|
||||
|
||||
if source.job_card:
|
||||
target.purpose = "Material Transfer for Manufacture"
|
||||
|
||||
|
||||
@@ -31,5 +31,21 @@
|
||||
"enabled": 1,
|
||||
"price_list_name": "_Test Price List Rest of the World",
|
||||
"selling": 1
|
||||
},
|
||||
{
|
||||
"buying": 0,
|
||||
"currency": "USD",
|
||||
"doctype": "Price List",
|
||||
"enabled": 1,
|
||||
"price_list_name": "_Test Selling Price List",
|
||||
"selling": 1
|
||||
},
|
||||
{
|
||||
"buying": 1,
|
||||
"currency": "USD",
|
||||
"doctype": "Price List",
|
||||
"enabled": 1,
|
||||
"price_list_name": "_Test Buying Price List",
|
||||
"selling": 0
|
||||
}
|
||||
]
|
||||
|
||||
@@ -436,7 +436,7 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
divisional_loss = flt(
|
||||
valuation_amount_as_per_doc - stock_value_diff, d.precision("base_net_amount")
|
||||
valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount")
|
||||
)
|
||||
|
||||
if divisional_loss:
|
||||
@@ -1064,13 +1064,25 @@ def get_item_account_wise_additional_cost(purchase_document):
|
||||
account.expense_account, {"amount": 0.0, "base_amount": 0.0}
|
||||
)
|
||||
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
|
||||
"amount"
|
||||
] += (account.amount * item.get(based_on_field) / total_item_cost)
|
||||
if total_item_cost > 0:
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["amount"] += (
|
||||
account.amount * item.get(based_on_field) / total_item_cost
|
||||
)
|
||||
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
|
||||
"base_amount"
|
||||
] += (account.base_amount * item.get(based_on_field) / total_item_cost)
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["base_amount"] += (
|
||||
account.base_amount * item.get(based_on_field) / total_item_cost
|
||||
)
|
||||
else:
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["amount"] += item.applicable_charges
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["base_amount"] += item.applicable_charges
|
||||
|
||||
return item_account_wise_cost
|
||||
|
||||
|
||||
@@ -1262,7 +1262,9 @@ class StockEntry(StockController):
|
||||
and ret.get("has_batch_no")
|
||||
and not args.get("batch_no")
|
||||
):
|
||||
args.batch_no = get_batch_no(args["item_code"], args["s_warehouse"], args["qty"])
|
||||
args.batch_no = get_batch_no(args["item_code"], args["s_warehouse"], args["qty"]).get(
|
||||
"batch_no", None
|
||||
)
|
||||
|
||||
if (
|
||||
self.purpose == "Send to Subcontractor" and self.get("purchase_order") and args.get("item_code")
|
||||
@@ -2440,7 +2442,7 @@ def get_uom_details(item_code, uom, qty):
|
||||
|
||||
if not conversion_factor:
|
||||
frappe.msgprint(
|
||||
_("UOM coversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
|
||||
_("UOM conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
|
||||
)
|
||||
ret = {"uom": ""}
|
||||
else:
|
||||
|
||||
@@ -1571,6 +1571,48 @@ class TestStockEntry(FrappeTestCase):
|
||||
|
||||
self.assertRaises(BatchExpiredError, se.save)
|
||||
|
||||
def test_negative_stock_reco(self):
|
||||
from erpnext.controllers.stock_controller import BatchExpiredError
|
||||
from erpnext.stock.doctype.batch.test_batch import make_new_batch
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0)
|
||||
|
||||
item_code = "Test Negative Item - 001"
|
||||
item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
posting_date=add_days(today(), -3),
|
||||
posting_time="00:00:00",
|
||||
purpose="Material Receipt",
|
||||
qty=10,
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
do_not_save=True,
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
posting_date=today(),
|
||||
posting_time="00:00:00",
|
||||
purpose="Material Receipt",
|
||||
qty=8,
|
||||
from_warehouse="_Test Warehouse - _TC",
|
||||
do_not_save=True,
|
||||
)
|
||||
|
||||
sr_doc = create_stock_reconciliation(
|
||||
purpose="Stock Reconciliation",
|
||||
posting_date=add_days(today(), -3),
|
||||
posting_time="00:00:00",
|
||||
item_code=item_code,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
valuation_rate=10,
|
||||
qty=7,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, sr_doc.submit)
|
||||
|
||||
|
||||
def make_serialized_item(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -2,7 +2,22 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Stock Reposting Settings', {
|
||||
// refresh: function(frm) {
|
||||
refresh: function(frm) {
|
||||
frm.trigger('convert_to_item_based_reposting');
|
||||
},
|
||||
|
||||
// }
|
||||
convert_to_item_based_reposting: function(frm) {
|
||||
frm.add_custom_button(__('Convert to Item Based Reposting'), function() {
|
||||
frm.call({
|
||||
method: 'convert_to_item_wh_reposting',
|
||||
frezz: true,
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_to_date, get_datetime, get_time_str, time_diff_in_hours
|
||||
|
||||
@@ -24,3 +26,62 @@ class StockRepostingSettings(Document):
|
||||
|
||||
if diff < 10:
|
||||
self.end_time = get_time_str(add_to_date(self.start_time, hours=10, as_datetime=True))
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_to_item_wh_reposting(self):
|
||||
"""Convert Transaction reposting to Item Warehouse based reposting if Item Based Reposting has enabled."""
|
||||
|
||||
reposting_data = get_reposting_entries()
|
||||
|
||||
vouchers = [d.voucher_no for d in reposting_data]
|
||||
|
||||
item_warehouses = {}
|
||||
|
||||
for ledger in get_stock_ledgers(vouchers):
|
||||
key = (ledger.item_code, ledger.warehouse)
|
||||
if key not in item_warehouses:
|
||||
item_warehouses[key] = ledger.posting_date
|
||||
elif frappe.utils.getdate(item_warehouses.get(key)) > frappe.utils.getdate(ledger.posting_date):
|
||||
item_warehouses[key] = ledger.posting_date
|
||||
|
||||
for key, posting_date in item_warehouses.items():
|
||||
item_code, warehouse = key
|
||||
create_repost_item_valuation(item_code, warehouse, posting_date)
|
||||
|
||||
for row in reposting_data:
|
||||
frappe.db.set_value("Repost Item Valuation", row.name, "status", "Skipped")
|
||||
|
||||
self.db_set("item_based_reposting", 1)
|
||||
frappe.msgprint(_("Item Warehouse based reposting has been enabled."))
|
||||
|
||||
|
||||
def get_reposting_entries():
|
||||
return frappe.get_all(
|
||||
"Repost Item Valuation",
|
||||
fields=["voucher_no", "name"],
|
||||
filters={"status": ("in", ["Queued", "In Progress"]), "docstatus": 1, "based_on": "Transaction"},
|
||||
)
|
||||
|
||||
|
||||
def get_stock_ledgers(vouchers):
|
||||
return frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["item_code", "warehouse", "posting_date"],
|
||||
filters={"voucher_no": ("in", vouchers)},
|
||||
)
|
||||
|
||||
|
||||
def create_repost_item_valuation(item_code, warehouse, posting_date):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Repost Item Valuation",
|
||||
"company": frappe.get_cached_value("Warehouse", warehouse, "company"),
|
||||
"posting_date": posting_date,
|
||||
"based_on": "Item and Warehouse",
|
||||
"posting_time": "00:00:01",
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"allow_negative_stock": True,
|
||||
"status": "Queued",
|
||||
}
|
||||
).submit()
|
||||
|
||||
@@ -88,8 +88,15 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
||||
|
||||
update_party_blanket_order(args, out)
|
||||
|
||||
# Never try to find a customer price if customer is set in these Doctype
|
||||
current_customer = args.customer
|
||||
if args.get("doctype") in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]:
|
||||
args.customer = None
|
||||
|
||||
out.update(get_price_list_rate(args, item))
|
||||
|
||||
args.customer = current_customer
|
||||
|
||||
if args.customer and cint(args.is_pos):
|
||||
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
|
||||
|
||||
@@ -146,7 +153,7 @@ def update_stock(args, out):
|
||||
):
|
||||
|
||||
if out.has_batch_no and not args.get("batch_no"):
|
||||
out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty)
|
||||
out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty).get("batch_no", None)
|
||||
actual_batch_qty = get_batch_qty(out.batch_no, out.warehouse, out.item_code)
|
||||
if actual_batch_qty:
|
||||
out.update(actual_batch_qty)
|
||||
|
||||
@@ -62,7 +62,7 @@ def execute(filters=None):
|
||||
continue
|
||||
|
||||
total_stock_value = sum(item_value[(item, item_group)])
|
||||
row = [item, item_group, total_stock_value]
|
||||
row = [item, item_map[item]["item_name"], item_group, total_stock_value]
|
||||
|
||||
fifo_queue = item_ageing[item]["fifo_queue"]
|
||||
average_age = 0.00
|
||||
@@ -89,10 +89,11 @@ def get_columns(filters):
|
||||
"""return columns"""
|
||||
|
||||
columns = [
|
||||
_("Item") + ":Link/Item:180",
|
||||
_("Item Group") + "::100",
|
||||
_("Value") + ":Currency:100",
|
||||
_("Age") + ":Float:60",
|
||||
_("Item") + ":Link/Item:150",
|
||||
_("Item Name") + ":Link/Item:150",
|
||||
_("Item Group") + "::120",
|
||||
_("Value") + ":Currency:120",
|
||||
_("Age") + ":Float:120",
|
||||
]
|
||||
return columns
|
||||
|
||||
@@ -132,7 +133,7 @@ def get_warehouse_list(filters):
|
||||
|
||||
def add_warehouse_column(columns, warehouse_list):
|
||||
if len(warehouse_list) > 1:
|
||||
columns += [_("Total Qty") + ":Int:50"]
|
||||
columns += [_("Total Qty") + ":Int:120"]
|
||||
|
||||
for wh in warehouse_list:
|
||||
columns += [_(wh.name) + ":Int:54"]
|
||||
columns += [_(wh.name) + ":Int:100"]
|
||||
|
||||
@@ -122,7 +122,7 @@ def get_reserved_qty(item_code, warehouse):
|
||||
and parenttype="Sales Order"
|
||||
and item_code != parent_item
|
||||
and exists (select * from `tabSales Order` so
|
||||
where name = dnpi_in.parent and docstatus = 1 and status != 'Closed')
|
||||
where name = dnpi_in.parent and docstatus = 1 and status not in ('On Hold', 'Closed'))
|
||||
) dnpi)
|
||||
union
|
||||
(select stock_qty as dnpi_qty, qty as so_item_qty,
|
||||
@@ -132,7 +132,7 @@ def get_reserved_qty(item_code, warehouse):
|
||||
and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0)
|
||||
and exists(select * from `tabSales Order` so
|
||||
where so.name = so_item.parent and so.docstatus = 1
|
||||
and so.status != 'Closed'))
|
||||
and so.status not in ('On Hold', 'Closed')))
|
||||
) tab
|
||||
where
|
||||
so_item_qty >= so_item_delivered_qty
|
||||
|
||||
@@ -1021,7 +1021,7 @@ class update_entries_after(object):
|
||||
frappe.db.set_value("Bin", bin_name, updated_values)
|
||||
|
||||
|
||||
def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
|
||||
def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_voucher=False):
|
||||
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
||||
|
||||
args["time_format"] = "%H:%i:%s"
|
||||
@@ -1043,11 +1043,17 @@ def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
|
||||
and warehouse = %(warehouse)s
|
||||
and is_cancelled = 0
|
||||
{voucher_condition}
|
||||
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
|
||||
and (
|
||||
posting_date < %(posting_date)s or
|
||||
(
|
||||
posting_date = %(posting_date)s and
|
||||
time_format(posting_time, %(time_format)s) {operator} time_format(%(posting_time)s, %(time_format)s)
|
||||
)
|
||||
)
|
||||
order by timestamp(posting_date, posting_time) desc, creation desc
|
||||
limit 1
|
||||
for update""".format(
|
||||
voucher_condition=voucher_condition
|
||||
operator=operator, voucher_condition=voucher_condition
|
||||
),
|
||||
args,
|
||||
as_dict=1,
|
||||
@@ -1144,7 +1150,7 @@ def get_stock_ledger_entries(
|
||||
def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
|
||||
return frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle]},
|
||||
{"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle], "is_cancelled": 0},
|
||||
[
|
||||
"item_code",
|
||||
"warehouse",
|
||||
@@ -1285,7 +1291,7 @@ def get_stock_reco_qty_shift(args):
|
||||
stock_reco_qty_shift = flt(args.actual_qty)
|
||||
else:
|
||||
# reco is being submitted
|
||||
last_balance = get_previous_sle_of_current_voucher(args, exclude_current_voucher=True).get(
|
||||
last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get(
|
||||
"qty_after_transaction"
|
||||
)
|
||||
|
||||
|
||||
40
erpnext/stock/tests/test_get_item_details.py
Normal file
40
erpnext/stock/tests/test_get_item_details.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.test_runner import make_test_records
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
|
||||
test_ignore = ["BOM"]
|
||||
test_dependencies = ["Customer", "Supplier", "Item", "Price List", "Item Price"]
|
||||
|
||||
|
||||
class TestGetItemDetail(FrappeTestCase):
|
||||
def setUp(self):
|
||||
make_test_records("Price List")
|
||||
super().setUp()
|
||||
|
||||
def test_get_item_detail_purchase_order(self):
|
||||
|
||||
args = frappe._dict(
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"company": "_Test Company",
|
||||
"customer": "_Test Customer",
|
||||
"conversion_rate": 1.0,
|
||||
"price_list_currency": "USD",
|
||||
"plc_conversion_rate": 1.0,
|
||||
"doctype": "Purchase Order",
|
||||
"name": None,
|
||||
"supplier": "_Test Supplier",
|
||||
"transaction_date": None,
|
||||
"conversion_rate": 1.0,
|
||||
"price_list": "_Test Buying Price List",
|
||||
"is_subcontracted": 0,
|
||||
"ignore_pricing_rule": 1,
|
||||
"qty": 1,
|
||||
}
|
||||
)
|
||||
details = get_item_details(args)
|
||||
self.assertEqual(details.get("price_list_rate"), 100)
|
||||
@@ -4047,7 +4047,7 @@ Server Error,Serverfehler,
|
||||
Service Level Agreement has been changed to {0}.,Service Level Agreement wurde in {0} geändert.,
|
||||
Service Level Agreement was reset.,Service Level Agreement wurde zurückgesetzt.,
|
||||
Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Service Level Agreement mit Entitätstyp {0} und Entität {1} ist bereits vorhanden.,
|
||||
Set,Menge,
|
||||
Set Loyalty Program,Treueprogramm eintragen,
|
||||
Set Meta Tags,Festlegen von Meta-Tags,
|
||||
Set {0} in company {1},{0} in Firma {1} festlegen,
|
||||
Setup,Einstellungen,
|
||||
@@ -4227,10 +4227,8 @@ To date cannot be before From date,Bis-Datum kann nicht vor Von-Datum liegen,
|
||||
Write Off,Abschreiben,
|
||||
{0} Created,{0} Erstellt,
|
||||
Email Id,E-Mail-ID,
|
||||
No,Kein,
|
||||
Reference Doctype,Referenz-DocType,
|
||||
User Id,Benutzeridentifikation,
|
||||
Yes,Ja,
|
||||
Actual ,Tatsächlich,
|
||||
Add to cart,In den Warenkorb legen,
|
||||
Budget,Budget,
|
||||
@@ -9899,3 +9897,5 @@ Total Asset,Aktiva,
|
||||
Total Liability,Verbindlichkeiten,
|
||||
Total Equity,Eigenkapital,
|
||||
Warehouse wise Stock Value,Warenwert nach Lager,
|
||||
Discount Validity,Frist für den Rabatt,
|
||||
Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -51,36 +51,31 @@ def get_tabs(categories):
|
||||
return tab_values
|
||||
|
||||
|
||||
def get_category_records(categories):
|
||||
def get_category_records(categories: list):
|
||||
categorical_data = {}
|
||||
for category in categories:
|
||||
if category == "item_group":
|
||||
categorical_data["item_group"] = frappe.db.sql(
|
||||
"""
|
||||
Select
|
||||
name, parent_item_group, is_group, image, route
|
||||
from
|
||||
`tabItem Group`
|
||||
where
|
||||
parent_item_group = 'All Item Groups'
|
||||
and show_in_website = 1
|
||||
""",
|
||||
as_dict=1,
|
||||
|
||||
for c in categories:
|
||||
if c == "item_group":
|
||||
categorical_data["item_group"] = frappe.db.get_all(
|
||||
"Item Group",
|
||||
filters={"parent_item_group": "All Item Groups", "show_in_website": 1},
|
||||
fields=["name", "parent_item_group", "is_group", "image", "route"],
|
||||
)
|
||||
else:
|
||||
doctype = frappe.unscrub(category)
|
||||
fields = ["name"]
|
||||
if frappe.get_meta(doctype, cached=True).get_field("image"):
|
||||
|
||||
continue
|
||||
|
||||
doctype = frappe.unscrub(c)
|
||||
fields = ["name"]
|
||||
|
||||
try:
|
||||
meta = frappe.get_meta(doctype, cached=True)
|
||||
if meta.get_field("image"):
|
||||
fields += ["image"]
|
||||
|
||||
categorical_data[category] = frappe.db.sql(
|
||||
f"""
|
||||
Select
|
||||
{",".join(fields)}
|
||||
from
|
||||
`tab{doctype}`
|
||||
""",
|
||||
as_dict=1,
|
||||
)
|
||||
data = frappe.db.get_all(doctype, fields=fields)
|
||||
categorical_data[c] = data
|
||||
except BaseException:
|
||||
frappe.throw(_("DocType {} not found").format(doctype))
|
||||
continue
|
||||
|
||||
return categorical_data
|
||||
|
||||
Reference in New Issue
Block a user