Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62d1256d44 | ||
|
|
e9b445c853 | ||
|
|
41793bddd2 | ||
|
|
b23049a55e | ||
|
|
99bf6c0927 | ||
|
|
b528c5cad2 | ||
|
|
dc76823a81 | ||
|
|
862c36832a | ||
|
|
bd9dad96ae | ||
|
|
8ca933a83f | ||
|
|
f0107db3ca | ||
|
|
c01342190c | ||
|
|
f763c1af25 | ||
|
|
cbfd387c0b | ||
|
|
ac07b1cc07 | ||
|
|
4a1319289e | ||
|
|
c5a43e46d8 | ||
|
|
04dfd7b98f | ||
|
|
85e0e87f01 | ||
|
|
e7a3c7c5ab | ||
|
|
acc32fe7f7 | ||
|
|
487454e817 | ||
|
|
dc278a7427 | ||
|
|
698ee434c0 | ||
|
|
e116cb596c | ||
|
|
169c3ed09d | ||
|
|
d7ba618b92 | ||
|
|
0a37f5575f | ||
|
|
b1bae1bde8 | ||
|
|
bcfd4277f4 | ||
|
|
2b6915519f | ||
|
|
5eb84352ec | ||
|
|
1580bf9ca4 | ||
|
|
4c4c534dcd | ||
|
|
0da11f1b10 | ||
|
|
cb4784c940 | ||
|
|
4c8ee279f3 | ||
|
|
58aea1f819 | ||
|
|
760bfb27d0 | ||
|
|
c0c4e866be | ||
|
|
b16474e606 | ||
|
|
bbed8972c3 | ||
|
|
35f94dfbc6 | ||
|
|
832fa2e76b | ||
|
|
682ce24f8c | ||
|
|
9f02b08427 | ||
|
|
2645980f62 | ||
|
|
ae1a91835e | ||
|
|
094755415a | ||
|
|
746eae4d1a | ||
|
|
cdbb448f6c | ||
|
|
907ea7dd8a | ||
|
|
9a73b7a319 | ||
|
|
2e54da2ea5 | ||
|
|
7e4b93f48e | ||
|
|
b01cc1b449 | ||
|
|
f738b951c5 | ||
|
|
bb1679b74b | ||
|
|
6ddcac7cee | ||
|
|
bcf7da6b1e | ||
|
|
c51d5ba5df | ||
|
|
57ca765d9e | ||
|
|
92e7d1f41d | ||
|
|
ed40542658 | ||
|
|
207b3efed7 | ||
|
|
3131c732ff | ||
|
|
1828c12481 | ||
|
|
4a91c49e0d | ||
|
|
ba1f4263dd | ||
|
|
7d45929872 | ||
|
|
b70f871592 | ||
|
|
d58df13150 | ||
|
|
4101a48869 | ||
|
|
28eff7fb91 | ||
|
|
6dc40e9baf | ||
|
|
ac6d11eb3c | ||
|
|
1c6eeb228f | ||
|
|
b44f26d1ba | ||
|
|
8ea2f45713 | ||
|
|
dde65752b6 | ||
|
|
845980c010 | ||
|
|
6f593130d0 | ||
|
|
52f3bfca73 | ||
|
|
3c3a3ecea8 | ||
|
|
246e47e76e | ||
|
|
f6aad5ed2d | ||
|
|
aa87931172 | ||
|
|
fc155c7712 | ||
|
|
47b5e6272d | ||
|
|
eae56cae54 | ||
|
|
ea925d26a8 | ||
|
|
56f5156f3c | ||
|
|
ee069d47b5 | ||
|
|
6c83b6bddc | ||
|
|
815b460ddd | ||
|
|
d1416542a0 | ||
|
|
69095e7285 | ||
|
|
d6bdad7adf | ||
|
|
48cccca9af | ||
|
|
7c5124140e | ||
|
|
143f384986 | ||
|
|
37fdc43c88 | ||
|
|
ceb761852e | ||
|
|
65d8de36d0 | ||
|
|
8142cd2865 | ||
|
|
ed3a6cf748 | ||
|
|
2285eb7649 | ||
|
|
1648605950 | ||
|
|
039154faa9 | ||
|
|
147acfd502 | ||
|
|
b4c7bad33d | ||
|
|
ff04bf6346 | ||
|
|
aa5182bb9e | ||
|
|
329afe88f9 | ||
|
|
ffa1e1c3bc | ||
|
|
f6616b6cbd | ||
|
|
d95b8e530a | ||
|
|
0e1314c5b5 | ||
|
|
cc920a7e4c | ||
|
|
5bd394278d | ||
|
|
a208c56813 | ||
|
|
862a2eb975 | ||
|
|
1385f20042 | ||
|
|
950250d444 | ||
|
|
63b98ec113 | ||
|
|
4a0edd04c4 | ||
|
|
b7c0c55d61 | ||
|
|
232ad777c9 | ||
|
|
7edb951fdb | ||
|
|
845bbe3e27 | ||
|
|
f7bf50d6fe | ||
|
|
374559bfad | ||
|
|
e7c6605455 | ||
|
|
5bbe823106 | ||
|
|
ad44b00f33 | ||
|
|
893db7a5c3 | ||
|
|
433cdc960d | ||
|
|
4f2832ecd2 | ||
|
|
1755a5f298 | ||
|
|
b3b059b0ea | ||
|
|
d615d4fdcd | ||
|
|
da77abc25b | ||
|
|
7cb19f6e9f | ||
|
|
441a75b1d4 | ||
|
|
03bccb81d8 | ||
|
|
1db0b6de0c | ||
|
|
db53a789be | ||
|
|
c3e1f6bb96 | ||
|
|
f2e5e7fc22 | ||
|
|
541927d58a | ||
|
|
b16b9cd7a0 | ||
|
|
94f15fcec6 | ||
|
|
ba9dbb1b09 | ||
|
|
b7f2066250 | ||
|
|
b30c40ec8a | ||
|
|
2f5587ab25 | ||
|
|
75e9733283 | ||
|
|
69588f57e7 | ||
|
|
88d8c164f7 | ||
|
|
c330a49e9d | ||
|
|
b75f5fd5ab | ||
|
|
6f5815838e | ||
|
|
fe53651287 | ||
|
|
d8546c4316 | ||
|
|
17405d4130 | ||
|
|
2f443a5789 | ||
|
|
a5e11d7195 | ||
|
|
3832e1f9be | ||
|
|
67079d292c | ||
|
|
01a3a81f71 | ||
|
|
befa8d613f | ||
|
|
9e26bcfeeb | ||
|
|
f948fea3bf | ||
|
|
d851bd8d85 | ||
|
|
30672c6b55 | ||
|
|
e73941b9cc | ||
|
|
f33787a706 | ||
|
|
fcc4021e44 | ||
|
|
3b67c89e0b | ||
|
|
7364ebaff6 | ||
|
|
fd8f34018d | ||
|
|
799fa09a23 | ||
|
|
d1dc622914 | ||
|
|
8ce47862f9 | ||
|
|
b24d2efc4b | ||
|
|
a184fbfa66 | ||
|
|
763821d57d | ||
|
|
a132767126 | ||
|
|
453cc374d4 | ||
|
|
43e46a8506 | ||
|
|
2c95130451 | ||
|
|
d46fb5eb8c | ||
|
|
e95c18a42d | ||
|
|
18c2c54633 | ||
|
|
0edec9d25b | ||
|
|
15216fdd6f | ||
|
|
9117af3077 | ||
|
|
92b6f76612 | ||
|
|
8bf0b89595 | ||
|
|
746c162055 | ||
|
|
9eb9ccd785 | ||
|
|
1e046aa49a | ||
|
|
3ad26e4dd1 | ||
|
|
2f3b097d63 | ||
|
|
c505cbc988 | ||
|
|
1e8025b327 | ||
|
|
0e6f2474e8 | ||
|
|
e102332f08 | ||
|
|
145227e4ec | ||
|
|
18e033514e | ||
|
|
26052df76e | ||
|
|
b2f354a614 | ||
|
|
49a59c075f | ||
|
|
ff3b220c79 | ||
|
|
cc7cb2a70a | ||
|
|
79bf233734 | ||
|
|
3698b84d7c | ||
|
|
3fbbb71afc | ||
|
|
a2f18ba794 | ||
|
|
9088432f10 | ||
|
|
c80059e10e | ||
|
|
8e8e9c61ca | ||
|
|
424f0c7b84 | ||
|
|
aa5fb5e50e | ||
|
|
08a60653ba | ||
|
|
8b96fdac12 | ||
|
|
e319598c51 | ||
|
|
5c73bafeaa |
@@ -2,7 +2,7 @@
|
||||
|
||||
## Questions
|
||||
|
||||
If you have questions on how to use ERPNext or want help in customization or debugging of your scripts, please post on https://discuss.frappe.io. This is only for bug reports and feature requests.
|
||||
If you have questions on how to use ERPNext or want help in customization or debugging of your scripts, please post on https://discuss.erpnext.com. This is only for bug reports and feature requests.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a
|
||||
|
||||
- [User Guide](https://manual.erpnext.com)
|
||||
- [Getting Help](http://erpnext.org/getting-help.html)
|
||||
- [Discussion Forum](https://discuss.frappe.io/)
|
||||
- [Discussion Forum](https://discuss.erpnext.com/)
|
||||
|
||||
---
|
||||
|
||||
### Full Install
|
||||
|
||||
The Easy Way install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench
|
||||
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
|
||||
|
||||
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
|
||||
|
||||
@@ -71,6 +71,6 @@ We do not allow the use of the trademark in advertising, including AdSense/AdWor
|
||||
|
||||
Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them.
|
||||
|
||||
When in doubt about your use of the ERPNext name or logo, please contact the Frappe Technologies for clarification.
|
||||
When in doubt about your use of the ERPNext name or logo, please contact Frappe Technologies for clarification.
|
||||
|
||||
(inspired from WordPress)
|
||||
(inspired by WordPress)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from __future__ import unicode_literals
|
||||
__version__ = '5.3.1'
|
||||
__version__ = '5.6.4'
|
||||
|
||||
@@ -23,6 +23,7 @@ class Account(Document):
|
||||
def validate(self):
|
||||
self.validate_parent()
|
||||
self.validate_root_details()
|
||||
self.set_root_and_report_type()
|
||||
self.validate_mandatory()
|
||||
self.validate_warehouse_account()
|
||||
self.validate_frozen_accounts_modifier()
|
||||
@@ -32,7 +33,7 @@ class Account(Document):
|
||||
"""Fetch Parent Details and validate parent account"""
|
||||
if self.parent_account:
|
||||
par = frappe.db.get_value("Account", self.parent_account,
|
||||
["name", "is_group", "report_type", "root_type", "company"], as_dict=1)
|
||||
["name", "is_group", "company"], as_dict=1)
|
||||
if not par:
|
||||
throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
|
||||
elif par.name == self.name:
|
||||
@@ -43,10 +44,24 @@ class Account(Document):
|
||||
throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
|
||||
.format(self.name, self.parent_account, self.company))
|
||||
|
||||
def set_root_and_report_type(self):
|
||||
if self.parent_account:
|
||||
par = frappe.db.get_value("Account", self.parent_account, ["report_type", "root_type"], as_dict=1)
|
||||
|
||||
if par.report_type:
|
||||
self.report_type = par.report_type
|
||||
if par.root_type:
|
||||
self.root_type = par.root_type
|
||||
|
||||
if self.is_group:
|
||||
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
|
||||
if db_value:
|
||||
if self.report_type != db_value.report_type:
|
||||
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
|
||||
(self.report_type, self.lft, self.rgt))
|
||||
if self.root_type != db_value.root_type:
|
||||
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
|
||||
(self.root_type, self.lft, self.rgt))
|
||||
|
||||
def validate_root_details(self):
|
||||
# does not exists parent
|
||||
|
||||
@@ -92,13 +92,13 @@ def get_charts_for_country(country):
|
||||
with open(os.path.join(path, fname), "r") as f:
|
||||
_get_chart_name(f.read())
|
||||
|
||||
countries_use_OHADA_system = ["Benin", "Burkina Faso", "Cameroon", "Central African Republic", "Comoros",
|
||||
"Congo", "Ivory Coast", "Gabon", "Guinea", "Guinea Bissau", "Equatorial Guinea", "Mali", "Niger",
|
||||
"Replica of Democratic Congo", "Senegal", "Chad", "Togo"]
|
||||
|
||||
if country in countries_use_OHADA_system:
|
||||
with open(os.path.join(os.path.dirname(__file__), "syscohada_syscohada_chart_template.json"), "r") as f:
|
||||
_get_chart_name(f.read())
|
||||
# countries_use_OHADA_system = ["Benin", "Burkina Faso", "Cameroon", "Central African Republic", "Comoros",
|
||||
# "Congo", "Ivory Coast", "Gabon", "Guinea", "Guinea Bissau", "Equatorial Guinea", "Mali", "Niger",
|
||||
# "Replica of Democratic Congo", "Senegal", "Chad", "Togo"]
|
||||
#
|
||||
# if country in countries_use_OHADA_system:
|
||||
# with open(os.path.join(os.path.dirname(__file__), "syscohada_syscohada_chart_template.json"), "r") as f:
|
||||
# _get_chart_name(f.read())
|
||||
|
||||
if len(charts) != 1:
|
||||
charts.append("Standard")
|
||||
|
||||
@@ -19,7 +19,8 @@ class BankReconciliation(Document):
|
||||
|
||||
|
||||
dl = frappe.db.sql("""select t1.name, t1.cheque_no, t1.cheque_date, t2.debit,
|
||||
t2.credit, t1.posting_date, t2.against_account, t1.clearance_date
|
||||
t2.credit, t1.posting_date, t2.against_account, t1.clearance_date,
|
||||
t2.reference_type, t2.reference_name
|
||||
from
|
||||
`tabJournal Entry` t1, `tabJournal Entry Account` t2
|
||||
where
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2013-02-22 01:27:37",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "voucher_id",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Voucher ID",
|
||||
"no_copy": 0,
|
||||
@@ -13,46 +21,84 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Journal Entry",
|
||||
"permlevel": 0,
|
||||
"search_index": 0
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "clearance_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Clearance Date",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "clearance_date",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"search_index": 0
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "against_account",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Against Account",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "against_account",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "cheque_number",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Cheque Number",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "cheque_number",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "debit",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Debit",
|
||||
"no_copy": 0,
|
||||
@@ -60,12 +106,21 @@
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "credit",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Credit",
|
||||
"no_copy": 0,
|
||||
@@ -73,40 +128,113 @@
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Type",
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 0,
|
||||
"options": "reference_type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Posting Date",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "cheque_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Cheque Date",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "cheque_date",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-04-21 01:29:29.570890",
|
||||
"modified": "2015-08-10 16:59:43.974705",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Reconciliation Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": []
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
}
|
||||
@@ -4,11 +4,13 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate, cstr, cint
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate, cstr
|
||||
from frappe import _
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
class CustomerFrozen(frappe.ValidationError): pass
|
||||
|
||||
class GLEntry(Document):
|
||||
def validate(self):
|
||||
self.flags.ignore_submit_comment = True
|
||||
@@ -17,6 +19,7 @@ class GLEntry(Document):
|
||||
self.validate_posting_date()
|
||||
self.check_pl_account()
|
||||
self.validate_cost_center()
|
||||
self.validate_party()
|
||||
|
||||
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
|
||||
self.validate_account_details(adv_adj)
|
||||
@@ -88,6 +91,13 @@ class GLEntry(Document):
|
||||
|
||||
if self.cost_center and _get_cost_center_company() != self.company:
|
||||
frappe.throw(_("Cost Center {0} does not belong to Company {1}").format(self.cost_center, self.company))
|
||||
|
||||
def validate_party(self):
|
||||
if self.party_type and self.party:
|
||||
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
|
||||
if not frozen_accounts_modifier in frappe.get_roles():
|
||||
if frappe.db.get_value(self.party_type, self.party, "is_frozen"):
|
||||
frappe.throw("{0} {1} is frozen".format(self.party_type, self.party), CustomerFrozen)
|
||||
|
||||
def validate_balance_type(account, adv_adj=False):
|
||||
if not adv_adj and account:
|
||||
|
||||
@@ -48,34 +48,49 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
}
|
||||
});
|
||||
|
||||
$.each([["against_voucher", "Purchase Invoice", "supplier"],
|
||||
["against_invoice", "Sales Invoice", "customer"]], function(i, opts) {
|
||||
me.frm.set_query(opts[0], "accounts", function(doc, cdt, cdn) {
|
||||
var jvd = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.validate_missing(jvd, "party_type");
|
||||
frappe.model.validate_missing(jvd, "party");
|
||||
return {
|
||||
filters: [
|
||||
[opts[1], opts[2], "=", jvd.party],
|
||||
[opts[1], "docstatus", "=", 1],
|
||||
[opts[1], "outstanding_amount", ">", 0]
|
||||
]
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
this.frm.set_query("against_jv", "accounts", function(doc, cdt, cdn) {
|
||||
me.frm.set_query("reference_name", "accounts", function(doc, cdt, cdn) {
|
||||
var jvd = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.validate_missing(jvd, "account");
|
||||
|
||||
return {
|
||||
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_jv",
|
||||
filters: {
|
||||
account: jvd.account,
|
||||
party: jvd.party
|
||||
}
|
||||
// expense claim
|
||||
if(jvd.reference_type==="Expense Claim") {
|
||||
return {};
|
||||
}
|
||||
|
||||
// journal entry
|
||||
if(jvd.reference_type==="Journal Entry") {
|
||||
frappe.model.validate_missing(jvd, "account");
|
||||
|
||||
return {
|
||||
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_jv",
|
||||
filters: {
|
||||
account: jvd.account,
|
||||
party: jvd.party
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// against party
|
||||
|
||||
frappe.model.validate_missing(jvd, "party_type");
|
||||
frappe.model.validate_missing(jvd, "party");
|
||||
|
||||
var out = {
|
||||
filters: [
|
||||
[jvd.reference_type, jvd.reference_type.indexOf("Sales")===0 ? "customer" : "supplier", "=", jvd.party],
|
||||
[jvd.reference_type, "docstatus", "=", 1],
|
||||
]
|
||||
};
|
||||
|
||||
if(in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
|
||||
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
|
||||
} else {
|
||||
out.filters.push([jvd.reference_type, "per_billed", "<", 100]);
|
||||
}
|
||||
|
||||
return out;
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
setup_balance_formatter: function() {
|
||||
@@ -93,24 +108,16 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
})
|
||||
},
|
||||
|
||||
against_voucher: function(doc, cdt, cdn) {
|
||||
reference_name: function(doc, cdt, cdn) {
|
||||
var d = frappe.get_doc(cdt, cdn);
|
||||
if (d.against_voucher && !flt(d.debit)) {
|
||||
this.get_outstanding('Purchase Invoice', d.against_voucher, d);
|
||||
if (d.reference_type==="Purchase Invoice" && !flt(d.debit)) {
|
||||
this.get_outstanding('Purchase Invoice', d.reference_name, d);
|
||||
}
|
||||
},
|
||||
|
||||
against_invoice: function(doc, cdt, cdn) {
|
||||
var d = frappe.get_doc(cdt, cdn);
|
||||
if (d.against_invoice && !flt(d.credit)) {
|
||||
this.get_outstanding('Sales Invoice', d.against_invoice, d);
|
||||
if (d.reference_type==="Sales Invoice" && !flt(d.credit)) {
|
||||
this.get_outstanding('Sales Invoice', d.reference_name, d);
|
||||
}
|
||||
},
|
||||
|
||||
against_jv: function(doc, cdt, cdn) {
|
||||
var d = frappe.get_doc(cdt, cdn);
|
||||
if (d.against_jv && !flt(d.credit) && !flt(d.debit)) {
|
||||
this.get_outstanding('Journal Entry', d.against_jv, d);
|
||||
if (d.reference_type==="Journal Entry" && !flt(d.credit) && !flt(d.debit)) {
|
||||
this.get_outstanding('Journal Entry', d.reference_name, d);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -214,11 +221,12 @@ cur_frm.cscript.account = function(doc,dt,dn) {
|
||||
var d = locals[dt][dn];
|
||||
if(d.account) {
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.utils.get_balance_on",
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
|
||||
args: {account: d.account, date: doc.posting_date},
|
||||
callback: function(r) {
|
||||
d.balance = r.message;
|
||||
$.extend(d, r.message);
|
||||
refresh_field('balance', d.name, 'accounts');
|
||||
refresh_field('party_type', d.name, 'accounts');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,13 +28,10 @@ class JournalEntry(AccountsController):
|
||||
self.validate_entries_for_advance()
|
||||
self.validate_debit_and_credit()
|
||||
self.validate_against_jv()
|
||||
self.validate_against_sales_invoice()
|
||||
self.validate_against_purchase_invoice()
|
||||
self.validate_reference_doc()
|
||||
self.set_against_account()
|
||||
self.create_remarks()
|
||||
self.set_print_format_fields()
|
||||
self.validate_against_sales_order()
|
||||
self.validate_against_purchase_order()
|
||||
self.check_due_date()
|
||||
self.validate_expense_claim()
|
||||
self.validate_credit_debit_note()
|
||||
@@ -54,10 +51,8 @@ class JournalEntry(AccountsController):
|
||||
advance_paid = frappe._dict()
|
||||
for d in self.get("accounts"):
|
||||
if d.is_advance:
|
||||
if d.against_sales_order:
|
||||
advance_paid.setdefault("Sales Order", []).append(d.against_sales_order)
|
||||
elif d.against_purchase_order:
|
||||
advance_paid.setdefault("Purchase Order", []).append(d.against_purchase_order)
|
||||
if d.reference_type in ("Sales Order", "Purchase Order"):
|
||||
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
|
||||
|
||||
for voucher_type, order_list in advance_paid.items():
|
||||
for voucher_no in list(set(order_list)):
|
||||
@@ -65,7 +60,7 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def on_cancel(self):
|
||||
from erpnext.accounts.utils import remove_against_link_from_jv
|
||||
remove_against_link_from_jv(self.doctype, self.name, "against_jv")
|
||||
remove_against_link_from_jv(self.doctype, self.name)
|
||||
|
||||
self.make_gl_entries(1)
|
||||
self.update_advance_paid()
|
||||
@@ -93,10 +88,8 @@ class JournalEntry(AccountsController):
|
||||
for d in self.get("accounts"):
|
||||
if d.party_type and d.party and d.get("credit" if d.party_type=="Customer" else "debit") > 0:
|
||||
due_date = None
|
||||
if d.against_invoice:
|
||||
due_date = frappe.db.get_value("Sales Invoice", d.against_invoice, "due_date")
|
||||
elif d.against_voucher:
|
||||
due_date = frappe.db.get_value("Purchase Invoice", d.against_voucher, "due_date")
|
||||
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
due_date = frappe.db.get_value(d.reference_type, d.reference_name, "due_date")
|
||||
|
||||
if due_date and getdate(self.cheque_date) > getdate(due_date):
|
||||
diff = date_diff(self.cheque_date, due_date)
|
||||
@@ -115,17 +108,17 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def validate_entries_for_advance(self):
|
||||
for d in self.get('accounts'):
|
||||
if not (d.against_voucher and d.against_invoice and d.against_jv):
|
||||
if d.reference_type not in ("Sales Invoice", "Purchase Invoice", "Journal Entry"):
|
||||
if (d.party_type == 'Customer' and flt(d.credit) > 0) or \
|
||||
(d.party_type == 'Supplier' and flt(d.debit) > 0):
|
||||
if not d.is_advance:
|
||||
if d.is_advance=="No":
|
||||
msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.").format(d.idx, d.account))
|
||||
elif (d.against_sales_order or d.against_purchase_order) and d.is_advance != "Yes":
|
||||
elif d.reference_type in ("Sales Order", "Purchase Order") and d.is_advance != "Yes":
|
||||
frappe.throw(_("Row {0}: Payment against Sales/Purchase Order should always be marked as advance").format(d.idx))
|
||||
|
||||
def validate_against_jv(self):
|
||||
for d in self.get('accounts'):
|
||||
if d.against_jv:
|
||||
if d.reference_type=="Journal Entry":
|
||||
account_root_type = frappe.db.get_value("Account", d.account, "root_type")
|
||||
if account_root_type == "Asset" and flt(d.debit) > 0:
|
||||
frappe.throw(_("For {0}, only credit accounts can be linked against another debit entry")
|
||||
@@ -134,17 +127,17 @@ class JournalEntry(AccountsController):
|
||||
frappe.throw(_("For {0}, only debit accounts can be linked against another credit entry")
|
||||
.format(d.account))
|
||||
|
||||
if d.against_jv == self.name:
|
||||
if d.reference_name == self.name:
|
||||
frappe.throw(_("You can not enter current voucher in 'Against Journal Entry' column"))
|
||||
|
||||
against_entries = frappe.db.sql("""select * from `tabJournal Entry Account`
|
||||
where account = %s and docstatus = 1 and parent = %s
|
||||
and ifnull(against_jv, '') = '' and ifnull(against_invoice, '') = ''
|
||||
and ifnull(against_voucher, '') = ''""", (d.account, d.against_jv), as_dict=True)
|
||||
and ifnull(reference_type, '') in ("", "Sales Order", "Purchase Order")
|
||||
""", (d.account, d.reference_name), as_dict=True)
|
||||
|
||||
if not against_entries:
|
||||
frappe.throw(_("Journal Entry {0} does not have account {1} or already matched against other voucher")
|
||||
.format(d.against_jv, d.account))
|
||||
.format(d.reference_name, d.account))
|
||||
else:
|
||||
dr_or_cr = "debit" if d.credit > 0 else "credit"
|
||||
valid = False
|
||||
@@ -153,90 +146,99 @@ class JournalEntry(AccountsController):
|
||||
valid = True
|
||||
if not valid:
|
||||
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
|
||||
.format(d.against_jv, dr_or_cr))
|
||||
.format(d.reference_name, dr_or_cr))
|
||||
|
||||
def validate_against_sales_invoice(self):
|
||||
payment_against_voucher = self.validate_account_in_against_voucher("against_invoice", "Sales Invoice")
|
||||
self.validate_against_invoice_fields("Sales Invoice", payment_against_voucher)
|
||||
|
||||
def validate_against_purchase_invoice(self):
|
||||
payment_against_voucher = self.validate_account_in_against_voucher("against_voucher", "Purchase Invoice")
|
||||
self.validate_against_invoice_fields("Purchase Invoice", payment_against_voucher)
|
||||
|
||||
def validate_against_sales_order(self):
|
||||
payment_against_voucher = self.validate_account_in_against_voucher("against_sales_order", "Sales Order")
|
||||
self.validate_against_order_fields("Sales Order", payment_against_voucher)
|
||||
|
||||
def validate_against_purchase_order(self):
|
||||
payment_against_voucher = self.validate_account_in_against_voucher("against_purchase_order", "Purchase Order")
|
||||
self.validate_against_order_fields("Purchase Order", payment_against_voucher)
|
||||
|
||||
def validate_account_in_against_voucher(self, against_field, doctype):
|
||||
payment_against_voucher = frappe._dict()
|
||||
field_dict = {'Sales Invoice': ["Customer", "Debit To"],
|
||||
def validate_reference_doc(self):
|
||||
"""Validates reference document"""
|
||||
field_dict = {
|
||||
'Sales Invoice': ["Customer", "Debit To"],
|
||||
'Purchase Invoice': ["Supplier", "Credit To"],
|
||||
'Sales Order': ["Customer"],
|
||||
'Purchase Order': ["Supplier"]
|
||||
}
|
||||
}
|
||||
|
||||
self.reference_totals = {}
|
||||
self.reference_types = {}
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if d.get(against_field):
|
||||
dr_or_cr = "credit" if against_field in ["against_invoice", "against_sales_order"] \
|
||||
if not d.reference_type:
|
||||
d.reference_name = None
|
||||
if not d.reference_name:
|
||||
d.reference_type = None
|
||||
if d.reference_type and d.reference_name and (d.reference_type in field_dict.keys()):
|
||||
dr_or_cr = "credit" if d.reference_type in ("Sales Order", "Sales Invoice") \
|
||||
else "debit"
|
||||
if against_field in ["against_invoice", "against_sales_order"] and flt(d.debit) > 0:
|
||||
frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, doctype))
|
||||
|
||||
if against_field in ["against_voucher", "against_purchase_order"] and flt(d.credit) > 0:
|
||||
frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, doctype))
|
||||
# check debit or credit type Sales / Purchase Order
|
||||
if d.reference_type=="Sales Order" and flt(d.debit) > 0:
|
||||
frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, d.reference_type))
|
||||
|
||||
against_voucher = frappe.db.get_value(doctype, d.get(against_field),
|
||||
[scrub(dt) for dt in field_dict.get(doctype)])
|
||||
if d.reference_type == "Purchase Order" and flt(d.credit) > 0:
|
||||
frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, d.reference_type))
|
||||
|
||||
if against_field in ["against_invoice", "against_voucher"]:
|
||||
if (against_voucher[0] !=d.party or against_voucher[1] != d.account):
|
||||
frappe.throw(_("Row {0}: Party / Account does not match with \
|
||||
Customer / Debit To in {1}").format(d.idx, doctype))
|
||||
else:
|
||||
payment_against_voucher.setdefault(d.get(against_field), []).append(flt(d.get(dr_or_cr)))
|
||||
# set totals
|
||||
if not d.reference_name in self.reference_totals:
|
||||
self.reference_totals[d.reference_name] = 0.0
|
||||
self.reference_totals[d.reference_name] += flt(d.get(dr_or_cr))
|
||||
self.reference_types[d.reference_name] = d.reference_type
|
||||
|
||||
if against_field in ["against_sales_order", "against_purchase_order"]:
|
||||
against_voucher = frappe.db.get_value(d.reference_type, d.reference_name,
|
||||
[scrub(dt) for dt in field_dict.get(d.reference_type)])
|
||||
|
||||
# check if party and account match
|
||||
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
if (against_voucher[0] != d.party or against_voucher[1] != d.account):
|
||||
frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
|
||||
.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
|
||||
d.reference_type, d.reference_name))
|
||||
|
||||
# check if party matches for Sales / Purchase Order
|
||||
if d.reference_type in ("Sales Order", "Purchase Order"):
|
||||
# set totals
|
||||
if against_voucher != d.party:
|
||||
frappe.throw(_("Row {0}: {1} {2} does not match with {3}") \
|
||||
.format(d.idx, d.party_type, d.party, doctype))
|
||||
elif d.is_advance == "Yes":
|
||||
payment_against_voucher.setdefault(d.get(against_field), []).append(flt(d.get(dr_or_cr)))
|
||||
.format(d.idx, d.party_type, d.party, d.reference_type))
|
||||
|
||||
return payment_against_voucher
|
||||
self.validate_orders()
|
||||
self.validate_invoices()
|
||||
|
||||
def validate_against_invoice_fields(self, doctype, payment_against_voucher):
|
||||
for voucher_no, payment_list in payment_against_voucher.items():
|
||||
voucher_properties = frappe.db.get_value(doctype, voucher_no,
|
||||
["docstatus", "outstanding_amount"])
|
||||
def validate_orders(self):
|
||||
"""Validate totals, stopped and docstatus for orders"""
|
||||
for reference_name, total in self.reference_totals.iteritems():
|
||||
reference_type = self.reference_types[reference_name]
|
||||
|
||||
if voucher_properties[0] != 1:
|
||||
frappe.throw(_("{0} {1} is not submitted").format(doctype, voucher_no))
|
||||
if reference_type in ("Sales Order", "Purchase Order"):
|
||||
voucher_properties = frappe.db.get_value(reference_type, reference_name,
|
||||
["docstatus", "per_billed", "status", "advance_paid", "base_grand_total"])
|
||||
|
||||
if flt(voucher_properties[1]) < flt(sum(payment_list)):
|
||||
frappe.throw(_("Payment against {0} {1} cannot be greater \
|
||||
than Outstanding Amount {2}").format(doctype, voucher_no, voucher_properties[1]))
|
||||
if voucher_properties[0] != 1:
|
||||
frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name))
|
||||
|
||||
def validate_against_order_fields(self, doctype, payment_against_voucher):
|
||||
for voucher_no, payment_list in payment_against_voucher.items():
|
||||
voucher_properties = frappe.db.get_value(doctype, voucher_no,
|
||||
["docstatus", "per_billed", "status", "advance_paid", "base_grand_total"])
|
||||
if flt(voucher_properties[1]) >= 100:
|
||||
frappe.throw(_("{0} {1} is fully billed").format(reference_type, reference_name))
|
||||
|
||||
if voucher_properties[0] != 1:
|
||||
frappe.throw(_("{0} {1} is not submitted").format(doctype, voucher_no))
|
||||
if cstr(voucher_properties[2]) == "Stopped":
|
||||
frappe.throw(_("{0} {1} is stopped").format(reference_type, reference_name))
|
||||
|
||||
if flt(voucher_properties[1]) >= 100:
|
||||
frappe.throw(_("{0} {1} is fully billed").format(doctype, voucher_no))
|
||||
if flt(voucher_properties[4]) < (flt(voucher_properties[3]) + total):
|
||||
frappe.throw(_("Advance paid against {0} {1} cannot be greater \
|
||||
than Grand Total {2}").format(reference_type, reference_name, voucher_properties[4]))
|
||||
|
||||
if cstr(voucher_properties[2]) == "Stopped":
|
||||
frappe.throw(_("{0} {1} is stopped").format(doctype, voucher_no))
|
||||
def validate_invoices(self):
|
||||
"""Validate totals and docstatus for invoices"""
|
||||
for reference_name, total in self.reference_totals.iteritems():
|
||||
reference_type = self.reference_types[reference_name]
|
||||
|
||||
if flt(voucher_properties[4]) < flt(voucher_properties[3]) + flt(sum(payment_list)):
|
||||
frappe.throw(_("Advance paid against {0} {1} cannot be greater \
|
||||
than Grand Total {2}").format(doctype, voucher_no, voucher_properties[3]))
|
||||
if reference_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
voucher_properties = frappe.db.get_value(reference_type, reference_name,
|
||||
["docstatus", "outstanding_amount"])
|
||||
|
||||
if voucher_properties[0] != 1:
|
||||
frappe.throw(_("{0} {1} is not submitted").format(reference_type, reference_name))
|
||||
|
||||
if flt(voucher_properties[1]) < total:
|
||||
frappe.throw(_("Payment against {0} {1} cannot be greater \
|
||||
than Outstanding Amount {2}").format(reference_type, reference_name, voucher_properties[1]))
|
||||
|
||||
def set_against_account(self):
|
||||
accounts_debited, accounts_credited = [], []
|
||||
@@ -276,25 +278,25 @@ class JournalEntry(AccountsController):
|
||||
company_currency = get_company_currency(self.company)
|
||||
|
||||
for d in self.get('accounts'):
|
||||
if d.against_invoice and d.credit:
|
||||
if d.reference_type=="Sales Invoice" and d.credit:
|
||||
r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
|
||||
d.against_invoice))
|
||||
d.reference_name))
|
||||
|
||||
if d.against_sales_order and d.credit:
|
||||
if d.reference_type=="Sales Order" and d.credit:
|
||||
r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
|
||||
d.against_sales_order))
|
||||
d.reference_name))
|
||||
|
||||
if d.against_voucher and d.debit:
|
||||
if d.reference_type == "Purchase Invoice" and d.debit:
|
||||
bill_no = frappe.db.sql("""select bill_no, bill_date
|
||||
from `tabPurchase Invoice` where name=%s""", d.against_voucher)
|
||||
from `tabPurchase Invoice` where name=%s""", d.reference_name)
|
||||
if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \
|
||||
not in ['na', 'not applicable', 'none']:
|
||||
r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=company_currency), bill_no[0][0],
|
||||
bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d'))))
|
||||
|
||||
if d.against_purchase_order and d.debit:
|
||||
if d.reference_type == "Purchase Order" and d.debit:
|
||||
r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
|
||||
d.against_purchase_order))
|
||||
d.reference_name))
|
||||
|
||||
if self.user_remark:
|
||||
r.append(_("Note: {0}").format(self.user_remark))
|
||||
@@ -333,13 +335,8 @@ class JournalEntry(AccountsController):
|
||||
"against": d.against_account,
|
||||
"debit": flt(d.debit, self.precision("debit", "accounts")),
|
||||
"credit": flt(d.credit, self.precision("credit", "accounts")),
|
||||
"against_voucher_type": (("Purchase Invoice" if d.against_voucher else None)
|
||||
or ("Sales Invoice" if d.against_invoice else None)
|
||||
or ("Journal Entry" if d.against_jv else None)
|
||||
or ("Sales Order" if d.against_sales_order else None)
|
||||
or ("Purchase Order" if d.against_purchase_order else None)),
|
||||
"against_voucher": d.against_voucher or d.against_invoice or d.against_jv
|
||||
or d.against_sales_order or d.against_purchase_order,
|
||||
"against_voucher_type": d.reference_type,
|
||||
"against_voucher": d.reference_name,
|
||||
"remarks": self.remark,
|
||||
"cost_center": d.cost_center
|
||||
})
|
||||
@@ -386,11 +383,13 @@ class JournalEntry(AccountsController):
|
||||
if self.write_off_based_on == 'Accounts Receivable':
|
||||
jd1.party_type = "Customer"
|
||||
jd1.credit = flt(d.outstanding_amount, self.precision("credit", "accounts"))
|
||||
jd1.against_invoice = cstr(d.name)
|
||||
jd1.reference_type = "Sales Invoice"
|
||||
jd1.reference_name = cstr(d.name)
|
||||
elif self.write_off_based_on == 'Accounts Payable':
|
||||
jd1.party_type = "Supplier"
|
||||
jd1.debit = flt(d.outstanding_amount, self.precision("debit", "accounts"))
|
||||
jd1.against_voucher = cstr(d.name)
|
||||
jd1.reference_type = "Purchase Invoice"
|
||||
jd1.reference_name = cstr(d.name)
|
||||
|
||||
jd2 = self.append('accounts', {})
|
||||
if self.write_off_based_on == 'Accounts Receivable':
|
||||
@@ -416,19 +415,20 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def update_expense_claim(self):
|
||||
for d in self.accounts:
|
||||
if d.against_expense_claim:
|
||||
if d.reference_type=="Expense Claim":
|
||||
amt = frappe.db.sql("""select sum(debit) as amt from `tabJournal Entry Account`
|
||||
where against_expense_claim = %s and docstatus = 1""", d.against_expense_claim ,as_dict=1)[0].amt
|
||||
frappe.db.set_value("Expense Claim", d.against_expense_claim , "total_amount_reimbursed", amt)
|
||||
where reference_type = "Expense Claim" and
|
||||
reference_name = %s and docstatus = 1""", d.reference_name ,as_dict=1)[0].amt
|
||||
frappe.db.set_value("Expense Claim", d.reference_name , "total_amount_reimbursed", amt)
|
||||
|
||||
def validate_expense_claim(self):
|
||||
for d in self.accounts:
|
||||
if d.against_expense_claim:
|
||||
if d.reference_type=="Expense Claim":
|
||||
sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim",
|
||||
d.against_expense_claim, ("total_sanctioned_amount", "total_amount_reimbursed"))
|
||||
d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed"))
|
||||
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
|
||||
if d.debit > pending_amount:
|
||||
frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}".format(d.idx, d.against_expense_claim, pending_amount)))
|
||||
frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}".format(d.idx, d.reference_name, pending_amount)))
|
||||
|
||||
def validate_credit_debit_note(self):
|
||||
if self.stock_entry:
|
||||
@@ -468,6 +468,7 @@ def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_from_sales_invoice(sales_invoice):
|
||||
"""Returns new Journal Entry document as dict for given Sales Invoice"""
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
si = frappe.get_doc("Sales Invoice", sales_invoice)
|
||||
jv = get_payment_entry(si)
|
||||
@@ -480,7 +481,8 @@ def get_payment_entry_from_sales_invoice(sales_invoice):
|
||||
jv.get("accounts")[0].balance = get_balance_on(si.debit_to)
|
||||
jv.get("accounts")[0].party_balance = get_balance_on(party=si.customer, party_type="Customer")
|
||||
jv.get("accounts")[0].credit = si.outstanding_amount
|
||||
jv.get("accounts")[0].against_invoice = si.name
|
||||
jv.get("accounts")[0].reference_type = si.doctype
|
||||
jv.get("accounts")[0].reference_name = si.name
|
||||
|
||||
# debit bank
|
||||
jv.get("accounts")[1].debit = si.outstanding_amount
|
||||
@@ -489,6 +491,7 @@ def get_payment_entry_from_sales_invoice(sales_invoice):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_from_purchase_invoice(purchase_invoice):
|
||||
"""Returns new Journal Entry document as dict for given Purchase Invoice"""
|
||||
pi = frappe.get_doc("Purchase Invoice", purchase_invoice)
|
||||
jv = get_payment_entry(pi)
|
||||
jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks)
|
||||
@@ -500,13 +503,78 @@ def get_payment_entry_from_purchase_invoice(purchase_invoice):
|
||||
jv.get("accounts")[0].balance = get_balance_on(pi.credit_to)
|
||||
jv.get("accounts")[0].party_balance = get_balance_on(party=pi.supplier, party_type="Supplier")
|
||||
jv.get("accounts")[0].debit = pi.outstanding_amount
|
||||
jv.get("accounts")[0].against_voucher = pi.name
|
||||
jv.get("accounts")[0].reference_type = pi.doctype
|
||||
jv.get("accounts")[0].reference_name = pi.name
|
||||
|
||||
# credit bank
|
||||
jv.get("accounts")[1].credit = pi.outstanding_amount
|
||||
|
||||
return jv.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_from_sales_order(sales_order):
|
||||
"""Returns new Journal Entry document as dict for given Sales Order"""
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
from erpnext.accounts.party import get_party_account
|
||||
so = frappe.get_doc("Sales Order", sales_order)
|
||||
|
||||
if flt(so.per_billed, 2) != 0.0:
|
||||
frappe.throw(_("Can only make payment against unbilled Sales Order"))
|
||||
|
||||
jv = get_payment_entry(so)
|
||||
jv.remark = 'Advance payment received against Sales Order {0}.'.format(so.name)
|
||||
party_account = get_party_account(so.company, so.customer, "Customer")
|
||||
|
||||
amount = flt(so.base_grand_total) - flt(so.advance_paid)
|
||||
|
||||
# credit customer
|
||||
jv.get("accounts")[0].account = party_account
|
||||
jv.get("accounts")[0].party_type = "Customer"
|
||||
jv.get("accounts")[0].party = so.customer
|
||||
jv.get("accounts")[0].balance = get_balance_on(party_account)
|
||||
jv.get("accounts")[0].party_balance = get_balance_on(party=so.customer, party_type="Customer")
|
||||
jv.get("accounts")[0].credit = amount
|
||||
jv.get("accounts")[0].reference_type = so.doctype
|
||||
jv.get("accounts")[0].reference_name = so.name
|
||||
jv.get("accounts")[0].is_advance = "Yes"
|
||||
|
||||
# debit bank
|
||||
jv.get("accounts")[1].debit = amount
|
||||
|
||||
return jv.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_from_purchase_order(purchase_order):
|
||||
"""Returns new Journal Entry document as dict for given Sales Order"""
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
from erpnext.accounts.party import get_party_account
|
||||
po = frappe.get_doc("Purchase Order", purchase_order)
|
||||
|
||||
if flt(po.per_billed, 2) != 0.0:
|
||||
frappe.throw(_("Can only make payment against unbilled Sales Order"))
|
||||
|
||||
jv = get_payment_entry(po)
|
||||
jv.remark = 'Advance payment made against Purchase Order {0}.'.format(po.name)
|
||||
party_account = get_party_account(po.company, po.supplier, "Supplier")
|
||||
|
||||
amount = flt(po.base_grand_total) - flt(po.advance_paid)
|
||||
|
||||
# credit customer
|
||||
jv.get("accounts")[0].account = party_account
|
||||
jv.get("accounts")[0].party_type = "Supplier"
|
||||
jv.get("accounts")[0].party = po.supplier
|
||||
jv.get("accounts")[0].balance = get_balance_on(party_account)
|
||||
jv.get("accounts")[0].party_balance = get_balance_on(party=po.supplier, party_type="Supplier")
|
||||
jv.get("accounts")[0].debit = amount
|
||||
jv.get("accounts")[0].reference_type = po.doctype
|
||||
jv.get("accounts")[0].reference_name = po.name
|
||||
jv.get("accounts")[0].is_advance = "Yes"
|
||||
|
||||
# debit bank
|
||||
jv.get("accounts")[1].credit = amount
|
||||
|
||||
return jv.as_dict()
|
||||
|
||||
def get_payment_entry(doc):
|
||||
bank_account = get_default_bank_cash_account(doc.company, "Bank Entry")
|
||||
|
||||
@@ -537,40 +605,43 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark
|
||||
from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
|
||||
where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s
|
||||
and (ifnull(jv_detail.against_invoice, '') = '' and ifnull(jv_detail.against_voucher, '') = ''
|
||||
and ifnull(jv_detail.against_jv, '') = '' )
|
||||
and ifnull(jv_detail.reference_type, '') = ''
|
||||
and jv.docstatus = 1 and jv.{0} like %s order by jv.name desc limit %s, %s""".format(searchfield),
|
||||
(filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding(args):
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
args = eval(args)
|
||||
if args.get("doctype") == "Journal Entry":
|
||||
condition = " and party=%(party)s" if args.get("party") else ""
|
||||
|
||||
|
||||
against_jv_amount = frappe.db.sql("""
|
||||
select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
|
||||
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0}
|
||||
and ifnull(against_invoice, '')='' and ifnull(against_voucher, '')=''
|
||||
and ifnull(against_jv, '')=''""".format(condition), args)
|
||||
and ifnull(reference_type, '')=''""".format(condition), args)
|
||||
|
||||
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
|
||||
if against_jv_amount > 0:
|
||||
return {"credit": against_jv_amount}
|
||||
else:
|
||||
return {"debit": -1* against_jv_amount}
|
||||
|
||||
elif args.get("doctype") == "Sales Invoice":
|
||||
return {
|
||||
"credit": flt(frappe.db.get_value("Sales Invoice", args["docname"], "outstanding_amount"))
|
||||
("credit" if against_jv_amount > 0 else "debit"): abs(against_jv_amount)
|
||||
}
|
||||
elif args.get("doctype") == "Sales Invoice":
|
||||
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", args["docname"], "outstanding_amount"))
|
||||
return {
|
||||
("credit" if outstanding_amount > 0 else "debit"): abs(outstanding_amount)
|
||||
}
|
||||
elif args.get("doctype") == "Purchase Invoice":
|
||||
outstanding_amount = flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount"))
|
||||
return {
|
||||
"debit": flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount"))
|
||||
("debit" if outstanding_amount > 0 else "credit"): abs(outstanding_amount)
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_account_and_balance(company, party_type, party):
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
from erpnext.accounts.party import get_party_account
|
||||
account = get_party_account(company, party, party_type)
|
||||
|
||||
@@ -582,3 +653,16 @@ def get_party_account_and_balance(company, party_type, party):
|
||||
"balance": account_balance,
|
||||
"party_balance": party_balance
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_balance_and_party_type(account, date):
|
||||
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
account_type = frappe.db.get_value("Account", account, "account_type")
|
||||
return {
|
||||
"balance": get_balance_on(account, date),
|
||||
"party_type": {"Receivable":"Customer", "Payable":"Supplier"}.get(account_type, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,6 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
def jv_against_voucher_testcase(self, base_jv, test_voucher):
|
||||
dr_or_cr = "credit" if test_voucher.doctype in ["Sales Order", "Journal Entry"] else "debit"
|
||||
field_dict = {'Journal Entry': "against_jv",
|
||||
'Sales Order': "against_sales_order",
|
||||
'Purchase Order': "against_purchase_order"
|
||||
}
|
||||
|
||||
test_voucher.insert()
|
||||
test_voucher.submit()
|
||||
@@ -42,21 +38,20 @@ class TestJournalEntry(unittest.TestCase):
|
||||
where account = %s and docstatus = 1 and parent = %s""",
|
||||
("_Test Receivable - _TC", test_voucher.name)))
|
||||
|
||||
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where %s=%s""" % (field_dict.get(test_voucher.doctype), '%s'), (test_voucher.name)))
|
||||
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where reference_type = %s and reference_name = %s""", (test_voucher.doctype, test_voucher.name)))
|
||||
|
||||
base_jv.get("accounts")[0].is_advance = "Yes" if (test_voucher.doctype in ["Sales Order", "Purchase Order"]) else "No"
|
||||
base_jv.get("accounts")[0].set(field_dict.get(test_voucher.doctype), test_voucher.name)
|
||||
base_jv.get("accounts")[0].set("reference_type", test_voucher.doctype)
|
||||
base_jv.get("accounts")[0].set("reference_name", test_voucher.name)
|
||||
base_jv.insert()
|
||||
base_jv.submit()
|
||||
|
||||
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
|
||||
|
||||
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where %s=%s""" % (field_dict.get(test_voucher.doctype), '%s'), (submitted_voucher.name)))
|
||||
|
||||
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where %s=%s and %s=400""" % (field_dict.get(submitted_voucher.doctype), '%s', dr_or_cr), (submitted_voucher.name)))
|
||||
where reference_type = %s and reference_name = %s and {0}=400""".format(dr_or_cr),
|
||||
(submitted_voucher.doctype, submitted_voucher.name)))
|
||||
|
||||
if base_jv.get("accounts")[0].is_advance == "Yes":
|
||||
self.advance_paid_testcase(base_jv, submitted_voucher, dr_or_cr)
|
||||
@@ -74,8 +69,8 @@ class TestJournalEntry(unittest.TestCase):
|
||||
if test_voucher.doctype == "Journal Entry":
|
||||
# if test_voucher is a Journal Entry, test cancellation of test_voucher
|
||||
test_voucher.cancel()
|
||||
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where against_jv=%s""", test_voucher.name))
|
||||
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where reference_type='Journal Entry' and reference_name=%s""", test_voucher.name))
|
||||
|
||||
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
|
||||
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
|
||||
@@ -102,23 +97,23 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
def test_monthly_budget_crossed_ignore(self):
|
||||
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore")
|
||||
|
||||
|
||||
self.set_total_expense_zero("2013-02-28")
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Account Bank Account - _TC", 40000, "_Test Cost Center - _TC", submit=True)
|
||||
|
||||
|
||||
self.assertTrue(frappe.db.get_value("GL Entry",
|
||||
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
|
||||
|
||||
def test_monthly_budget_crossed_stop(self):
|
||||
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Stop")
|
||||
|
||||
|
||||
self.set_total_expense_zero("2013-02-28")
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Account Bank Account - _TC", 40000, "_Test Cost Center - _TC")
|
||||
|
||||
|
||||
self.assertRaises(BudgetError, jv.submit)
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore")
|
||||
@@ -127,37 +122,37 @@ class TestJournalEntry(unittest.TestCase):
|
||||
self.test_monthly_budget_crossed_ignore()
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "yearly_bgt_flag", "Stop")
|
||||
|
||||
|
||||
self.set_total_expense_zero("2013-02-28")
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Account Bank Account - _TC", 150000, "_Test Cost Center - _TC")
|
||||
|
||||
|
||||
self.assertRaises(BudgetError, jv.submit)
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "yearly_bgt_flag", "Ignore")
|
||||
|
||||
def test_monthly_budget_on_cancellation(self):
|
||||
self.set_total_expense_zero("2013-02-28")
|
||||
|
||||
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
|
||||
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Account Bank Account - _TC", 20000, "_Test Cost Center - _TC", submit=True)
|
||||
|
||||
|
||||
self.assertTrue(frappe.db.get_value("GL Entry",
|
||||
{"voucher_type": "Journal Entry", "voucher_no": jv1.name}))
|
||||
|
||||
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
|
||||
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Account Bank Account - _TC", 20000, "_Test Cost Center - _TC", submit=True)
|
||||
|
||||
|
||||
self.assertTrue(frappe.db.get_value("GL Entry",
|
||||
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
|
||||
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Stop")
|
||||
|
||||
self.assertRaises(BudgetError, jv1.cancel)
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore")
|
||||
|
||||
|
||||
def get_actual_expense(self, monthly_end_date):
|
||||
return get_actual_expense({
|
||||
"account": "_Test Account Cost for Goods Sold - _TC",
|
||||
@@ -166,19 +161,19 @@ class TestJournalEntry(unittest.TestCase):
|
||||
"company": "_Test Company",
|
||||
"fiscal_year": get_fiscal_year(monthly_end_date)[0]
|
||||
})
|
||||
|
||||
|
||||
def set_total_expense_zero(self, posting_date):
|
||||
existing_expense = self.get_actual_expense(posting_date)
|
||||
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Account Bank Account - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True)
|
||||
|
||||
|
||||
def make_journal_entry(account1, account2, amount, cost_center=None, submit=False):
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.posting_date = "2013-02-14"
|
||||
jv.company = "_Test Company"
|
||||
jv.fiscal_year = "_Test Fiscal Year 2013"
|
||||
jv.user_remark = "test"
|
||||
|
||||
|
||||
jv.set("accounts", [
|
||||
{
|
||||
"account": account1,
|
||||
@@ -193,11 +188,11 @@ def make_journal_entry(account1, account2, amount, cost_center=None, submit=Fals
|
||||
}
|
||||
])
|
||||
jv.insert()
|
||||
|
||||
|
||||
if submit:
|
||||
jv.submit()
|
||||
|
||||
|
||||
return jv
|
||||
|
||||
|
||||
|
||||
test_records = frappe.get_test_records('Journal Entry')
|
||||
|
||||
@@ -1,28 +1,45 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"creation": "2013-02-22 01:27:39",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "account",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_width": "250px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "250px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "balance",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Account Balance",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "balance",
|
||||
@@ -30,186 +47,336 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"default": ":Company",
|
||||
"description": "If Income or Expense",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_list_view": 0,
|
||||
"label": "Cost Center",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "cost_center",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_width": "180px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "180px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Party Type",
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Party",
|
||||
"no_copy": 0,
|
||||
"options": "party_type",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "party_balance",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Party Balance",
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "sec_break1",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Amount",
|
||||
"permlevel": 0
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "debit",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Debit",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "debit",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "col_break2",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "credit",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Credit",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "credit",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "reference",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "against_invoice",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"label": "Against Sales Invoice",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "against_invoice",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Sales Invoice",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"search_index": 1
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Type",
|
||||
"no_copy": 0,
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Against Purchase Invoice",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "against_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Purchase Invoice",
|
||||
"label": "Reference Name",
|
||||
"no_copy": 0,
|
||||
"options": "reference_type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_jv",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"label": "Against Journal Entry",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "against_jv",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Journal Entry",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"search_index": 1
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "col_break3",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "against_sales_order",
|
||||
"fieldtype": "Link",
|
||||
"label": "Against Sales Order",
|
||||
"options": "Sales Order",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "against_purchase_order",
|
||||
"fieldtype": "Link",
|
||||
"label": "Against Purchase Order",
|
||||
"options": "Purchase Order",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "against_expense_claim",
|
||||
"fieldtype": "Link",
|
||||
"label": "Against Expense Claim",
|
||||
"options": "Expense Claim",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "is_advance",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Is Advance",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "is_advance",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "No\nYes",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "against_account",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Against Account",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "against_account",
|
||||
"oldfieldtype": "Text",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-02-19 01:07:00.388689",
|
||||
"modified": "2015-08-17 02:11:33.991361",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": []
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
}
|
||||
@@ -34,8 +34,8 @@ class PaymentReconciliation(Document):
|
||||
t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
|
||||
and t2.party_type = %(party_type)s and t2.party = %(party)s
|
||||
and t2.account = %(account)s and {dr_or_cr} > 0
|
||||
and ifnull(t2.against_voucher, '')='' and ifnull(t2.against_invoice, '')=''
|
||||
and ifnull(t2.against_jv, '')='' {cond}
|
||||
and ifnull(t2.reference_type, '') in ('', 'Sales Order', 'Purchase Order')
|
||||
{cond}
|
||||
and (CASE
|
||||
WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
|
||||
THEN 1=1
|
||||
@@ -191,9 +191,10 @@ class PaymentReconciliation(Document):
|
||||
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to JV amount {2}")
|
||||
.format(p.idx, p.allocated_amount, p.amount))
|
||||
|
||||
if flt(p.allocated_amount) > unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number):
|
||||
invoice_outstanding = unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)
|
||||
if flt(p.allocated_amount) - invoice_outstanding > 0.009:
|
||||
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}")
|
||||
.format(p.idx, p.allocated_amount, unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)))
|
||||
.format(p.idx, p.allocated_amount, invoice_outstanding))
|
||||
|
||||
if not invoices_to_reconcile:
|
||||
frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))
|
||||
|
||||
@@ -12,13 +12,6 @@ class PaymentTool(Document):
|
||||
def make_journal_entry(self):
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
total_payment_amount = 0.00
|
||||
invoice_voucher_type = {
|
||||
'Sales Invoice': 'against_invoice',
|
||||
'Purchase Invoice': 'against_voucher',
|
||||
'Journal Entry': 'against_jv',
|
||||
'Sales Order': 'against_sales_order',
|
||||
'Purchase Order': 'against_purchase_order',
|
||||
}
|
||||
|
||||
jv = frappe.new_doc('Journal Entry')
|
||||
jv.voucher_type = 'Journal Entry'
|
||||
@@ -41,7 +34,8 @@ class PaymentTool(Document):
|
||||
d1.party = self.party
|
||||
d1.balance = get_balance_on(self.party_account)
|
||||
d1.set("debit" if self.received_or_paid=="Paid" else "credit", flt(v.payment_amount))
|
||||
d1.set(invoice_voucher_type.get(v.against_voucher_type), v.against_voucher_no)
|
||||
d1.set("reference_type", v.against_voucher_type)
|
||||
d1.set("reference_name", v.against_voucher_no)
|
||||
d1.set('is_advance', 'Yes' if v.against_voucher_type in ['Sales Order', 'Purchase Order'] else 'No')
|
||||
total_payment_amount = flt(total_payment_amount) + flt(d1.debit) - flt(d1.credit)
|
||||
|
||||
|
||||
@@ -23,10 +23,11 @@ class TestPaymentTool(unittest.TestCase):
|
||||
|
||||
# Create SO with partial outstanding
|
||||
so1 = make_sales_order(customer="_Test Customer 3", qty=10, rate=100)
|
||||
|
||||
|
||||
self.create_against_jv(jv_test_records[0], {
|
||||
"party": "_Test Customer 3",
|
||||
"against_sales_order": so1.name,
|
||||
"reference_type": "Sales Order",
|
||||
"reference_name": so1.name,
|
||||
"is_advance": "Yes"
|
||||
})
|
||||
|
||||
@@ -36,7 +37,8 @@ class TestPaymentTool(unittest.TestCase):
|
||||
|
||||
self.create_against_jv(jv_test_records[0], {
|
||||
"party": "_Test Customer 3",
|
||||
"against_sales_order": so2.name,
|
||||
"reference_type": "Sales Order",
|
||||
"reference_name": so2.name,
|
||||
"credit": 1000,
|
||||
"is_advance": "Yes"
|
||||
})
|
||||
@@ -52,7 +54,8 @@ class TestPaymentTool(unittest.TestCase):
|
||||
|
||||
self.create_against_jv(jv_test_records[0], {
|
||||
"party": "_Test Customer 3",
|
||||
"against_invoice": si1.name
|
||||
"reference_type": si1.doctype,
|
||||
"reference_name": si1.name
|
||||
})
|
||||
#Create SI with no outstanding
|
||||
si2 = self.create_voucher(si_test_records[0], {
|
||||
@@ -62,7 +65,8 @@ class TestPaymentTool(unittest.TestCase):
|
||||
|
||||
self.create_against_jv(jv_test_records[0], {
|
||||
"party": "_Test Customer 3",
|
||||
"against_invoice": si2.name,
|
||||
"reference_type": si2.doctype,
|
||||
"reference_name": si2.name,
|
||||
"credit": 561.80
|
||||
})
|
||||
|
||||
@@ -125,7 +129,7 @@ class TestPaymentTool(unittest.TestCase):
|
||||
def make_voucher_for_party(self, args, expected_outstanding):
|
||||
#Make Journal Entry for Party
|
||||
payment_tool_doc = frappe.new_doc("Payment Tool")
|
||||
|
||||
|
||||
for k, v in args.items():
|
||||
payment_tool_doc.set(k, v)
|
||||
|
||||
@@ -153,29 +157,12 @@ class TestPaymentTool(unittest.TestCase):
|
||||
|
||||
new_jv = paytool.make_journal_entry()
|
||||
|
||||
#Create a list of expected values as [party account, payment against, against_jv, against_invoice,
|
||||
#against_voucher, against_sales_order, against_purchase_order]
|
||||
expected_values = [
|
||||
[paytool.party_account, paytool.party, 100.00, expected_outstanding.get("Journal Entry")[0], None, None, None, None],
|
||||
[paytool.party_account, paytool.party, 100.00, None, expected_outstanding.get("Sales Invoice")[0], None, None, None],
|
||||
[paytool.party_account, paytool.party, 100.00, None, None, expected_outstanding.get("Purchase Invoice")[0], None, None],
|
||||
[paytool.party_account, paytool.party, 100.00, None, None, None, expected_outstanding.get("Sales Order")[0], None],
|
||||
[paytool.party_account, paytool.party, 100.00, None, None, None, None, expected_outstanding.get("Purchase Order")[0]]
|
||||
]
|
||||
|
||||
for jv_entry in new_jv.get("accounts"):
|
||||
if paytool.party_account == jv_entry.get("account") and paytool.party == jv_entry.get("party"):
|
||||
row = [
|
||||
jv_entry.get("account"),
|
||||
jv_entry.get("party"),
|
||||
jv_entry.get("debit" if paytool.party_type=="Supplier" else "credit"),
|
||||
jv_entry.get("against_jv"),
|
||||
jv_entry.get("against_invoice"),
|
||||
jv_entry.get("against_voucher"),
|
||||
jv_entry.get("against_sales_order"),
|
||||
jv_entry.get("against_purchase_order"),
|
||||
]
|
||||
self.assertTrue(row in expected_values)
|
||||
self.assertEquals(100.00,
|
||||
jv_entry.get("debit" if paytool.party_type=="Supplier" else "credit"))
|
||||
self.assertEquals(jv_entry.reference_name,
|
||||
expected_outstanding[jv_entry.reference_type][0])
|
||||
|
||||
self.assertEquals(new_jv.get("cheque_no"), paytool.reference_no)
|
||||
self.assertEquals(new_jv.get("cheque_date"), paytool.reference_date)
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"options": "Territory",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
@@ -167,7 +167,7 @@
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "income_account",
|
||||
@@ -178,7 +178,7 @@
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)",
|
||||
@@ -201,7 +201,7 @@
|
||||
"options": "Cost Center",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "write_off_account",
|
||||
@@ -234,7 +234,7 @@
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"modified": "2015-07-07 08:56:04.381471",
|
||||
"modified": "2015-07-28 15:07:14.417200",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
||||
@@ -11,7 +11,6 @@ from frappe.model.document import Document
|
||||
class POSProfile(Document):
|
||||
def validate(self):
|
||||
self.check_for_duplicate()
|
||||
self.validate_expense_account()
|
||||
self.validate_all_link_fields()
|
||||
|
||||
def check_for_duplicate(self):
|
||||
@@ -26,11 +25,6 @@ class POSProfile(Document):
|
||||
msgprint(_("Global POS Profile {0} already created for company {1}").format(res[0][0],
|
||||
self.company), raise_exception=1)
|
||||
|
||||
def validate_expense_account(self):
|
||||
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \
|
||||
and not self.expense_account:
|
||||
msgprint(_("Expense Account is mandatory"), raise_exception=1)
|
||||
|
||||
def validate_all_link_fields(self):
|
||||
accounts = {"Account": [self.cash_bank_account, self.income_account,
|
||||
self.expense_account], "Cost Center": [self.cost_center],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -147,6 +147,7 @@ def get_pricing_rule_for_item(args):
|
||||
|
||||
if pricing_rule:
|
||||
item_details.pricing_rule = pricing_rule.name
|
||||
item_details.pricing_rule_for = pricing_rule.price_or_discount
|
||||
if pricing_rule.price_or_discount == "Price":
|
||||
item_details.update({
|
||||
"price_list_rate": pricing_rule.price/flt(args.conversion_rate) \
|
||||
|
||||
@@ -20,27 +20,18 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
this._super();
|
||||
|
||||
// Show / Hide button
|
||||
if(doc.docstatus==1 && doc.outstanding_amount > 0)
|
||||
this.frm.add_custom_button(__('Make Payment Entry'), this.make_bank_entry);
|
||||
this.show_general_ledger();
|
||||
|
||||
if(doc.docstatus==1) {
|
||||
cur_frm.add_custom_button(__('Make Purchase Return'), this.make_purchase_return);
|
||||
|
||||
cur_frm.add_custom_button(__('View Ledger'), function() {
|
||||
frappe.route_options = {
|
||||
"voucher_no": doc.name,
|
||||
"from_date": doc.posting_date,
|
||||
"to_date": doc.posting_date,
|
||||
"company": doc.company,
|
||||
group_by_voucher: 0
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
});
|
||||
}
|
||||
if(!doc.is_return) {
|
||||
if(doc.docstatus==1) {
|
||||
if(doc.outstanding_amount > 0) {
|
||||
this.frm.add_custom_button(__('Payment'), this.make_bank_entry).addClass("btn-primary");
|
||||
}
|
||||
cur_frm.add_custom_button(__('Debit Note'), this.make_debit_note);
|
||||
}
|
||||
|
||||
if(doc.docstatus===0) {
|
||||
cur_frm.add_custom_button(__('From Purchase Order'),
|
||||
function() {
|
||||
if(doc.docstatus===0) {
|
||||
cur_frm.add_custom_button(__('From Purchase Order'), function() {
|
||||
frappe.model.map_current_doc({
|
||||
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice",
|
||||
source_doctype: "Purchase Order",
|
||||
@@ -54,8 +45,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
})
|
||||
});
|
||||
|
||||
cur_frm.add_custom_button(__('From Purchase Receipt'),
|
||||
function() {
|
||||
cur_frm.add_custom_button(__('From Purchase Receipt'), function() {
|
||||
frappe.model.map_current_doc({
|
||||
method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
|
||||
source_doctype: "Purchase Receipt",
|
||||
@@ -66,10 +56,10 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
supplier: function() {
|
||||
var me = this;
|
||||
if(this.frm.updating_party_details)
|
||||
@@ -111,10 +101,10 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
if(row.purchase_receipt) frappe.model.clear_doc("Purchase Receipt", row.purchase_receipt)
|
||||
})
|
||||
},
|
||||
|
||||
make_purchase_return: function() {
|
||||
|
||||
make_debit_note: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_return",
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_debit_note",
|
||||
frm: cur_frm
|
||||
})
|
||||
},
|
||||
@@ -152,7 +142,7 @@ cur_frm.fields_dict['items'].grid.get_field("item_code").get_query = function(do
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{
|
||||
'is_purchase_item': 'Yes'
|
||||
'is_purchase_item': 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,4 +222,3 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
else
|
||||
cur_frm.pformat.print_heading = __("Purchase Invoice");
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ class PurchaseInvoice(BuyingController):
|
||||
self.po_required()
|
||||
self.pr_required()
|
||||
self.validate_supplier_invoice()
|
||||
self.validate_advance_jv("advances", "purchase_order")
|
||||
|
||||
self.validate_advance_jv("Purchase Order")
|
||||
|
||||
self.check_active_purchase_items()
|
||||
self.check_conversion_rate()
|
||||
self.validate_credit_to_acc()
|
||||
@@ -80,7 +80,7 @@ class PurchaseInvoice(BuyingController):
|
||||
def check_active_purchase_items(self):
|
||||
for d in self.get('items'):
|
||||
if d.item_code: # extra condn coz item_code is not mandatory in PV
|
||||
if frappe.db.get_value("Item", d.item_code, "is_purchase_item") != 'Yes':
|
||||
if frappe.db.get_value("Item", d.item_code, "is_purchase_item") != 1:
|
||||
msgprint(_("Item {0} is not Purchase Item").format(d.item_code), raise_exception=True)
|
||||
|
||||
def check_conversion_rate(self):
|
||||
@@ -233,7 +233,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.update_against_document_in_jv()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
|
||||
|
||||
self.update_project()
|
||||
|
||||
def make_gl_entries(self):
|
||||
@@ -255,7 +255,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"against": self.against_expense_account,
|
||||
"credit": self.total_amount_to_pay,
|
||||
"remarks": self.remarks,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
})
|
||||
)
|
||||
@@ -365,7 +365,7 @@ class PurchaseInvoice(BuyingController):
|
||||
def on_cancel(self):
|
||||
if not self.is_return:
|
||||
from erpnext.accounts.utils import remove_against_link_from_jv
|
||||
remove_against_link_from_jv(self.doctype, self.name, "against_voucher")
|
||||
remove_against_link_from_jv(self.doctype, self.name)
|
||||
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
@@ -411,6 +411,6 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype)})
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_return(source_name, target_doc=None):
|
||||
def make_debit_note(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return make_return_doc("Purchase Invoice", source_name, target_doc)
|
||||
return make_return_doc("Purchase Invoice", source_name, target_doc)
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
// render
|
||||
frappe.listview_settings['Purchase Invoice'] = {
|
||||
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
||||
"currency"],
|
||||
"currency", "is_return"],
|
||||
get_indicator: function(doc) {
|
||||
if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
||||
if(cint(doc.is_return)==1) {
|
||||
return [__("Return"), "darkgrey", "is_return,=,1"];
|
||||
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
||||
if(frappe.datetime.get_diff(doc.due_date) < 0) {
|
||||
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
|
||||
} else {
|
||||
|
||||
@@ -218,23 +218,20 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pi.load_from_db()
|
||||
|
||||
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where against_voucher=%s""", pi.name))
|
||||
|
||||
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where against_voucher=%s and debit=300""", pi.name))
|
||||
where reference_type='Purchase Invoice' and reference_name=%s and debit=300""", pi.name))
|
||||
|
||||
self.assertEqual(pi.outstanding_amount, 1212.30)
|
||||
|
||||
pi.cancel()
|
||||
|
||||
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where against_voucher=%s""", pi.name))
|
||||
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where reference_type='Purchase Invoice' and reference_name=%s""", pi.name))
|
||||
|
||||
def test_recurring_invoice(self):
|
||||
from erpnext.controllers.tests.test_recurring_document import test_recurring_document
|
||||
test_recurring_document(self, test_records)
|
||||
|
||||
def test_total_purchase_cost_for_project(self):
|
||||
|
||||
def test_total_purchase_cost_for_project(self):
|
||||
purchase_invoice = frappe.new_doc('Purchase Invoice')
|
||||
purchase_invoice.update({
|
||||
"credit_to": "_Test Payable - _TC",
|
||||
@@ -260,29 +257,29 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
]
|
||||
})
|
||||
purchase_invoice.save()
|
||||
purchase_invoice.submit()
|
||||
purchase_invoice.submit()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 2000)
|
||||
|
||||
|
||||
purchase_invoice1 = frappe.copy_doc(purchase_invoice)
|
||||
purchase_invoice1.save()
|
||||
purchase_invoice1.submit()
|
||||
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 4000)
|
||||
|
||||
purchase_invoice1.cancel()
|
||||
|
||||
purchase_invoice1.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 2000)
|
||||
|
||||
purchase_invoice.cancel()
|
||||
|
||||
purchase_invoice.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 0)
|
||||
|
||||
|
||||
def test_return_purchase_invoice(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
|
||||
|
||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2)
|
||||
|
||||
|
||||
|
||||
|
||||
# check gl entries for return
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
@@ -298,9 +295,9 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
|
||||
def make_purchase_invoice(**args):
|
||||
pi = frappe.new_doc("Purchase Invoice")
|
||||
args = frappe._dict(args)
|
||||
@@ -313,7 +310,7 @@ def make_purchase_invoice(**args):
|
||||
pi.currency = args.currency or "INR"
|
||||
pi.is_return = args.is_return
|
||||
pi.return_against = args.return_against
|
||||
|
||||
|
||||
pi.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
|
||||
@@ -11,9 +11,9 @@ def get_items(price_list, sales_or_purchase, item=None):
|
||||
args = {"price_list": price_list}
|
||||
|
||||
if sales_or_purchase == "Sales":
|
||||
condition = "i.is_sales_item='Yes'"
|
||||
condition = "i.is_sales_item=1"
|
||||
else:
|
||||
condition = "i.is_purchase_item='Yes'"
|
||||
condition = "i.is_purchase_item=1"
|
||||
|
||||
if item:
|
||||
# search serial no
|
||||
|
||||
@@ -40,20 +40,16 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
this._super();
|
||||
|
||||
cur_frm.dashboard.reset();
|
||||
|
||||
|
||||
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
|
||||
|
||||
if(doc.docstatus==1) {
|
||||
cur_frm.add_custom_button('View Ledger', function() {
|
||||
frappe.route_options = {
|
||||
"voucher_no": doc.name,
|
||||
"from_date": doc.posting_date,
|
||||
"to_date": doc.posting_date,
|
||||
"company": doc.company,
|
||||
group_by_voucher: 0
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
});
|
||||
|
||||
this.show_general_ledger();
|
||||
|
||||
if(doc.update_stock) this.show_stock_ledger();
|
||||
|
||||
if(doc.docstatus==1 && !doc.is_return) {
|
||||
cur_frm.add_custom_button(doc.update_stock ? __('Sales Return') : __('Credit Note'),
|
||||
this.make_sales_return);
|
||||
|
||||
if(cint(doc.update_stock)!=1) {
|
||||
// show Make Delivery Note button only if Sales Invoice is not created from Delivery Note
|
||||
@@ -64,19 +60,17 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
});
|
||||
|
||||
if(!from_delivery_note) {
|
||||
cur_frm.add_custom_button(__('Make Delivery'), cur_frm.cscript['Make Delivery Note'])
|
||||
cur_frm.add_custom_button(__('Delivery'), cur_frm.cscript['Make Delivery Note']).addClass("btn-primary");
|
||||
}
|
||||
}
|
||||
|
||||
if(doc.outstanding_amount!=0 && !cint(doc.is_return)) {
|
||||
cur_frm.add_custom_button(__('Make Payment Entry'), cur_frm.cscript.make_bank_entry);
|
||||
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry).addClass("btn-primary");
|
||||
}
|
||||
|
||||
cur_frm.add_custom_button(__('Make Sales Return'), this.make_sales_return);
|
||||
}
|
||||
|
||||
// Show buttons only when pos view is active
|
||||
if (cint(doc.docstatus==0) && cur_frm.page.current_view_name!=="pos") {
|
||||
if (cint(doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !doc.is_return) {
|
||||
cur_frm.cscript.sales_order_btn();
|
||||
cur_frm.cscript.delivery_note_btn();
|
||||
}
|
||||
@@ -207,7 +201,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
items_on_form_rendered: function() {
|
||||
erpnext.setup_serial_no();
|
||||
},
|
||||
|
||||
|
||||
make_sales_return: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_sales_return",
|
||||
@@ -396,4 +390,4 @@ cur_frm.set_query("debit_to", function(doc) {
|
||||
['Account', 'account_type', '=', 'Receivable']
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
from frappe.utils import cint, cstr, flt
|
||||
from frappe.utils import cint, flt
|
||||
from frappe import _, msgprint, throw
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from erpnext.controllers.stock_controller import update_gl_entries_after
|
||||
@@ -46,7 +46,7 @@ class SalesInvoice(SellingController):
|
||||
self.validate_debit_to_acc()
|
||||
self.validate_fixed_asset_account()
|
||||
self.clear_unallocated_advances("Sales Invoice Advance", "advances")
|
||||
self.validate_advance_jv("advances", "sales_order")
|
||||
self.validate_advance_jv("Sales Order")
|
||||
self.add_remarks()
|
||||
self.validate_write_off_account()
|
||||
|
||||
@@ -66,6 +66,7 @@ class SalesInvoice(SellingController):
|
||||
self.validate_c_form()
|
||||
self.validate_time_logs_are_submitted()
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
||||
self.update_packing_list()
|
||||
|
||||
def on_submit(self):
|
||||
super(SalesInvoice, self).on_submit()
|
||||
@@ -85,7 +86,7 @@ class SalesInvoice(SellingController):
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.check_credit_limit()
|
||||
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
@@ -102,15 +103,15 @@ class SalesInvoice(SellingController):
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.check_stop_sales_order("sales_order")
|
||||
|
||||
|
||||
from erpnext.accounts.utils import remove_against_link_from_jv
|
||||
remove_against_link_from_jv(self.doctype, self.name, "against_invoice")
|
||||
|
||||
remove_against_link_from_jv(self.doctype, self.name)
|
||||
|
||||
if not self.is_return:
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
|
||||
|
||||
self.validate_c_form_on_cancel()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
@@ -248,12 +249,10 @@ class SalesInvoice(SellingController):
|
||||
def validate_fixed_asset_account(self):
|
||||
"""Validate Fixed Asset and whether Income Account Entered Exists"""
|
||||
for d in self.get('items'):
|
||||
item = frappe.db.sql("""select name,is_asset_item,is_sales_item from `tabItem`
|
||||
where name = %s""", d.item_code)
|
||||
acc = frappe.db.sql("""select account_type from `tabAccount`
|
||||
where name = %s and docstatus != 2""", d.income_account)
|
||||
if item and item[0][1] == 'Yes' and acc and acc[0][0] != 'Fixed Asset':
|
||||
msgprint(_("Account {0} must be of type 'Fixed Asset' as Item {1} is an Asset Item").format(acc[0][0], d.item_code), raise_exception=True)
|
||||
is_asset_item = frappe.db.get_value("Item", d.item_code, "is_asset_item")
|
||||
account_type = frappe.db.get_value("Account", d.income_account, "account_type")
|
||||
if is_asset_item == 1 and account_type != 'Fixed Asset':
|
||||
msgprint(_("Account {0} must be of type 'Fixed Asset' as Item {1} is an Asset Item").format(d.income_account, d.item_code), raise_exception=True)
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(SalesInvoice, self).validate_with_previous_doc({
|
||||
@@ -271,7 +270,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')):
|
||||
self.validate_rate_with_reference_doc([
|
||||
["Sales Order", "sales_order", "so_detail"],
|
||||
["Sales Order", "sales_order", "so_detail"],
|
||||
["Delivery Note", "delivery_note", "dn_detail"]
|
||||
])
|
||||
|
||||
@@ -296,7 +295,7 @@ class SalesInvoice(SellingController):
|
||||
for i in dic:
|
||||
if frappe.db.get_value('Selling Settings', None, dic[i]) == 'Yes':
|
||||
for d in self.get('items'):
|
||||
if frappe.db.get_value('Item', d.item_code, 'is_stock_item') == 'Yes' \
|
||||
if frappe.db.get_value('Item', d.item_code, 'is_stock_item') == 1 \
|
||||
and not d.get(i.lower().replace(' ','_')):
|
||||
msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1)
|
||||
|
||||
@@ -365,6 +364,13 @@ class SalesInvoice(SellingController):
|
||||
d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
|
||||
d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0
|
||||
|
||||
def update_packing_list(self):
|
||||
if cint(self.update_stock) == 1:
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
make_packing_list(self, 'items')
|
||||
else:
|
||||
self.set('packed_items', [])
|
||||
|
||||
|
||||
def get_warehouse(self):
|
||||
user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile`
|
||||
@@ -383,20 +389,6 @@ class SalesInvoice(SellingController):
|
||||
return warehouse
|
||||
|
||||
def on_update(self):
|
||||
if cint(self.update_stock) == 1:
|
||||
# Set default warehouse from POS Profile
|
||||
if cint(self.is_pos) == 1:
|
||||
w = self.get_warehouse()
|
||||
if w:
|
||||
for d in self.get('items'):
|
||||
if not d.warehouse:
|
||||
d.warehouse = cstr(w)
|
||||
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
make_packing_list(self, 'items')
|
||||
else:
|
||||
self.set('packed_items', [])
|
||||
|
||||
if cint(self.is_pos) == 1:
|
||||
if flt(self.paid_amount) == 0:
|
||||
if self.cash_bank_account:
|
||||
@@ -426,11 +418,14 @@ class SalesInvoice(SellingController):
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
for d in self.get_item_list():
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse:
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and d.warehouse and flt(d['qty']):
|
||||
self.update_reserved_qty(d)
|
||||
|
||||
incoming_rate = 0
|
||||
if cint(self.is_return) and self.return_against and self.docstatus==1:
|
||||
incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against)
|
||||
|
||||
incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code,
|
||||
self.return_against)
|
||||
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"actual_qty": -1*flt(d.qty),
|
||||
"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
|
||||
@@ -444,7 +439,7 @@ class SalesInvoice(SellingController):
|
||||
if gl_entries:
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
|
||||
# if POS and amount is written off, there's no outstanding and hence no need to update it
|
||||
# if POS and amount is written off, updating outstanding amt after posting all gl entries
|
||||
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account) else "Yes"
|
||||
|
||||
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
|
||||
@@ -452,7 +447,8 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if update_outstanding == "No":
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
update_outstanding_amt(self.debit_to, "Customer", self.customer, self.doctype, self.name)
|
||||
update_outstanding_amt(self.debit_to, "Customer", self.customer,
|
||||
self.doctype, self.return_against if cint(self.is_return) else self.name)
|
||||
|
||||
if repost_future_gle and cint(self.update_stock) \
|
||||
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
|
||||
@@ -664,4 +660,4 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return make_return_doc("Sales Invoice", source_name, target_doc)
|
||||
return make_return_doc("Sales Invoice", source_name, target_doc)
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
// render
|
||||
frappe.listview_settings['Sales Invoice'] = {
|
||||
add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
||||
"currency"],
|
||||
"currency", "is_return"],
|
||||
get_indicator: function(doc) {
|
||||
if(flt(doc.outstanding_amount)==0) {
|
||||
if(cint(doc.is_return)==1) {
|
||||
return [__("Return"), "darkgrey", "is_return,=,1"];
|
||||
} else if(flt(doc.outstanding_amount)==0) {
|
||||
return [__("Paid"), "green", "outstanding_amount,=,0"]
|
||||
} else if (flt(doc.outstanding_amount) > 0 && doc.due_date > frappe.datetime.get_today()) {
|
||||
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>,Today"]
|
||||
|
||||
@@ -5,10 +5,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest, copy
|
||||
from frappe.utils import nowdate, add_days, flt
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
from erpnext.projects.doctype.time_log_batch.test_time_log_batch import *
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
||||
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def make(self):
|
||||
@@ -393,7 +391,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
import test_records as jv_test_records
|
||||
|
||||
jv = frappe.get_doc(frappe.copy_doc(jv_test_records[0]))
|
||||
jv.get("accounts")[0].against_invoice = w.name
|
||||
jv.get("accounts")[0].reference_type = w.doctype
|
||||
jv.get("accounts")[0].reference_name = w.name
|
||||
jv.insert()
|
||||
jv.submit()
|
||||
|
||||
@@ -402,32 +401,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
jv.cancel()
|
||||
self.assertEquals(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), 561.8)
|
||||
|
||||
def test_time_log_batch(self):
|
||||
delete_time_log_and_batch()
|
||||
time_log = create_time_log()
|
||||
tlb = create_time_log_batch(time_log)
|
||||
|
||||
tlb = frappe.get_doc("Time Log Batch", tlb.name)
|
||||
tlb.submit()
|
||||
|
||||
si = frappe.get_doc(frappe.copy_doc(test_records[0]))
|
||||
si.get("items")[0].time_log_batch = tlb.name
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log Batch", tlb.name, "status"), "Billed")
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log, "status"), "Billed")
|
||||
|
||||
si.cancel()
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log Batch", tlb.name, "status"), "Submitted")
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log, "status"), "Batched for Billing")
|
||||
|
||||
frappe.delete_doc("Sales Invoice", si.name)
|
||||
delete_time_log_and_batch()
|
||||
|
||||
def test_sales_invoice_gl_entry_without_aii(self):
|
||||
set_perpetual_inventory(0)
|
||||
si = frappe.copy_doc(test_records[1])
|
||||
@@ -684,17 +657,17 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.load_from_db()
|
||||
|
||||
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where against_invoice=%s""", si.name))
|
||||
where reference_name=%s""", si.name))
|
||||
|
||||
self.assertTrue(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where against_invoice=%s and credit=300""", si.name))
|
||||
where reference_name=%s and credit=300""", si.name))
|
||||
|
||||
self.assertEqual(si.outstanding_amount, 261.8)
|
||||
|
||||
si.cancel()
|
||||
|
||||
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where against_invoice=%s""", si.name))
|
||||
where reference_name=%s""", si.name))
|
||||
|
||||
def test_recurring_invoice(self):
|
||||
from erpnext.controllers.tests.test_recurring_document import test_recurring_document
|
||||
@@ -756,68 +729,119 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
# hack! because stock ledger entires are already inserted and are not rolled back!
|
||||
self.assertRaises(SerialNoDuplicateError, si.cancel)
|
||||
|
||||
|
||||
def test_invoice_due_date_against_customers_credit_days(self):
|
||||
# set customer's credit days
|
||||
frappe.db.set_value("Customer", "_Test Customer", "credit_days_based_on", "Fixed Days")
|
||||
frappe.db.set_value("Customer", "_Test Customer", "credit_days", 10)
|
||||
|
||||
|
||||
si = create_sales_invoice()
|
||||
self.assertEqual(si.due_date, add_days(nowdate(), 10))
|
||||
|
||||
|
||||
# set customer's credit days is last day of the next month
|
||||
frappe.db.set_value("Customer", "_Test Customer", "credit_days_based_on", "Last Day of the Next Month")
|
||||
|
||||
si1 = create_sales_invoice(posting_date="2015-07-05")
|
||||
|
||||
si1 = create_sales_invoice(posting_date="2015-07-05")
|
||||
self.assertEqual(si1.due_date, "2015-08-31")
|
||||
|
||||
|
||||
def test_return_sales_invoice(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100)
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
|
||||
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
|
||||
|
||||
si = create_sales_invoice(qty=5, rate=500, update_stock=1)
|
||||
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_0 - 5, actual_qty_1)
|
||||
|
||||
|
||||
# outgoing_rate
|
||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Sales Invoice",
|
||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name}, "stock_value_difference") / 5
|
||||
|
||||
|
||||
# return entry
|
||||
si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1)
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
|
||||
|
||||
self.assertEquals(actual_qty_1 + 2, actual_qty_2)
|
||||
|
||||
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Sales Invoice", "voucher_no": si1.name},
|
||||
|
||||
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Sales Invoice", "voucher_no": si1.name},
|
||||
["incoming_rate", "stock_value_difference"])
|
||||
|
||||
|
||||
self.assertEquals(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
|
||||
|
||||
|
||||
|
||||
|
||||
# Check gl entry
|
||||
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
|
||||
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si1.name, "account": "_Test Warehouse - _TC"}, "debit")
|
||||
|
||||
|
||||
self.assertEquals(gle_warehouse_amount, stock_value_difference)
|
||||
|
||||
party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
|
||||
|
||||
party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si1.name, "account": "Debtors - _TC", "party": "_Test Customer"}, "credit")
|
||||
|
||||
|
||||
self.assertEqual(party_credited, 1000)
|
||||
|
||||
|
||||
# Check outstanding amount
|
||||
self.assertFalse(si1.outstanding_amount)
|
||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
||||
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
|
||||
def test_discount_on_net_total(self):
|
||||
si = frappe.copy_doc(test_records[2])
|
||||
si.apply_discount_on = "Net Total"
|
||||
si.discount_amount = 625
|
||||
si.insert()
|
||||
|
||||
expected_values = {
|
||||
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
|
||||
"base_price_list_rate", "base_rate", "base_amount",
|
||||
"net_rate", "base_net_rate", "net_amount", "base_net_amount"],
|
||||
"_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500, 25, 25, 250, 250],
|
||||
"_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750, 75, 75, 375, 375],
|
||||
}
|
||||
|
||||
# check if children are saved
|
||||
self.assertEquals(len(si.get("items")),
|
||||
len(expected_values)-1)
|
||||
|
||||
# check if item values are calculated
|
||||
for d in si.get("items"):
|
||||
for i, k in enumerate(expected_values["keys"]):
|
||||
self.assertEquals(d.get(k), expected_values[d.item_code][i])
|
||||
|
||||
# check net total
|
||||
self.assertEquals(si.base_total, 1250)
|
||||
self.assertEquals(si.total, 1250)
|
||||
self.assertEquals(si.base_net_total, 625)
|
||||
self.assertEquals(si.net_total, 625)
|
||||
|
||||
# check tax calculation
|
||||
expected_values = {
|
||||
"keys": ["tax_amount", "tax_amount_after_discount_amount",
|
||||
"base_tax_amount_after_discount_amount"],
|
||||
"_Test Account Shipping Charges - _TC": [100, 100, 100],
|
||||
"_Test Account Customs Duty - _TC": [62.5, 62.5, 62.5],
|
||||
"_Test Account Excise Duty - _TC": [70, 70, 70],
|
||||
"_Test Account Education Cess - _TC": [1.4, 1.4, 1.4],
|
||||
"_Test Account S&H Education Cess - _TC": [.7, 0.7, 0.7],
|
||||
"_Test Account CST - _TC": [17.2, 17.2, 17.2],
|
||||
"_Test Account VAT - _TC": [78.13, 78.13, 78.13],
|
||||
"_Test Account Discount - _TC": [-95.49, -95.49, -95.49]
|
||||
}
|
||||
|
||||
for d in si.get("taxes"):
|
||||
for i, k in enumerate(expected_values["keys"]):
|
||||
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
||||
|
||||
|
||||
self.assertEquals(si.total_taxes_and_charges, 234.44)
|
||||
self.assertEquals(si.base_grand_total, 859.44)
|
||||
self.assertEquals(si.grand_total, 859.44)
|
||||
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
si = frappe.new_doc("Sales Invoice")
|
||||
|
||||
@@ -11,8 +11,7 @@ from erpnext.accounts.utils import validate_expense_against_budget
|
||||
|
||||
class StockAccountInvalidTransaction(frappe.ValidationError): pass
|
||||
|
||||
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True,
|
||||
update_outstanding='Yes'):
|
||||
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'):
|
||||
if gl_map:
|
||||
if not cancel:
|
||||
gl_map = process_gl_map(gl_map, merge_entries)
|
||||
@@ -51,7 +50,7 @@ def merge_similar_entries(gl_map):
|
||||
merged_gl_map.append(entry)
|
||||
|
||||
# filter zero debit and credit entries
|
||||
merged_gl_map = filter(lambda x: flt(x.debit)!=0 or flt(x.credit)!=0, merged_gl_map)
|
||||
merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map)
|
||||
return merged_gl_map
|
||||
|
||||
def check_if_in_list(gle, gl_map):
|
||||
|
||||
@@ -202,7 +202,7 @@ erpnext.AccountsChart = Class.extend({
|
||||
title:__('New Account'),
|
||||
fields: [
|
||||
{fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true,
|
||||
description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers, they are created automatically from the Customer and Supplier master")},
|
||||
description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")},
|
||||
{fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
|
||||
description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
|
||||
{fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
|
||||
|
||||
@@ -26,9 +26,9 @@ def get_children():
|
||||
acc = frappe.db.sql(""" select
|
||||
name as value, is_group as expandable %s
|
||||
from `tab%s`
|
||||
where ifnull(parent_%s,'') = ''
|
||||
where ifnull(`parent_%s`,'') = ''
|
||||
and `company` = %s and docstatus<2
|
||||
order by name""" % (select_cond, ctype, ctype.lower().replace(' ','_'), '%s'),
|
||||
order by name""" % (select_cond, frappe.db.escape(ctype), frappe.db.escape(ctype.lower().replace(' ','_')), '%s'),
|
||||
company, as_dict=1)
|
||||
|
||||
if args["parent"]=="Accounts":
|
||||
@@ -38,9 +38,9 @@ def get_children():
|
||||
acc = frappe.db.sql("""select
|
||||
name as value, is_group as expandable
|
||||
from `tab%s`
|
||||
where ifnull(parent_%s,'') = %s
|
||||
where ifnull(`parent_%s`,'') = %s
|
||||
and docstatus<2
|
||||
order by name""" % (ctype, ctype.lower().replace(' ','_'), '%s'),
|
||||
order by name""" % (frappe.db.escape(ctype), frappe.db.escape(ctype.lower().replace(' ','_')), '%s'),
|
||||
args['parent'], as_dict=1)
|
||||
|
||||
if ctype == 'Account':
|
||||
|
||||
@@ -215,7 +215,7 @@ class GrossProfitGenerator(object):
|
||||
if self.filters.to_date:
|
||||
conditions += " and posting_date <= %(to_date)s"
|
||||
|
||||
self.si_list = frappe.db.sql("""select item.parenttype, item.parent,
|
||||
self.si_list = frappe.db.sql("""select item.parenttype, item.parent,
|
||||
si.posting_date, si.posting_time, si.project_name, si.update_stock,
|
||||
si.customer, si.customer_group, si.territory,
|
||||
item.item_code, item.item_name, item.description, item.warehouse,
|
||||
@@ -257,4 +257,4 @@ class GrossProfitGenerator(object):
|
||||
|
||||
def load_non_stock_items(self):
|
||||
self.non_stock_items = frappe.db.sql_list("""select name from tabItem
|
||||
where ifnull(is_stock_item, 'No')='No'""")
|
||||
where is_stock_item=0""")
|
||||
|
||||
@@ -10,26 +10,24 @@ from frappe.utils import flt
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
validate_filters(filters)
|
||||
|
||||
|
||||
columns = get_columns(filters)
|
||||
entries = get_entries(filters)
|
||||
invoice_posting_date_map = get_invoice_posting_date_map(filters)
|
||||
against_date = ""
|
||||
outstanding_amount = 0.0
|
||||
|
||||
data = []
|
||||
for d in entries:
|
||||
if d.against_voucher:
|
||||
against_date = d.against_voucher and invoice_posting_date_map[d.against_voucher] or ""
|
||||
against_date = invoice_posting_date_map.get(d.reference_name) or ""
|
||||
if d.reference_type=="Purchase Invoice":
|
||||
payment_amount = flt(d.debit) or -1 * flt(d.credit)
|
||||
else:
|
||||
against_date = d.against_invoice and invoice_posting_date_map[d.against_invoice] or ""
|
||||
payment_amount = flt(d.credit) or -1 * flt(d.debit)
|
||||
|
||||
row = [d.name, d.party_type, d.party, d.posting_date, d.against_voucher or d.against_invoice,
|
||||
row = [d.name, d.party_type, d.party, d.posting_date, d.reference_name,
|
||||
against_date, d.debit, d.credit, d.cheque_no, d.cheque_date, d.remark]
|
||||
|
||||
if d.against_voucher or d.against_invoice:
|
||||
if d.reference_name:
|
||||
row += get_ageing_data(30, 60, 90, d.posting_date, against_date, payment_amount)
|
||||
else:
|
||||
row += ["", "", "", "", ""]
|
||||
@@ -37,7 +35,7 @@ def execute(filters=None):
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def validate_filters(filters):
|
||||
if (filters.get("payment_type") == "Incoming" and filters.get("party_type") == "Supplier") or \
|
||||
(filters.get("payment_type") == "Outgoing" and filters.get("party_type") == "Customer"):
|
||||
@@ -45,9 +43,9 @@ def validate_filters(filters):
|
||||
.format(filters.payment_type, filters.party_type))
|
||||
|
||||
def get_columns(filters):
|
||||
return [_("Journal Entry") + ":Link/Journal Entry:140",
|
||||
_("Party Type") + ":Link/DocType:100", _("Party") + ":Dynamic Link/Party Type:140",
|
||||
_("Posting Date") + ":Date:100",
|
||||
return [_("Journal Entry") + ":Link/Journal Entry:140",
|
||||
_("Party Type") + "::100", _("Party") + ":Dynamic Link/Party Type:140",
|
||||
_("Posting Date") + ":Date:100",
|
||||
_("Against Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"),
|
||||
_("Against Invoice Posting Date") + ":Date:130", _("Debit") + ":Currency:120", _("Credit") + ":Currency:120",
|
||||
_("Reference No") + "::100", _("Reference Date") + ":Date:100", _("Remarks") + "::150", _("Age") +":Int:40",
|
||||
@@ -62,7 +60,7 @@ def get_conditions(filters):
|
||||
filters["party_type"] = "Supplier"
|
||||
else:
|
||||
filters["party_type"] = "Customer"
|
||||
|
||||
|
||||
if filters.get("party_type"):
|
||||
conditions.append("jvd.party_type=%(party_type)s")
|
||||
|
||||
@@ -82,7 +80,7 @@ def get_conditions(filters):
|
||||
def get_entries(filters):
|
||||
conditions = get_conditions(filters)
|
||||
entries = frappe.db.sql("""select jv.name, jvd.party_type, jvd.party, jv.posting_date,
|
||||
jvd.against_voucher, jvd.against_invoice, jvd.debit, jvd.credit,
|
||||
jvd.reference_type, jvd.reference_name, jvd.debit, jvd.credit,
|
||||
jv.cheque_no, jv.cheque_date, jv.remark
|
||||
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
|
||||
where jvd.parent = jv.name and jv.docstatus=1 %s order by jv.name DESC""" %
|
||||
|
||||
@@ -62,7 +62,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None):
|
||||
|
||||
cond = []
|
||||
if date:
|
||||
cond.append("posting_date <= '%s'" % date)
|
||||
cond.append("posting_date <= '%s'" % frappe.db.escape(date))
|
||||
else:
|
||||
# get balance of all entries that exist
|
||||
date = nowdate()
|
||||
@@ -95,19 +95,20 @@ def get_balance_on(account=None, date=None, party_type=None, party=None):
|
||||
and ac.lft >= %s and ac.rgt <= %s
|
||||
)""" % (acc.lft, acc.rgt))
|
||||
else:
|
||||
cond.append("""gle.account = "%s" """ % (account.replace('"', '\\"'), ))
|
||||
cond.append("""gle.account = "%s" """ % (frappe.db.escape(account),))
|
||||
|
||||
if party_type and party:
|
||||
cond.append("""gle.party_type = "%s" and gle.party = "%s" """ %
|
||||
(party_type.replace('"', '\\"'), party.replace('"', '\\"')))
|
||||
(frappe.db.escape(party_type), frappe.db.escape(party)))
|
||||
|
||||
bal = frappe.db.sql("""
|
||||
SELECT sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
|
||||
FROM `tabGL Entry` gle
|
||||
WHERE %s""" % " and ".join(cond))[0][0]
|
||||
if account or (party_type and party):
|
||||
bal = frappe.db.sql("""
|
||||
SELECT sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
|
||||
FROM `tabGL Entry` gle
|
||||
WHERE %s""" % " and ".join(cond))[0][0]
|
||||
|
||||
# if bal is None, return 0
|
||||
return flt(bal)
|
||||
# if bal is None, return 0
|
||||
return flt(bal)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_ac(args=None):
|
||||
@@ -142,13 +143,6 @@ def reconcile_against_document(args):
|
||||
for d in args:
|
||||
check_if_jv_modified(d)
|
||||
validate_allocated_amount(d)
|
||||
against_fld = {
|
||||
'Journal Entry' : 'against_jv',
|
||||
'Sales Invoice' : 'against_invoice',
|
||||
'Purchase Invoice' : 'against_voucher'
|
||||
}
|
||||
|
||||
d['against_fld'] = against_fld[d['against_voucher_type']]
|
||||
|
||||
# cancel JV
|
||||
jv_obj = frappe.get_doc('Journal Entry', d['voucher_no'])
|
||||
@@ -173,8 +167,7 @@ def check_if_jv_modified(args):
|
||||
select t2.{dr_or_cr} from `tabJournal Entry` t1, `tabJournal Entry Account` t2
|
||||
where t1.name = t2.parent and t2.account = %(account)s
|
||||
and t2.party_type = %(party_type)s and t2.party = %(party)s
|
||||
and ifnull(t2.against_voucher, '')=''
|
||||
and ifnull(t2.against_invoice, '')='' and ifnull(t2.against_jv, '')=''
|
||||
and ifnull(t2.reference_type, '') in ("", "Sales Order", "Purchase Order")
|
||||
and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
|
||||
and t1.docstatus=1 """.format(dr_or_cr = args.get("dr_or_cr")), args)
|
||||
|
||||
@@ -193,7 +186,12 @@ def update_against_doc(d, jv_obj):
|
||||
"""
|
||||
jv_detail = jv_obj.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
||||
jv_detail.set(d["dr_or_cr"], d["allocated_amt"])
|
||||
jv_detail.set(d["against_fld"], d["against_voucher"])
|
||||
|
||||
original_reference_type = jv_detail.reference_type
|
||||
original_reference_name = jv_detail.reference_name
|
||||
|
||||
jv_detail.set("reference_type", d["against_voucher_type"])
|
||||
jv_detail.set("reference_name", d["against_voucher"])
|
||||
|
||||
if d['allocated_amt'] < d['unadjusted_amt']:
|
||||
jvd = frappe.db.sql("""select cost_center, balance, against_account, is_advance
|
||||
@@ -208,6 +206,8 @@ def update_against_doc(d, jv_obj):
|
||||
ch.set(d['dr_or_cr'], flt(d['unadjusted_amt']) - flt(d['allocated_amt']))
|
||||
ch.set(d['dr_or_cr']== 'debit' and 'credit' or 'debit', 0)
|
||||
ch.against_account = cstr(jvd[0][2])
|
||||
ch.reference_type = original_reference_type
|
||||
ch.reference_name = original_reference_name
|
||||
ch.is_advance = cstr(jvd[0][3])
|
||||
ch.docstatus = 1
|
||||
|
||||
@@ -215,15 +215,16 @@ def update_against_doc(d, jv_obj):
|
||||
jv_obj.flags.ignore_validate_update_after_submit = True
|
||||
jv_obj.save()
|
||||
|
||||
def remove_against_link_from_jv(ref_type, ref_no, against_field):
|
||||
def remove_against_link_from_jv(ref_type, ref_no):
|
||||
linked_jv = frappe.db.sql_list("""select parent from `tabJournal Entry Account`
|
||||
where `%s`=%s and docstatus < 2""" % (against_field, "%s"), (ref_no))
|
||||
where reference_type=%s and reference_name=%s and docstatus < 2""", (ref_type, ref_no))
|
||||
|
||||
if linked_jv:
|
||||
frappe.db.sql("""update `tabJournal Entry Account` set `%s`=null,
|
||||
frappe.db.sql("""update `tabJournal Entry Account`
|
||||
set reference_type=null, reference_name = null,
|
||||
modified=%s, modified_by=%s
|
||||
where `%s`=%s and docstatus < 2""" % (against_field, "%s", "%s", against_field, "%s"),
|
||||
(now(), frappe.session.user, ref_no))
|
||||
where reference_type=%s and reference_name=%s
|
||||
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
|
||||
|
||||
frappe.db.sql("""update `tabGL Entry`
|
||||
set against_voucher_type=null, against_voucher=null,
|
||||
|
||||
@@ -44,12 +44,12 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
if(me.frm.doc.is_subcontracted == "Yes") {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{ 'is_sub_contracted_item': 'Yes' }
|
||||
filters:{ 'is_sub_contracted_item': 1 }
|
||||
}
|
||||
} else {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: { 'is_purchase_item': 'Yes' }
|
||||
filters: { 'is_purchase_item': 1 }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -56,24 +56,25 @@ class PurchaseCommon(BuyingController):
|
||||
d.set(x, f_lst[x])
|
||||
|
||||
item = frappe.db.sql("""select is_stock_item, is_purchase_item,
|
||||
is_sub_contracted_item, end_of_life from `tabItem` where name=%s""", d.item_code)
|
||||
is_sub_contracted_item, end_of_life from `tabItem` where name=%s""",
|
||||
d.item_code, as_dict=1)[0]
|
||||
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||
validate_end_of_life(d.item_code, item[0][3])
|
||||
validate_end_of_life(d.item_code, item.end_of_life)
|
||||
|
||||
# validate stock item
|
||||
if item[0][0]=='Yes' and d.qty and not d.warehouse:
|
||||
if item.is_stock_item==1 and d.qty and not d.warehouse:
|
||||
frappe.throw(_("Warehouse is mandatory for stock Item {0} in row {1}").format(d.item_code, d.idx))
|
||||
|
||||
# validate purchase item
|
||||
if not (obj.doctype=="Material Request" and getattr(obj, "material_request_type", None)=="Material Transfer"):
|
||||
if item[0][1] != 'Yes' and item[0][2] != 'Yes':
|
||||
if item.is_purchase_item != 1 and item.is_sub_contracted_item != 1:
|
||||
frappe.throw(_("{0} must be a Purchased or Sub-Contracted Item in row {1}").format(d.item_code, d.idx))
|
||||
|
||||
items.append(cstr(d.item_code))
|
||||
if items and len(items) != len(set(items)):
|
||||
frappe.msgprint(_("Warning: Same item has been entered multiple times."))
|
||||
|
||||
|
||||
|
||||
def check_for_stopped_status(self, doctype, docname):
|
||||
stopped = frappe.db.sql("""select name from `tab%s` where name = %s and
|
||||
|
||||
@@ -5,6 +5,14 @@ frappe.provide("erpnext.buying");
|
||||
|
||||
{% include 'buying/doctype/purchase_common/purchase_common.js' %};
|
||||
|
||||
frappe.ui.form.on("Purchase Order", {
|
||||
onload: function(frm) {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
||||
refresh: function(doc, cdt, cdn) {
|
||||
var me = this;
|
||||
@@ -12,31 +20,38 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
// this.frm.dashboard.reset();
|
||||
|
||||
if(doc.docstatus == 1 && doc.status != 'Stopped') {
|
||||
|
||||
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100)
|
||||
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Purchase Order']);
|
||||
|
||||
if(flt(doc.per_billed)==0) {
|
||||
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry);
|
||||
}
|
||||
|
||||
if(flt(doc.per_received, 2) < 100) {
|
||||
cur_frm.add_custom_button(__('Make Purchase Receipt'), this.make_purchase_receipt);
|
||||
|
||||
cur_frm.add_custom_button(__('Receive'), this.make_purchase_receipt).addClass("btn-primary");
|
||||
|
||||
if(doc.is_subcontracted==="Yes") {
|
||||
cur_frm.add_custom_button(__('Transfer Material to Supplier'), this.make_stock_entry);
|
||||
}
|
||||
}
|
||||
|
||||
if(flt(doc.per_billed, 2) < 100)
|
||||
cur_frm.add_custom_button(__('Make Invoice'), this.make_purchase_invoice);
|
||||
|
||||
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100)
|
||||
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Purchase Order']);
|
||||
cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice);
|
||||
|
||||
|
||||
} else if(doc.docstatus===0) {
|
||||
cur_frm.cscript.add_from_mappers();
|
||||
}
|
||||
|
||||
if(doc.docstatus == 1 && doc.status == 'Stopped')
|
||||
cur_frm.add_custom_button(__('Unstop Purchase Order'), cur_frm.cscript['Unstop Purchase Order']);
|
||||
cur_frm.add_custom_button(__('Unstop'), cur_frm.cscript['Unstop Purchase Order']);
|
||||
},
|
||||
|
||||
make_stock_entry: function() {
|
||||
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
|
||||
var me = this;
|
||||
|
||||
|
||||
if(items.length===1) {
|
||||
me._make_stock_entry(items[0]);
|
||||
return;
|
||||
@@ -126,7 +141,21 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
items_add: function(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]);
|
||||
},
|
||||
|
||||
make_bank_entry: function() {
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_from_purchase_order",
|
||||
args: {
|
||||
"purchase_order": cur_frm.doc.name
|
||||
},
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
|
||||
@@ -153,7 +153,7 @@ class PurchaseOrder(BuyingController):
|
||||
item_wh_list = []
|
||||
for d in self.get("items"):
|
||||
if (not po_item_rows or d.name in po_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
|
||||
and frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse:
|
||||
and frappe.db.get_value("Item", d.item_code, "is_stock_item") and d.warehouse:
|
||||
item_wh_list.append([d.item_code, d.warehouse])
|
||||
|
||||
for item_code, warehouse in item_wh_list:
|
||||
|
||||
@@ -5,7 +5,13 @@ frappe.listview_settings['Purchase Order'] = {
|
||||
if(doc.status==="Stopped") {
|
||||
return [__("Stopped"), "darkgrey", "status,=,Stopped"];
|
||||
} else if(flt(doc.per_received) < 100 && doc.status!=="Stopped") {
|
||||
return [__("Not Received"), "orange", "per_received,<,100|status,!=,Stopped"];
|
||||
if(flt(doc.per_billed) < 100) {
|
||||
return [__("To Receive and Bill"), "orange",
|
||||
"per_received,<,100|per_billed,<,100|status,!=,Stopped"];
|
||||
} else {
|
||||
return [__("To Receive"), "orange",
|
||||
"per_received,<,100|per_billed,=,100|status,!=,Stopped"];
|
||||
}
|
||||
} else if(flt(doc.per_received) == 100 && flt(doc.per_billed) < 100 && doc.status!=="Stopped") {
|
||||
return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Stopped"];
|
||||
} else if(flt(doc.per_received) == 100 && flt(doc.per_billed) == 100 && doc.status!=="Stopped") {
|
||||
|
||||
@@ -46,6 +46,8 @@ cur_frm.cscript.make_dashboard = function(doc) {
|
||||
+ '</b> / <span class="text-muted">' + __("Total Unpaid") + ": <b>"
|
||||
+ format_currency(r.message.total_unpaid, r.message.company_currency[0])
|
||||
+ '</b></span>');
|
||||
} else {
|
||||
cur_frm.dashboard.set_headline("");
|
||||
}
|
||||
}
|
||||
cur_frm.dashboard.set_badge_count(r.message);
|
||||
|
||||
@@ -54,6 +54,13 @@
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "is_frozen",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Frozen",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "address_contacts",
|
||||
@@ -172,7 +179,7 @@
|
||||
],
|
||||
"icon": "icon-user",
|
||||
"idx": 1,
|
||||
"modified": "2015-07-13 05:28:29.121285",
|
||||
"modified": "2015-07-17 09:39:05.318826",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
9
erpnext/change_log/v5/v5_4_0.md
Normal file
9
erpnext/change_log/v5/v5_4_0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
- Sales / Purchase Return Enahancement - **Sponsored by [Strella Consulting Sdn Bhd](http://www.strellagroup.com)**
|
||||
* Now you can make Return entry by creating Delivery Note, Purchase Receipt or Sales / Purchase Invoice with negative quantity
|
||||
- **Item** form cleanups: "Yes" / "No" type fields changed to checkboxes.<br>
|
||||
**Warning**: This could break your 3rd party integrations with Item, if any
|
||||
- Leave Application: Consideration of **holidays** in calculation of 'Number of Days' is now optional. You can set it in Leave Type record.
|
||||
- Customer / Supplier can be **freezed** now
|
||||
- Fix: Reserved qty calculation while delivering Product Bundle via Sales Invoice
|
||||
- Fix: Deleted stock ledger entries on cancellation of Sales Invoice while Product Bundle delivered
|
||||
- Fix: Fetch default expense account and cost center from item based on selected company
|
||||
2
erpnext/change_log/v5/v5_5_0.md
Normal file
2
erpnext/change_log/v5/v5_5_0.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Automatically insert Price List Rate in Price List if added in transaction if permission exists and allowed from Stock Settings
|
||||
- Product Bundle now allowed for all Items (stock or non-stock)
|
||||
6
erpnext/change_log/v5/v5_6_0.md
Normal file
6
erpnext/change_log/v5/v5_6_0.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- For referencing a line in **Journal Entry**, now you can reference by the **Reference Type** and **Reference Name** columns, instead of "Against Sales Invoice", "Against Purchase Invoice", etc.
|
||||
- Additional Costs in Stock Entry **[Sponsored by PT. Ridho Sribumi Sejahtera]**
|
||||
Now additional costs like shipping charges, operating costs etc can be added in Stock Entry in item valuation
|
||||
- **Update Finished Goods** in Production Order can now use the items from **Transfer Materials for Manufacture** step instead of items from the Bill of Materials. This can be configured in Manufacturing Settings
|
||||
- Added field **Tax ID** in Customer
|
||||
- Bug fixes in Item, Time Log Batch, Pricing Rule, Salary Slip, Address and Stock Entry
|
||||
@@ -11,16 +11,21 @@ from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
|
||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||
|
||||
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
|
||||
|
||||
class CustomerFrozen(frappe.ValidationError): pass
|
||||
|
||||
class AccountsController(TransactionBase):
|
||||
def validate(self):
|
||||
if self.get("_action") and self._action != "update_after_submit":
|
||||
self.set_missing_values(for_validate=True)
|
||||
self.validate_date_with_fiscal_year()
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
self.calculate_taxes_and_totals()
|
||||
if not self.meta.get_field("is_return") or not self.is_return:
|
||||
self.validate_value("base_grand_total", ">=", 0)
|
||||
|
||||
|
||||
validate_return(self)
|
||||
self.set_total_in_words()
|
||||
|
||||
@@ -33,6 +38,8 @@ class AccountsController(TransactionBase):
|
||||
if self.meta.get_field("taxes_and_charges"):
|
||||
self.validate_enabled_taxes_and_charges()
|
||||
|
||||
self.validate_party()
|
||||
|
||||
def on_submit(self):
|
||||
if self.meta.get_field("is_recurring"):
|
||||
convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date"))
|
||||
@@ -81,7 +88,7 @@ class AccountsController(TransactionBase):
|
||||
if self.doctype == "Sales Invoice":
|
||||
if not self.due_date:
|
||||
frappe.throw(_("Due Date is mandatory"))
|
||||
|
||||
|
||||
validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company)
|
||||
elif self.doctype == "Purchase Invoice":
|
||||
validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company)
|
||||
@@ -137,7 +144,8 @@ class AccountsController(TransactionBase):
|
||||
|
||||
for fieldname, value in ret.items():
|
||||
if item.meta.get_field(fieldname) and \
|
||||
item.get(fieldname) is None and value is not None:
|
||||
(item.get(fieldname) is None or fieldname in force_item_fields) \
|
||||
and value is not None:
|
||||
item.set(fieldname, value)
|
||||
|
||||
if fieldname == "cost_center" and item.meta.get_field("cost_center") \
|
||||
@@ -145,9 +153,10 @@ class AccountsController(TransactionBase):
|
||||
item.set(fieldname, value)
|
||||
|
||||
if ret.get("pricing_rule"):
|
||||
for field in ["base_price_list_rate", "price_list_rate",
|
||||
"discount_percentage", "base_rate", "rate"]:
|
||||
item.set(field, ret.get(field))
|
||||
item.set("discount_percentage", ret.get("discount_percentage"))
|
||||
if ret.get("pricing_rule_for") == "Price":
|
||||
item.set("pricing_list_rate", ret.get("pricing_list_rate"))
|
||||
|
||||
|
||||
def set_taxes(self):
|
||||
if not self.meta.get_field("taxes"):
|
||||
@@ -203,29 +212,32 @@ class AccountsController(TransactionBase):
|
||||
and ifnull(allocated_amount, 0) = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
|
||||
|
||||
def get_advances(self, account_head, party_type, party, child_doctype, parentfield, dr_or_cr, against_order_field):
|
||||
so_list = list(set([d.get(against_order_field) for d in self.get("items") if d.get(against_order_field)]))
|
||||
cond = ""
|
||||
if so_list:
|
||||
cond = "or (ifnull(t2.%s, '') in (%s))" % ("against_" + against_order_field, ', '.join(['%s']*len(so_list)))
|
||||
"""Returns list of advances against Account, Party, Reference"""
|
||||
order_list = list(set([d.get(against_order_field) for d in self.get("items") if d.get(against_order_field)]))
|
||||
|
||||
if not order_list:
|
||||
return
|
||||
|
||||
in_placeholder = ', '.join(['%s'] * len(order_list))
|
||||
|
||||
# conver sales_order to "Sales Order"
|
||||
reference_type = against_order_field.replace("_", " ").title()
|
||||
|
||||
res = frappe.db.sql("""
|
||||
select
|
||||
t1.name as jv_no, t1.remark, t2.{0} as amount, t2.name as jv_detail_no, `against_{1}` as against_order
|
||||
t1.name as jv_no, t1.remark, t2.{0} as amount, t2.name as jv_detail_no,
|
||||
reference_name as against_order
|
||||
from
|
||||
`tabJournal Entry` t1, `tabJournal Entry Account` t2
|
||||
where
|
||||
t1.name = t2.parent and t2.account = %s
|
||||
and t2.party_type=%s and t2.party=%s
|
||||
and t2.party_type = %s and t2.party = %s
|
||||
and t2.is_advance = 'Yes' and t1.docstatus = 1
|
||||
and ((
|
||||
ifnull(t2.against_voucher, '') = ''
|
||||
and ifnull(t2.against_invoice, '') = ''
|
||||
and ifnull(t2.against_jv, '') = ''
|
||||
and ifnull(t2.against_sales_order, '') = ''
|
||||
and ifnull(t2.against_purchase_order, '') = ''
|
||||
) {2})
|
||||
order by t1.posting_date""".format(dr_or_cr, against_order_field, cond),
|
||||
[account_head, party_type, party] + so_list, as_dict=1)
|
||||
and (
|
||||
ifnull(t2.reference_type, '')=''
|
||||
or (t2.reference_type = %s and ifnull(t2.reference_name, '') in ({1})))
|
||||
order by t1.posting_date""".format(dr_or_cr, in_placeholder),
|
||||
[account_head, party_type, party, reference_type] + order_list, as_dict=1)
|
||||
|
||||
self.set(parentfield, [])
|
||||
for d in res:
|
||||
@@ -238,25 +250,26 @@ class AccountsController(TransactionBase):
|
||||
"allocated_amount": flt(d.amount) if d.against_order else 0
|
||||
})
|
||||
|
||||
def validate_advance_jv(self, advance_table_fieldname, against_order_field):
|
||||
def validate_advance_jv(self, reference_type):
|
||||
against_order_field = frappe.scrub(reference_type)
|
||||
order_list = list(set([d.get(against_order_field) for d in self.get("items") if d.get(against_order_field)]))
|
||||
if order_list:
|
||||
account = self.get("debit_to" if self.doctype=="Sales Invoice" else "credit_to")
|
||||
|
||||
jv_against_order = frappe.db.sql("""select parent, %s as against_order
|
||||
jv_against_order = frappe.db.sql("""select parent, reference_name as against_order
|
||||
from `tabJournal Entry Account`
|
||||
where docstatus=1 and account=%s and ifnull(is_advance, 'No') = 'Yes'
|
||||
and ifnull(against_sales_order, '') in (%s)
|
||||
group by parent, against_sales_order""" %
|
||||
("against_" + against_order_field, '%s', ', '.join(['%s']*len(order_list))),
|
||||
tuple([account] + order_list), as_dict=1)
|
||||
and reference_type=%s
|
||||
and ifnull(reference_name, '') in ({0})
|
||||
group by parent, reference_name""".format(', '.join(['%s']*len(order_list))),
|
||||
tuple([account, reference_type] + order_list), as_dict=1)
|
||||
|
||||
if jv_against_order:
|
||||
order_jv_map = {}
|
||||
for d in jv_against_order:
|
||||
order_jv_map.setdefault(d.against_order, []).append(d.parent)
|
||||
|
||||
advance_jv_against_si = [d.journal_entry for d in self.get(advance_table_fieldname)]
|
||||
advance_jv_against_si = [d.journal_entry for d in self.get("advances")]
|
||||
|
||||
for order, jv_list in order_jv_map.items():
|
||||
for jv in jv_list:
|
||||
@@ -302,7 +315,7 @@ class AccountsController(TransactionBase):
|
||||
item_codes = list(set(item.item_code for item in self.get("items")))
|
||||
if item_codes:
|
||||
stock_items = [r[0] for r in frappe.db.sql("""select name
|
||||
from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \
|
||||
from `tabItem` where name in (%s) and is_stock_item=1""" % \
|
||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
||||
|
||||
return stock_items
|
||||
@@ -310,10 +323,8 @@ class AccountsController(TransactionBase):
|
||||
def set_total_advance_paid(self):
|
||||
if self.doctype == "Sales Order":
|
||||
dr_or_cr = "credit"
|
||||
against_field = "against_sales_order"
|
||||
else:
|
||||
dr_or_cr = "debit"
|
||||
against_field = "against_purchase_order"
|
||||
|
||||
advance_paid = frappe.db.sql("""
|
||||
select
|
||||
@@ -321,8 +332,10 @@ class AccountsController(TransactionBase):
|
||||
from
|
||||
`tabJournal Entry Account`
|
||||
where
|
||||
{against_field} = %s and docstatus = 1 and is_advance = "Yes" """.format(dr_or_cr=dr_or_cr, \
|
||||
against_field=against_field), self.name)
|
||||
reference_type = %s and
|
||||
reference_name = %s and
|
||||
docstatus = 1 and is_advance = "Yes" """.format(dr_or_cr=dr_or_cr),
|
||||
(self.doctype, self.name))
|
||||
|
||||
if advance_paid:
|
||||
advance_paid = flt(advance_paid[0][0], self.precision("advance_paid"))
|
||||
@@ -340,6 +353,23 @@ class AccountsController(TransactionBase):
|
||||
|
||||
return self._abbr
|
||||
|
||||
def validate_party(self):
|
||||
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
|
||||
if frozen_accounts_modifier in frappe.get_roles():
|
||||
return
|
||||
|
||||
party_type = None
|
||||
if self.meta.get_field("customer"):
|
||||
party_type = 'Customer'
|
||||
|
||||
elif self.meta.get_field("supplier"):
|
||||
party_type = 'Supplier'
|
||||
|
||||
if party_type:
|
||||
party = self.get(party_type.lower())
|
||||
if frappe.db.get_value(party_type, party, "is_frozen"):
|
||||
frappe.throw("{0} {1} is frozen".format(party_type, party), CustomerFrozen)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tax_rate(account_head):
|
||||
return frappe.db.get_value("Account", account_head, "tax_rate")
|
||||
|
||||
@@ -112,8 +112,8 @@ class BuyingController(StockController):
|
||||
valuation_amount_adjustment -= item.item_tax_amount
|
||||
|
||||
self.round_floats_in(item)
|
||||
|
||||
item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
|
||||
if flt(item.conversion_factor)==0:
|
||||
item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
|
||||
|
||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||
rm_supp_cost = flt(item.rm_supp_cost) if self.doctype=="Purchase Receipt" else 0.0
|
||||
@@ -238,8 +238,8 @@ class BuyingController(StockController):
|
||||
t2.rate, t2.stock_uom, t2.name, t2.description
|
||||
from `tabBOM` t1, `tabBOM Item` t2, tabItem t3
|
||||
where t2.parent = t1.name and t1.item = %s
|
||||
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
|
||||
and t2.item_code = t3.name and ifnull(t3.is_stock_item, 'No') = 'Yes'""", (item_code, bom), as_dict=1)
|
||||
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
|
||||
and t2.item_code = t3.name and t3.is_stock_item = 1""", (item_code, bom), as_dict=1)
|
||||
|
||||
if not bom_items:
|
||||
msgprint(_("Specified BOM {0} does not exist for Item {1}").format(bom, item_code), raise_exception=1)
|
||||
@@ -254,7 +254,7 @@ class BuyingController(StockController):
|
||||
self.get("items")))
|
||||
if item_codes:
|
||||
self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name
|
||||
from `tabItem` where name in (%s) and is_sub_contracted_item='Yes'""" % \
|
||||
from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \
|
||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
@@ -8,7 +8,6 @@ from frappe.utils import flt, get_datetime, format_datetime
|
||||
|
||||
class StockOverReturnError(frappe.ValidationError): pass
|
||||
|
||||
|
||||
def validate_return(doc):
|
||||
if not doc.meta.get_field("is_return") or not doc.is_return:
|
||||
return
|
||||
@@ -50,13 +49,19 @@ def validate_return_against(doc):
|
||||
.format(doc.return_against))
|
||||
|
||||
def validate_returned_items(doc):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
valid_items = frappe._dict()
|
||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty, rate from `tab{0} Item`
|
||||
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
select_fields = "item_code, sum(qty) as qty, rate" if doc.doctype=="Purchase Invoice" \
|
||||
else "item_code, sum(qty) as qty, rate, serial_no, batch_no"
|
||||
|
||||
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s
|
||||
group by item_code""".format(select_fields, doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty from `tabPacked Item`
|
||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty, serial_no, batch_no from `tabPacked Item`
|
||||
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
@@ -81,8 +86,20 @@ def validate_returned_items(doc):
|
||||
elif ref.rate and flt(d.rate) != ref.rate:
|
||||
frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
|
||||
.format(d.idx, doc.doctype, doc.return_against))
|
||||
elif ref.batch_no and d.batch_no != ref.batch_no:
|
||||
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
|
||||
.format(d.idx, doc.doctype, doc.return_against))
|
||||
elif ref.serial_no:
|
||||
if not d.serial_no:
|
||||
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
|
||||
else:
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
ref_serial_nos = get_serial_nos(ref.serial_no)
|
||||
for s in serial_nos:
|
||||
if s not in ref_serial_nos:
|
||||
frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}")
|
||||
.format(d.idx, s, doc.doctype, doc.return_against))
|
||||
|
||||
|
||||
items_returned = True
|
||||
|
||||
if not items_returned:
|
||||
@@ -107,8 +124,15 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
doc.is_return = 1
|
||||
doc.return_against = source.name
|
||||
doc.ignore_pricing_rule = 1
|
||||
if doctype == "Sales Invoice":
|
||||
doc.is_pos = 0
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
if tax.charge_type == "Actual":
|
||||
tax.tax_amount = -1 * tax.tax_amount
|
||||
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1* source_doc.qty
|
||||
if doctype == "Purchase Receipt":
|
||||
@@ -127,9 +151,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
},
|
||||
doctype +" Item": {
|
||||
"doctype": doctype + " Item",
|
||||
"fields": {
|
||||
"field_map": {
|
||||
"purchase_order": "purchase_order",
|
||||
"purchase_receipt": "purchase_receipt"
|
||||
"purchase_receipt": "purchase_receipt",
|
||||
"serial_no": "serial_no",
|
||||
"batch_no": "batch_no"
|
||||
},
|
||||
"postprocess": update_item
|
||||
},
|
||||
|
||||
@@ -174,12 +174,14 @@ class SellingController(StockController):
|
||||
if flt(d.qty) > flt(d.delivered_qty):
|
||||
reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty)
|
||||
|
||||
elif self.doctype == "Delivery Note" and d.against_sales_order and not self.is_return:
|
||||
elif (((self.doctype == "Delivery Note" and d.against_sales_order)
|
||||
or (self.doctype == "Sales Invoice" and d.sales_order and self.update_stock))
|
||||
and not self.is_return):
|
||||
# if SO qty is 10 and there is tolerance of 20%, then it will allow DN of 12.
|
||||
# But in this case reserved qty should only be reduced by 10 and not 12
|
||||
|
||||
already_delivered_qty = self.get_already_delivered_qty(self.name,
|
||||
d.against_sales_order, d.so_detail)
|
||||
d.against_sales_order if self.doctype=="Delivery Note" else d.sales_order, d.so_detail)
|
||||
so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.so_detail)
|
||||
|
||||
if already_delivered_qty + d.qty > so_qty:
|
||||
@@ -221,12 +223,21 @@ class SellingController(StockController):
|
||||
return frappe.db.sql("""select name from `tabProduct Bundle`
|
||||
where new_item_code=%s and docstatus != 2""", item_code)
|
||||
|
||||
def get_already_delivered_qty(self, dn, so, so_detail):
|
||||
qty = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item`
|
||||
def get_already_delivered_qty(self, current_docname, so, so_detail):
|
||||
delivered_via_dn = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item`
|
||||
where so_detail = %s and docstatus = 1
|
||||
and against_sales_order = %s
|
||||
and parent != %s""", (so_detail, so, dn))
|
||||
return qty and flt(qty[0][0]) or 0.0
|
||||
and parent != %s""", (so_detail, so, current_docname))
|
||||
|
||||
delivered_via_si = frappe.db.sql("""select sum(qty) from `tabSales Invoice Item`
|
||||
where so_detail = %s and docstatus = 1
|
||||
and sales_order = %s
|
||||
and parent != %s""", (so_detail, so, current_docname))
|
||||
|
||||
total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) \
|
||||
+ (flt(delivered_via_si[0][0]) if delivered_via_si else 0)
|
||||
|
||||
return total_delivered_qty
|
||||
|
||||
def get_so_qty_and_warehouse(self, so_detail):
|
||||
so_item = frappe.db.sql("""select qty, warehouse from `tabSales Order Item`
|
||||
@@ -248,7 +259,7 @@ def check_active_sales_items(obj):
|
||||
item = frappe.db.sql("""select docstatus, is_sales_item,
|
||||
is_service_item, income_account from tabItem where name = %s""",
|
||||
d.item_code, as_dict=True)[0]
|
||||
if item.is_sales_item == 'No' and item.is_service_item == 'No':
|
||||
if item.is_sales_item == 0 and item.is_service_item == 0:
|
||||
frappe.throw(_("Item {0} must be Sales or Service Item in {1}").format(d.item_code, d.idx))
|
||||
if getattr(d, "income_account", None) and not item.income_account:
|
||||
frappe.db.set_value("Item", d.item_code, "income_account",
|
||||
|
||||
@@ -9,6 +9,8 @@ import frappe.defaults
|
||||
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
|
||||
from erpnext.stock.utils import update_bin
|
||||
|
||||
|
||||
class StockController(AccountsController):
|
||||
def make_gl_entries(self, repost_future_gle=True):
|
||||
@@ -212,11 +214,11 @@ class StockController(AccountsController):
|
||||
item_codes = list(set([d.item_code for d in self.get("items")]))
|
||||
if item_codes:
|
||||
serialized_items = frappe.db.sql_list("""select name from `tabItem`
|
||||
where has_serial_no='Yes' and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
|
||||
where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
|
||||
tuple(item_codes))
|
||||
|
||||
return serialized_items
|
||||
|
||||
|
||||
def get_incoming_rate_for_sales_return(self, item_code, against_document):
|
||||
incoming_rate = 0.0
|
||||
if against_document and item_code:
|
||||
@@ -228,6 +230,23 @@ class StockController(AccountsController):
|
||||
|
||||
return incoming_rate
|
||||
|
||||
def update_reserved_qty(self, d):
|
||||
if d['reserved_qty'] < 0 :
|
||||
# Reduce reserved qty from reserved warehouse mentioned in so
|
||||
if not d["reserved_warehouse"]:
|
||||
frappe.throw(_("Delivery Warehouse is missing in Sales Order"))
|
||||
|
||||
args = {
|
||||
"item_code": d['item_code'],
|
||||
"warehouse": d["reserved_warehouse"],
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"reserved_qty": (self.docstatus==1 and 1 or -1)*flt(d['reserved_qty']),
|
||||
"posting_date": self.posting_date,
|
||||
"is_amended": self.amended_from and 'Yes' or 'No'
|
||||
}
|
||||
update_bin(args)
|
||||
|
||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||
warehouse_account=None):
|
||||
def _delete_gl_entries(voucher_type, voucher_no):
|
||||
|
||||
@@ -77,9 +77,6 @@ class calculate_taxes_and_totals(object):
|
||||
if not self.discount_amount_applied:
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self.doc)
|
||||
|
||||
if self.doc.meta.get_field("is_return") and self.doc.is_return and tax.charge_type == "Actual":
|
||||
tax.tax_amount = -1 * tax.tax_amount
|
||||
|
||||
tax.item_wise_tax_detail = {}
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
@@ -222,7 +219,7 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
# adjust Discount Amount loss in last tax iteration
|
||||
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
||||
and self.doc.discount_amount:
|
||||
and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
|
||||
self.adjust_discount_amount_loss(tax)
|
||||
|
||||
|
||||
@@ -306,9 +303,9 @@ class calculate_taxes_and_totals(object):
|
||||
for tax in self.doc.get("taxes"):
|
||||
if tax.category in ["Valuation and Total", "Total"]:
|
||||
if tax.add_deduct_tax == "Add":
|
||||
self.doc.taxes_and_charges_added += flt(tax.tax_amount)
|
||||
self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
|
||||
else:
|
||||
self.doc.taxes_and_charges_deducted += flt(tax.tax_amount)
|
||||
self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
|
||||
|
||||
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||
|
||||
|
||||
@@ -138,8 +138,6 @@ def subscribe(email):
|
||||
_("Click here to verify")
|
||||
)
|
||||
|
||||
print url
|
||||
|
||||
content = """
|
||||
<p>{0}. {1}.</p>
|
||||
<p><a href="{2}">{3}</a></p>
|
||||
|
||||
@@ -34,10 +34,8 @@ class NewsletterList(Document):
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
added += 1
|
||||
except Exception, e:
|
||||
# already added, ignore
|
||||
if e.args[0]!=1062:
|
||||
raise
|
||||
except frappe.UniqueValidationError:
|
||||
pass
|
||||
|
||||
frappe.msgprint(_("{0} subscribers added").format(added))
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: me.frm.doc.enquiry_type === "Maintenance" ?
|
||||
{"is_service_item": "Yes"} : {"is_sales_item": "Yes"}
|
||||
{"is_service_item": 1} : {"is_sales_item":1}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -80,9 +80,23 @@ cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
||||
if(doc.status!=="Quotation")
|
||||
cur_frm.add_custom_button(__('Opportunity Lost'),
|
||||
cur_frm.cscript['Declare Opportunity Lost'], "icon-remove", "btn-default");
|
||||
|
||||
|
||||
}
|
||||
|
||||
var frm = cur_frm;
|
||||
if(frm.perm[0].write && doc.docstatus==0) {
|
||||
if(frm.doc.status==="Open") {
|
||||
frm.add_custom_button("Close", function() {
|
||||
frm.set_value("status", "Closed");
|
||||
frm.save();
|
||||
});
|
||||
} else {
|
||||
frm.add_custom_button("Reopen", function() {
|
||||
frm.set_value("status", "Open");
|
||||
frm.save();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) {
|
||||
|
||||
@@ -27,7 +27,7 @@ blogs.
|
||||
"""
|
||||
app_icon = "icon-th"
|
||||
app_color = "#e74c3c"
|
||||
app_version = "5.3.1"
|
||||
app_version = "5.6.4"
|
||||
github_link = "https://github.com/frappe/erpnext"
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
|
||||
@@ -24,13 +24,15 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
|
||||
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
|
||||
d1.debit = expense[i].sanctioned_amount;
|
||||
d1.account = expense[i].default_account;
|
||||
d1.against_expense_claim = cur_frm.doc.name;
|
||||
d1.reference_type = cur_frm.doc.doctype;
|
||||
d1.reference_name = cur_frm.doc.name;
|
||||
}
|
||||
|
||||
// credit to bank
|
||||
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
|
||||
d1.credit = cur_frm.doc.total_sanctioned_amount;
|
||||
d1.against_expense_claim = cur_frm.doc.name;
|
||||
d1.reference_type = cur_frm.doc.doctype;
|
||||
d1.reference_name = cur_frm.doc.name;
|
||||
if(r.message) {
|
||||
d1.account = r.message.account;
|
||||
d1.balance = r.message.balance;
|
||||
@@ -179,5 +181,5 @@ cur_frm.fields_dict['task'].get_query = function(doc) {
|
||||
filters:{
|
||||
'project': doc.project
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,10 +238,16 @@ def get_total_leave_days(leave_app):
|
||||
ret = {'total_leave_days' : 0.5}
|
||||
if not leave_app.half_day:
|
||||
tot_days = date_diff(leave_app.to_date, leave_app.from_date) + 1
|
||||
holidays = leave_app.get_holidays()
|
||||
ret = {
|
||||
'total_leave_days' : flt(tot_days)-flt(holidays)
|
||||
}
|
||||
if frappe.db.get_value("Leave Type", leave_app.leave_type, "include_holiday"):
|
||||
ret = {
|
||||
'total_leave_days' : flt(tot_days)
|
||||
}
|
||||
else:
|
||||
holidays = leave_app.get_holidays()
|
||||
ret = {
|
||||
'total_leave_days' : flt(tot_days)-flt(holidays)
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -246,4 +246,4 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
frappe.db.sql("""delete from `tabEmployee Leave Approver` where parent=%s""",
|
||||
"_T-Employee-0001")
|
||||
|
||||
frappe.db.set_value("Employee", "_T-Employee-0001", "department", original_department)
|
||||
frappe.db.set_value("Employee", "_T-Employee-0001", "department", original_department)
|
||||
@@ -59,11 +59,17 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Negative Balance",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "include_holiday",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include holidays within leaves as leaves",
|
||||
"permlevel": 0
|
||||
}
|
||||
],
|
||||
"icon": "icon-flag",
|
||||
"idx": 1,
|
||||
"modified": "2015-05-08 05:15:24.194053",
|
||||
"modified": "2015-07-29 05:15:24",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Type",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Leave Type",
|
||||
"leave_type_name": "_Test Leave Type"
|
||||
"leave_type_name": "_Test Leave Type",
|
||||
"include_holiday": 1
|
||||
},
|
||||
{
|
||||
"doctype": "Leave Type",
|
||||
"is_lwp": 1,
|
||||
"leave_type_name": "_Test Leave Type LWP"
|
||||
"leave_type_name": "_Test Leave Type LWP",
|
||||
"include_holiday": 1
|
||||
}
|
||||
]
|
||||
@@ -1,193 +1,193 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "Offer-.#####",
|
||||
"creation": "2015-03-04 14:20:17.662207",
|
||||
"custom": 0,
|
||||
"default_print_format": "Offer Letter",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Transaction",
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "Offer-.#####",
|
||||
"creation": "2015-03-04 14:20:17.662207",
|
||||
"custom": 0,
|
||||
"default_print_format": "Offer Letter",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Transaction",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "job_applicant",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Job Applicant",
|
||||
"no_copy": 0,
|
||||
"options": "Job Applicant",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "job_applicant",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Job Applicant",
|
||||
"no_copy": 0,
|
||||
"options": "Job Applicant",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "applicant_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Applicant Name",
|
||||
"options": "job_applicant.applicant_name",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1,
|
||||
"fieldname": "applicant_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Applicant Name",
|
||||
"options": "job_applicant.applicant_name",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "Awaiting Response\nAccepted\nRejected ",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "Awaiting Response\nAccepted\nRejected",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"default": "",
|
||||
"fieldname": "offer_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Offer Date",
|
||||
"permlevel": 0,
|
||||
"default": "",
|
||||
"fieldname": "offer_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Offer Date",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "designation",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Designation",
|
||||
"no_copy": 0,
|
||||
"options": "Designation",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "designation",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Designation",
|
||||
"no_copy": 0,
|
||||
"options": "Designation",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "offer_terms",
|
||||
"fieldtype": "Table",
|
||||
"label": "Offer Letter Terms",
|
||||
"options": "Offer Letter Term",
|
||||
"permlevel": 0,
|
||||
"fieldname": "offer_terms",
|
||||
"fieldtype": "Table",
|
||||
"label": "Offer Letter Terms",
|
||||
"options": "Offer Letter Term",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "select_terms",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Select Terms and Conditions",
|
||||
"no_copy": 0,
|
||||
"options": "Terms and Conditions",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "select_terms",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Select Terms and Conditions",
|
||||
"no_copy": 0,
|
||||
"options": "Terms and Conditions",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "terms",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Terms and Conditions",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"fieldname": "terms",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Terms and Conditions",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Offer Letter",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Offer Letter",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-04-01 05:51:39.841591",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Offer Letter",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-07-29 05:51:39.841591",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Offer Letter",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "applicant_name"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +152,8 @@ class SalarySlip(TransactionBase):
|
||||
self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
|
||||
for d in self.get("earnings"):
|
||||
if cint(d.e_depends_on_lwp) == 1:
|
||||
d.e_modified_amount = rounded(flt(d.e_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month), 2)
|
||||
d.e_modified_amount = rounded((flt(d.e_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month)), self.precision("e_modified_amount", "earnings"))
|
||||
elif not self.payment_days:
|
||||
d.e_modified_amount = 0
|
||||
elif not d.e_modified_amount:
|
||||
@@ -164,8 +164,8 @@ class SalarySlip(TransactionBase):
|
||||
self.total_deduction = 0
|
||||
for d in self.get('deductions'):
|
||||
if cint(d.d_depends_on_lwp) == 1:
|
||||
d.d_modified_amount = rounded(flt(d.d_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month), 2)
|
||||
d.d_modified_amount = rounded((flt(d.d_amount) * flt(self.payment_days)
|
||||
/ cint(self.total_days_in_month)), self.precision("d_modified_amount", "deductions"))
|
||||
elif not self.payment_days:
|
||||
d.d_modified_amount = 0
|
||||
elif not d.d_modified_amount:
|
||||
@@ -174,10 +174,13 @@ class SalarySlip(TransactionBase):
|
||||
self.total_deduction += flt(d.d_modified_amount)
|
||||
|
||||
def calculate_net_pay(self):
|
||||
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
|
||||
|
||||
self.calculate_earning_total()
|
||||
self.calculate_ded_total()
|
||||
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
|
||||
self.rounded_total = rounded(self.net_pay)
|
||||
self.rounded_total = rounded(self.net_pay,
|
||||
self.precision("net_pay") if disable_rounded_total else 0)
|
||||
|
||||
def on_submit(self):
|
||||
if(self.email_check == 1):
|
||||
|
||||
@@ -24,7 +24,7 @@ class HubSettings(Document):
|
||||
def publish_selling_items(self):
|
||||
"""Set `publish_in_hub`=1 for all Sales Items"""
|
||||
for item in frappe.get_all("Item", fields=["name"],
|
||||
filters={"is_sales_item": "Yes", "publish_in_hub": "0"}):
|
||||
filters={"is_sales_item": 1, "publish_in_hub": "0"}):
|
||||
frappe.db.set_value("Item", item.name, "publish_in_hub", 1)
|
||||
|
||||
def register(self):
|
||||
|
||||
@@ -107,7 +107,7 @@ class BOM(Document):
|
||||
rate = 0
|
||||
if arg['bom_no']:
|
||||
rate = self.get_bom_unitcost(arg['bom_no'])
|
||||
elif arg and (arg['is_purchase_item'] == 'Yes' or arg['is_sub_contracted_item'] == 'Yes'):
|
||||
elif arg and (arg['is_purchase_item'] == 1 or arg['is_sub_contracted_item'] == 1):
|
||||
if self.rm_cost_as_per == 'Valuation Rate':
|
||||
rate = self.get_valuation_rate(arg)
|
||||
elif self.rm_cost_as_per == 'Last Purchase Rate':
|
||||
@@ -364,7 +364,7 @@ class BOM(Document):
|
||||
if self.with_operations and not self.get('operations'):
|
||||
frappe.throw(_("Operations cannot be left blank."))
|
||||
|
||||
def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1):
|
||||
def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1):
|
||||
item_dict = {}
|
||||
|
||||
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
|
||||
@@ -385,14 +385,14 @@ def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1):
|
||||
and bom_item.docstatus < 2
|
||||
and bom_item.parent = %(bom)s
|
||||
and item.name = bom_item.item_code
|
||||
and ifnull(item.is_stock_item, 'No') = 'Yes'
|
||||
and is_stock_item = 1
|
||||
{conditions}
|
||||
group by item_code, stock_uom"""
|
||||
|
||||
if fetch_exploded:
|
||||
query = query.format(table="BOM Explosion Item",
|
||||
conditions="""and ifnull(item.is_pro_applicable, 'No') = 'No'
|
||||
and ifnull(item.is_sub_contracted_item, 'No') = 'No' """)
|
||||
conditions="""and item.is_pro_applicable = 0
|
||||
and item.is_sub_contracted_item = 0 """)
|
||||
items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True)
|
||||
else:
|
||||
query = query.format(table="BOM Item", conditions="")
|
||||
@@ -405,11 +405,18 @@ def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1):
|
||||
else:
|
||||
item_dict[item.item_code] = item
|
||||
|
||||
for item, item_details in item_dict.items():
|
||||
for d in [["Account", "expense_account", "default_expense_account"],
|
||||
["Cost Center", "cost_center", "cost_center"], ["Warehouse", "default_warehouse", ""]]:
|
||||
company_in_record = frappe.db.get_value(d[0], item_details.get(d[1]), "company")
|
||||
if not item_details.get(d[1]) or (company_in_record and company != company_in_record):
|
||||
item_dict[item][d[1]] = frappe.db.get_value("Company", company, d[2]) if d[2] else None
|
||||
|
||||
return item_dict
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bom_items(bom, qty=1, fetch_exploded=1):
|
||||
items = get_bom_items_as_dict(bom, qty, fetch_exploded).values()
|
||||
def get_bom_items(bom, company, qty=1, fetch_exploded=1):
|
||||
items = get_bom_items_as_dict(bom, company, qty, fetch_exploded).values()
|
||||
items.sort(lambda a, b: a.item_code > b.item_code and 1 or -1)
|
||||
return items
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ test_records = frappe.get_test_records('BOM')
|
||||
class TestBOM(unittest.TestCase):
|
||||
def test_get_items(self):
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||
items_dict = get_bom_items_as_dict(bom=get_default_bom(), qty=1, fetch_exploded=0)
|
||||
items_dict = get_bom_items_as_dict(bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=0)
|
||||
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
|
||||
self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict)
|
||||
self.assertEquals(len(items_dict.values()), 2)
|
||||
|
||||
def test_get_items_exploded(self):
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||
items_dict = get_bom_items_as_dict(bom=get_default_bom(), qty=1, fetch_exploded=1)
|
||||
items_dict = get_bom_items_as_dict(bom=get_default_bom(), company="_Test Company", qty=1, fetch_exploded=1)
|
||||
self.assertTrue(test_records[2]["items"][0]["item_code"] in items_dict)
|
||||
self.assertFalse(test_records[2]["items"][1]["item_code"] in items_dict)
|
||||
self.assertTrue(test_records[0]["items"][0]["item_code"] in items_dict)
|
||||
@@ -28,7 +28,7 @@ class TestBOM(unittest.TestCase):
|
||||
|
||||
def test_get_items_list(self):
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items
|
||||
self.assertEquals(len(get_bom_items(bom=get_default_bom())), 3)
|
||||
self.assertEquals(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3)
|
||||
|
||||
def test_default_bom(self):
|
||||
def _get_default_bom_in_item():
|
||||
|
||||
@@ -1,114 +1,123 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2014-11-27 14:12:07.542534",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2014-11-27 14:12:07.542534",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "capacity_planning",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Capacity Planning",
|
||||
"permlevel": 0,
|
||||
"fieldname": "capacity_planning",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Capacity Planning",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "Disables creation of time logs against Production Orders.\nOperations shall not be tracked against Production Order",
|
||||
"fieldname": "disable_capacity_planning",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Capacity Planning and Time Tracking",
|
||||
"permlevel": 0,
|
||||
"description": "Disables creation of time logs against Production Orders.\nOperations shall not be tracked against Production Order",
|
||||
"fieldname": "disable_capacity_planning",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Capacity Planning and Time Tracking",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "Plan time logs outside Workstation Working Hours.",
|
||||
"fieldname": "allow_overtime",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Overtime",
|
||||
"permlevel": 0,
|
||||
"description": "Plan time logs outside Workstation Working Hours.",
|
||||
"fieldname": "allow_overtime",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Overtime",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"default": "",
|
||||
"fieldname": "allow_production_on_holidays",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Allow Production on Holidays",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"default": "",
|
||||
"fieldname": "allow_production_on_holidays",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Allow Production on Holidays",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"default": "BOM",
|
||||
"fieldname": "backflush_raw_materials_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Backflush Raw Materials Based On",
|
||||
"options": "BOM\nMaterial Transferred for Manufacture",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"default": "30",
|
||||
"description": "Try planning operations for X days in advance.",
|
||||
"fieldname": "capacity_planning_for_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Capacity Planning For (Days)",
|
||||
"permlevel": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "Default 10 mins",
|
||||
"fieldname": "mins_between_operations",
|
||||
"fieldtype": "Int",
|
||||
"label": "Time Between Operations (in mins)",
|
||||
"permlevel": 0,
|
||||
"default": "30",
|
||||
"description": "Try planning operations for X days in advance.",
|
||||
"fieldname": "capacity_planning_for_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Capacity Planning For (Days)",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "over_production_allowance_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Over Production Allowance Percentage",
|
||||
"permlevel": 0,
|
||||
"description": "Default 10 mins",
|
||||
"fieldname": "mins_between_operations",
|
||||
"fieldtype": "Int",
|
||||
"label": "Time Between Operations (in mins)",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "over_production_allowance_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Over Production Allowance Percentage",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-wrench",
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"modified": "2015-07-23 08:12:33.889753",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-wrench",
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"modified": "2015-08-12 08:12:33.889753",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Manufacturing Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Manufacturing Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
class ManufacturingSettings(Document):
|
||||
pass
|
||||
|
||||
def get_mins_between_operations():
|
||||
if not hasattr(frappe.local, "_mins_between_operations"):
|
||||
frappe.local._mins_between_operations = cint(frappe.db.get_single_value("Manufacturing Settings",
|
||||
"mins_between_operations")) or 10
|
||||
return relativedelta(minutes=frappe.local._mins_between_operations)
|
||||
|
||||
@@ -152,7 +152,9 @@ $.extend(cur_frm.cscript, {
|
||||
method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details",
|
||||
args: { item: doc.production_item },
|
||||
callback: function(r) {
|
||||
cur_frm.set_value(r.message);
|
||||
$.each(["description", "stock_uom", "bom_no"], function(i, field) {
|
||||
cur_frm.set_value(field, r.message[field]);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -160,7 +162,7 @@ $.extend(cur_frm.cscript, {
|
||||
make_se: function(purpose) {
|
||||
var me = this;
|
||||
var max = (purpose === "Manufacture") ?
|
||||
flt(this.frm.doc.qty) - flt(this.frm.doc.produced_qty) :
|
||||
flt(this.frm.doc.material_transferred_for_manufacturing) - flt(this.frm.doc.produced_qty) :
|
||||
flt(this.frm.doc.qty) - flt(this.frm.doc.material_transferred_for_manufacturing);
|
||||
|
||||
frappe.prompt({fieldtype:"Int", label: __("Qty for {0}", [purpose]), fieldname:"qty",
|
||||
@@ -187,15 +189,14 @@ $.extend(cur_frm.cscript, {
|
||||
|
||||
bom_no: function() {
|
||||
return this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "set_production_order_operations"
|
||||
doc: this.frm.doc,
|
||||
method: "set_production_order_operations"
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
qty: function() {
|
||||
frappe.ui.form.trigger("Production Order", 'bom_no')
|
||||
},
|
||||
|
||||
show_time_logs: function(doc, cdt, cdn) {
|
||||
var child = locals[cdt][cdn]
|
||||
frappe.route_options = {"operation_id": child.name};
|
||||
@@ -250,8 +251,8 @@ cur_frm.cscript['Update Finished Goods'] = function() {
|
||||
cur_frm.fields_dict['production_item'].get_query = function(doc) {
|
||||
return {
|
||||
filters:[
|
||||
['Item', 'is_pro_applicable', '=', 'Yes'],
|
||||
['Item', 'has_variants', '=', 'No'],
|
||||
['Item', 'is_pro_applicable', '=', 1],
|
||||
['Item', 'has_variants', '=', 0],
|
||||
['Item', 'end_of_life', '>=', frappe.datetime.nowdate()]
|
||||
]
|
||||
}
|
||||
@@ -264,7 +265,3 @@ cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ from frappe.model.document import Document
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
|
||||
from erpnext.projects.doctype.time_log.time_log import OverlapError
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
|
||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||
|
||||
class OverProductionError(frappe.ValidationError): pass
|
||||
class StockOverProductionError(frappe.ValidationError): pass
|
||||
@@ -17,9 +21,6 @@ class OperationTooLongError(frappe.ValidationError): pass
|
||||
class ProductionNotApplicableError(frappe.ValidationError): pass
|
||||
class ItemHasVariantError(frappe.ValidationError): pass
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
|
||||
from erpnext.projects.doctype.time_log.time_log import OverlapError
|
||||
|
||||
form_grid_templates = {
|
||||
"operations": "templates/form_grid/production_order_grid.html"
|
||||
}
|
||||
@@ -94,7 +95,7 @@ class ProductionOrder(Document):
|
||||
(self.sales_order, self.production_item))[0][0]
|
||||
# total qty in SO
|
||||
so_qty = flt(so_item_qty) + flt(dnpi_qty)
|
||||
|
||||
|
||||
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "over_production_allowance_percentage"))
|
||||
if total_qty > so_qty + (allowance_percentage/100 * so_qty):
|
||||
frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}").format(self.production_item,
|
||||
@@ -231,6 +232,7 @@ class ProductionOrder(Document):
|
||||
original_start_time = time_log.from_time
|
||||
while True:
|
||||
_from_time = time_log.from_time
|
||||
|
||||
try:
|
||||
time_log.save()
|
||||
break
|
||||
@@ -248,6 +250,7 @@ class ProductionOrder(Document):
|
||||
frappe.msgprint(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation))
|
||||
break
|
||||
|
||||
# if time log needs to be moved, make sure that the from time is not the same
|
||||
if _from_time == time_log.from_time:
|
||||
frappe.throw("Capacity Planning Error")
|
||||
|
||||
@@ -273,19 +276,13 @@ class ProductionOrder(Document):
|
||||
d.planned_start_time = self.planned_start_date
|
||||
else:
|
||||
d.planned_start_time = get_datetime(self.operations[i-1].planned_end_time)\
|
||||
+ self.get_mins_between_operations()
|
||||
+ get_mins_between_operations()
|
||||
|
||||
d.planned_end_time = get_datetime(d.planned_start_time) + relativedelta(minutes = d.time_in_mins)
|
||||
|
||||
if d.planned_start_time == d.planned_end_time:
|
||||
frappe.throw(_("Capacity Planning Error"))
|
||||
|
||||
def get_mins_between_operations(self):
|
||||
if not hasattr(self, "_mins_between_operations"):
|
||||
self._mins_between_operations = cint(frappe.db.get_single_value("Manufacturing Settings",
|
||||
"mins_between_operations")) or 10
|
||||
return relativedelta(minutes=self._mins_between_operations)
|
||||
|
||||
def check_operation_fits_in_working_hours(self, d):
|
||||
"""Raises expection if operation is longer than working hours in the given workstation."""
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours
|
||||
@@ -320,14 +317,14 @@ class ProductionOrder(Document):
|
||||
def delete_time_logs(self):
|
||||
for time_log in frappe.get_all("Time Log", ["name"], {"production_order": self.name}):
|
||||
frappe.delete_doc("Time Log", time_log.name)
|
||||
|
||||
|
||||
def validate_production_item(self):
|
||||
if frappe.db.get_value("Item", self.production_item, "is_pro_applicable")=='No':
|
||||
if not frappe.db.get_value("Item", self.production_item, "is_pro_applicable"):
|
||||
frappe.throw(_("Item is not allowed to have Production Order."), ProductionNotApplicableError)
|
||||
|
||||
|
||||
if frappe.db.get_value("Item", self.production_item, "has_variants"):
|
||||
frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError)
|
||||
|
||||
|
||||
validate_end_of_life(self.production_item)
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -356,7 +353,6 @@ def make_stock_entry(production_order_id, purpose, qty=None):
|
||||
stock_entry.company = production_order.company
|
||||
stock_entry.from_bom = 1
|
||||
stock_entry.bom_no = production_order.bom_no
|
||||
stock_entry.additional_operating_cost = production_order.additional_operating_cost
|
||||
stock_entry.use_multi_level_bom = production_order.use_multi_level_bom
|
||||
stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty))
|
||||
|
||||
@@ -365,6 +361,8 @@ def make_stock_entry(production_order_id, purpose, qty=None):
|
||||
else:
|
||||
stock_entry.from_warehouse = production_order.wip_warehouse
|
||||
stock_entry.to_warehouse = production_order.fg_warehouse
|
||||
additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty)
|
||||
stock_entry.set("additional_costs", additional_costs)
|
||||
|
||||
stock_entry.get_items()
|
||||
return stock_entry.as_dict()
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.utils import flt, get_datetime, time_diff_in_hours
|
||||
from frappe.utils import flt, time_diff_in_hours, now, add_days
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
from erpnext.manufacturing.doctype.production_order.production_order \
|
||||
import make_stock_entry, make_time_log, ProductionNotApplicableError,ItemHasVariantError
|
||||
import make_stock_entry, ProductionNotApplicableError,ItemHasVariantError
|
||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||
from erpnext.projects.doctype.time_log.time_log import OverProductionLoggedError
|
||||
|
||||
@@ -28,9 +28,9 @@ class TestProductionOrder(unittest.TestCase):
|
||||
|
||||
# add raw materials to stores
|
||||
test_stock_entry.make_stock_entry(item_code="_Test Item",
|
||||
target="Stores - _TC", qty=100, incoming_rate=100)
|
||||
target="Stores - _TC", qty=100, basic_rate=100)
|
||||
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
|
||||
target="Stores - _TC", qty=100, incoming_rate=100)
|
||||
target="Stores - _TC", qty=100, basic_rate=100)
|
||||
|
||||
# from stores to wip
|
||||
s = frappe.get_doc(make_stock_entry(pro_order.name, "Material Transfer for Manufacture", 4))
|
||||
@@ -58,9 +58,9 @@ class TestProductionOrder(unittest.TestCase):
|
||||
pro_doc = self.check_planned_qty()
|
||||
|
||||
test_stock_entry.make_stock_entry(item_code="_Test Item",
|
||||
target="_Test Warehouse - _TC", qty=100, incoming_rate=100)
|
||||
target="_Test Warehouse - _TC", qty=100, basic_rate=100)
|
||||
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
|
||||
target="_Test Warehouse - _TC", qty=100, incoming_rate=100)
|
||||
target="_Test Warehouse - _TC", qty=100, basic_rate=100)
|
||||
|
||||
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 7))
|
||||
s.insert()
|
||||
@@ -68,8 +68,9 @@ class TestProductionOrder(unittest.TestCase):
|
||||
self.assertRaises(StockOverProductionError, s.submit)
|
||||
|
||||
def test_make_time_log(self):
|
||||
from erpnext.projects.doctype.time_log.test_time_log import make_time_log_test_record
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item 2",
|
||||
planned_start_date="2014-11-25 00:00:00", qty=1, do_not_save=True)
|
||||
planned_start_date=now(), qty=1, do_not_save=True)
|
||||
|
||||
prod_order.set_production_order_operations()
|
||||
prod_order.insert()
|
||||
@@ -79,17 +80,13 @@ class TestProductionOrder(unittest.TestCase):
|
||||
|
||||
d.completed_qty = flt(d.completed_qty)
|
||||
|
||||
time_log = make_time_log(prod_order.name, d.operation, \
|
||||
d.planned_start_time, d.planned_end_time, prod_order.qty - d.completed_qty,
|
||||
operation_id=d.name)
|
||||
time_log = make_time_log_test_record(hours=1, production_order= prod_order.name, operation= d.operation,
|
||||
completed_qty= prod_order.qty - d.completed_qty, operation_id=d.name, for_manufacturing=1, simulate=True)
|
||||
|
||||
self.assertEqual(prod_order.name, time_log.production_order)
|
||||
self.assertEqual((prod_order.qty - d.completed_qty), time_log.completed_qty)
|
||||
self.assertEqual(time_diff_in_hours(d.planned_end_time, d.planned_start_time),time_log.hours)
|
||||
|
||||
time_log.save()
|
||||
time_log.submit()
|
||||
|
||||
|
||||
manufacturing_settings = frappe.get_doc({
|
||||
"doctype": "Manufacturing Settings",
|
||||
"allow_production_on_holidays": 0
|
||||
@@ -101,11 +98,6 @@ class TestProductionOrder(unittest.TestCase):
|
||||
self.assertEqual(prod_order.operations[0].status, "Completed")
|
||||
self.assertEqual(prod_order.operations[0].completed_qty, prod_order.qty)
|
||||
|
||||
self.assertEqual(get_datetime(prod_order.operations[0].actual_start_time),
|
||||
get_datetime(time_log.from_time))
|
||||
self.assertEqual(get_datetime(prod_order.operations[0].actual_end_time),
|
||||
get_datetime(time_log.to_time))
|
||||
|
||||
self.assertEqual(prod_order.operations[0].actual_operation_time, 60)
|
||||
self.assertEqual(prod_order.operations[0].actual_operating_cost, 100)
|
||||
|
||||
@@ -118,37 +110,33 @@ class TestProductionOrder(unittest.TestCase):
|
||||
self.assertEqual(flt(prod_order.operations[0].actual_operation_time), 0)
|
||||
self.assertEqual(flt(prod_order.operations[0].actual_operating_cost), 0)
|
||||
|
||||
time_log2 = frappe.copy_doc(time_log)
|
||||
time_log2.update({
|
||||
"completed_qty": 10,
|
||||
"from_time": "2014-11-26 00:00:00",
|
||||
"to_time": "2014-11-26 00:00:00",
|
||||
"docstatus": 0
|
||||
})
|
||||
time_log2 = make_time_log_test_record(from_time= add_days(time_log.to_time, 1) ,production_order= prod_order.name, operation= d.operation,
|
||||
completed_qty= 5, operation_id=d.name, for_manufacturing=1, do_not_save=True)
|
||||
|
||||
self.assertRaises(OverProductionLoggedError, time_log2.save)
|
||||
|
||||
|
||||
def test_planned_operating_cost(self):
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item 2",
|
||||
planned_start_date="2014-11-25 00:00:00", qty=1, do_not_save=True)
|
||||
planned_start_date=now(), qty=1, do_not_save=True)
|
||||
prod_order.set_production_order_operations()
|
||||
cost = prod_order.planned_operating_cost
|
||||
prod_order.qty = 2
|
||||
prod_order.set_production_order_operations()
|
||||
self.assertEqual(prod_order.planned_operating_cost, cost*2)
|
||||
|
||||
|
||||
def test_production_item(self):
|
||||
frappe.db.set_value("Item", "_Test FG Item", "is_pro_applicable", "No")
|
||||
frappe.db.set_value("Item", "_Test FG Item", "is_pro_applicable", 0)
|
||||
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item", qty=1, do_not_save=True)
|
||||
self.assertRaises(ProductionNotApplicableError, prod_order.save)
|
||||
|
||||
frappe.db.set_value("Item", "_Test FG Item", "is_pro_applicable", "Yes")
|
||||
|
||||
frappe.db.set_value("Item", "_Test FG Item", "is_pro_applicable", 1)
|
||||
frappe.db.set_value("Item", "_Test FG Item", "end_of_life", "2000-1-1")
|
||||
|
||||
|
||||
self.assertRaises(frappe.ValidationError, prod_order.save)
|
||||
|
||||
|
||||
frappe.db.set_value("Item", "_Test FG Item", "end_of_life", None)
|
||||
|
||||
|
||||
prod_order = make_prod_order_test_record(item="_Test Variant Item", qty=1, do_not_save=True)
|
||||
self.assertRaises(ItemHasVariantError, prod_order.save)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ cur_frm.fields_dict['sales_orders'].grid.get_field('sales_order').get_query = fu
|
||||
|
||||
cur_frm.fields_dict['items'].grid.get_field('item_code').get_query = function(doc) {
|
||||
return erpnext.queries.item({
|
||||
'is_pro_applicable': 'Yes'
|
||||
'is_pro_applicable': 1
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,31 +44,35 @@ class ProductionPlanningTool(Document):
|
||||
""" Pull sales orders which are pending to deliver based on criteria selected"""
|
||||
so_filter = item_filter = ""
|
||||
if self.from_date:
|
||||
so_filter += ' and so.transaction_date >= "' + self.from_date + '"'
|
||||
so_filter += " and so.transaction_date >= %(from_date)s"
|
||||
if self.to_date:
|
||||
so_filter += ' and so.transaction_date <= "' + self.to_date + '"'
|
||||
so_filter += " and so.transaction_date <= %(to_date)s"
|
||||
if self.customer:
|
||||
so_filter += ' and so.customer = "' + self.customer + '"'
|
||||
so_filter += " and so.customer = %(customer)s"
|
||||
|
||||
if self.fg_item:
|
||||
item_filter += ' and item.name = "' + self.fg_item + '"'
|
||||
item_filter += " and item.name = %(item)s"
|
||||
|
||||
open_so = frappe.db.sql("""
|
||||
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
|
||||
from `tabSales Order` so, `tabSales Order Item` so_item
|
||||
where so_item.parent = so.name
|
||||
and so.docstatus = 1 and so.status != "Stopped"
|
||||
and so.company = %s
|
||||
and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) %s
|
||||
and so.company = %(company)s
|
||||
and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) {0}
|
||||
and (exists (select name from `tabItem` item where item.name=so_item.item_code
|
||||
and (ifnull(item.is_pro_applicable, 'No') = 'Yes'
|
||||
or ifnull(item.is_sub_contracted_item, 'No') = 'Yes') %s)
|
||||
and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1 {1}))
|
||||
or exists (select name from `tabPacked Item` pi
|
||||
where pi.parent = so.name and pi.parent_item = so_item.item_code
|
||||
and exists (select name from `tabItem` item where item.name=pi.item_code
|
||||
and (ifnull(item.is_pro_applicable, 'No') = 'Yes'
|
||||
or ifnull(item.is_sub_contracted_item, 'No') = 'Yes') %s)))
|
||||
""" % ('%s', so_filter, item_filter, item_filter), self.company, as_dict=1)
|
||||
and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1) {2})))
|
||||
""".format(so_filter, item_filter, item_filter), {
|
||||
"from_date": self.from_date,
|
||||
"to_date": self.to_date,
|
||||
"customer": self.customer,
|
||||
"item": self.fg_item,
|
||||
"company": self.company
|
||||
}, as_dict=1)
|
||||
|
||||
self.add_so_in_table(open_so)
|
||||
|
||||
@@ -108,8 +112,8 @@ class ProductionPlanningTool(Document):
|
||||
from `tabSales Order Item` so_item
|
||||
where parent in (%s) and docstatus = 1 and ifnull(qty, 0) > ifnull(delivered_qty, 0)
|
||||
and exists (select * from `tabItem` item where item.name=so_item.item_code
|
||||
and (ifnull(item.is_pro_applicable, 'No') = 'Yes'
|
||||
or ifnull(item.is_sub_contracted_item, 'No') = 'Yes')) %s""" % \
|
||||
and (item.is_pro_applicable = 1
|
||||
or item.is_sub_contracted_item = 1)) %s""" % \
|
||||
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
|
||||
|
||||
if self.fg_item:
|
||||
@@ -123,8 +127,8 @@ class ProductionPlanningTool(Document):
|
||||
and pi.parent_item = so_item.item_code
|
||||
and so_item.parent in (%s) and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0)
|
||||
and exists (select * from `tabItem` item where item.name=pi.item_code
|
||||
and (ifnull(item.is_pro_applicable, 'No') = 'Yes'
|
||||
or ifnull(item.is_sub_contracted_item, 'No') = 'Yes')) %s""" % \
|
||||
and (item.is_pro_applicable = 1
|
||||
or item.is_sub_contracted_item = 1)) %s""" % \
|
||||
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
|
||||
|
||||
return items + packed_items
|
||||
@@ -178,7 +182,7 @@ class ProductionPlanningTool(Document):
|
||||
for d in self.get("items"):
|
||||
if d.bom_no:
|
||||
bom_dict.setdefault(d.bom_no, []).append([d.sales_order, flt(d.planned_qty)])
|
||||
if frappe.db.get_value("Item", d.item_code, "is_pro_applicable") == "Yes":
|
||||
if frappe.db.get_value("Item", d.item_code, "is_pro_applicable"):
|
||||
item_dict[(d.item_code, d.sales_order, d.warehouse)] = {
|
||||
"production_item" : d.item_code,
|
||||
"sales_order" : d.sales_order,
|
||||
@@ -239,10 +243,10 @@ class ProductionPlanningTool(Document):
|
||||
ifnull(sum(ifnull(fb.qty, 0)/ifnull(bom.quantity, 1)), 0) as qty,
|
||||
fb.description, fb.stock_uom, it.min_order_qty
|
||||
from `tabBOM Explosion Item` fb, `tabBOM` bom, `tabItem` it
|
||||
where bom.name = fb.parent and it.name = fb.item_code
|
||||
and ifnull(it.is_pro_applicable, 'No') = 'No'
|
||||
and ifnull(it.is_sub_contracted_item, 'No') = 'No'
|
||||
and ifnull(it.is_stock_item, 'No') = 'Yes'
|
||||
where bom.name = fb.parent and it.name = fb.item_code
|
||||
and is_pro_applicable = 0
|
||||
and is_sub_contracted_item = 0
|
||||
and is_stock_item = 1
|
||||
and fb.docstatus<2 and bom.name=%s
|
||||
group by item_code, stock_uom""", bom, as_dict=1):
|
||||
bom_wise_item_details.setdefault(d.item_code, d)
|
||||
@@ -255,7 +259,7 @@ class ProductionPlanningTool(Document):
|
||||
from `tabBOM Item` bom_item, `tabBOM` bom, tabItem item
|
||||
where bom.name = bom_item.parent and bom.name = %s and bom_item.docstatus < 2
|
||||
and bom_item.item_code = item.name
|
||||
and ifnull(item.is_stock_item, 'No') = 'Yes'
|
||||
and item.is_stock_item = 1
|
||||
group by item_code""", bom, as_dict=1):
|
||||
bom_wise_item_details.setdefault(d.item_code, d)
|
||||
|
||||
@@ -350,7 +354,7 @@ class ProductionPlanningTool(Document):
|
||||
|
||||
def insert_purchase_request(self):
|
||||
items_to_be_requested = self.get_requested_items()
|
||||
|
||||
|
||||
purchase_request_list = []
|
||||
if items_to_be_requested:
|
||||
for item in items_to_be_requested:
|
||||
|
||||
@@ -24,6 +24,9 @@ erpnext.patches.v4_0.map_charge_to_taxes_and_charges
|
||||
execute:frappe.reload_doc('support', 'doctype', 'newsletter') # 2014-01-31
|
||||
execute:frappe.reload_doc('hr', 'doctype', 'employee') # 2014-02-03
|
||||
execute:frappe.db.sql("update tabPage set module='Core' where name='Setup'")
|
||||
|
||||
# inserted this patch here since Item types are being changed
|
||||
erpnext.patches.v5_2.change_item_selects_to_checks
|
||||
erpnext.patches.v4_0.fields_to_be_renamed
|
||||
erpnext.patches.v4_0.rename_sitemap_to_route
|
||||
erpnext.patches.v4_0.fix_contact_address
|
||||
@@ -96,7 +99,6 @@ execute:frappe.db.sql("update `tabMaterial Request` set material_request_type =
|
||||
execute:frappe.reload_doc('stock', 'doctype', 'item')
|
||||
execute:frappe.db.sql("update `tabItem` i set apply_warehouse_wise_reorder_level=1, re_order_level=0, re_order_qty=0 where exists(select name from `tabItem Reorder` where parent=i.name)")
|
||||
erpnext.patches.v5_0.set_default_company_in_bom
|
||||
erpnext.patches.v5_0.capacity_planning
|
||||
execute:frappe.reload_doc('crm', 'doctype', 'lead')
|
||||
execute:frappe.reload_doc('crm', 'doctype', 'opportunity')
|
||||
erpnext.patches.v5_0.rename_taxes_and_charges_master
|
||||
@@ -176,4 +178,16 @@ erpnext.patches.v5_1.fix_credit_days_based_on
|
||||
execute:frappe.rename_doc("DocType", "Salary Manager", "Process Payroll", force=True)
|
||||
erpnext.patches.v5_1.rename_roles
|
||||
erpnext.patches.v5_1.default_bom
|
||||
execute:frappe.delete_doc("DocType", "Party Type")
|
||||
execute:frappe.delete_doc("DocType", "Party Type")
|
||||
execute:frappe.delete_doc("Module Def", "Contacts")
|
||||
erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items # 30-07-2015
|
||||
execute:frappe.reload_doctype("Leave Type")
|
||||
execute:frappe.db.sql("update `tabLeave Type` set include_holiday=0")
|
||||
erpnext.patches.v5_4.set_root_and_report_type
|
||||
erpnext.patches.v5_4.notify_system_managers_regarding_wrong_tax_calculation
|
||||
erpnext.patches.v5_4.fix_invoice_outstanding
|
||||
execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' where voucher_type = 'Stock Reconciliation' and ifnull(qty_after_transaction, 0) = 0")
|
||||
erpnext.patches.v5_4.fix_missing_item_images
|
||||
erpnext.patches.v5_4.stock_entry_additional_costs
|
||||
erpnext.patches.v5_4.cleanup_journal_entry #2015-08-14
|
||||
execute:frappe.db.sql("update `tabProduction Order` pro set description = (select description from tabItem where name=pro.production_item) where ifnull(description, '') = ''")
|
||||
|
||||
@@ -5,61 +5,61 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql("update tabItem set has_batch_no = 'No' where ifnull(has_batch_no, '') = ''")
|
||||
frappe.db.sql("update tabItem set has_serial_no = 'No' where ifnull(has_serial_no, '') = ''")
|
||||
|
||||
item_list = frappe.db.sql("""select name, has_batch_no, has_serial_no from tabItem
|
||||
where ifnull(is_stock_item, 'No') = 'Yes'""", as_dict=1)
|
||||
|
||||
frappe.db.sql("update tabItem set has_batch_no = 0 where ifnull(has_batch_no, '') = ''")
|
||||
frappe.db.sql("update tabItem set has_serial_no = 0 where ifnull(has_serial_no, '') = ''")
|
||||
|
||||
item_list = frappe.db.sql("""select name, has_batch_no, has_serial_no from tabItem
|
||||
where is_stock_item = 1""", as_dict=1)
|
||||
|
||||
sle_count = get_sle_count()
|
||||
sle_with_batch = get_sle_with_batch()
|
||||
sle_with_serial = get_sle_with_serial()
|
||||
|
||||
|
||||
batch_items = get_items_with_batch()
|
||||
serialized_items = get_items_with_serial()
|
||||
|
||||
for d in item_list:
|
||||
if d.has_batch_no == 'Yes':
|
||||
|
||||
for d in item_list:
|
||||
if d.has_batch_no == 1:
|
||||
if d.name not in batch_items and sle_count.get(d.name) and sle_count.get(d.name) != sle_with_batch.get(d.name):
|
||||
frappe.db.set_value("Item", d.name, "has_batch_no", "No")
|
||||
frappe.db.set_value("Item", d.name, "has_batch_no", 0)
|
||||
else:
|
||||
if d.name in batch_items or (sle_count.get(d.name) and sle_count.get(d.name) == sle_with_batch.get(d.name)):
|
||||
frappe.db.set_value("Item", d.name, "has_batch_no", "Yes")
|
||||
|
||||
if d.has_serial_no == 'Yes':
|
||||
frappe.db.set_value("Item", d.name, "has_batch_no", 1)
|
||||
|
||||
if d.has_serial_no == 1:
|
||||
if d.name not in serialized_items and sle_count.get(d.name) and sle_count.get(d.name) != sle_with_serial.get(d.name):
|
||||
frappe.db.set_value("Item", d.name, "has_serial_no", "No")
|
||||
frappe.db.set_value("Item", d.name, "has_serial_no", 0)
|
||||
else:
|
||||
if d.name in serialized_items or (sle_count.get(d.name) and sle_count.get(d.name) == sle_with_serial.get(d.name)):
|
||||
frappe.db.set_value("Item", d.name, "has_serial_no", "Yes")
|
||||
|
||||
|
||||
frappe.db.set_value("Item", d.name, "has_serial_no", 1)
|
||||
|
||||
|
||||
def get_sle_count():
|
||||
sle_count = {}
|
||||
for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry` group by item_code""", as_dict=1):
|
||||
sle_count.setdefault(d.item_code, d.cnt)
|
||||
|
||||
|
||||
return sle_count
|
||||
|
||||
|
||||
def get_sle_with_batch():
|
||||
sle_with_batch = {}
|
||||
for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry`
|
||||
for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry`
|
||||
where ifnull(batch_no, '') != '' group by item_code""", as_dict=1):
|
||||
sle_with_batch.setdefault(d.item_code, d.cnt)
|
||||
|
||||
|
||||
return sle_with_batch
|
||||
|
||||
|
||||
|
||||
def get_sle_with_serial():
|
||||
sle_with_serial = {}
|
||||
for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry`
|
||||
for d in frappe.db.sql("""select item_code, count(name) as cnt from `tabStock Ledger Entry`
|
||||
where ifnull(serial_no, '') != '' group by item_code""", as_dict=1):
|
||||
sle_with_serial.setdefault(d.item_code, d.cnt)
|
||||
|
||||
|
||||
return sle_with_serial
|
||||
|
||||
|
||||
def get_items_with_batch():
|
||||
return frappe.db.sql_list("select item from tabBatch")
|
||||
|
||||
|
||||
def get_items_with_serial():
|
||||
return frappe.db.sql_list("select item_code from `tabSerial No`")
|
||||
return frappe.db.sql_list("select item_code from `tabSerial No`")
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("stock", "doctype", "stock_entry")
|
||||
if "total_fixed_cost" in frappe.db.get_table_columns("Stock Entry"):
|
||||
frappe.db.sql("update `tabStock Entry` set additional_operating_cost = total_fixed_cost")
|
||||
1
erpnext/patches/v5_2/__init__.py
Normal file
1
erpnext/patches/v5_2/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
21
erpnext/patches/v5_2/change_item_selects_to_checks.py
Normal file
21
erpnext/patches/v5_2/change_item_selects_to_checks.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
fields = ("is_stock_item", "is_asset_item", "has_batch_no", "has_serial_no",
|
||||
"is_purchase_item", "is_sales_item", "is_service_item", "inspection_required",
|
||||
"is_pro_applicable", "is_sub_contracted_item")
|
||||
|
||||
|
||||
# convert to 1 or 0
|
||||
update_str = ", ".join(["`{0}`=if(`{0}`='Yes',1,0)".format(f) for f in fields])
|
||||
frappe.db.sql("update tabItem set {0}".format(update_str))
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
# alter fields to int
|
||||
for f in fields:
|
||||
frappe.db.sql("alter table tabItem change {0} {0} int(1) default '0'".format(f, f))
|
||||
|
||||
frappe.reload_doctype("Item")
|
||||
20
erpnext/patches/v5_4/cleanup_journal_entry.py
Normal file
20
erpnext/patches/v5_4/cleanup_journal_entry.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import frappe
|
||||
from MySQLdb import OperationalError
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Journal Entry Account")
|
||||
for doctype, fieldname in (
|
||||
("Sales Order", "against_sales_order"),
|
||||
("Purchase Order", "against_purchase_order"),
|
||||
("Sales Invoice", "against_invoice"),
|
||||
("Purchase Invoice", "against_voucher"),
|
||||
("Journal Entry", "against_jv"),
|
||||
("Expense Claim", "against_expense_claim"),
|
||||
):
|
||||
try:
|
||||
frappe.db.sql("""update `tabJournal Entry Account`
|
||||
set reference_type=%s, reference_name={0} where ifnull({0}, '') != ''
|
||||
""".format(fieldname), doctype)
|
||||
except OperationalError:
|
||||
# column not found
|
||||
pass
|
||||
13
erpnext/patches/v5_4/fix_invoice_outstanding.py
Normal file
13
erpnext/patches/v5_4/fix_invoice_outstanding.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Sales Invoice")
|
||||
return_entries = frappe.get_list("Sales Invoice", filters={"is_return": 1, "docstatus": 1},
|
||||
fields=["debit_to", "customer", "return_against"])
|
||||
for d in return_entries:
|
||||
update_outstanding_amt(d.debit_to, "Customer", d.customer, "Sales Invoice", d.return_against)
|
||||
116
erpnext/patches/v5_4/fix_missing_item_images.py
Normal file
116
erpnext/patches/v5_4/fix_missing_item_images.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import os
|
||||
from frappe.utils import get_files_path
|
||||
from frappe.utils.file_manager import get_content_hash
|
||||
|
||||
def execute():
|
||||
files_path = get_files_path()
|
||||
|
||||
# get files that don't have attached_to_name but exist
|
||||
unlinked_files = get_unlinked_files(files_path)
|
||||
if not unlinked_files:
|
||||
return
|
||||
|
||||
fixed_files = fix_files_for_item(files_path, unlinked_files)
|
||||
|
||||
# fix remaining files
|
||||
for key, file_data in unlinked_files.items():
|
||||
if key not in fixed_files:
|
||||
rename_and_set_content_hash(files_path, unlinked_files, key)
|
||||
frappe.db.commit()
|
||||
|
||||
def fix_files_for_item(files_path, unlinked_files):
|
||||
fixed_files = []
|
||||
|
||||
# make a list of files/something and /files/something to check in child table's image column
|
||||
file_urls = [key for key in unlinked_files.keys()] + ["/" + key for key in unlinked_files.keys()]
|
||||
file_item_code = get_file_item_code(file_urls)
|
||||
|
||||
for (file_url, item_code), children in file_item_code.items():
|
||||
new_file_url = "/files/{0}".format(unlinked_files[file_url]["file_name"])
|
||||
|
||||
for row in children:
|
||||
# print file_url, new_file_url, item_code, row.doctype, row.name
|
||||
|
||||
# replace image in these rows with the new file url
|
||||
frappe.db.set_value(row.doctype, row.name, "image", new_file_url, update_modified=False)
|
||||
|
||||
# set it as attachment of this item code
|
||||
file_data = frappe.get_doc("File Data", unlinked_files[file_url]["file"])
|
||||
file_data.attached_to_doctype = "Item"
|
||||
file_data.attached_to_name = item_code
|
||||
file_data.save()
|
||||
|
||||
# set it as image in Item
|
||||
if not frappe.db.get_value("Item", item_code, "image"):
|
||||
frappe.db.set_value("Item", item_code, "image", new_file_url, update_modified=False)
|
||||
|
||||
rename_and_set_content_hash(files_path, unlinked_files, file_url)
|
||||
|
||||
fixed_files.append(file_url)
|
||||
|
||||
# commit
|
||||
frappe.db.commit()
|
||||
|
||||
return fixed_files
|
||||
|
||||
def rename_and_set_content_hash(files_path, unlinked_files, file_url):
|
||||
# rename this file
|
||||
old_filename = os.path.join(files_path, unlinked_files[file_url]["file"])
|
||||
new_filename = os.path.join(files_path, unlinked_files[file_url]["file_name"])
|
||||
|
||||
if not os.path.exists(new_filename):
|
||||
os.rename(old_filename, new_filename)
|
||||
|
||||
# set content hash if missing
|
||||
file_data_name = unlinked_files[file_url]["file"]
|
||||
if not frappe.db.get_value("File Data", file_data_name, "content_hash"):
|
||||
with open(new_filename, "r") as f:
|
||||
content_hash = get_content_hash(f.read())
|
||||
frappe.db.set_value("File Data", file_data_name, "content_hash", content_hash)
|
||||
|
||||
def get_unlinked_files(files_path):
|
||||
# find files that have the same name as a File Data doc
|
||||
# and the file_name mentioned in that File Data doc doesn't exist
|
||||
# and it isn't already attached to a doc
|
||||
unlinked_files = {}
|
||||
files = os.listdir(files_path)
|
||||
for file in files:
|
||||
if not frappe.db.exists("File Data", {"file_name": file}):
|
||||
file_data = frappe.db.get_value("File Data", {"name": file},
|
||||
["file_name", "attached_to_doctype", "attached_to_name"], as_dict=True)
|
||||
|
||||
if (file_data
|
||||
and file_data.file_name
|
||||
and file_data.file_name not in files
|
||||
and not file_data.attached_to_doctype
|
||||
and not file_data.attached_to_name):
|
||||
|
||||
file_data["file"] = file
|
||||
unlinked_files["files/{0}".format(file)] = file_data
|
||||
|
||||
return unlinked_files
|
||||
|
||||
def get_file_item_code(file_urls):
|
||||
# get a map of file_url, item_code and list of documents where file_url will need to be changed in image field
|
||||
file_item_code = {}
|
||||
|
||||
doctypes = frappe.db.sql_list("""select name from `tabDocType` dt
|
||||
where istable=1
|
||||
and exists (select name from `tabDocField` df where df.parent=dt.name and df.fieldname='item_code')
|
||||
and exists (select name from `tabDocField` df where df.parent=dt.name and df.fieldname='image')""")
|
||||
|
||||
for doctype in doctypes:
|
||||
result = frappe.db.sql("""select name, image, item_code, '{0}' as doctype from `tab{0}`
|
||||
where image in ({1})""".format(doctype, ", ".join(["%s"]*len(file_urls))),
|
||||
file_urls, as_dict=True)
|
||||
|
||||
for r in result:
|
||||
key = (r.image, r.item_code)
|
||||
if key not in file_item_code:
|
||||
file_item_code[key] = []
|
||||
|
||||
file_item_code[key].append(r)
|
||||
|
||||
return file_item_code
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty, repost_actual_qty
|
||||
|
||||
def execute():
|
||||
cancelled_invoices = frappe.db.sql_list("""select name from `tabSales Invoice`
|
||||
where docstatus = 2 and ifnull(update_stock, 0) = 1""")
|
||||
|
||||
if cancelled_invoices:
|
||||
repost_for = frappe.db.sql("""select distinct item_code, warehouse from `tabStock Ledger Entry`
|
||||
where voucher_type = 'Sales Invoice' and voucher_no in (%s)"""
|
||||
% (', '.join(['%s']*len(cancelled_invoices))), tuple(cancelled_invoices))
|
||||
|
||||
frappe.db.sql("""delete from `tabStock Ledger Entry`
|
||||
where voucher_type = 'Sales Invoice' and voucher_no in (%s)"""
|
||||
% (', '.join(['%s']*len(cancelled_invoices))), tuple(cancelled_invoices))
|
||||
|
||||
for item_code, warehouse in repost_for:
|
||||
repost_actual_qty(item_code, warehouse)
|
||||
|
||||
for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse
|
||||
from `tabPacked Item` where parenttype = 'Sales Invoice' and docstatus = 1"""):
|
||||
update_bin_qty(item_code, warehouse, {
|
||||
"reserved_qty": get_reserved_qty(item_code, warehouse)
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.email import sendmail_to_system_managers
|
||||
from frappe.utils import get_url_to_form
|
||||
|
||||
def execute():
|
||||
wrong_records = []
|
||||
for dt in ("Quotation", "Sales Order", "Delivery Note", "Sales Invoice",
|
||||
"Purchase Order", "Purchase Receipt", "Purchase Invoice"):
|
||||
records = frappe.db.sql_list("""select name from `tab{0}`
|
||||
where apply_discount_on = 'Net Total' and ifnull(discount_amount, 0) != 0
|
||||
and modified >= '2015-02-17' and docstatus=1""".format(dt))
|
||||
|
||||
if records:
|
||||
records = [get_url_to_form(dt, d) for d in records]
|
||||
wrong_records.append([dt, records])
|
||||
|
||||
if wrong_records:
|
||||
content = """Dear System Manager,
|
||||
|
||||
Due to an error related to Discount Amount on Net Total, tax calculation might be wrong in the following records. We did not fix the tax amount automatically because it can corrupt the entries, so we request you to check these records and amend if you found the calculation wrong.
|
||||
|
||||
Please check following Entries:
|
||||
|
||||
%s
|
||||
|
||||
|
||||
Regards,
|
||||
|
||||
Administrator""" % "\n".join([(d[0] + ": " + ", ".join(d[1])) for d in wrong_records])
|
||||
try:
|
||||
sendmail_to_system_managers("[Important] [ERPNext] Tax calculation might be wrong, please check.", content)
|
||||
except:
|
||||
pass
|
||||
|
||||
print "="*50
|
||||
print content
|
||||
print "="*50
|
||||
12
erpnext/patches/v5_4/set_root_and_report_type.py
Normal file
12
erpnext/patches/v5_4/set_root_and_report_type.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
roots = frappe.db.sql("""select lft, rgt, report_type, root_type
|
||||
from `tabAccount` where ifnull(parent_account, '')=''""", as_dict=1)
|
||||
for d in roots:
|
||||
frappe.db.sql("update `tabAccount` set report_type=%s, root_type=%s where lft > %s and rgt < %s",
|
||||
(d.report_type, d.root_type, d.lft, d.rgt))
|
||||
53
erpnext/patches/v5_4/stock_entry_additional_costs.py
Normal file
53
erpnext/patches/v5_4/stock_entry_additional_costs.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import flt
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Stock Entry")
|
||||
frappe.reload_doctype("Stock Entry Detail")
|
||||
frappe.reload_doctype("Landed Cost Taxes and Charges")
|
||||
|
||||
stock_entry_db_columns = frappe.db.get_table_columns("Stock Entry")
|
||||
if "additional_operating_cost" in stock_entry_db_columns:
|
||||
operating_cost_fieldname = "additional_operating_cost"
|
||||
elif "total_fixed_cost" in stock_entry_db_columns:
|
||||
operating_cost_fieldname = "total_fixed_cost"
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
frappe.db.sql("""update `tabStock Entry Detail` sed, `tabStock Entry` se
|
||||
set sed.valuation_rate=sed.incoming_rate, sed.basic_rate=sed.incoming_rate, sed.basic_amount=sed.amount
|
||||
where sed.parent = se.name
|
||||
and (se.purpose not in ('Manufacture', 'Repack') or ifnull({0}, 0)=0)
|
||||
""".format(operating_cost_fieldname))
|
||||
|
||||
|
||||
stock_entries = frappe.db.sql_list("""select name from `tabStock Entry`
|
||||
where purpose in ('Manufacture', 'Repack') and ifnull({0}, 0)!=0
|
||||
and docstatus < 2""".format(operating_cost_fieldname))
|
||||
|
||||
for d in stock_entries:
|
||||
stock_entry = frappe.get_doc("Stock Entry", d)
|
||||
stock_entry.append("additional_costs", {
|
||||
"description": "Additional Operating Cost",
|
||||
"amount": stock_entry.get(operating_cost_fieldname)
|
||||
})
|
||||
|
||||
number_of_fg_items = len([t.t_warehouse for t in stock_entry.get("items") if t.t_warehouse])
|
||||
|
||||
for d in stock_entry.get("items"):
|
||||
d.valuation_rate = d.incoming_rate
|
||||
|
||||
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
|
||||
d.additional_cost = stock_entry.get(operating_cost_fieldname)
|
||||
|
||||
d.basic_rate = flt(d.valuation_rate) - flt(d.additional_cost)
|
||||
d.basic_amount = flt(flt(d.basic_rate) *flt(d.transfer_qty), d.precision("basic_amount"))
|
||||
|
||||
stock_entry.flags.ignore_validate = True
|
||||
stock_entry.flags.ignore_validate_update_after_submit = True
|
||||
stock_entry.save()
|
||||
@@ -15,10 +15,11 @@ class TestActivityCost(unittest.TestCase):
|
||||
activity_cost1.update({
|
||||
"employee": "_T-Employee-0001",
|
||||
"employee_name": "_Test Employee",
|
||||
"activity_type": "_Test Activity Type",
|
||||
"activity_type": "_Test Activity Type 1",
|
||||
"billing_rate": 100,
|
||||
"costing_rate": 50
|
||||
})
|
||||
activity_cost1.insert()
|
||||
activity_cost2 = frappe.copy_doc(activity_cost1)
|
||||
self.assertRaises(DuplicationError, activity_cost2.insert )
|
||||
frappe.db.sql("delete from `tabActivity Cost`")
|
||||
@@ -1,10 +1 @@
|
||||
[
|
||||
{
|
||||
"activity_type": "_Test Activity Type",
|
||||
"docstatus": 1,
|
||||
"doctype": "Time Log",
|
||||
"from_time": "2013-01-01 10:00:00.000000",
|
||||
"note": "_Test Note",
|
||||
"to_time": "2013-01-01 11:00:00.000000"
|
||||
}
|
||||
]
|
||||
[]
|
||||
@@ -5,63 +5,44 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
from erpnext.projects.doctype.time_log.time_log import OverlapError
|
||||
from erpnext.projects.doctype.time_log.time_log import NotSubmittedError
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import NotInWorkingHoursError
|
||||
import datetime
|
||||
from frappe.utils import now_datetime, now
|
||||
from erpnext.projects.doctype.time_log.time_log import OverlapError, NotSubmittedError, NegativeHoursError
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
|
||||
from erpnext.manufacturing.doctype.production_order.test_production_order import make_prod_order_test_record
|
||||
|
||||
|
||||
class TestTimeLog(unittest.TestCase):
|
||||
def test_duplication(self):
|
||||
frappe.db.sql("delete from `tabTime Log`")
|
||||
tl1 = make_time_log_test_record(user= "test@example.com", employee= "_T-Employee-0002", simulate= True)
|
||||
|
||||
tl1 = frappe.get_doc(frappe.copy_doc(test_records[0]))
|
||||
tl1.user = "test@example.com"
|
||||
tl1.insert()
|
||||
|
||||
tl2 = frappe.get_doc(frappe.copy_doc(test_records[0]))
|
||||
tl2.user = "test@example.com"
|
||||
tl2 = make_time_log_test_record(user= "test@example.com", employee= "_T-Employee-0002",
|
||||
from_time= tl1.from_time, to_time= tl1.to_time, do_not_save= 1)
|
||||
|
||||
self.assertRaises(OverlapError, tl2.insert)
|
||||
|
||||
frappe.db.sql("delete from `tabTime Log`")
|
||||
|
||||
def test_production_order_status(self):
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item 2", qty=1, do_not_submit=True)
|
||||
prod_order = make_prod_order_test_record(item= "_Test FG Item 2", qty= 1, do_not_submit= True)
|
||||
prod_order.set_production_order_operations()
|
||||
prod_order.save()
|
||||
|
||||
time_log = frappe.get_doc({
|
||||
"doctype": "Time Log",
|
||||
"for_manufacturing": 1,
|
||||
"production_order": prod_order.name,
|
||||
"qty": 1,
|
||||
"from_time": "2014-12-26 00:00:00",
|
||||
"to_time": "2014-12-26 00:00:00"
|
||||
})
|
||||
time_log = make_time_log_test_record(for_manufacturing= 1, production_order= prod_order.name, qty= 1,
|
||||
employee= "_T-Employee-0003", do_not_save= True, simulate=1)
|
||||
|
||||
self.assertRaises(NotSubmittedError, time_log.save)
|
||||
|
||||
def test_time_log_on_holiday(self):
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item 2", qty=1,
|
||||
planned_start_date="2014-11-25 00:00:00", do_not_save=True)
|
||||
prod_order = make_prod_order_test_record(item= "_Test FG Item 2", qty= 1,
|
||||
planned_start_date= now(), do_not_save= True)
|
||||
prod_order.set_production_order_operations()
|
||||
prod_order.save()
|
||||
prod_order.submit()
|
||||
|
||||
time_log = frappe.get_doc({
|
||||
"doctype": "Time Log",
|
||||
"for_manufacturing": 1,
|
||||
"production_order": prod_order.name,
|
||||
"operation": prod_order.operations[0].operation,
|
||||
"operation_id": prod_order.operations[0].name,
|
||||
"qty": 1,
|
||||
"activity_type": "_Test Activity Type",
|
||||
"from_time": "2013-02-01 10:00:00",
|
||||
"to_time": "2013-02-01 20:00:00",
|
||||
"workstation": "_Test Workstation 1"
|
||||
})
|
||||
time_log = make_time_log_test_record(from_time= "2013-02-01 10:00:00", to_time= "2013-02-01 20:00:00",
|
||||
for_manufacturing= 1, production_order= prod_order.name, qty= 1,
|
||||
operation= prod_order.operations[0].operation, operation_id= prod_order.operations[0].name,
|
||||
workstation= "_Test Workstation 1", do_not_save= True)
|
||||
|
||||
self.assertRaises(WorkstationHolidayError , time_log.save)
|
||||
|
||||
time_log.update({
|
||||
@@ -76,28 +57,23 @@ class TestTimeLog(unittest.TestCase):
|
||||
time_log.cancel()
|
||||
|
||||
def test_negative_hours(self):
|
||||
frappe.db.sql("delete from `tabTime Log`")
|
||||
test_time_log = frappe.new_doc("Time Log")
|
||||
test_time_log.activity_type = "Communication"
|
||||
test_time_log.from_time = "2013-01-01 11:00:00.000000"
|
||||
test_time_log.to_time = "2013-01-01 10:00:00.000000"
|
||||
self.assertRaises(frappe.ValidationError, test_time_log.save)
|
||||
frappe.db.sql("delete from `tabTime Log`")
|
||||
|
||||
time_log = make_time_log_test_record(to_time= now_datetime() + datetime.timedelta(minutes=-1),
|
||||
employee="_T-Employee-0006",do_not_save= True)
|
||||
self.assertRaises(NegativeHoursError, time_log.save)
|
||||
|
||||
def test_total_activity_cost_for_project(self):
|
||||
frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """)
|
||||
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
|
||||
frappe.db.sql("""delete from `tabActivity Cost` where employee = "_T-Employee-0001" and activity_type = "_Test Activity Type" """)
|
||||
|
||||
activity_cost = frappe.new_doc('Activity Cost')
|
||||
activity_cost.update({
|
||||
"employee": "_T-Employee-0001",
|
||||
"employee_name": "_Test Employee",
|
||||
"activity_type": "_Test Activity Type",
|
||||
"billing_rate": 100,
|
||||
"costing_rate": 50
|
||||
})
|
||||
activity_cost.insert()
|
||||
if not frappe.db.exists('Activity Cost', {"activity_type": "_Test Activity Type"}):
|
||||
activity_cost = frappe.get_doc({
|
||||
"doctype": "Activity Cost",
|
||||
"employee": "",
|
||||
"activity_type": "_Test Activity Type",
|
||||
"billing_rate": 100,
|
||||
"costing_rate": 50
|
||||
})
|
||||
activity_cost.insert()
|
||||
|
||||
frappe.get_doc({
|
||||
"project_name": "_Test Project 1",
|
||||
@@ -108,40 +84,16 @@ class TestTimeLog(unittest.TestCase):
|
||||
|
||||
task_name = frappe.db.get_value("Task",{"project": "_Test Project 1"})
|
||||
|
||||
time_log = frappe.get_doc({
|
||||
"activity_type": "_Test Activity Type",
|
||||
"docstatus": 1,
|
||||
"doctype": "Time Log",
|
||||
"from_time": "2013-02-02 09:00:00.000000",
|
||||
"to_time": "2013-02-02 11:00:00.000000",
|
||||
"employee": "_T-Employee-0001",
|
||||
"project": "_Test Project 1",
|
||||
"task": task_name,
|
||||
"billable": 1
|
||||
})
|
||||
time_log.save()
|
||||
time_log = make_time_log_test_record(employee="_T-Employee-0002", hours=2, task= task_name)
|
||||
self.assertEqual(time_log.costing_rate, 50)
|
||||
self.assertEqual(time_log.costing_amount, 100)
|
||||
self.assertEqual(time_log.billing_rate, 100)
|
||||
self.assertEqual(time_log.billing_amount, 200)
|
||||
time_log.submit()
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 200)
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 200)
|
||||
|
||||
time_log2 = frappe.get_doc({
|
||||
"activity_type": "_Test Activity Type",
|
||||
"docstatus": 1,
|
||||
"doctype": "Time Log",
|
||||
"from_time": "2013-02-03 09:00:00.000000",
|
||||
"to_time": "2013-02-03 11:00:00.000000",
|
||||
"employee": "_T-Employee-0001",
|
||||
"project": "_Test Project 1",
|
||||
"task": task_name,
|
||||
"billable": 1
|
||||
})
|
||||
time_log2.save()
|
||||
|
||||
time_log2 = make_time_log_test_record(employee="_T-Employee-0003", hours=2, task= task_name)
|
||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 400)
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 400)
|
||||
|
||||
@@ -149,6 +101,44 @@ class TestTimeLog(unittest.TestCase):
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 200)
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 200)
|
||||
time_log.cancel()
|
||||
|
||||
test_records = frappe.get_test_records('Time Log')
|
||||
test_ignore = ["Time Log Batch", "Sales Invoice"]
|
||||
|
||||
def make_time_log_test_record(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
time_log = frappe.new_doc("Time Log")
|
||||
|
||||
time_log.from_time = args.from_time or now_datetime()
|
||||
time_log.hours = args.hours or 1
|
||||
time_log.to_time = args.to_time or time_log.from_time + datetime.timedelta(hours= time_log.hours)
|
||||
|
||||
time_log.project = args.project
|
||||
time_log.task = args.task
|
||||
time_log.for_manufacturing = args.for_manufacturing
|
||||
time_log.production_order = args.production_order
|
||||
time_log.operation = args.operation
|
||||
time_log.operation_id = args.operation_id
|
||||
time_log.workstation = args.workstation
|
||||
time_log.completed_qty = args.completed_qty
|
||||
time_log.activity_type = args.activity_type or "_Test Activity Type"
|
||||
time_log.billable = args.billable or 1
|
||||
time_log.employee = args.employee
|
||||
time_log.user = args.user
|
||||
|
||||
if not args.do_not_save:
|
||||
if args.simulate:
|
||||
while True:
|
||||
try:
|
||||
time_log.save()
|
||||
break
|
||||
except OverlapError:
|
||||
time_log.from_time = time_log.from_time + datetime.timedelta(minutes=10)
|
||||
time_log.to_time = time_log.from_time + datetime.timedelta(hours= time_log.hours)
|
||||
else:
|
||||
time_log.save()
|
||||
if not args.do_not_submit:
|
||||
time_log.submit()
|
||||
|
||||
return time_log
|
||||
@@ -6,10 +6,12 @@ import frappe, json
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, flt, get_datetime, get_time, getdate
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
class OverProductionLoggedError(frappe.ValidationError): pass
|
||||
class NotSubmittedError(frappe.ValidationError): pass
|
||||
class NegativeHoursError(frappe.ValidationError): pass
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
@@ -101,8 +103,8 @@ class TimeLog(Document):
|
||||
return existing[0] if existing else None
|
||||
|
||||
def validate_timings(self):
|
||||
if self.to_time and self.from_time and get_datetime(self.to_time) < get_datetime(self.from_time):
|
||||
frappe.throw(_("From Time cannot be greater than To Time"))
|
||||
if self.to_time and self.from_time and get_datetime(self.to_time) <= get_datetime(self.from_time):
|
||||
frappe.throw(_("To Time must be greater than From Time"), NegativeHoursError)
|
||||
|
||||
def calculate_total_hours(self):
|
||||
if self.to_time and self.from_time:
|
||||
@@ -181,9 +183,14 @@ class TimeLog(Document):
|
||||
|
||||
def move_to_next_non_overlapping_slot(self):
|
||||
"""If in overlap, set start as the end point of the overlapping time log"""
|
||||
overlapping = self.get_overlap_for("workstation")
|
||||
if overlapping:
|
||||
self.from_time = get_datetime(overlapping.to_time) + relativedelta(minutes=10)
|
||||
overlapping = self.get_overlap_for("workstation") \
|
||||
or self.get_overlap_for("employee") \
|
||||
or self.get_overlap_for("user")
|
||||
|
||||
if not overlapping:
|
||||
frappe.throw("Logical error: Must find overlapping")
|
||||
|
||||
self.from_time = get_datetime(overlapping.to_time) + get_mins_between_operations()
|
||||
|
||||
def get_time_log_summary(self):
|
||||
"""Returns 'Actual Operating Time'. """
|
||||
|
||||
@@ -3,43 +3,19 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, unittest
|
||||
from erpnext.projects.doctype.time_log.test_time_log import make_time_log_test_record
|
||||
|
||||
class TimeLogBatchTest(unittest.TestCase):
|
||||
def test_time_log_status(self):
|
||||
delete_time_log_and_batch()
|
||||
time_log = create_time_log()
|
||||
time_log = make_time_log_test_record(simulate=True)
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log, "status"), "Submitted")
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
|
||||
|
||||
tlb = create_time_log_batch(time_log)
|
||||
tlb = create_time_log_batch(time_log.name)
|
||||
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log, "status"), "Batched for Billing")
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Batched for Billing")
|
||||
tlb.cancel()
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log, "status"), "Submitted")
|
||||
|
||||
delete_time_log_and_batch()
|
||||
|
||||
def delete_time_log_and_batch():
|
||||
for name in frappe.db.sql_list("select name from `tabTime Log Batch` where docstatus=1"):
|
||||
frappe.get_doc("Time Log Batch", name).cancel()
|
||||
frappe.delete_doc("Time Log Batch", name)
|
||||
|
||||
for name in frappe.db.sql_list("select name from `tabTime Log` where docstatus=1"):
|
||||
frappe.get_doc("Time Log", name).cancel()
|
||||
frappe.delete_doc("Time Log", name)
|
||||
|
||||
def create_time_log():
|
||||
from erpnext.projects.doctype.time_log.test_time_log import test_records as time_log_records
|
||||
time_log = frappe.copy_doc(time_log_records[0])
|
||||
time_log.update({
|
||||
"from_time": "2013-01-02 10:00:00.000000",
|
||||
"to_time": "2013-01-02 11:00:00.000000",
|
||||
"docstatus": 0,
|
||||
"for_manufacturing": 0
|
||||
})
|
||||
time_log.insert()
|
||||
time_log.submit()
|
||||
return time_log.name
|
||||
self.assertEquals(frappe.db.get_value("Time Log", time_log.name, "status"), "Submitted")
|
||||
|
||||
def create_time_log_batch(time_log):
|
||||
tlb = frappe.get_doc({
|
||||
|
||||
@@ -15,6 +15,8 @@ class TimeLogBatch(Document):
|
||||
|
||||
def validate(self):
|
||||
self.set_status()
|
||||
self.total_hours = 0.0
|
||||
self.total_billing_amount = 0.0
|
||||
for d in self.get("time_logs"):
|
||||
tl = frappe.get_doc("Time Log", d.time_log)
|
||||
self.update_time_log_values(d, tl)
|
||||
|
||||
@@ -11,7 +11,8 @@ $(document).bind('toolbar_setup', function() {
|
||||
href="https://discuss.erpnext.com">Feedback</a></p>'
|
||||
|
||||
|
||||
$('.navbar-home').html('<img class="erpnext-icon" src="/assets/erpnext/images/erp-icon.svg" />');
|
||||
$('.navbar-home').html('<img class="erpnext-icon" src="'+
|
||||
frappe.urllib.get_base_url()+'/assets/erpnext/images/erp-icon.svg" />');
|
||||
|
||||
$('[data-link="docs"]').attr("href", "https://manual.erpnext.com")
|
||||
});
|
||||
|
||||
@@ -13,30 +13,8 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
|
||||
|
||||
setup_warehouse_query: function() {
|
||||
var me = this;
|
||||
var warehouse_query_method = function() {
|
||||
erpnext.queries.setup_queries(this.frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(me.frm.doc);
|
||||
};
|
||||
|
||||
var _set_warehouse_query = function(doctype, parentfield) {
|
||||
var warehouse_link_fields = frappe.meta.get_docfields(doctype, me.frm.doc.name,
|
||||
{"fieldtype": "Link", "options": "Warehouse"});
|
||||
$.each(warehouse_link_fields, function(i, df) {
|
||||
if(parentfield) {
|
||||
me.frm.set_query(df.fieldname, parentfield, warehouse_query_method);
|
||||
} else {
|
||||
me.frm.set_query(df.fieldname, warehouse_query_method);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_set_warehouse_query(me.frm.doc.doctype);
|
||||
|
||||
// warehouse field in tables
|
||||
var table_fields = frappe.meta.get_docfields(me.frm.doc.doctype, me.frm.doc.name,
|
||||
{"fieldtype": "Table"});
|
||||
|
||||
$.each(table_fields, function(i, df) {
|
||||
_set_warehouse_query(df.options, df.fieldname);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -58,7 +36,7 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
|
||||
|
||||
show_general_ledger: function() {
|
||||
var me = this;
|
||||
if(this.frm.doc.docstatus===1 && cint(frappe.defaults.get_default("auto_accounting_for_stock"))) {
|
||||
if(this.frm.doc.docstatus===1) {
|
||||
cur_frm.add_custom_button(__('Accounting Ledger'), function() {
|
||||
frappe.route_options = {
|
||||
voucher_no: me.frm.doc.name,
|
||||
|
||||
@@ -94,10 +94,6 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
"tax_amount_for_current_item", "grand_total_for_current_item",
|
||||
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||
|
||||
if (frappe.meta.get_docfield(me.frm.doc.doctype, "is_return") && me.frm.doc.is_return
|
||||
&& tax.charge_type == "Actual")
|
||||
tax.tax_amount = -1 * tax.tax_amount;
|
||||
|
||||
if (cstr(tax.charge_type) != "Actual" &&
|
||||
!(me.discount_amount_applied && me.frm.doc.apply_discount_on=="Grand Total"))
|
||||
@@ -260,7 +256,8 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
|
||||
me.round_off_totals(tax);
|
||||
|
||||
// adjust Discount Amount loss in last tax iteration
|
||||
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied && me.frm.doc.apply_discount_on == "Grand Total")
|
||||
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied
|
||||
&& me.frm.doc.apply_discount_on == "Grand Total" && me.frm.doc.discount_amount)
|
||||
me.adjust_discount_amount_loss(tax);
|
||||
}
|
||||
});
|
||||
@@ -369,9 +366,9 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
if (in_list(["Valuation and Total", "Total"], tax.category)) {
|
||||
if(tax.add_deduct_tax == "Add") {
|
||||
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount);
|
||||
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount);
|
||||
} else {
|
||||
me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount);
|
||||
me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -36,7 +36,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(this.frm.fields_dict["items"]) {
|
||||
this["items_remove"] = this.calculate_taxes_and_totals;
|
||||
}
|
||||
|
||||
|
||||
if(this.frm.fields_dict["recurring_print_format"]) {
|
||||
this.frm.set_query("recurring_print_format", function(doc) {
|
||||
return{
|
||||
@@ -46,7 +46,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if(this.frm.fields_dict["return_against"]) {
|
||||
this.frm.set_query("return_against", function(doc) {
|
||||
var filters = {
|
||||
@@ -56,13 +56,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
};
|
||||
if (me.frm.fields_dict["customer"] && doc.customer) filters["customer"] = doc.customer;
|
||||
if (me.frm.fields_dict["supplier"] && doc.supplier) filters["supplier"] = doc.supplier;
|
||||
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
onload_post_render: function() {
|
||||
@@ -218,7 +218,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
item.serial_no += sr_no[x] + '\n';
|
||||
|
||||
refresh_field("serial_no", item.name, item.parentfield);
|
||||
frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length);
|
||||
if(!doc.is_return) {
|
||||
frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -273,7 +275,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
posting_date: function() {
|
||||
var me = this;
|
||||
if (this.frm.doc.posting_date) {
|
||||
if ((this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.customer) ||
|
||||
if ((this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.customer) ||
|
||||
(this.frm.doc.doctype == "Purchase Invoice" && this.frm.doc.supplier)) {
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.party.get_due_date",
|
||||
@@ -282,7 +284,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
"party_type": me.frm.doc.doctype == "Sales Invoice" ? "Customer" : "Supplier",
|
||||
"party": me.frm.doc.doctype == "Sales Invoice" ? me.frm.doc.customer : me.frm.doc.supplier,
|
||||
"company": me.frm.doc.company
|
||||
},
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message) {
|
||||
me.frm.set_value("due_date", r.message);
|
||||
@@ -299,7 +301,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
get_company_currency: function() {
|
||||
return erpnext.get_currency(this.frm.doc.company);
|
||||
},
|
||||
|
||||
|
||||
contact_person: function() {
|
||||
erpnext.utils.get_contact_details(this.frm);
|
||||
},
|
||||
@@ -371,7 +373,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
plc_conversion_rate: function() {
|
||||
if(this.frm.doc.price_list_currency === this.get_company_currency()) {
|
||||
this.frm.set_value("plc_conversion_rate", 1.0);
|
||||
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
|
||||
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
|
||||
&& this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 &&
|
||||
cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) {
|
||||
this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate);
|
||||
|
||||
@@ -75,3 +75,26 @@ $.extend(erpnext.queries, {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.queries.setup_queries = function(frm, options, query_fn) {
|
||||
var me = this;
|
||||
var set_query = function(doctype, parentfield) {
|
||||
var link_fields = frappe.meta.get_docfields(doctype, frm.doc.name,
|
||||
{"fieldtype": "Link", "options": options});
|
||||
$.each(link_fields, function(i, df) {
|
||||
if(parentfield) {
|
||||
frm.set_query(df.fieldname, parentfield, query_fn);
|
||||
} else {
|
||||
frm.set_query(df.fieldname, query_fn);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
set_query(frm.doc.doctype);
|
||||
|
||||
// warehouse field in tables
|
||||
$.each(frappe.meta.get_docfields(frm.doc.doctype, frm.doc.name, {"fieldtype": "Table"}),
|
||||
function(i, df) {
|
||||
set_query(df.options, df.fieldname);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ $.extend(erpnext, {
|
||||
return {
|
||||
filters: {
|
||||
item_code:grid_row.doc.item_code ,
|
||||
warehouse:grid_row.doc.warehouse
|
||||
warehouse:cur_frm.doc.is_return ? null : grid_row.doc.warehouse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ cur_frm.cscript.setup_dashboard = function(doc) {
|
||||
cur_frm.dashboard.add_doctype_badge("Sales Order", "customer");
|
||||
cur_frm.dashboard.add_doctype_badge("Delivery Note", "customer");
|
||||
cur_frm.dashboard.add_doctype_badge("Sales Invoice", "customer");
|
||||
cur_frm.dashboard.add_doctype_badge("Project", "customer");
|
||||
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
@@ -62,6 +63,8 @@ cur_frm.cscript.setup_dashboard = function(doc) {
|
||||
+ '</b> / <span class="text-muted">' + __("Unpaid") + ": <b>"
|
||||
+ format_currency(r.message.total_unpaid, r.message["company_currency"][0])
|
||||
+ '</b></span>');
|
||||
} else {
|
||||
cur_frm.dashboard.set_headline("");
|
||||
}
|
||||
}
|
||||
cur_frm.dashboard.set_badge_count(r.message);
|
||||
|
||||
@@ -1,35 +1,62 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-06-11 14:26:44",
|
||||
"custom": 0,
|
||||
"description": "Buyer of Goods and Services.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "basic_info",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "",
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "icon-user",
|
||||
"permlevel": 0,
|
||||
"reqd": 0
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"options": "CUST-",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "customer_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 0,
|
||||
"label": "Full Name",
|
||||
@@ -38,25 +65,43 @@
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "customer_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Type",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "customer_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nCompany\nIndividual",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "lead_name",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 0,
|
||||
"label": "From Lead",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "lead_name",
|
||||
@@ -64,221 +109,580 @@
|
||||
"options": "Lead",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
"read_only": 0,
|
||||
"report_hide": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "column_break0",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"description": "",
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Customer Group",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "customer_group",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Customer Group",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"description": "",
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Territory",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "territory",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "tax_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Tax ID",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "is_frozen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Is Frozen",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "address_contacts",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "",
|
||||
"no_copy": 0,
|
||||
"options": "icon-map-marker",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "address_html",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Address HTML",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "contact_html",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Contact HTML",
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "HTML",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "default_receivable_accounts",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Default Receivable Accounts",
|
||||
"permlevel": 0
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"description": "Mention if non-standard receivable account applicable",
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Accounts",
|
||||
"no_copy": 0,
|
||||
"options": "Party Account",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "more_info",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "",
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "icon-file-text",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "column_break2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"description": "Your Customer's TAX registration numbers (if applicable) or any general information",
|
||||
"allow_on_submit": 0,
|
||||
"description": "Additional information regarding the customer.",
|
||||
"fieldname": "customer_details",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Customer Details",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "customer_details",
|
||||
"oldfieldtype": "Code",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "column_break3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "default_currency",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Currency",
|
||||
"no_copy": 1,
|
||||
"options": "Currency",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "default_price_list",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Price List",
|
||||
"no_copy": 0,
|
||||
"options": "Price List",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "default_taxes_and_charges",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Taxes and Charges",
|
||||
"no_copy": 0,
|
||||
"options": "Sales Taxes and Charges Template",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "credit_days_based_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Credit Days Based On",
|
||||
"no_copy": 0,
|
||||
"options": "\nFixed Days\nLast Day of the Next Month",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"depends_on": "eval:doc.credit_days_based_on=='Fixed Days'",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Credit Days",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "credit_days",
|
||||
"oldfieldtype": "Int",
|
||||
"permlevel": 1
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "credit_limit",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Credit Limit",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "credit_limit",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 1
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "website",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Website",
|
||||
"permlevel": 0
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "sales_team_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "",
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "icon-group",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "default_sales_partner",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sales Partner",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "default_sales_partner",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Sales Partner",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "default_commission_rate",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Commission Rate",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "default_commission_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "sales_team",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sales Team Details",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "sales_team",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Sales Team",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "communications",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Communications",
|
||||
"no_copy": 0,
|
||||
"options": "Communication",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-user",
|
||||
"idx": 1,
|
||||
"modified": "2015-07-13 05:28:25.753684",
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-08-07 20:34:25.761769",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
@@ -287,38 +691,73 @@
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 1,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"role": "Sales User"
|
||||
"report": 0,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager"
|
||||
"role": "Sales Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
@@ -330,46 +769,108 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 1,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Sales Master Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock User"
|
||||
"role": "Stock User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock Manager"
|
||||
"role": "Stock Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User"
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager"
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "customer_name,customer_group,territory",
|
||||
"title_field": "customer_name"
|
||||
}
|
||||
@@ -119,7 +119,8 @@ def get_dashboard_info(customer):
|
||||
frappe.msgprint(_("Not permitted"), raise_exception=True)
|
||||
|
||||
out = {}
|
||||
for doctype in ["Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
|
||||
for doctype in ["Opportunity", "Quotation", "Sales Order", "Delivery Note",
|
||||
"Sales Invoice", "Project"]:
|
||||
out[doctype] = frappe.db.get_value(doctype,
|
||||
{"customer": customer, "docstatus": ["!=", 2] }, "count(*)")
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.test_runner import make_test_records
|
||||
from erpnext.controllers.accounts_controller import CustomerFrozen
|
||||
|
||||
test_ignore = ["Price List"]
|
||||
|
||||
@@ -65,5 +66,15 @@ class TestCustomer(unittest.TestCase):
|
||||
{"comment_doctype": "Customer", "comment_docname": "_Test Customer 1 Renamed"}), comment.name)
|
||||
|
||||
frappe.rename_doc("Customer", "_Test Customer 1 Renamed", "_Test Customer 1")
|
||||
|
||||
|
||||
|
||||
def test_freezed_customer(self):
|
||||
frappe.db.set_value("Customer", "_Test Customer", "is_frozen", 1)
|
||||
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
so = make_sales_order(do_not_save= True)
|
||||
self.assertRaises(CustomerFrozen, so.save)
|
||||
|
||||
frappe.db.set_value("Customer", "_Test Customer", "is_frozen", 0)
|
||||
|
||||
so.save()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user