Compare commits
392 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98ddbf05b1 | ||
|
|
67d25028b2 | ||
|
|
328d5920bd | ||
|
|
81d614b6bd | ||
|
|
38540e85e8 | ||
|
|
b3d97a560f | ||
|
|
39436c6d38 | ||
|
|
55bf951ff5 | ||
|
|
220a208f4e | ||
|
|
0c8e46fdea | ||
|
|
ee4901f4a0 | ||
|
|
5dd7503516 | ||
|
|
5cc0e08a41 | ||
|
|
fecf5a9a15 | ||
|
|
5b4050a4ff | ||
|
|
4d7862ef4c | ||
|
|
6e41475612 | ||
|
|
803e0ec27c | ||
|
|
b3addff99e | ||
|
|
200ceb5352 | ||
|
|
8c50f5c23f | ||
|
|
86600ac8b9 | ||
|
|
b76a04b470 | ||
|
|
80913994da | ||
|
|
92ecdbe0c8 | ||
|
|
c920efc156 | ||
|
|
3b9fe1ae6f | ||
|
|
c76c5e699b | ||
|
|
666fba94e2 | ||
|
|
48a8a40703 | ||
|
|
5646816282 | ||
|
|
f8df3c7af2 | ||
|
|
62d4dfa883 | ||
|
|
b8f9fd023b | ||
|
|
0df3c93737 | ||
|
|
e0e7dcd2f6 | ||
|
|
d5b1baed39 | ||
|
|
800545ff5b | ||
|
|
388a177f75 | ||
|
|
821166c628 | ||
|
|
2b8df06f8e | ||
|
|
4e8e466a98 | ||
|
|
31d4482336 | ||
|
|
5cd8c7c722 | ||
|
|
e14d9b5476 | ||
|
|
6a8ff1bebe | ||
|
|
a41d464198 | ||
|
|
980793bde0 | ||
|
|
b7329eac19 | ||
|
|
9ec5cb2570 | ||
|
|
44296a392d | ||
|
|
9097c7e11c | ||
|
|
0256d7549c | ||
|
|
94d8b99ef9 | ||
|
|
3fe1335f7b | ||
|
|
dc7a4ac8af | ||
|
|
c0ff769214 | ||
|
|
0a527b9f9a | ||
|
|
e03871f9de | ||
|
|
c0286780bd | ||
|
|
9cc484650b | ||
|
|
319f126258 | ||
|
|
234de12836 | ||
|
|
4c19000ed9 | ||
|
|
a1651ca5f2 | ||
|
|
4d042cd81a | ||
|
|
d72fae670a | ||
|
|
d458e25dc5 | ||
|
|
43474a3afa | ||
|
|
7daa2a2085 | ||
|
|
45075d8915 | ||
|
|
1de3040ecb | ||
|
|
af10f659d9 | ||
|
|
6f36691c64 | ||
|
|
dfe629aff7 | ||
|
|
23bf2a6647 | ||
|
|
b69cb8080c | ||
|
|
f23b5ed23b | ||
|
|
0a28387c70 | ||
|
|
caae8c57bc | ||
|
|
44ae135c36 | ||
|
|
47e786ef62 | ||
|
|
f10be395c1 | ||
|
|
ac967d09ec | ||
|
|
d1e8e8652f | ||
|
|
72649c207f | ||
|
|
d06b685fdf | ||
|
|
6411a56cdc | ||
|
|
34b3b04fb0 | ||
|
|
b1a2a16f43 | ||
|
|
f092e68a58 | ||
|
|
6d497ccb4c | ||
|
|
a7b97f7bac | ||
|
|
f40d3bd10f | ||
|
|
1e2be32860 | ||
|
|
6aec9e32d4 | ||
|
|
59cc0e5029 | ||
|
|
851f39cee1 | ||
|
|
6822a30f8c | ||
|
|
495ba1618b | ||
|
|
778d7595aa | ||
|
|
a40dbd0384 | ||
|
|
80dfb9f834 | ||
|
|
dabb303358 | ||
|
|
d16ef54665 | ||
|
|
dc248b9458 | ||
|
|
bf0f26b4a4 | ||
|
|
929fd4ce47 | ||
|
|
81c895b21e | ||
|
|
27a21f80d7 | ||
|
|
aa7085e11c | ||
|
|
6e5363ba48 | ||
|
|
53746636c3 | ||
|
|
485d48c101 | ||
|
|
0e1ef35968 | ||
|
|
35effe9be0 | ||
|
|
648d6e46f3 | ||
|
|
d6d9a3ddd7 | ||
|
|
18f05db19a | ||
|
|
586fecfe73 | ||
|
|
1c196f958f | ||
|
|
9b64e2e24c | ||
|
|
da5e227ad6 | ||
|
|
4f95e5d092 | ||
|
|
6a8fd0102f | ||
|
|
2b172ec4b4 | ||
|
|
208c69f196 | ||
|
|
32b69bf122 | ||
|
|
b1fac1817c | ||
|
|
6516358a71 | ||
|
|
c6e2087673 | ||
|
|
d8469a7bfa | ||
|
|
cf645aceae | ||
|
|
3dd72e238f | ||
|
|
b74ce74ec9 | ||
|
|
074aaa6005 | ||
|
|
9d5f43f4f0 | ||
|
|
7522aadc6e | ||
|
|
326fdcb454 | ||
|
|
c41addec96 | ||
|
|
defed15528 | ||
|
|
cbc29989fe | ||
|
|
a791170f29 | ||
|
|
b5991e9264 | ||
|
|
fb1e87710b | ||
|
|
8d2b0d800c | ||
|
|
2515022377 | ||
|
|
5558ee8597 | ||
|
|
31a4fa5dd3 | ||
|
|
53a66ee386 | ||
|
|
0881557c7d | ||
|
|
9326fb78f2 | ||
|
|
ecc6b1917b | ||
|
|
a7d168c05f | ||
|
|
fc5d8fcd9f | ||
|
|
b84e56ebb5 | ||
|
|
565d3efcdf | ||
|
|
d63ad3bb5f | ||
|
|
f9dec5201f | ||
|
|
cf3a2f6579 | ||
|
|
032baeac5b | ||
|
|
754c43f6c3 | ||
|
|
e0c9f3c282 | ||
|
|
3ec5eabaf6 | ||
|
|
f2752bf38c | ||
|
|
a9ff7df2e6 | ||
|
|
2fd6814cbf | ||
|
|
1ed1c4e6a4 | ||
|
|
04e3a506e4 | ||
|
|
a077795581 | ||
|
|
eefc492ff4 | ||
|
|
c9203a1bee | ||
|
|
1919af2ff1 | ||
|
|
aff4a67954 | ||
|
|
b5c296da9e | ||
|
|
7ca472780b | ||
|
|
763660b2e4 | ||
|
|
6c368e2dfb | ||
|
|
b3354198f1 | ||
|
|
b10526dd86 | ||
|
|
290253fdd0 | ||
|
|
24cde55e28 | ||
|
|
49cd19d917 | ||
|
|
046137caa2 | ||
|
|
1a92eb14ed | ||
|
|
f5112905dc | ||
|
|
c4e6c42950 | ||
|
|
6a743be1de | ||
|
|
4a28144941 | ||
|
|
5717a265b7 | ||
|
|
ae90ea9547 | ||
|
|
682956543e | ||
|
|
45e9dd9c51 | ||
|
|
fe2147a496 | ||
|
|
dbde140e46 | ||
|
|
c8e66a0f71 | ||
|
|
26c46282e8 | ||
|
|
95270ad14b | ||
|
|
616254d2a0 | ||
|
|
248585b5a1 | ||
|
|
c42312ea12 | ||
|
|
a85ddf2fb4 | ||
|
|
238521c2bd | ||
|
|
353f73a153 | ||
|
|
c436d93303 | ||
|
|
e13b769813 | ||
|
|
2578d49b84 | ||
|
|
776ff2f75d | ||
|
|
f3ecfd8e58 | ||
|
|
539ea2cefb | ||
|
|
b9460ed22c | ||
|
|
ee69f707a9 | ||
|
|
0debcf9f2f | ||
|
|
9c1c4ef3dd | ||
|
|
8131f7657e | ||
|
|
6ef057a2a3 | ||
|
|
39eeac265b | ||
|
|
466702200f | ||
|
|
f69b9a8c47 | ||
|
|
53b65ab8ed | ||
|
|
18fda5a571 | ||
|
|
57bd1308eb | ||
|
|
511780a4d4 | ||
|
|
793ba8fc06 | ||
|
|
4006eb5277 | ||
|
|
ccd9b38463 | ||
|
|
c31808f5b2 | ||
|
|
6e6954cab8 | ||
|
|
b1e9fb9e14 | ||
|
|
67f191df4e | ||
|
|
c72e1f812d | ||
|
|
a92f060740 | ||
|
|
f25e2a29f7 | ||
|
|
cce000a6d0 | ||
|
|
db64c69dac | ||
|
|
f75ea952e3 | ||
|
|
929676fceb | ||
|
|
83100c9c84 | ||
|
|
0671ea8137 | ||
|
|
76b20a5fa4 | ||
|
|
151853b887 | ||
|
|
51208b3f0b | ||
|
|
75db6f7073 | ||
|
|
fce8f36bb2 | ||
|
|
6f1d2eeffd | ||
|
|
d1ee962d4b | ||
|
|
4d3dc87a1a | ||
|
|
af18b2cdc5 | ||
|
|
e3bc213262 | ||
|
|
e573bd9074 | ||
|
|
53ec8c6322 | ||
|
|
97f6576213 | ||
|
|
54f33f4e5d | ||
|
|
3609872760 | ||
|
|
6de68c8671 | ||
|
|
957c9f5ff0 | ||
|
|
4701bc8bfc | ||
|
|
e494144c96 | ||
|
|
60093d98b0 | ||
|
|
d1c530c564 | ||
|
|
ad013264eb | ||
|
|
7c27436d21 | ||
|
|
2f9ef85614 | ||
|
|
3d73a4f944 | ||
|
|
29c7d5fc63 | ||
|
|
aa918e8528 | ||
|
|
2c99594688 | ||
|
|
9ccc43980b | ||
|
|
50e66d81de | ||
|
|
604febb398 | ||
|
|
e434e8e2e2 | ||
|
|
96930e25f3 | ||
|
|
5e4ec85574 | ||
|
|
1f1b7eb7d5 | ||
|
|
e18388ade3 | ||
|
|
911e034d1c | ||
|
|
0082b78075 | ||
|
|
bfe18d6085 | ||
|
|
c1bc0f9dfb | ||
|
|
25148d0de5 | ||
|
|
9e36a9ee04 | ||
|
|
faf39ecef4 | ||
|
|
22189ec9e8 | ||
|
|
a1d39cab21 | ||
|
|
d40c020e0e | ||
|
|
bec88bc52a | ||
|
|
afe52e8e09 | ||
|
|
72aac09d62 | ||
|
|
43331564b4 | ||
|
|
42cf5f279f | ||
|
|
8640a01f85 | ||
|
|
1dccc039b7 | ||
|
|
59c543570a | ||
|
|
76cbb9132f | ||
|
|
4856645b6d | ||
|
|
c5420bb453 | ||
|
|
93670fedda | ||
|
|
1dcedb5054 | ||
|
|
c6da5fb38e | ||
|
|
2ea9b3e6f2 | ||
|
|
14a3e64c80 | ||
|
|
7f4bc64d22 | ||
|
|
250bae2603 | ||
|
|
fd46bf2616 | ||
|
|
774167192a | ||
|
|
291e161793 | ||
|
|
9f86022c2b | ||
|
|
d45c12b382 | ||
|
|
c9cf5aebea | ||
|
|
7ab861fe95 | ||
|
|
558d44e519 | ||
|
|
8393ebbbca | ||
|
|
8b744b2d03 | ||
|
|
a35e34b5f0 | ||
|
|
83dee07420 | ||
|
|
3eccb84eaa | ||
|
|
e40b100110 | ||
|
|
b6b27d9256 | ||
|
|
7b7962d28c | ||
|
|
77744766dd | ||
|
|
dcfc849946 | ||
|
|
d9ab09ab2b | ||
|
|
f8cc86bfed | ||
|
|
0800031c0d | ||
|
|
6b0fea16b6 | ||
|
|
9c0f462336 | ||
|
|
07bb46e3fb | ||
|
|
aa7d0c0163 | ||
|
|
a9f5a697bb | ||
|
|
73420e462f | ||
|
|
fa4a2a53e8 | ||
|
|
df1a5a9633 | ||
|
|
5324234bd0 | ||
|
|
ca2509423a | ||
|
|
4109f88c04 | ||
|
|
5bf52ebed6 | ||
|
|
ba99945359 | ||
|
|
ec1dae023c | ||
|
|
7d476a3e35 | ||
|
|
81449ece54 | ||
|
|
7323bfdad7 | ||
|
|
3c8bea65ef | ||
|
|
57c311f8e9 | ||
|
|
91a564989f | ||
|
|
33daf281bd | ||
|
|
a8752db012 | ||
|
|
018f0d3bbd | ||
|
|
1cd762e9d0 | ||
|
|
d88f850d0f | ||
|
|
cf045d86b0 | ||
|
|
a3b8c77af1 | ||
|
|
1564f1476c | ||
|
|
c9c8e19ec2 | ||
|
|
469247bf73 | ||
|
|
a2dbd391b3 | ||
|
|
8051ca1859 | ||
|
|
249cdd92e0 | ||
|
|
e543fc483f | ||
|
|
a322b159ab | ||
|
|
0cc837eac5 | ||
|
|
4897897a3a | ||
|
|
5038d6a6db | ||
|
|
2d7370a525 | ||
|
|
c4950a0281 | ||
|
|
6f486f3719 | ||
|
|
5c008ef023 | ||
|
|
5c211d8abf | ||
|
|
4ac25f28a3 | ||
|
|
06facea895 | ||
|
|
110f4ea0c9 | ||
|
|
db21f86b26 | ||
|
|
20c7c290fa | ||
|
|
5945144c08 | ||
|
|
10711dd09d | ||
|
|
63dbacd7c0 | ||
|
|
48e43e2421 | ||
|
|
217aadba7e | ||
|
|
eb2ab3bf76 | ||
|
|
d272fd42c9 | ||
|
|
c5b2a58669 | ||
|
|
a3d04cdbca | ||
|
|
2791054327 | ||
|
|
eb4fa966b0 | ||
|
|
ad6151b690 | ||
|
|
64095084e7 | ||
|
|
828fea6d66 | ||
|
|
2cd994977b | ||
|
|
17906d5599 | ||
|
|
25052a0ab7 | ||
|
|
dbd72ea89d | ||
|
|
adba6c833d | ||
|
|
7b9e30914f |
@@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '12.2.2'
|
||||
__version__ = '12.3.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -109,12 +109,13 @@ class Account(NestedSet):
|
||||
if not descendants: return
|
||||
|
||||
parent_acc_name_map = {}
|
||||
parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name")
|
||||
parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
|
||||
["account_name", "account_number"])
|
||||
for d in frappe.db.get_values('Account',
|
||||
{"company": ["in", descendants], "account_name": parent_acc_name},
|
||||
{ "company": ["in", descendants], "account_name": parent_acc_name,
|
||||
"account_number": parent_acc_number },
|
||||
["company", "name"], as_dict=True):
|
||||
parent_acc_name_map[d["company"]] = d["name"]
|
||||
|
||||
if not parent_acc_name_map: return
|
||||
|
||||
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
||||
|
||||
@@ -15,8 +15,8 @@ def upload_bank_statement():
|
||||
with open(frappe.uploaded_file, "rb") as upfile:
|
||||
fcontent = upfile.read()
|
||||
else:
|
||||
from frappe.utils.file_manager import get_uploaded_content
|
||||
fname, fcontent = get_uploaded_content()
|
||||
fcontent = frappe.local.uploaded_file
|
||||
fname = frappe.local.uploaded_filename
|
||||
|
||||
if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')):
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
|
||||
@@ -398,7 +398,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
|
||||
args: {
|
||||
"account_type": (doc.voucher_type=="Bank Entry" ?
|
||||
"Bank" : (doc.voucher_type=="Cash" ? "Cash" : null)),
|
||||
"Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)),
|
||||
"company": doc.company
|
||||
},
|
||||
callback: function(r) {
|
||||
|
||||
@@ -931,9 +931,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
grand_total = doc.rounded_total or doc.grand_total
|
||||
outstanding_amount = doc.outstanding_amount
|
||||
elif dt in ("Expense Claim"):
|
||||
grand_total = doc.total_sanctioned_amount
|
||||
outstanding_amount = doc.total_sanctioned_amount \
|
||||
- doc.total_amount_reimbursed - flt(doc.total_advance_amount)
|
||||
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
|
||||
outstanding_amount = doc.grand_total \
|
||||
- doc.total_amount_reimbursed
|
||||
elif dt == "Employee Advance":
|
||||
grand_total = doc.advance_amount
|
||||
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
|
||||
|
||||
@@ -350,13 +350,13 @@ def get_amount(ref_doc):
|
||||
if dt in ["Sales Order", "Purchase Order"]:
|
||||
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
|
||||
|
||||
if dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if ref_doc.party_account_currency == ref_doc.currency:
|
||||
grand_total = flt(ref_doc.outstanding_amount)
|
||||
else:
|
||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||
|
||||
if dt == "Fees":
|
||||
elif dt == "Fees":
|
||||
grand_total = ref_doc.outstanding_amount
|
||||
|
||||
if grand_total > 0 :
|
||||
|
||||
@@ -389,8 +389,7 @@
|
||||
"fieldname": "rate_or_discount",
|
||||
"fieldtype": "Select",
|
||||
"label": "Rate or Discount",
|
||||
"options": "\nRate\nDiscount Percentage\nDiscount Amount",
|
||||
"reqd": 1
|
||||
"options": "\nRate\nDiscount Percentage\nDiscount Amount"
|
||||
},
|
||||
{
|
||||
"default": "Grand Total",
|
||||
@@ -439,19 +438,20 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.mixed_conditions",
|
||||
"depends_on": "eval:!doc.mixed_conditions && doc.apply_on != 'Transaction'",
|
||||
"fieldname": "same_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Same Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.same_item || doc.mixed_conditions",
|
||||
"depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions",
|
||||
"fieldname": "free_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Free Item",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "free_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty"
|
||||
@@ -554,7 +554,7 @@
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"modified": "2019-10-15 12:39:40.399792",
|
||||
"modified": "2019-12-18 17:29:22.957077",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
||||
@@ -48,6 +48,9 @@ class PricingRule(Document):
|
||||
if tocheck and not self.get(tocheck):
|
||||
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
|
||||
|
||||
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
|
||||
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
|
||||
|
||||
def validate_applicable_for_selling_or_buying(self):
|
||||
if not self.selling and not self.buying:
|
||||
throw(_("Atleast one of the Selling or Buying must be selected"))
|
||||
@@ -181,8 +184,9 @@ def get_serial_no_for_item(args):
|
||||
item_details.serial_no = get_serial_no(args)
|
||||
return item_details
|
||||
|
||||
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rules
|
||||
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
|
||||
get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
|
||||
|
||||
if isinstance(doc, string_types):
|
||||
doc = json.loads(doc)
|
||||
@@ -209,6 +213,57 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
|
||||
item_details, args.get('item_code'))
|
||||
return item_details
|
||||
|
||||
update_args_for_pricing_rule(args)
|
||||
|
||||
pricing_rules = (get_applied_pricing_rules(args)
|
||||
if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
|
||||
|
||||
if pricing_rules:
|
||||
rules = []
|
||||
|
||||
for pricing_rule in pricing_rules:
|
||||
if not pricing_rule: continue
|
||||
|
||||
if isinstance(pricing_rule, string_types):
|
||||
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
|
||||
|
||||
if pricing_rule.get('suggestion'): continue
|
||||
|
||||
item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
|
||||
item_details.price_or_product_discount = pricing_rule.get("price_or_product_discount")
|
||||
|
||||
rules.append(get_pricing_rule_details(args, pricing_rule))
|
||||
|
||||
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
|
||||
item_details.update({
|
||||
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
|
||||
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
|
||||
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
|
||||
})
|
||||
|
||||
if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
|
||||
return item_details
|
||||
|
||||
if not pricing_rule.validate_applied_rule:
|
||||
if pricing_rule.price_or_product_discount == "Price":
|
||||
apply_price_discount_rule(pricing_rule, item_details, args)
|
||||
else:
|
||||
get_product_discount_rule(pricing_rule, item_details, doc)
|
||||
|
||||
item_details.has_pricing_rule = 1
|
||||
|
||||
item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
|
||||
|
||||
if not doc: return item_details
|
||||
|
||||
elif args.get("pricing_rules"):
|
||||
item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
|
||||
item_details, args.get('item_code'))
|
||||
|
||||
return item_details
|
||||
|
||||
def update_args_for_pricing_rule(args):
|
||||
if not (args.item_group and args.brand):
|
||||
try:
|
||||
args.item_group, args.brand = frappe.get_cached_value("Item", args.item_code, ["item_group", "brand"])
|
||||
@@ -235,56 +290,16 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
|
||||
args.supplier_group = frappe.get_cached_value("Supplier", args.supplier, "supplier_group")
|
||||
args.customer = args.customer_group = args.territory = None
|
||||
|
||||
pricing_rules = get_pricing_rules(args, doc)
|
||||
|
||||
if pricing_rules:
|
||||
rules = []
|
||||
|
||||
for pricing_rule in pricing_rules:
|
||||
if not pricing_rule or pricing_rule.get('suggestion'): continue
|
||||
|
||||
item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
|
||||
|
||||
rules.append(get_pricing_rule_details(args, pricing_rule))
|
||||
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
|
||||
continue
|
||||
|
||||
if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
|
||||
return item_details
|
||||
|
||||
if (not pricing_rule.validate_applied_rule and
|
||||
pricing_rule.price_or_product_discount == "Price"):
|
||||
apply_price_discount_pricing_rule(pricing_rule, item_details, args)
|
||||
|
||||
item_details.has_pricing_rule = 1
|
||||
|
||||
# if discount is applied on the rate and not on price list rate
|
||||
# if price_list_rate:
|
||||
# set_discount_amount(price_list_rate, item_details)
|
||||
|
||||
item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
|
||||
|
||||
if not doc: return item_details
|
||||
|
||||
for rule in rules:
|
||||
doc.append('pricing_rules', rule)
|
||||
|
||||
elif args.get("pricing_rules"):
|
||||
item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
|
||||
item_details, args.get('item_code'))
|
||||
|
||||
return item_details
|
||||
|
||||
def get_pricing_rule_details(args, pricing_rule):
|
||||
return frappe._dict({
|
||||
'pricing_rule': pricing_rule.name,
|
||||
'rate_or_discount': pricing_rule.rate_or_discount,
|
||||
'margin_type': pricing_rule.margin_type,
|
||||
'item_code': pricing_rule.item_code or args.get("item_code"),
|
||||
'item_code': args.get("item_code"),
|
||||
'child_docname': args.get('child_docname')
|
||||
})
|
||||
|
||||
def apply_price_discount_pricing_rule(pricing_rule, item_details, args):
|
||||
def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
item_details.pricing_rule_for = pricing_rule.rate_or_discount
|
||||
|
||||
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
|
||||
@@ -327,10 +342,10 @@ def set_discount_amount(rate, item_details):
|
||||
item_details.rate = rate
|
||||
|
||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_apply_on_and_items
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items
|
||||
for d in pricing_rules.split(','):
|
||||
if not d or not frappe.db.exists("Pricing Rule", d): continue
|
||||
pricing_rule = frappe.get_doc('Pricing Rule', d)
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
if pricing_rule.price_or_product_discount == 'Price':
|
||||
if pricing_rule.rate_or_discount == 'Discount Percentage':
|
||||
@@ -348,8 +363,9 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
||||
else pricing_rule.get('free_item'))
|
||||
|
||||
if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"):
|
||||
apply_on, items = get_apply_on_and_items(pricing_rule, item_details)
|
||||
item_details.apply_on = apply_on
|
||||
items = get_pricing_rule_items(pricing_rule)
|
||||
item_details.apply_on = (frappe.scrub(pricing_rule.apply_rule_on_other)
|
||||
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
|
||||
item_details.applied_on_items = ','.join(items)
|
||||
|
||||
item_details.pricing_rules = ''
|
||||
|
||||
@@ -7,7 +7,8 @@ from __future__ import unicode_literals
|
||||
import frappe, copy, json
|
||||
from frappe import throw, _
|
||||
from six import string_types
|
||||
from frappe.utils import flt, cint, get_datetime
|
||||
from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
|
||||
@@ -173,10 +174,11 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
||||
|
||||
if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return
|
||||
|
||||
pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name)
|
||||
pr_doc = frappe.get_cached_doc('Pricing Rule', pricing_rules[0].name)
|
||||
|
||||
if pricing_rules[0].mixed_conditions and doc:
|
||||
stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
|
||||
stock_qty, amount, items = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
|
||||
pricing_rules[0].apply_rule_on_other_items = items
|
||||
|
||||
elif pricing_rules[0].is_cumulative:
|
||||
items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
|
||||
@@ -282,7 +284,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
|
||||
status = True
|
||||
|
||||
# if user has created item price against the transaction UOM
|
||||
if rule.get("uom") == args.get("uom"):
|
||||
if args and rule.get("uom") == args.get("uom"):
|
||||
conversion_factor = 1.0
|
||||
|
||||
if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
|
||||
@@ -339,17 +341,19 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
|
||||
sum_qty += data[0]
|
||||
sum_amt += data[1]
|
||||
|
||||
return sum_qty, sum_amt
|
||||
return sum_qty, sum_amt, items
|
||||
|
||||
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
|
||||
for d in get_pricing_rule_items(pr_doc):
|
||||
for row in doc.items:
|
||||
if d == row.get(frappe.scrub(pr_doc.apply_on)):
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
|
||||
row.get("amount"), pricing_rules, row)
|
||||
items = get_pricing_rule_items(pr_doc)
|
||||
|
||||
if pricing_rules and pricing_rules[0]:
|
||||
return pricing_rules
|
||||
for row in doc.items:
|
||||
if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items:
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
|
||||
row.get("amount"), pricing_rules, row)
|
||||
|
||||
if pricing_rules and pricing_rules[0]:
|
||||
pricing_rules[0].apply_rule_on_other_items = items
|
||||
return pricing_rules
|
||||
|
||||
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
|
||||
sum_qty, sum_amt = [0, 0]
|
||||
@@ -397,38 +401,15 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
|
||||
|
||||
return [sum_qty, sum_amt]
|
||||
|
||||
def validate_pricing_rules(doc):
|
||||
validate_pricing_rule_on_transactions(doc)
|
||||
|
||||
for d in doc.items:
|
||||
validate_pricing_rule_on_items(doc, d)
|
||||
|
||||
doc.calculate_taxes_and_totals()
|
||||
|
||||
def validate_pricing_rule_on_items(doc, item_row, do_not_validate = False):
|
||||
value = 0
|
||||
for pricing_rule in get_applied_pricing_rules(doc, item_row):
|
||||
pr_doc = frappe.get_doc('Pricing Rule', pricing_rule)
|
||||
|
||||
if pr_doc.get('apply_on') == 'Transaction': continue
|
||||
|
||||
if pr_doc.get('price_or_product_discount') == 'Product':
|
||||
apply_pricing_rule_for_free_items(doc, pr_doc)
|
||||
else:
|
||||
for field in ['discount_percentage', 'discount_amount', 'rate']:
|
||||
if not pr_doc.get(field): continue
|
||||
|
||||
value += pr_doc.get(field)
|
||||
apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate)
|
||||
|
||||
def validate_pricing_rule_on_transactions(doc):
|
||||
def apply_pricing_rule_on_transaction(doc):
|
||||
conditions = "apply_on = 'Transaction'"
|
||||
|
||||
values = {}
|
||||
conditions = get_other_conditions(conditions, values, doc)
|
||||
|
||||
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
|
||||
where {conditions} """.format(conditions = conditions), values, as_dict=1)
|
||||
where {conditions} and `tabPricing Rule`.disable = 0
|
||||
""".format(conditions = conditions), values, as_dict=1)
|
||||
|
||||
if pricing_rules:
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
|
||||
@@ -440,98 +421,83 @@ def validate_pricing_rule_on_transactions(doc):
|
||||
doc.set('apply_discount_on', d.apply_discount_on)
|
||||
|
||||
for field in ['additional_discount_percentage', 'discount_amount']:
|
||||
if not d.get(field): continue
|
||||
|
||||
pr_field = ('discount_percentage'
|
||||
if field == 'additional_discount_percentage' else field)
|
||||
|
||||
if not d.get(pr_field): continue
|
||||
|
||||
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
|
||||
frappe.msgprint(_("User has not applied rule on the invoice {0}")
|
||||
.format(doc.name))
|
||||
else:
|
||||
doc.set(field, d.get(pr_field))
|
||||
elif d.price_or_product_discount == 'Product':
|
||||
apply_pricing_rule_for_free_items(doc, d)
|
||||
|
||||
def get_applied_pricing_rules(doc, item_row):
|
||||
doc.calculate_taxes_and_totals()
|
||||
elif d.price_or_product_discount == 'Product':
|
||||
item_details = frappe._dict({'parenttype': doc.doctype})
|
||||
get_product_discount_rule(d, item_details, doc)
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
doc.set_missing_values()
|
||||
|
||||
def get_applied_pricing_rules(item_row):
|
||||
return (item_row.get("pricing_rules").split(',')
|
||||
if item_row.get("pricing_rules") else [])
|
||||
|
||||
def apply_pricing_rule_for_free_items(doc, pricing_rule):
|
||||
if pricing_rule.get('free_item'):
|
||||
def get_product_discount_rule(pricing_rule, item_details, doc=None):
|
||||
free_item = (pricing_rule.free_item
|
||||
if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code)
|
||||
|
||||
if not free_item:
|
||||
frappe.throw(_("Free item not set in the pricing rule {0}")
|
||||
.format(get_link_to_form("Pricing Rule", pricing_rule.name)))
|
||||
|
||||
item_details.free_item_data = {
|
||||
'item_code': free_item,
|
||||
'qty': pricing_rule.free_qty or 1,
|
||||
'rate': pricing_rule.free_item_rate or 0,
|
||||
'price_list_rate': pricing_rule.free_item_rate or 0,
|
||||
'is_free_item': 1
|
||||
}
|
||||
|
||||
item_data = frappe.get_cached_value('Item', free_item, ['item_name',
|
||||
'description', 'stock_uom'], as_dict=1)
|
||||
|
||||
item_details.free_item_data.update(item_data)
|
||||
item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
|
||||
item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
|
||||
item_details.free_item_data['uom']).get("conversion_factor", 1)
|
||||
|
||||
if item_details.get("parenttype") == 'Purchase Order':
|
||||
item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
|
||||
|
||||
if item_details.get("parenttype") == 'Sales Order':
|
||||
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
|
||||
|
||||
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
||||
if pricing_rule_args.get('item_code'):
|
||||
items = [d.item_code for d in doc.items
|
||||
if d.item_code == (d.item_code
|
||||
if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
|
||||
if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
|
||||
|
||||
if not items:
|
||||
doc.append('items', {
|
||||
'item_code': pricing_rule.get('free_item'),
|
||||
'qty': pricing_rule.get('free_qty'),
|
||||
'uom': pricing_rule.get('free_item_uom'),
|
||||
'rate': pricing_rule.get('free_item_rate'),
|
||||
'is_free_item': 1
|
||||
})
|
||||
|
||||
doc.set_missing_values()
|
||||
|
||||
def apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate=False):
|
||||
apply_on, items = get_apply_on_and_items(pr_doc, item_row)
|
||||
|
||||
rule_applied = {}
|
||||
|
||||
for item in doc.get("items"):
|
||||
if item.get(apply_on) in items:
|
||||
if not item.pricing_rules:
|
||||
item.pricing_rules = item_row.pricing_rules
|
||||
|
||||
for field in ['discount_percentage', 'discount_amount', 'rate']:
|
||||
if not pr_doc.get(field): continue
|
||||
|
||||
key = (item.name, item.pricing_rules)
|
||||
if not pr_doc.validate_applied_rule:
|
||||
rule_applied[key] = 1
|
||||
item.set(field, value)
|
||||
elif item.get(field) < value:
|
||||
if not do_not_validate and item.idx == item_row.idx:
|
||||
rule_applied[key] = 0
|
||||
frappe.msgprint(_("Row {0}: user has not applied rule <b>{1}</b> on the item <b>{2}</b>")
|
||||
.format(item.idx, pr_doc.title, item.item_code))
|
||||
|
||||
if rule_applied and doc.get("pricing_rules"):
|
||||
for d in doc.get("pricing_rules"):
|
||||
key = (d.child_docname, d.pricing_rule)
|
||||
if key in rule_applied:
|
||||
d.rule_applied = 1
|
||||
|
||||
def get_apply_on_and_items(pr_doc, item_row):
|
||||
# for mixed or other items conditions
|
||||
apply_on = frappe.scrub(pr_doc.get('apply_on'))
|
||||
items = (get_pricing_rule_items(pr_doc)
|
||||
if pr_doc.mixed_conditions else [item_row.get(apply_on)])
|
||||
|
||||
if pr_doc.apply_rule_on_other:
|
||||
apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
|
||||
items = [pr_doc.get(apply_on)]
|
||||
|
||||
return apply_on, items
|
||||
doc.append('items', pricing_rule_args)
|
||||
|
||||
def get_pricing_rule_items(pr_doc):
|
||||
apply_on_data = []
|
||||
apply_on = frappe.scrub(pr_doc.get('apply_on'))
|
||||
|
||||
pricing_rule_apply_on = apply_on_table.get(pr_doc.get('apply_on'))
|
||||
|
||||
return [item.get(apply_on) for item in pr_doc.get(pricing_rule_apply_on)] or []
|
||||
for d in pr_doc.get(pricing_rule_apply_on):
|
||||
if apply_on == 'item_group':
|
||||
get_child_item_groups(d.get(apply_on))
|
||||
else:
|
||||
apply_on_data.append(d.get(apply_on))
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_pricing_rule_for_different_cond(doc):
|
||||
if isinstance(doc, string_types):
|
||||
doc = json.loads(doc)
|
||||
if pr_doc.apply_rule_on_other:
|
||||
apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
|
||||
apply_on_data.append(pr_doc.get(apply_on))
|
||||
|
||||
doc = frappe.get_doc(doc)
|
||||
for d in doc.get("items"):
|
||||
validate_pricing_rule_on_items(doc, d, True)
|
||||
|
||||
return doc
|
||||
return list(set(apply_on_data))
|
||||
|
||||
def validate_coupon_code(coupon_name):
|
||||
from frappe.utils import today,getdate
|
||||
|
||||
@@ -248,7 +248,7 @@ class PurchaseInvoice(BuyingController):
|
||||
def set_against_expense_account(self):
|
||||
against_accounts = []
|
||||
for item in self.get("items"):
|
||||
if item.expense_account not in against_accounts:
|
||||
if item.expense_account and (item.expense_account not in against_accounts):
|
||||
against_accounts.append(item.expense_account)
|
||||
|
||||
self.against_expense_account = ",".join(against_accounts)
|
||||
@@ -830,7 +830,11 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||
if self.rounding_adjustment:
|
||||
# if rounding adjustment in small and conversion rate is also small then
|
||||
# base_rounding_adjustment may become zero due to small precision
|
||||
# eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
|
||||
# then base_rounding_adjustment becomes zero and error is thrown in GL Entry
|
||||
if self.rounding_adjustment and self.base_rounding_adjustment:
|
||||
round_off_account, round_off_cost_center = \
|
||||
get_round_off_account_and_cost_center(self.company)
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Purchase Invoice');
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@@ -507,7 +508,8 @@
|
||||
"depends_on": "enable_deferred_expense",
|
||||
"fieldname": "service_stop_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Service Stop Date"
|
||||
"label": "Service Stop Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -523,13 +525,15 @@
|
||||
"depends_on": "enable_deferred_expense",
|
||||
"fieldname": "service_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Service Start Date"
|
||||
"label": "Service Start Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_deferred_expense",
|
||||
"fieldname": "service_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Service End Date"
|
||||
"label": "Service End Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference",
|
||||
@@ -766,7 +770,8 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-11-21 16:27:52.043744",
|
||||
"links": [],
|
||||
"modified": "2019-12-04 12:23:17.046413",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -1,300 +1,108 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:08",
|
||||
"custom": 0,
|
||||
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2013-01-10 16:34:08",
|
||||
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"field_order": [
|
||||
"title",
|
||||
"is_default",
|
||||
"disabled",
|
||||
"column_break4",
|
||||
"company",
|
||||
"tax_category",
|
||||
"section_break6",
|
||||
"taxes"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "title",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "title",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "is_default",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Default",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "is_default",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Default"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break6",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Purchase Taxes and Charges",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "purchase_tax_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Purchase Taxes and Charges",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Purchase Taxes and Charges",
|
||||
"oldfieldname": "purchase_tax_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Purchase Taxes and Charges"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Category",
|
||||
"options": "Tax Category"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-11-07 05:18:44.095798",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges Template",
|
||||
"owner": "wasim@webnotestech.com",
|
||||
],
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"modified": "2019-11-25 13:05:26.220275",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges Template",
|
||||
"owner": "wasim@webnotestech.com",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Master Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Master Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Purchase User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"read": 1,
|
||||
"role": "Purchase User"
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -357,14 +357,11 @@ def get_customer_wise_price_list():
|
||||
|
||||
def get_bin_data(pos_profile):
|
||||
itemwise_bin_data = {}
|
||||
cond = "1=1"
|
||||
filters = { 'actual_qty': ['>', 0] }
|
||||
if pos_profile.get('warehouse'):
|
||||
cond = "warehouse = %(warehouse)s"
|
||||
filters.update({ 'warehouse': pos_profile.get('warehouse') })
|
||||
|
||||
bin_data = frappe.db.sql(""" select item_code, warehouse, actual_qty from `tabBin`
|
||||
where actual_qty > 0 and {cond}""".format(cond=cond), {
|
||||
'warehouse': frappe.db.escape(pos_profile.get('warehouse'))
|
||||
}, as_dict=1)
|
||||
bin_data = frappe.db.get_all('Bin', fields = ['item_code', 'warehouse', 'actual_qty'], filters=filters)
|
||||
|
||||
for bins in bin_data:
|
||||
if bins.item_code not in itemwise_bin_data:
|
||||
@@ -550,11 +547,15 @@ def make_address(args, customer):
|
||||
|
||||
def make_email_queue(email_queue):
|
||||
name_list = []
|
||||
|
||||
for key, data in iteritems(email_queue):
|
||||
name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name')
|
||||
if not name: continue
|
||||
|
||||
data = json.loads(data)
|
||||
sender = frappe.session.user
|
||||
print_format = "POS Invoice" if not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')) else None
|
||||
|
||||
attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)]
|
||||
|
||||
make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'),
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||
|
||||
frappe.ui.form.on("Sales Invoice", {
|
||||
setup: function(frm) {
|
||||
frm.set_query('transporter', function() {
|
||||
@@ -34,5 +38,8 @@ frappe.ui.form.on("Sales Invoice", {
|
||||
}
|
||||
}, __("Make"));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -697,8 +697,8 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
if (frm.doc.company)
|
||||
{
|
||||
frappe.call({
|
||||
method:"frappe.contacts.doctype.address.address.get_default_address",
|
||||
args:{ doctype:'Company',name:frm.doc.company},
|
||||
method:"erpnext.setup.doctype.company.company.get_default_company_address",
|
||||
args:{name:frm.doc.company, existing_address: frm.doc.company_address},
|
||||
callback: function(r){
|
||||
if (r.message){
|
||||
frm.set_value("company_address",r.message)
|
||||
|
||||
@@ -953,7 +953,7 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
|
||||
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||
if flt(self.rounding_adjustment, self.precision("rounding_adjustment")):
|
||||
if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment:
|
||||
round_off_account, round_off_cost_center = \
|
||||
get_round_off_account_and_cost_center(self.company)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-06-04 11:02:19",
|
||||
"doctype": "DocType",
|
||||
@@ -484,7 +485,8 @@
|
||||
"depends_on": "enable_deferred_revenue",
|
||||
"fieldname": "service_stop_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Service Stop Date"
|
||||
"label": "Service Stop Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -500,13 +502,15 @@
|
||||
"depends_on": "enable_deferred_revenue",
|
||||
"fieldname": "service_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Service Start Date"
|
||||
"label": "Service Start Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_deferred_revenue",
|
||||
"fieldname": "service_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Service End Date"
|
||||
"label": "Service End Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@@ -783,7 +787,8 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-07-16 16:36:46.527606",
|
||||
"links": [],
|
||||
"modified": "2019-12-04 12:22:38.517710",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -1,299 +1,119 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:09",
|
||||
"custom": 0,
|
||||
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2013-01-10 16:34:09",
|
||||
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"is_default",
|
||||
"disabled",
|
||||
"column_break_3",
|
||||
"company",
|
||||
"tax_category",
|
||||
"section_break_5",
|
||||
"taxes"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "title",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "title",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "is_default",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Default",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "is_default",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Default"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "* Will be calculated in the transaction.",
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Sales Taxes and Charges",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "other_charges",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Sales Taxes and Charges",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"description": "* Will be calculated in the transaction.",
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Sales Taxes and Charges",
|
||||
"oldfieldname": "other_charges",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Sales Taxes and Charges"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Category",
|
||||
"options": "Tax Category"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-11-07 05:18:41.743257",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges Template",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"modified": "2019-11-25 13:06:03.279099",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges Template",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Master Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Master Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -13,9 +13,9 @@ from frappe.utils import nowdate
|
||||
class ShareDontExists(ValidationError): pass
|
||||
|
||||
class ShareTransfer(Document):
|
||||
def before_submit(self):
|
||||
def on_submit(self):
|
||||
if self.transfer_type == 'Issue':
|
||||
shareholder = self.get_shareholder_doc(self.company)
|
||||
shareholder = self.get_company_shareholder()
|
||||
shareholder.append('share_balance', {
|
||||
'share_type': self.share_type,
|
||||
'from_no': self.from_no,
|
||||
@@ -28,7 +28,7 @@ class ShareTransfer(Document):
|
||||
})
|
||||
shareholder.save()
|
||||
|
||||
doc = frappe.get_doc('Shareholder', self.to_shareholder)
|
||||
doc = self.get_shareholder_doc(self.to_shareholder)
|
||||
doc.append('share_balance', {
|
||||
'share_type': self.share_type,
|
||||
'from_no': self.from_no,
|
||||
@@ -41,11 +41,11 @@ class ShareTransfer(Document):
|
||||
|
||||
elif self.transfer_type == 'Purchase':
|
||||
self.remove_shares(self.from_shareholder)
|
||||
self.remove_shares(self.get_shareholder_doc(self.company).name)
|
||||
self.remove_shares(self.get_company_shareholder().name)
|
||||
|
||||
elif self.transfer_type == 'Transfer':
|
||||
self.remove_shares(self.from_shareholder)
|
||||
doc = frappe.get_doc('Shareholder', self.to_shareholder)
|
||||
doc = self.get_shareholder_doc(self.to_shareholder)
|
||||
doc.append('share_balance', {
|
||||
'share_type': self.share_type,
|
||||
'from_no': self.from_no,
|
||||
@@ -56,143 +56,127 @@ class ShareTransfer(Document):
|
||||
})
|
||||
doc.save()
|
||||
|
||||
def on_cancel(self):
|
||||
if self.transfer_type == 'Issue':
|
||||
compnay_shareholder = self.get_company_shareholder()
|
||||
self.remove_shares(compnay_shareholder.name)
|
||||
self.remove_shares(self.to_shareholder)
|
||||
|
||||
elif self.transfer_type == 'Purchase':
|
||||
compnay_shareholder = self.get_company_shareholder()
|
||||
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
|
||||
|
||||
from_shareholder.append('share_balance', {
|
||||
'share_type': self.share_type,
|
||||
'from_no': self.from_no,
|
||||
'to_no': self.to_no,
|
||||
'rate': self.rate,
|
||||
'amount': self.amount,
|
||||
'no_of_shares': self.no_of_shares
|
||||
})
|
||||
|
||||
from_shareholder.save()
|
||||
|
||||
compnay_shareholder.append('share_balance', {
|
||||
'share_type': self.share_type,
|
||||
'from_no': self.from_no,
|
||||
'to_no': self.to_no,
|
||||
'rate': self.rate,
|
||||
'amount': self.amount,
|
||||
'no_of_shares': self.no_of_shares
|
||||
})
|
||||
|
||||
compnay_shareholder.save()
|
||||
|
||||
elif self.transfer_type == 'Transfer':
|
||||
self.remove_shares(self.to_shareholder)
|
||||
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
|
||||
from_shareholder.append('share_balance', {
|
||||
'share_type': self.share_type,
|
||||
'from_no': self.from_no,
|
||||
'to_no': self.to_no,
|
||||
'rate': self.rate,
|
||||
'amount': self.amount,
|
||||
'no_of_shares': self.no_of_shares
|
||||
})
|
||||
from_shareholder.save()
|
||||
|
||||
def validate(self):
|
||||
self.get_company_shareholder()
|
||||
self.basic_validations()
|
||||
self.folio_no_validation()
|
||||
|
||||
if self.transfer_type == 'Issue':
|
||||
if not self.get_shareholder_doc(self.company):
|
||||
shareholder = frappe.get_doc({
|
||||
'doctype': 'Shareholder',
|
||||
'title': self.company,
|
||||
'company': self.company,
|
||||
'is_company': 1
|
||||
})
|
||||
shareholder.insert()
|
||||
# validate share doesnt exist in company
|
||||
ret_val = self.share_exists(self.get_shareholder_doc(self.company).name)
|
||||
if ret_val != False:
|
||||
# validate share doesn't exist in company
|
||||
ret_val = self.share_exists(self.get_company_shareholder().name)
|
||||
if ret_val in ('Complete', 'Partial'):
|
||||
frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError)
|
||||
else:
|
||||
# validate share exists with from_shareholder
|
||||
ret_val = self.share_exists(self.from_shareholder)
|
||||
if ret_val != True:
|
||||
if ret_val in ('Outside', 'Partial'):
|
||||
frappe.throw(_("The shares don't exist with the {0}")
|
||||
.format(self.from_shareholder), ShareDontExists)
|
||||
|
||||
def basic_validations(self):
|
||||
if self.transfer_type == 'Purchase':
|
||||
self.to_shareholder = ''
|
||||
if self.from_shareholder is None or self.from_shareholder is '':
|
||||
if not self.from_shareholder:
|
||||
frappe.throw(_('The field From Shareholder cannot be blank'))
|
||||
if self.from_folio_no is None or self.from_folio_no is '':
|
||||
if not self.from_folio_no:
|
||||
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
||||
if self.asset_account is None:
|
||||
if not self.asset_account:
|
||||
frappe.throw(_('The field Asset Account cannot be blank'))
|
||||
elif (self.transfer_type == 'Issue'):
|
||||
self.from_shareholder = ''
|
||||
if self.to_shareholder is None or self.to_shareholder == '':
|
||||
if not self.to_shareholder:
|
||||
frappe.throw(_('The field To Shareholder cannot be blank'))
|
||||
if self.to_folio_no is None or self.to_folio_no is '':
|
||||
if not self.to_folio_no:
|
||||
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
||||
if self.asset_account is None:
|
||||
if not self.asset_account:
|
||||
frappe.throw(_('The field Asset Account cannot be blank'))
|
||||
else:
|
||||
if self.from_shareholder is None or self.to_shareholder is None:
|
||||
if not self.from_shareholder or not self.to_shareholder:
|
||||
frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
|
||||
if self.to_folio_no is None or self.to_folio_no is '':
|
||||
if not self.to_folio_no:
|
||||
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
||||
if self.equity_or_liability_account is None:
|
||||
if not self.equity_or_liability_account:
|
||||
frappe.throw(_('The field Equity/Liability Account cannot be blank'))
|
||||
if self.from_shareholder == self.to_shareholder:
|
||||
frappe.throw(_('The seller and the buyer cannot be the same'))
|
||||
if self.no_of_shares != self.to_no - self.from_no + 1:
|
||||
frappe.throw(_('The number of shares and the share numbers are inconsistent'))
|
||||
if self.amount is None:
|
||||
if not self.amount:
|
||||
self.amount = self.rate * self.no_of_shares
|
||||
if self.amount != self.rate * self.no_of_shares:
|
||||
frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated'))
|
||||
|
||||
def share_exists(self, shareholder):
|
||||
# return True if exits,
|
||||
# False if completely doesn't exist,
|
||||
# 'partially exists' if partailly doesn't exist
|
||||
ret_val = self.recursive_share_check(shareholder, self.share_type,
|
||||
query = {
|
||||
'from_no': self.from_no,
|
||||
'to_no': self.to_no
|
||||
}
|
||||
)
|
||||
if all(boolean == True for boolean in ret_val):
|
||||
return True
|
||||
elif True in ret_val:
|
||||
return 'partially exists'
|
||||
else:
|
||||
return False
|
||||
|
||||
def recursive_share_check(self, shareholder, share_type, query):
|
||||
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
|
||||
# Recursive check if a given part of shares is held by the shareholder
|
||||
# return a list containing True and False
|
||||
# Eg. [True, False, True]
|
||||
# All True implies its completely inside
|
||||
# All False implies its completely outside
|
||||
# A mix implies its partially inside/outside
|
||||
does_share_exist = []
|
||||
doc = frappe.get_doc('Shareholder', shareholder)
|
||||
doc = self.get_shareholder_doc(shareholder)
|
||||
for entry in doc.share_balance:
|
||||
if entry.share_type != share_type or \
|
||||
entry.from_no > query['to_no'] or \
|
||||
entry.to_no < query['from_no']:
|
||||
if entry.share_type != self.share_type or \
|
||||
entry.from_no > self.to_no or \
|
||||
entry.to_no < self.from_no:
|
||||
continue # since query lies outside bounds
|
||||
elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
|
||||
return [True] # absolute truth!
|
||||
elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
|
||||
# split and check
|
||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
||||
share_type,
|
||||
{
|
||||
'from_no': query['from_no'],
|
||||
'to_no': entry.from_no - 1
|
||||
}
|
||||
))
|
||||
does_share_exist.append(True)
|
||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
||||
share_type,
|
||||
{
|
||||
'from_no': entry.to_no + 1,
|
||||
'to_no': query['to_no']
|
||||
}
|
||||
))
|
||||
elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
|
||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
||||
share_type,
|
||||
{
|
||||
'from_no': query['from_no'],
|
||||
'to_no': entry.from_no - 1
|
||||
}
|
||||
))
|
||||
elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
|
||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
||||
share_type,
|
||||
{
|
||||
'from_no': entry.to_no + 1,
|
||||
'to_no': query['to_no']
|
||||
}
|
||||
))
|
||||
elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #both inside
|
||||
return 'Complete' # absolute truth!
|
||||
elif entry.from_no <= self.from_no <= self.to_no:
|
||||
return 'Partial'
|
||||
elif entry.from_no <= self.to_no <= entry.to_no:
|
||||
return 'Partial'
|
||||
|
||||
does_share_exist.append(False)
|
||||
return does_share_exist
|
||||
return 'Outside'
|
||||
|
||||
def folio_no_validation(self):
|
||||
shareholders = ['from_shareholder', 'to_shareholder']
|
||||
shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not '']
|
||||
for shareholder in shareholders:
|
||||
doc = frappe.get_doc('Shareholder', self.get(shareholder))
|
||||
doc = self.get_shareholder_doc(self.get(shareholder))
|
||||
if doc.company != self.company:
|
||||
frappe.throw(_('The shareholder does not belong to this company'))
|
||||
if doc.folio_no is '' or doc.folio_no is None:
|
||||
if not doc.folio_no:
|
||||
doc.folio_no = self.from_folio_no \
|
||||
if (shareholder == 'from_shareholder') else self.to_folio_no;
|
||||
if (shareholder == 'from_shareholder') else self.to_folio_no
|
||||
doc.save()
|
||||
else:
|
||||
if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no):
|
||||
@@ -200,24 +184,14 @@ class ShareTransfer(Document):
|
||||
|
||||
def autoname_folio(self, shareholder, is_company=False):
|
||||
if is_company:
|
||||
doc = self.get_shareholder_doc(shareholder)
|
||||
doc = self.get_company_shareholder()
|
||||
else:
|
||||
doc = frappe.get_doc('Shareholder' , shareholder)
|
||||
doc = self.get_shareholder_doc(shareholder)
|
||||
doc.folio_no = make_autoname('FN.#####')
|
||||
doc.save()
|
||||
return doc.folio_no
|
||||
|
||||
def remove_shares(self, shareholder):
|
||||
self.iterative_share_removal(shareholder, self.share_type,
|
||||
{
|
||||
'from_no': self.from_no,
|
||||
'to_no' : self.to_no
|
||||
},
|
||||
rate = self.rate,
|
||||
amount = self.amount
|
||||
)
|
||||
|
||||
def iterative_share_removal(self, shareholder, share_type, query, rate, amount):
|
||||
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
|
||||
# Shares exist for sure
|
||||
# Iterate over all entries and modify entry if in entry
|
||||
@@ -227,31 +201,31 @@ class ShareTransfer(Document):
|
||||
|
||||
for entry in current_entries:
|
||||
# use spaceage logic here
|
||||
if entry.share_type != share_type or \
|
||||
entry.from_no > query['to_no'] or \
|
||||
entry.to_no < query['from_no']:
|
||||
if entry.share_type != self.share_type or \
|
||||
entry.from_no > self.to_no or \
|
||||
entry.to_no < self.from_no:
|
||||
new_entries.append(entry)
|
||||
continue # since query lies outside bounds
|
||||
elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
|
||||
elif entry.from_no <= self.from_no and entry.to_no >= self.to_no:
|
||||
#split
|
||||
if entry.from_no == query['from_no']:
|
||||
if entry.to_no == query['to_no']:
|
||||
if entry.from_no == self.from_no:
|
||||
if entry.to_no == self.to_no:
|
||||
pass #nothing to append
|
||||
else:
|
||||
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
|
||||
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
|
||||
else:
|
||||
if entry.to_no == query['to_no']:
|
||||
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
|
||||
if entry.to_no == self.to_no:
|
||||
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
|
||||
else:
|
||||
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
|
||||
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
|
||||
elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
|
||||
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
|
||||
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
|
||||
elif entry.from_no >= self.from_no and entry.to_no <= self.to_no:
|
||||
# split and check
|
||||
pass #nothing to append
|
||||
elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
|
||||
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
|
||||
elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
|
||||
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
|
||||
elif self.from_no <= entry.from_no <= self.to_no and entry.to_no >= self.to_no:
|
||||
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
|
||||
elif self.from_no <= entry.to_no <= self.to_no and entry.from_no <= self.from_no:
|
||||
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
|
||||
else:
|
||||
new_entries.append(entry)
|
||||
|
||||
@@ -272,16 +246,34 @@ class ShareTransfer(Document):
|
||||
}
|
||||
|
||||
def get_shareholder_doc(self, shareholder):
|
||||
# Get Shareholder doc based on the Shareholder title
|
||||
doc = frappe.get_list('Shareholder',
|
||||
filters = [
|
||||
('Shareholder', 'title', '=', shareholder)
|
||||
]
|
||||
)
|
||||
if len(doc) == 1:
|
||||
return frappe.get_doc('Shareholder', doc[0]['name'])
|
||||
else: #It will necessarily by 0 indicating it doesn't exist
|
||||
return False
|
||||
# Get Shareholder doc based on the Shareholder name
|
||||
if shareholder:
|
||||
query_filters = {'name': shareholder}
|
||||
|
||||
name = frappe.db.get_value('Shareholder', {'name': shareholder}, 'name')
|
||||
|
||||
return frappe.get_doc('Shareholder', name)
|
||||
|
||||
def get_company_shareholder(self):
|
||||
# Get company doc or create one if not present
|
||||
company_shareholder = frappe.db.get_value('Shareholder',
|
||||
{
|
||||
'company': self.company,
|
||||
'is_company': 1
|
||||
}, 'name')
|
||||
|
||||
if company_shareholder:
|
||||
return frappe.get_doc('Shareholder', company_shareholder)
|
||||
else:
|
||||
shareholder = frappe.get_doc({
|
||||
'doctype': 'Shareholder',
|
||||
'title': self.company,
|
||||
'company': self.company,
|
||||
'is_company': 1
|
||||
})
|
||||
shareholder.insert()
|
||||
|
||||
return shareholder
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_jv_entry( company, account, amount, payment_account,\
|
||||
|
||||
@@ -15,73 +15,73 @@ class TestShareTransfer(unittest.TestCase):
|
||||
frappe.db.sql("delete from `tabShare Balance`")
|
||||
share_transfers = [
|
||||
{
|
||||
"doctype" : "Share Transfer",
|
||||
"transfer_type" : "Issue",
|
||||
"date" : "2018-01-01",
|
||||
"to_shareholder" : "SH-00001",
|
||||
"share_type" : "Equity",
|
||||
"from_no" : 1,
|
||||
"to_no" : 500,
|
||||
"no_of_shares" : 500,
|
||||
"rate" : 10,
|
||||
"company" : "_Test Company",
|
||||
"asset_account" : "Cash - _TC",
|
||||
"doctype": "Share Transfer",
|
||||
"transfer_type": "Issue",
|
||||
"date": "2018-01-01",
|
||||
"to_shareholder": "SH-00001",
|
||||
"share_type": "Equity",
|
||||
"from_no": 1,
|
||||
"to_no": 500,
|
||||
"no_of_shares": 500,
|
||||
"rate": 10,
|
||||
"company": "_Test Company",
|
||||
"asset_account": "Cash - _TC",
|
||||
"equity_or_liability_account": "Creditors - _TC"
|
||||
},
|
||||
{
|
||||
"doctype" : "Share Transfer",
|
||||
"transfer_type" : "Transfer",
|
||||
"date" : "2018-01-02",
|
||||
"from_shareholder" : "SH-00001",
|
||||
"to_shareholder" : "SH-00002",
|
||||
"share_type" : "Equity",
|
||||
"from_no" : 101,
|
||||
"to_no" : 200,
|
||||
"no_of_shares" : 100,
|
||||
"rate" : 15,
|
||||
"company" : "_Test Company",
|
||||
"doctype": "Share Transfer",
|
||||
"transfer_type": "Transfer",
|
||||
"date": "2018-01-02",
|
||||
"from_shareholder": "SH-00001",
|
||||
"to_shareholder": "SH-00002",
|
||||
"share_type": "Equity",
|
||||
"from_no": 101,
|
||||
"to_no": 200,
|
||||
"no_of_shares": 100,
|
||||
"rate": 15,
|
||||
"company": "_Test Company",
|
||||
"equity_or_liability_account": "Creditors - _TC"
|
||||
},
|
||||
{
|
||||
"doctype" : "Share Transfer",
|
||||
"transfer_type" : "Transfer",
|
||||
"date" : "2018-01-03",
|
||||
"from_shareholder" : "SH-00001",
|
||||
"to_shareholder" : "SH-00003",
|
||||
"share_type" : "Equity",
|
||||
"from_no" : 201,
|
||||
"to_no" : 500,
|
||||
"no_of_shares" : 300,
|
||||
"rate" : 20,
|
||||
"company" : "_Test Company",
|
||||
"doctype": "Share Transfer",
|
||||
"transfer_type": "Transfer",
|
||||
"date": "2018-01-03",
|
||||
"from_shareholder": "SH-00001",
|
||||
"to_shareholder": "SH-00003",
|
||||
"share_type": "Equity",
|
||||
"from_no": 201,
|
||||
"to_no": 500,
|
||||
"no_of_shares": 300,
|
||||
"rate": 20,
|
||||
"company": "_Test Company",
|
||||
"equity_or_liability_account": "Creditors - _TC"
|
||||
},
|
||||
{
|
||||
"doctype" : "Share Transfer",
|
||||
"transfer_type" : "Transfer",
|
||||
"date" : "2018-01-04",
|
||||
"from_shareholder" : "SH-00003",
|
||||
"to_shareholder" : "SH-00002",
|
||||
"share_type" : "Equity",
|
||||
"from_no" : 201,
|
||||
"to_no" : 400,
|
||||
"no_of_shares" : 200,
|
||||
"rate" : 15,
|
||||
"company" : "_Test Company",
|
||||
"doctype": "Share Transfer",
|
||||
"transfer_type": "Transfer",
|
||||
"date": "2018-01-04",
|
||||
"from_shareholder": "SH-00003",
|
||||
"to_shareholder": "SH-00002",
|
||||
"share_type": "Equity",
|
||||
"from_no": 201,
|
||||
"to_no": 400,
|
||||
"no_of_shares": 200,
|
||||
"rate": 15,
|
||||
"company": "_Test Company",
|
||||
"equity_or_liability_account": "Creditors - _TC"
|
||||
},
|
||||
{
|
||||
"doctype" : "Share Transfer",
|
||||
"transfer_type" : "Purchase",
|
||||
"date" : "2018-01-05",
|
||||
"from_shareholder" : "SH-00003",
|
||||
"share_type" : "Equity",
|
||||
"from_no" : 401,
|
||||
"to_no" : 500,
|
||||
"no_of_shares" : 100,
|
||||
"rate" : 25,
|
||||
"company" : "_Test Company",
|
||||
"asset_account" : "Cash - _TC",
|
||||
"doctype": "Share Transfer",
|
||||
"transfer_type": "Purchase",
|
||||
"date": "2018-01-05",
|
||||
"from_shareholder": "SH-00003",
|
||||
"share_type": "Equity",
|
||||
"from_no": 401,
|
||||
"to_no": 500,
|
||||
"no_of_shares": 100,
|
||||
"rate": 25,
|
||||
"company": "_Test Company",
|
||||
"asset_account": "Cash - _TC",
|
||||
"equity_or_liability_account": "Creditors - _TC"
|
||||
}
|
||||
]
|
||||
@@ -91,33 +91,33 @@ class TestShareTransfer(unittest.TestCase):
|
||||
|
||||
def test_invalid_share_transfer(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype" : "Share Transfer",
|
||||
"transfer_type" : "Transfer",
|
||||
"date" : "2018-01-05",
|
||||
"from_shareholder" : "SH-00003",
|
||||
"to_shareholder" : "SH-00002",
|
||||
"share_type" : "Equity",
|
||||
"from_no" : 1,
|
||||
"to_no" : 100,
|
||||
"no_of_shares" : 100,
|
||||
"rate" : 15,
|
||||
"company" : "_Test Company",
|
||||
"doctype": "Share Transfer",
|
||||
"transfer_type": "Transfer",
|
||||
"date": "2018-01-05",
|
||||
"from_shareholder": "SH-00003",
|
||||
"to_shareholder": "SH-00002",
|
||||
"share_type": "Equity",
|
||||
"from_no": 1,
|
||||
"to_no": 100,
|
||||
"no_of_shares": 100,
|
||||
"rate": 15,
|
||||
"company": "_Test Company",
|
||||
"equity_or_liability_account": "Creditors - _TC"
|
||||
})
|
||||
self.assertRaises(ShareDontExists, doc.insert)
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype" : "Share Transfer",
|
||||
"transfer_type" : "Purchase",
|
||||
"date" : "2018-01-02",
|
||||
"from_shareholder" : "SH-00001",
|
||||
"share_type" : "Equity",
|
||||
"from_no" : 1,
|
||||
"to_no" : 200,
|
||||
"no_of_shares" : 200,
|
||||
"rate" : 15,
|
||||
"company" : "_Test Company",
|
||||
"asset_account" : "Cash - _TC",
|
||||
"doctype": "Share Transfer",
|
||||
"transfer_type": "Purchase",
|
||||
"date": "2018-01-02",
|
||||
"from_shareholder": "SH-00001",
|
||||
"share_type": "Equity",
|
||||
"from_no": 1,
|
||||
"to_no": 200,
|
||||
"no_of_shares": 200,
|
||||
"rate": 15,
|
||||
"company": "_Test Company",
|
||||
"asset_account": "Cash - _TC",
|
||||
"equity_or_liability_account": "Creditors - _TC"
|
||||
})
|
||||
self.assertRaises(ShareDontExists, doc.insert)
|
||||
|
||||
@@ -1,587 +1,163 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "naming_series:",
|
||||
"beta": 0,
|
||||
"creation": "2017-12-25 16:50:53.878430",
|
||||
"custom": 0,
|
||||
"description": "",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2017-12-25 16:50:53.878430",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"column_break_2",
|
||||
"naming_series",
|
||||
"section_break_2",
|
||||
"folio_no",
|
||||
"column_break_4",
|
||||
"company",
|
||||
"is_company",
|
||||
"address_contacts",
|
||||
"address_html",
|
||||
"column_break_9",
|
||||
"contact_html",
|
||||
"section_break_3",
|
||||
"share_balance",
|
||||
"contact_list"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "ACC-SH-.YYYY.-",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"options": "ACC-SH-.YYYY.-"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "folio_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Folio no.",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"fieldname": "folio_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Folio no.",
|
||||
"read_only": 1,
|
||||
"unique": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "is_company",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "is_company",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Company",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "address_contacts",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Address and Contacts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "fa fa-map-marker",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "address_contacts",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Address and Contacts",
|
||||
"options": "fa fa-map-marker"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "address_html",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Address HTML",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "address_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Address HTML",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "contact_html",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Contact HTML",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "contact_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Contact HTML",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Share Balance",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Share Balance"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "share_balance",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Share Balance",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Share Balance",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "share_balance",
|
||||
"fieldtype": "Table",
|
||||
"label": "Share Balance",
|
||||
"options": "Share Balance",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Hidden list maintaining the list of contacts linked to Shareholder",
|
||||
"fieldname": "contact_list",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Contact List",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"description": "Hidden list maintaining the list of contacts linked to Shareholder",
|
||||
"fieldname": "contact_list",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 1,
|
||||
"label": "Contact List",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-09-18 14:14:24.953014",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Shareholder",
|
||||
"name_case": "Title Case",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"modified": "2019-11-17 23:24:11.395882",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Shareholder",
|
||||
"name_case": "Title Case",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "folio_no",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"search_fields": "folio_no",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class ShippingRule(Document):
|
||||
|
||||
def get_shipping_amount_from_rules(self, value):
|
||||
for condition in self.get("conditions"):
|
||||
if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
|
||||
if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)):
|
||||
return condition.shipping_amount
|
||||
|
||||
return 0.0
|
||||
|
||||
@@ -90,8 +90,12 @@ def merge_similar_entries(gl_map):
|
||||
else:
|
||||
merged_gl_map.append(entry)
|
||||
|
||||
company = gl_map[0].company if gl_map else erpnext.get_default_company()
|
||||
company_currency = erpnext.get_company_currency(company)
|
||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
|
||||
|
||||
# filter zero debit and credit entries
|
||||
merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map)
|
||||
merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
|
||||
merged_gl_map = list(merged_gl_map)
|
||||
|
||||
return merged_gl_map
|
||||
|
||||
@@ -23,7 +23,7 @@ class DuplicatePartyAccountError(frappe.ValidationError): pass
|
||||
@frappe.whitelist()
|
||||
def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
|
||||
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, fetch_payment_terms_template=True,
|
||||
party_address=None, shipping_address=None, pos_profile=None):
|
||||
party_address=None, company_address=None, shipping_address=None, pos_profile=None):
|
||||
|
||||
if not party:
|
||||
return {}
|
||||
@@ -31,14 +31,14 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N
|
||||
frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
|
||||
return _get_party_details(party, account, party_type,
|
||||
company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions,
|
||||
fetch_payment_terms_template, party_address, shipping_address, pos_profile)
|
||||
fetch_payment_terms_template, party_address, company_address, shipping_address, pos_profile)
|
||||
|
||||
def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
|
||||
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False,
|
||||
fetch_payment_terms_template=True, party_address=None, shipping_address=None, pos_profile=None):
|
||||
fetch_payment_terms_template=True, party_address=None, company_address=None,shipping_address=None, pos_profile=None):
|
||||
|
||||
out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
|
||||
party = out[party_type.lower()]
|
||||
party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
|
||||
party = party_details[party_type.lower()]
|
||||
|
||||
if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
|
||||
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
||||
@@ -46,76 +46,81 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
|
||||
party = frappe.get_doc(party_type, party)
|
||||
currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
|
||||
|
||||
party_address, shipping_address = set_address_details(out, party, party_type, doctype, company, party_address, shipping_address)
|
||||
set_contact_details(out, party, party_type)
|
||||
set_other_values(out, party, party_type)
|
||||
set_price_list(out, party, party_type, price_list, pos_profile)
|
||||
party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
|
||||
set_contact_details(party_details, party, party_type)
|
||||
set_other_values(party_details, party, party_type)
|
||||
set_price_list(party_details, party, party_type, price_list, pos_profile)
|
||||
|
||||
out["tax_category"] = get_address_tax_category(party.get("tax_category"),
|
||||
party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
|
||||
party_address, shipping_address if party_type != "Supplier" else party_address)
|
||||
out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
|
||||
customer_group=out.customer_group, supplier_group=out.supplier_group, tax_category=out.tax_category,
|
||||
billing_address=party_address, shipping_address=shipping_address)
|
||||
|
||||
if not party_details.get("taxes_and_charges"):
|
||||
party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
|
||||
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
|
||||
billing_address=party_address, shipping_address=shipping_address)
|
||||
|
||||
if fetch_payment_terms_template:
|
||||
out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
|
||||
party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
|
||||
|
||||
if not out.get("currency"):
|
||||
out["currency"] = currency
|
||||
if not party_details.get("currency"):
|
||||
party_details["currency"] = currency
|
||||
|
||||
# sales team
|
||||
if party_type=="Customer":
|
||||
out["sales_team"] = [{
|
||||
party_details["sales_team"] = [{
|
||||
"sales_person": d.sales_person,
|
||||
"allocated_percentage": d.allocated_percentage or None
|
||||
} for d in party.get("sales_team")]
|
||||
|
||||
# supplier tax withholding category
|
||||
if party_type == "Supplier" and party:
|
||||
out["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
|
||||
party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
|
||||
|
||||
return out
|
||||
return party_details
|
||||
|
||||
def set_address_details(out, party, party_type, doctype=None, company=None, party_address=None, shipping_address=None):
|
||||
def set_address_details(party_details, party, party_type, doctype=None, company=None, party_address=None, company_address=None, shipping_address=None):
|
||||
billing_address_field = "customer_address" if party_type == "Lead" \
|
||||
else party_type.lower() + "_address"
|
||||
out[billing_address_field] = party_address or get_default_address(party_type, party.name)
|
||||
party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
|
||||
if doctype:
|
||||
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
|
||||
party_details.update(get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]))
|
||||
# address display
|
||||
out.address_display = get_address_display(out[billing_address_field])
|
||||
party_details.address_display = get_address_display(party_details[billing_address_field])
|
||||
# shipping address
|
||||
if party_type in ["Customer", "Lead"]:
|
||||
out.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
|
||||
out.shipping_address = get_address_display(out["shipping_address_name"])
|
||||
party_details.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
|
||||
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
|
||||
if doctype:
|
||||
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
|
||||
party_details.update(get_fetch_values(doctype, 'shipping_address_name', party_details.shipping_address_name))
|
||||
|
||||
if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
|
||||
out.update(get_company_address(company))
|
||||
if out.company_address:
|
||||
out.update(get_fetch_values(doctype, 'company_address', out.company_address))
|
||||
get_regional_address_details(out, doctype, company)
|
||||
if company_address:
|
||||
party_details.update({'company_address': company_address})
|
||||
else:
|
||||
party_details.update(get_company_address(company))
|
||||
|
||||
elif doctype and doctype == "Purchase Invoice":
|
||||
out.update(get_company_address(company))
|
||||
if out.company_address:
|
||||
out["shipping_address"] = shipping_address or out["company_address"]
|
||||
out.shipping_address_display = get_address_display(out["shipping_address"])
|
||||
out.update(get_fetch_values(doctype, 'shipping_address', out.shipping_address))
|
||||
get_regional_address_details(out, doctype, company)
|
||||
if doctype and doctype in ['Delivery Note', 'Sales Invoice', 'Sales Order']:
|
||||
if party_details.company_address:
|
||||
party_details.update(get_fetch_values(doctype, 'company_address', party_details.company_address))
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
|
||||
return out.get(billing_address_field), out.shipping_address_name
|
||||
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
|
||||
if party_details.company_address:
|
||||
party_details["shipping_address"] = shipping_address or party_details["company_address"]
|
||||
party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
|
||||
party_details.update(get_fetch_values(doctype, 'shipping_address', party_details.shipping_address))
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
|
||||
return party_details.get(billing_address_field), party_details.shipping_address_name
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_regional_address_details(out, doctype, company):
|
||||
def get_regional_address_details(party_details, doctype, company):
|
||||
pass
|
||||
|
||||
def set_contact_details(out, party, party_type):
|
||||
out.contact_person = get_default_contact(party_type, party.name)
|
||||
def set_contact_details(party_details, party, party_type):
|
||||
party_details.contact_person = get_default_contact(party_type, party.name)
|
||||
|
||||
if not out.contact_person:
|
||||
out.update({
|
||||
if not party_details.contact_person:
|
||||
party_details.update({
|
||||
"contact_person": None,
|
||||
"contact_display": None,
|
||||
"contact_email": None,
|
||||
@@ -125,22 +130,22 @@ def set_contact_details(out, party, party_type):
|
||||
"contact_department": None
|
||||
})
|
||||
else:
|
||||
out.update(get_contact_details(out.contact_person))
|
||||
party_details.update(get_contact_details(party_details.contact_person))
|
||||
|
||||
def set_other_values(out, party, party_type):
|
||||
def set_other_values(party_details, party, party_type):
|
||||
# copy
|
||||
if party_type=="Customer":
|
||||
to_copy = ["customer_name", "customer_group", "territory", "language"]
|
||||
else:
|
||||
to_copy = ["supplier_name", "supplier_group", "language"]
|
||||
for f in to_copy:
|
||||
out[f] = party.get(f)
|
||||
party_details[f] = party.get(f)
|
||||
|
||||
# fields prepended with default in Customer doctype
|
||||
for f in ['currency'] \
|
||||
+ (['sales_partner', 'commission_rate'] if party_type=="Customer" else []):
|
||||
if party.get("default_" + f):
|
||||
out[f] = party.get("default_" + f)
|
||||
party_details[f] = party.get("default_" + f)
|
||||
|
||||
def get_default_price_list(party):
|
||||
"""Return default price list for party (Document object)"""
|
||||
@@ -155,7 +160,7 @@ def get_default_price_list(party):
|
||||
|
||||
return None
|
||||
|
||||
def set_price_list(out, party, party_type, given_price_list, pos=None):
|
||||
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
|
||||
# price list
|
||||
price_list = get_permitted_documents('Price List')
|
||||
|
||||
@@ -173,9 +178,9 @@ def set_price_list(out, party, party_type, given_price_list, pos=None):
|
||||
price_list = get_default_price_list(party) or given_price_list
|
||||
|
||||
if price_list:
|
||||
out.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
|
||||
party_details.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
|
||||
|
||||
out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
|
||||
party_details["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
|
||||
|
||||
|
||||
def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype):
|
||||
|
||||
@@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group"
|
||||
},
|
||||
{
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname":"tax_id",
|
||||
"label": __("Tax Id"),
|
||||
|
||||
@@ -88,6 +88,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
"label": __("Supplier Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group"
|
||||
},
|
||||
{
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ class ReceivablePayableReport(object):
|
||||
|
||||
def get_data(self):
|
||||
self.get_gl_entries()
|
||||
self.get_sales_invoices_or_customers_based_on_sales_person()
|
||||
self.voucher_balance = OrderedDict()
|
||||
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
|
||||
|
||||
@@ -103,12 +104,18 @@ class ReceivablePayableReport(object):
|
||||
|
||||
def get_invoices(self, gle):
|
||||
if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
|
||||
self.invoices.add(gle.voucher_no)
|
||||
if self.filters.get("sales_person"):
|
||||
if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \
|
||||
or gle.party in self.sales_person_records.get("Customer", []):
|
||||
self.invoices.add(gle.voucher_no)
|
||||
else:
|
||||
self.invoices.add(gle.voucher_no)
|
||||
|
||||
def update_voucher_balance(self, gle):
|
||||
# get the row where this balance needs to be updated
|
||||
# if its a payment, it will return the linked invoice or will be considered as advance
|
||||
row = self.get_voucher_balance(gle)
|
||||
if not row: return
|
||||
# gle_balance will be the total "debit - credit" for receivable type reports and
|
||||
# and vice-versa for payable type reports
|
||||
gle_balance = self.get_gle_balance(gle)
|
||||
@@ -129,8 +136,13 @@ class ReceivablePayableReport(object):
|
||||
row.paid -= gle_balance
|
||||
|
||||
def get_voucher_balance(self, gle):
|
||||
voucher_balance = None
|
||||
if self.filters.get("sales_person"):
|
||||
against_voucher = gle.against_voucher or gle.voucher_no
|
||||
if not (gle.party in self.sales_person_records.get("Customer", []) or \
|
||||
against_voucher in self.sales_person_records.get("Sales Invoice", [])):
|
||||
return
|
||||
|
||||
voucher_balance = None
|
||||
if gle.against_voucher:
|
||||
# find invoice
|
||||
against_voucher = gle.against_voucher
|
||||
@@ -318,7 +330,7 @@ class ReceivablePayableReport(object):
|
||||
self.append_payment_term(row, d, term)
|
||||
|
||||
def append_payment_term(self, row, d, term):
|
||||
if self.filters.get("customer") and d.currency == d.party_account_currency:
|
||||
if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency:
|
||||
invoiced = d.payment_amount
|
||||
else:
|
||||
invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
|
||||
@@ -512,6 +524,22 @@ class ReceivablePayableReport(object):
|
||||
order by posting_date, party"""
|
||||
.format(select_fields, conditions), values, as_dict=True)
|
||||
|
||||
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
||||
if self.filters.get("sales_person"):
|
||||
lft, rgt = frappe.db.get_value("Sales Person",
|
||||
self.filters.get("sales_person"), ["lft", "rgt"])
|
||||
|
||||
records = frappe.db.sql("""
|
||||
select distinct parent, parenttype
|
||||
from `tabSales Team` steam
|
||||
where parenttype in ('Customer', 'Sales Invoice')
|
||||
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
|
||||
""", (lft, rgt), as_dict=1)
|
||||
|
||||
self.sales_person_records = frappe._dict()
|
||||
for d in records:
|
||||
self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||
|
||||
def prepare_conditions(self):
|
||||
conditions = [""]
|
||||
values = [self.party_type, self.filters.report_date]
|
||||
@@ -564,16 +592,6 @@ class ReceivablePayableReport(object):
|
||||
conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
|
||||
values.append(self.filters.get("sales_partner"))
|
||||
|
||||
if self.filters.get("sales_person"):
|
||||
lft, rgt = frappe.db.get_value("Sales Person",
|
||||
self.filters.get("sales_person"), ["lft", "rgt"])
|
||||
|
||||
conditions.append("""exists(select name from `tabSales Team` steam where
|
||||
steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1})
|
||||
and ((steam.parent = voucher_no and steam.parenttype = voucher_type)
|
||||
or (steam.parent = against_voucher and steam.parenttype = against_voucher_type)
|
||||
or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt))
|
||||
|
||||
def add_supplier_filters(self, conditions, values):
|
||||
if self.filters.get("supplier_group"):
|
||||
conditions.append("""party in (select name from tabSupplier
|
||||
|
||||
@@ -106,6 +106,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
"label": __("Sales Person"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Person"
|
||||
},
|
||||
{
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
self.filters.report_date) or {}
|
||||
|
||||
for party, party_dict in iteritems(self.party_total):
|
||||
if party_dict.outstanding <= 0:
|
||||
if party_dict.outstanding == 0:
|
||||
continue
|
||||
|
||||
row = frappe._dict()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{%
|
||||
var report_columns = report.get_columns_for_print();
|
||||
report_columns = report_columns.filter(col => !col.hidden);
|
||||
|
||||
if (report_columns.length > 8) {
|
||||
frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application."));
|
||||
@@ -15,34 +16,35 @@
|
||||
height: 37px;
|
||||
}
|
||||
</style>
|
||||
{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %}
|
||||
{% if(letterhead) { %}
|
||||
<div style="margin-bottom: 7px;" class="text-center">
|
||||
{%= frappe.boot.letter_heads[letterhead].header %}
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<h2 class="text-center">{%= __(report.report_name) %}</h2>
|
||||
<h3 class="text-center">{%= filters.company %}</h3>
|
||||
|
||||
{% if 'cost_center' in filters %}
|
||||
<h3 class="text-center">{%= filters.cost_center %}</h3>
|
||||
{% endif %}
|
||||
|
||||
<h3 class="text-center">{%= filters.fiscal_year %}</h3>
|
||||
<h5 class="text-center">{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %} </h4>
|
||||
<h5 class="text-center">
|
||||
{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
|
||||
</h5>
|
||||
{% if (filters.from_date) { %}
|
||||
<h4 class="text-center">{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}</h3>
|
||||
<h5 class="text-center">
|
||||
{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}
|
||||
</h5>
|
||||
{% } %}
|
||||
<hr>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: {%= 100 - (report_columns.length - 2) * 13 %}%"></th>
|
||||
{% for(var i=2, l=report_columns.length; i<l; i++) { %}
|
||||
<th style="width: {%= 100 - (report_columns.length - 1) * 13 %}%"></th>
|
||||
{% for (let i=1, l=report_columns.length; i<l; i++) { %}
|
||||
<th class="text-right">{%= report_columns[i].label %}</th>
|
||||
{% } %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for(var j=0, k=data.length-1; j<k; j++) { %}
|
||||
{% for(let j=0, k=data.length-1; j<k; j++) { %}
|
||||
{%
|
||||
var row = data[j];
|
||||
var row_class = data[j].parent_account ? "" : "financial-statements-important";
|
||||
@@ -52,11 +54,11 @@
|
||||
<td>
|
||||
<span style="padding-left: {%= cint(data[j].indent) * 2 %}em">{%= row.account_name %}</span>
|
||||
</td>
|
||||
{% for(var i=2, l=report_columns.length; i<l; i++) { %}
|
||||
{% for(let i=1, l=report_columns.length; i<l; i++) { %}
|
||||
<td class="text-right">
|
||||
{% var fieldname = report_columns[i].fieldname; %}
|
||||
{% const fieldname = report_columns[i].fieldname; %}
|
||||
{% if (!is_null(row[fieldname])) { %}
|
||||
{%= format_currency(row[fieldname], filters.presentation_currency) %}
|
||||
{%= frappe.format(row[fieldname], report_columns[i], {}, row) %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
@@ -64,4 +66,6 @@
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
|
||||
<p class="text-right text-muted">
|
||||
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
|
||||
</p>
|
||||
|
||||
@@ -264,8 +264,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False
|
||||
|
||||
def add_total_row(out, root_type, balance_must_be, period_list, company_currency):
|
||||
total_row = {
|
||||
"account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
|
||||
"account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
|
||||
"account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
|
||||
"account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
|
||||
"currency": company_currency
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,17 @@ def execute(filters=None):
|
||||
return columns, data
|
||||
|
||||
def get_data(filters, show_party_name):
|
||||
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
|
||||
if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'):
|
||||
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
|
||||
if filters.get('party_type') == 'Student':
|
||||
party_name_field = 'first_name'
|
||||
elif filters.get('party_type') == 'Shareholder':
|
||||
party_name_field = 'title'
|
||||
else:
|
||||
party_name_field = 'name'
|
||||
|
||||
party_filters = {"name": filters.get("party")} if filters.get("party") else {}
|
||||
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
|
||||
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
|
||||
filters = party_filters, order_by="name")
|
||||
company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
|
||||
opening_balances = get_opening_balances(filters)
|
||||
@@ -70,7 +73,7 @@ def get_data(filters, show_party_name):
|
||||
# totals
|
||||
for col in total_row:
|
||||
total_row[col] += row.get(col)
|
||||
|
||||
|
||||
row.update({
|
||||
"currency": company_currency
|
||||
})
|
||||
@@ -78,7 +81,7 @@ def get_data(filters, show_party_name):
|
||||
has_value = False
|
||||
if (opening_debit or opening_credit or debit or credit or closing_debit or closing_credit):
|
||||
has_value =True
|
||||
|
||||
|
||||
if cint(filters.show_zero_values) or has_value:
|
||||
data.append(row)
|
||||
|
||||
@@ -94,9 +97,9 @@ def get_data(filters, show_party_name):
|
||||
|
||||
def get_opening_balances(filters):
|
||||
gle = frappe.db.sql("""
|
||||
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
|
||||
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
where company=%(company)s
|
||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
|
||||
group by party""", {
|
||||
@@ -114,11 +117,11 @@ def get_opening_balances(filters):
|
||||
|
||||
def get_balances_within_period(filters):
|
||||
gle = frappe.db.sql("""
|
||||
select party, sum(debit) as debit, sum(credit) as credit
|
||||
select party, sum(debit) as debit, sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
where company=%(company)s
|
||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
|
||||
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
|
||||
and ifnull(is_opening, 'No') = 'No'
|
||||
group by party""", {
|
||||
"company": filters.company,
|
||||
|
||||
@@ -569,7 +569,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
|
||||
|
||||
warehouse_account = get_warehouse_account_map(company)
|
||||
|
||||
account_balance = get_balance_on(account, posting_date, in_account_currency=False)
|
||||
account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
|
||||
|
||||
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
|
||||
if wh_details.account == account and not wh_details.is_group]
|
||||
|
||||
@@ -33,7 +33,7 @@ class Asset(AccountsController):
|
||||
self.make_asset_movement()
|
||||
if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category):
|
||||
self.make_gl_entries()
|
||||
|
||||
|
||||
def before_cancel(self):
|
||||
self.cancel_auto_gen_movement()
|
||||
|
||||
@@ -43,7 +43,7 @@ class Asset(AccountsController):
|
||||
self.set_status()
|
||||
delete_gl_entries(voucher_type='Asset', voucher_no=self.name)
|
||||
self.db_set('booked_fixed_asset', 0)
|
||||
|
||||
|
||||
def validate_asset_and_reference(self):
|
||||
if self.purchase_invoice or self.purchase_receipt:
|
||||
reference_doc = 'Purchase Invoice' if self.purchase_invoice else 'Purchase Receipt'
|
||||
@@ -51,8 +51,8 @@ class Asset(AccountsController):
|
||||
reference_doc = frappe.get_doc(reference_doc, reference_name)
|
||||
if reference_doc.get('company') != self.company:
|
||||
frappe.throw(_("Company of asset {0} and purchase document {1} doesn't matches.").format(self.name, reference_doc.get('name')))
|
||||
|
||||
|
||||
|
||||
|
||||
if self.is_existing_asset and self.purchase_invoice:
|
||||
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
|
||||
|
||||
@@ -135,7 +135,7 @@ class Asset(AccountsController):
|
||||
movement = frappe.get_doc('Asset Movement', movements[0].get('name'))
|
||||
movement.flags.ignore_validate = True
|
||||
movement.cancel()
|
||||
|
||||
|
||||
def make_asset_movement(self):
|
||||
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
|
||||
reference_docname = self.purchase_receipt or self.purchase_invoice
|
||||
@@ -204,7 +204,7 @@ class Asset(AccountsController):
|
||||
if has_pro_rata and n==0:
|
||||
depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
|
||||
self.available_for_use_date, d.depreciation_start_date)
|
||||
|
||||
|
||||
# For first depr schedule date will be the start date
|
||||
# so monthly schedule date is calculated by removing month difference between use date and start date
|
||||
monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1)
|
||||
@@ -262,7 +262,7 @@ class Asset(AccountsController):
|
||||
else:
|
||||
date = add_months(monthly_schedule_date, r)
|
||||
amount = depreciation_amount / month_range
|
||||
|
||||
|
||||
self.append("schedules", {
|
||||
"schedule_date": date,
|
||||
"depreciation_amount": amount,
|
||||
@@ -517,15 +517,18 @@ def update_maintenance_status():
|
||||
asset.set_status('Out of Order')
|
||||
|
||||
def make_post_gl_entry():
|
||||
if not is_cwip_accounting_enabled(self.asset_category):
|
||||
return
|
||||
|
||||
assets = frappe.db.sql_list(""" select name from `tabAsset`
|
||||
where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate())
|
||||
asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting'])
|
||||
|
||||
for asset in assets:
|
||||
doc = frappe.get_doc('Asset', asset)
|
||||
doc.make_gl_entries()
|
||||
for asset_category in asset_categories:
|
||||
if cint(asset_category.enable_cwip_accounting):
|
||||
assets = frappe.db.sql_list(""" select name from `tabAsset`
|
||||
where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0
|
||||
and available_for_use_date = %s""", (asset_category.name, nowdate()))
|
||||
|
||||
for asset in assets:
|
||||
doc = frappe.get_doc('Asset', asset)
|
||||
doc.make_gl_entries()
|
||||
|
||||
def get_asset_naming_series():
|
||||
meta = frappe.get_meta('Asset')
|
||||
@@ -607,13 +610,19 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
|
||||
if asset:
|
||||
account = get_asset_category_account(account_name, asset=asset,
|
||||
asset_category = asset_category, company = company)
|
||||
|
||||
if not asset and not account:
|
||||
account = get_asset_category_account(account_name, asset_category = asset_category, company = company)
|
||||
|
||||
if not account:
|
||||
account = frappe.get_cached_value('Company', company, account_name)
|
||||
|
||||
if not account:
|
||||
frappe.throw(_("Set {0} in asset category {1} or company {2}")
|
||||
.format(account_name.replace('_', ' ').title(), asset_category, company))
|
||||
if not asset_category:
|
||||
frappe.throw(_("Set {0} in company {2}").format(account_name.replace('_', ' ').title(), company))
|
||||
else:
|
||||
frappe.throw(_("Set {0} in asset category {1} or company {2}")
|
||||
.format(account_name.replace('_', ' ').title(), asset_category, company))
|
||||
|
||||
return account
|
||||
|
||||
@@ -652,10 +661,10 @@ def make_journal_entry(asset_name):
|
||||
def make_asset_movement(assets, purpose=None):
|
||||
import json
|
||||
from six import string_types
|
||||
|
||||
|
||||
if isinstance(assets, string_types):
|
||||
assets = json.loads(assets)
|
||||
|
||||
|
||||
if len(assets) == 0:
|
||||
frappe.throw(_('Atleast one asset has to be selected.'))
|
||||
|
||||
|
||||
@@ -26,5 +26,11 @@ frappe.query_reports["Fixed Asset Register"] = {
|
||||
fieldtype: "Link",
|
||||
options: "Finance Book"
|
||||
},
|
||||
{
|
||||
fieldname:"date",
|
||||
label: __("Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.get_today()
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, today, flt
|
||||
|
||||
def execute(filters=None):
|
||||
filters = frappe._dict(filters or {})
|
||||
@@ -86,8 +86,8 @@ def get_columns(filters):
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"label": _("Current Value"),
|
||||
"fieldname": "current_value",
|
||||
"label": _("Asset Value"),
|
||||
"fieldname": "asset_value",
|
||||
"options": "Currency",
|
||||
"width": 90
|
||||
},
|
||||
@@ -114,7 +114,7 @@ def get_data(filters):
|
||||
data = []
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
current_value_map = get_finance_book_value_map(filters.finance_book)
|
||||
depreciation_amount_map = get_finance_book_value_map(filters.date, filters.finance_book)
|
||||
pr_supplier_map = get_purchase_receipt_supplier_map()
|
||||
pi_supplier_map = get_purchase_invoice_supplier_map()
|
||||
|
||||
@@ -125,7 +125,9 @@ def get_data(filters):
|
||||
"available_for_use_date", "status", "purchase_invoice"])
|
||||
|
||||
for asset in assets_record:
|
||||
if current_value_map.get(asset.name) is not None:
|
||||
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
|
||||
- flt(depreciation_amount_map.get(asset.name))
|
||||
if asset_value:
|
||||
row = {
|
||||
"asset_id": asset.name,
|
||||
"asset_name": asset.asset_name,
|
||||
@@ -138,19 +140,24 @@ def get_data(filters):
|
||||
"location": asset.location,
|
||||
"asset_category": asset.asset_category,
|
||||
"purchase_date": asset.purchase_date,
|
||||
"current_value": current_value_map.get(asset.name)
|
||||
"asset_value": asset_value
|
||||
}
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
def get_finance_book_value_map(finance_book=''):
|
||||
def get_finance_book_value_map(date, finance_book=''):
|
||||
if not date:
|
||||
date = today()
|
||||
return frappe._dict(frappe.db.sql(''' Select
|
||||
parent, value_after_depreciation
|
||||
FROM `tabAsset Finance Book`
|
||||
parent, SUM(depreciation_amount)
|
||||
FROM `tabDepreciation Schedule`
|
||||
WHERE
|
||||
parentfield='finance_books'
|
||||
AND ifnull(finance_book, '')=%s''', cstr(finance_book)))
|
||||
parentfield='schedules'
|
||||
AND schedule_date<=%s
|
||||
AND journal_entry IS NOT NULL
|
||||
AND ifnull(finance_book, '')=%s
|
||||
GROUP BY parent''', (date, cstr(finance_book))))
|
||||
|
||||
def get_purchase_receipt_supplier_map():
|
||||
return frappe._dict(frappe.db.sql(''' Select
|
||||
|
||||
@@ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"name": ['!=', frm.doc.supplier_warehouse],
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
@@ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
})
|
||||
}
|
||||
|
||||
me.dialog.get_field('sub_con_rm_items').check_all_rows()
|
||||
|
||||
me.dialog.show()
|
||||
this.dialog.set_primary_action(__('Transfer'), function() {
|
||||
me.values = me.dialog.get_values();
|
||||
|
||||
3
erpnext/buying/doctype/purchase_order/regional/india.js
Normal file
3
erpnext/buying/doctype/purchase_order/regional/india.js
Normal file
@@ -0,0 +1,3 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Purchase Order');
|
||||
@@ -519,47 +519,62 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
def test_backflush_based_on_stock_entry(self):
|
||||
item_code = "_Test Subcontracted FG Item 1"
|
||||
make_subcontracted_item(item_code)
|
||||
make_item('Sub Contracted Raw Material 1', {
|
||||
'is_stock_item': 1,
|
||||
'is_sub_contracted_item': 1
|
||||
})
|
||||
|
||||
update_backflush_based_on("Material Transferred for Subcontract")
|
||||
po = create_purchase_order(item_code=item_code, qty=1,
|
||||
|
||||
order_qty = 5
|
||||
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Test Extra Item 2", qty=10, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100)
|
||||
|
||||
rm_item = [
|
||||
{"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item",
|
||||
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"},
|
||||
rm_items = [
|
||||
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item",
|
||||
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||
{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
|
||||
"qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"},
|
||||
"qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||
{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
|
||||
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}]
|
||||
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||
{'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos',
|
||||
'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}]
|
||||
|
||||
rm_item_string = json.dumps(rm_item)
|
||||
rm_item_string = json.dumps(rm_items)
|
||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||
se.append('items', {
|
||||
'item_code': "Test Extra Item 2",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"s_warehouse": "_Test Warehouse - _TC",
|
||||
"t_warehouse": "_Test Warehouse 1 - _TC"
|
||||
})
|
||||
se.set_missing_values()
|
||||
se.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
|
||||
received_qty = 2
|
||||
# partial receipt
|
||||
pr.get('items')[0].qty = received_qty
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
se_items = sorted([d.item_code for d in se.get('items')])
|
||||
supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
|
||||
transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
|
||||
issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
|
||||
|
||||
self.assertEquals(transferred_items, issued_items)
|
||||
self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
|
||||
|
||||
|
||||
transferred_rm_map = frappe._dict()
|
||||
for item in rm_items:
|
||||
transferred_rm_map[item.get('rm_item_code')] = item
|
||||
|
||||
for item in pr.get('supplied_items'):
|
||||
self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
|
||||
|
||||
self.assertEquals(se_items, supplied_items)
|
||||
update_backflush_based_on("BOM")
|
||||
|
||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||
|
||||
@@ -1,537 +1,168 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:42",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"creation": "2013-02-22 01:27:42",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"main_item_code",
|
||||
"rm_item_code",
|
||||
"description",
|
||||
"batch_no",
|
||||
"serial_no",
|
||||
"col_break1",
|
||||
"required_qty",
|
||||
"consumed_qty",
|
||||
"stock_uom",
|
||||
"rate",
|
||||
"amount",
|
||||
"conversion_factor",
|
||||
"current_stock",
|
||||
"reference_name",
|
||||
"bom_detail_no"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "main_item_code",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Item Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "main_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "main_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"oldfieldname": "main_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rm_item_code",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Raw Material Item Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "rm_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "rm_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Raw Material Item Code",
|
||||
"oldfieldname": "rm_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "300px",
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Data",
|
||||
"print_width": "300px",
|
||||
"read_only": 1,
|
||||
"width": "300px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Batch No",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Batch",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"no_copy": 1,
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Serial No",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Serial No",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "required_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Required Qty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "required_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "required_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Required Qty",
|
||||
"oldfieldname": "required_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "consumed_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Consumed Qty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "consumed_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "consumed_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Consumed Qty",
|
||||
"oldfieldname": "consumed_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Stock Uom",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Uom",
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate",
|
||||
"oldfieldname": "rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"oldfieldname": "amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "conversion_factor",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Conversion Factor",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "conversion_factor",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "conversion_factor",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Conversion Factor",
|
||||
"oldfieldname": "conversion_factor",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "current_stock",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Current Stock",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "current_stock",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "current_stock",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Current Stock",
|
||||
"oldfieldname": "current_stock",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "reference_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"oldfieldname": "reference_name",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "bom_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "BOM Detail No",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "bom_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "bom_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "BOM Detail No",
|
||||
"oldfieldname": "bom_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-01-07 16:51:59.536291",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Receipt Item Supplied",
|
||||
"owner": "wasim@webnotestech.com",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-11-21 16:25:29.909112",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Receipt Item Supplied",
|
||||
"owner": "wasim@webnotestech.com",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
33
erpnext/change_log/v12/v12_3_0.md
Normal file
33
erpnext/change_log/v12/v12_3_0.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Version 12.3.0 Release Notes
|
||||
|
||||
### Accounting
|
||||
|
||||
1. Statewise GST taxation for India
|
||||
- Added GST state in the tax category
|
||||
- Added tax category in the address, sales/purchase tax template
|
||||
- Based on the address system will fetch the tax template
|
||||
2. Accounts Payable report based on payment terms
|
||||
3. Trial Balance Report with filter "Party Name"
|
||||
4. Fixed asset register report with date filters
|
||||
|
||||
### CRM
|
||||
|
||||
1. Appointment Scheduling
|
||||
- Configure the appointment slots using Appointment Booking Settings
|
||||
- Users can book the appointment through the portal based on slot availability
|
||||
|
||||
### HR
|
||||
|
||||
1. Refactored Employee Attendance Tool
|
||||
2. Set allocated amount in employee advance as per total amount
|
||||
|
||||
### Fixes
|
||||
|
||||
1. Stock entry decimal issue while creating the GL entries
|
||||
2. Item wise stock balance report
|
||||
3. Valuation of subcontracting finished good item
|
||||
4. Not able to create Instructor, Student entries
|
||||
5. Pricing rule for a product discount
|
||||
6. POS for serialized items
|
||||
7. Not able to cancel share transfer entry
|
||||
8. Ledger entries for compensatory off were not getting created
|
||||
@@ -46,6 +46,16 @@ def get_data():
|
||||
"name": "Contract",
|
||||
"description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Appointment",
|
||||
"description" : _("Helps you manage appointments with your leads"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
"label": _("Newsletter"),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -165,6 +175,11 @@ def get_data():
|
||||
"type": "doctype",
|
||||
"name": "SMS Settings",
|
||||
"description": _("Setup SMS gateway settings")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"label": _("Email Group"),
|
||||
"name": "Email Group",
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -241,6 +241,10 @@ def get_data():
|
||||
"type": "doctype",
|
||||
"name": "Quality Inspection Template",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Quick Stock Balance",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,15 +5,17 @@ from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
import json
|
||||
from frappe import _, throw
|
||||
from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from frappe.utils import (today, flt, cint, fmt_money, formatdate,
|
||||
getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form)
|
||||
from erpnext.stock.get_item_details import get_conversion_factor, get_item_details
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.buying.utils import update_last_purchase_rate
|
||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||
from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import validate_pricing_rules
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction,
|
||||
apply_pricing_rule_for_free_items, get_applied_pricing_rules)
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
from six import text_type
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||
@@ -59,7 +61,6 @@ class AccountsController(TransactionBase):
|
||||
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1)
|
||||
|
||||
def validate(self):
|
||||
|
||||
if not self.get('is_return'):
|
||||
self.validate_qty_is_not_zero()
|
||||
|
||||
@@ -98,10 +99,22 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if self.is_return:
|
||||
self.validate_qty()
|
||||
else:
|
||||
self.validate_deferred_start_and_end_date()
|
||||
|
||||
validate_regional(self)
|
||||
if self.doctype != 'Material Request':
|
||||
validate_pricing_rules(self)
|
||||
apply_pricing_rule_on_transaction(self)
|
||||
|
||||
def validate_deferred_start_and_end_date(self):
|
||||
for d in self.items:
|
||||
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
||||
if not (d.service_start_date and d.service_end_date):
|
||||
frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
|
||||
elif getdate(d.service_start_date) > getdate(d.service_end_date):
|
||||
frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
|
||||
elif getdate(self.posting_date) > getdate(d.service_end_date):
|
||||
frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
|
||||
|
||||
def validate_invoice_documents_schedule(self):
|
||||
self.validate_payment_schedule_dates()
|
||||
@@ -232,7 +245,6 @@ class AccountsController(TransactionBase):
|
||||
|
||||
def set_missing_item_details(self, for_validate=False):
|
||||
"""set missing item values"""
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
if hasattr(self, "items"):
|
||||
@@ -244,7 +256,6 @@ class AccountsController(TransactionBase):
|
||||
document_type = "{} Item".format(self.doctype)
|
||||
parent_dict.update({"document_type": document_type})
|
||||
|
||||
self.set('pricing_rules', [])
|
||||
# party_name field used for customer in quotation
|
||||
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
|
||||
parent_dict.update({"customer": parent_dict.get("party_name")})
|
||||
@@ -264,7 +275,7 @@ class AccountsController(TransactionBase):
|
||||
if self.get("is_subcontracted"):
|
||||
args["is_subcontracted"] = self.is_subcontracted
|
||||
|
||||
ret = get_item_details(args, self, overwrite_warehouse=False)
|
||||
ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
|
||||
|
||||
for fieldname, value in ret.items():
|
||||
if item.meta.get_field(fieldname) and value is not None:
|
||||
@@ -285,24 +296,42 @@ class AccountsController(TransactionBase):
|
||||
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
|
||||
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
|
||||
|
||||
if ret.get("pricing_rules") and not ret.get("validate_applied_rule", 0):
|
||||
# if user changed the discount percentage then set user's discount percentage ?
|
||||
item.set("pricing_rules", ret.get("pricing_rules"))
|
||||
item.set("discount_percentage", ret.get("discount_percentage"))
|
||||
item.set("discount_amount", ret.get("discount_amount"))
|
||||
if ret.get("pricing_rule_for") == "Rate":
|
||||
item.set("price_list_rate", ret.get("price_list_rate"))
|
||||
|
||||
if item.get("price_list_rate"):
|
||||
item.rate = flt(item.price_list_rate *
|
||||
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
|
||||
|
||||
if item.get('discount_amount'):
|
||||
item.rate = item.price_list_rate - item.discount_amount
|
||||
if ret.get("pricing_rules"):
|
||||
self.apply_pricing_rule_on_items(item, ret)
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
self.set_expense_account(for_validate)
|
||||
|
||||
def apply_pricing_rule_on_items(self, item, pricing_rule_args):
|
||||
if not pricing_rule_args.get("validate_applied_rule", 0):
|
||||
# if user changed the discount percentage then set user's discount percentage ?
|
||||
if pricing_rule_args.get("price_or_product_discount") == 'Price':
|
||||
item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
|
||||
item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
|
||||
item.set("discount_amount", pricing_rule_args.get("discount_amount"))
|
||||
if pricing_rule_args.get("pricing_rule_for") == "Rate":
|
||||
item.set("price_list_rate", pricing_rule_args.get("price_list_rate"))
|
||||
|
||||
if item.get("price_list_rate"):
|
||||
item.rate = flt(item.price_list_rate *
|
||||
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
|
||||
|
||||
if item.get('discount_amount'):
|
||||
item.rate = item.price_list_rate - item.discount_amount
|
||||
|
||||
elif pricing_rule_args.get('free_item_data'):
|
||||
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
||||
|
||||
elif pricing_rule_args.get("validate_applied_rule"):
|
||||
for pricing_rule in get_applied_pricing_rules(item):
|
||||
pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||
for field in ['discount_percentage', 'discount_amount', 'rate']:
|
||||
if item.get(field) < pricing_rule_doc.get(field):
|
||||
title = get_link_to_form("Pricing Rule", pricing_rule)
|
||||
|
||||
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
|
||||
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
|
||||
|
||||
def set_taxes(self):
|
||||
if not self.meta.get_field("taxes"):
|
||||
return
|
||||
@@ -397,9 +426,10 @@ class AccountsController(TransactionBase):
|
||||
return gl_dict
|
||||
|
||||
def validate_qty_is_not_zero(self):
|
||||
for item in self.items:
|
||||
if not item.qty:
|
||||
frappe.throw(_("Item quantity can not be zero"))
|
||||
if self.doctype != "Purchase Receipt":
|
||||
for item in self.items:
|
||||
if not item.qty:
|
||||
frappe.throw(_("Item quantity can not be zero"))
|
||||
|
||||
def validate_account_currency(self, account, account_currency=None):
|
||||
valid_currency = [self.company_currency]
|
||||
|
||||
@@ -221,7 +221,7 @@ class BuyingController(StockController):
|
||||
"backflush_raw_materials_of_subcontract_based_on")
|
||||
if (self.doctype == 'Purchase Receipt' and
|
||||
backflush_raw_materials_based_on != 'BOM'):
|
||||
self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table)
|
||||
self.update_raw_materials_supplied_based_on_stock_entries()
|
||||
else:
|
||||
for item in self.get("items"):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
@@ -241,41 +241,96 @@ class BuyingController(StockController):
|
||||
if self.is_subcontracted == "No" and self.get("supplied_items"):
|
||||
self.set('supplied_items', [])
|
||||
|
||||
def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table):
|
||||
self.set(raw_material_table, [])
|
||||
purchase_orders = [d.purchase_order for d in self.items]
|
||||
if purchase_orders:
|
||||
items = get_subcontracted_raw_materials_from_se(purchase_orders)
|
||||
backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name)
|
||||
def update_raw_materials_supplied_based_on_stock_entries(self):
|
||||
self.set('supplied_items', [])
|
||||
|
||||
for d in items:
|
||||
qty = d.qty - backflushed_raw_materials.get(d.item_code, 0)
|
||||
rm = self.append(raw_material_table, {})
|
||||
rm.rm_item_code = d.item_code
|
||||
rm.item_name = d.item_name
|
||||
rm.main_item_code = d.main_item_code
|
||||
rm.description = d.description
|
||||
rm.stock_uom = d.stock_uom
|
||||
rm.required_qty = qty
|
||||
rm.consumed_qty = qty
|
||||
rm.serial_no = d.serial_no
|
||||
rm.batch_no = d.batch_no
|
||||
purchase_orders = set([d.purchase_order for d in self.items])
|
||||
|
||||
# get raw materials rate
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
rm.rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * qty,
|
||||
"serial_no": rm.serial_no
|
||||
})
|
||||
if not rm.rate:
|
||||
rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse,
|
||||
self.doctype, self.name, currency=self.company_currency, company = self.company)
|
||||
# qty of raw materials backflushed (for each item per purchase order)
|
||||
backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
|
||||
|
||||
rm.amount = qty * flt(rm.rate)
|
||||
# qty of "finished good" item yet to be received
|
||||
qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
|
||||
|
||||
for item in self.get('items'):
|
||||
# reset raw_material cost
|
||||
item.rm_supp_cost = 0
|
||||
|
||||
# qty of raw materials transferred to the supplier
|
||||
transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
|
||||
|
||||
non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
|
||||
|
||||
item_key = '{}{}'.format(item.item_code, item.purchase_order)
|
||||
|
||||
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
|
||||
|
||||
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||
|
||||
for raw_material in transferred_raw_materials + non_stock_items:
|
||||
rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order)
|
||||
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
||||
|
||||
consumed_qty = raw_material_data.get('qty', 0)
|
||||
consumed_serial_nos = raw_material_data.get('serial_nos', '')
|
||||
consumed_batch_nos = raw_material_data.get('batch_nos', '')
|
||||
|
||||
transferred_qty = raw_material.qty
|
||||
|
||||
rm_qty_to_be_consumed = transferred_qty - consumed_qty
|
||||
|
||||
# backflush all remaining transferred qty in the last Purchase Receipt
|
||||
if fg_yet_to_be_received == item.qty:
|
||||
qty = rm_qty_to_be_consumed
|
||||
else:
|
||||
qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
|
||||
|
||||
if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
|
||||
qty = frappe.utils.ceil(qty)
|
||||
|
||||
if qty > rm_qty_to_be_consumed:
|
||||
qty = rm_qty_to_be_consumed
|
||||
|
||||
if not qty: continue
|
||||
|
||||
if raw_material.serial_nos:
|
||||
set_serial_nos(raw_material, consumed_serial_nos, qty)
|
||||
|
||||
if raw_material.batch_nos:
|
||||
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
||||
qty, transferred_batch_qty_map, backflushed_batch_qty_map)
|
||||
for batch_data in batches_qty:
|
||||
qty = batch_data['qty']
|
||||
raw_material.batch_no = batch_data['batch']
|
||||
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||
else:
|
||||
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||
|
||||
def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
|
||||
rm = self.append('supplied_items', {})
|
||||
rm.update(raw_material_data)
|
||||
|
||||
rm.required_qty = qty
|
||||
rm.consumed_qty = qty
|
||||
|
||||
if not raw_material_data.get('non_stock_item'):
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
rm.rate = get_incoming_rate({
|
||||
"item_code": raw_material_data.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * qty,
|
||||
"serial_no": rm.serial_no
|
||||
})
|
||||
|
||||
if not rm.rate:
|
||||
rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
|
||||
self.doctype, self.name, currency=self.company_currency, company=self.company)
|
||||
|
||||
rm.amount = qty * flt(rm.rate)
|
||||
fg_item_doc.rm_supp_cost += rm.amount
|
||||
|
||||
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
|
||||
exploded_item = 1
|
||||
@@ -387,9 +442,11 @@ class BuyingController(StockController):
|
||||
item_codes = list(set(item.item_code for item in
|
||||
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=1""" % \
|
||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
||||
items = frappe.get_all('Item', filters={
|
||||
'name': ['in', item_codes],
|
||||
'is_sub_contracted_item': 1
|
||||
})
|
||||
self._sub_contracted_items = [item.name for item in items]
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
@@ -722,28 +779,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
|
||||
|
||||
return bom_items
|
||||
|
||||
def get_subcontracted_raw_materials_from_se(purchase_orders):
|
||||
return frappe.db.sql("""
|
||||
select
|
||||
sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description,
|
||||
sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no
|
||||
from `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||
where
|
||||
se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor'
|
||||
and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != ''
|
||||
group by sed.item_code, sed.t_warehouse
|
||||
""" % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1)
|
||||
def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
|
||||
common_query = """
|
||||
SELECT
|
||||
sed.item_code AS rm_item_code,
|
||||
SUM(sed.qty) AS qty,
|
||||
sed.description,
|
||||
sed.stock_uom,
|
||||
sed.subcontracted_item AS main_item_code,
|
||||
{serial_no_concat_syntax} AS serial_nos,
|
||||
{batch_no_concat_syntax} AS batch_nos
|
||||
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||
WHERE
|
||||
se.name = sed.parent
|
||||
AND se.docstatus=1
|
||||
AND se.purpose='Send to Subcontractor'
|
||||
AND se.purchase_order = %s
|
||||
AND IFNULL(sed.t_warehouse, '') != ''
|
||||
AND sed.subcontracted_item = %s
|
||||
GROUP BY sed.item_code, sed.subcontracted_item
|
||||
"""
|
||||
raw_materials = frappe.db.multisql({
|
||||
'mariadb': common_query.format(
|
||||
serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
|
||||
batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
|
||||
),
|
||||
'postgres': common_query.format(
|
||||
serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
|
||||
batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
|
||||
)
|
||||
}, (purchase_order, fg_item), as_dict=1)
|
||||
|
||||
def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt):
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
select
|
||||
prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty
|
||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
||||
where
|
||||
pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s)
|
||||
and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1
|
||||
group by prsi.rm_item_code
|
||||
""" % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders)))
|
||||
return raw_materials
|
||||
|
||||
def get_backflushed_subcontracted_raw_materials(purchase_orders):
|
||||
common_query = """
|
||||
SELECT
|
||||
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
|
||||
SUM(prsi.consumed_qty) AS qty,
|
||||
{serial_no_concat_syntax} AS serial_nos,
|
||||
{batch_no_concat_syntax} AS batch_nos
|
||||
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
||||
WHERE
|
||||
pr.name = pri.parent
|
||||
AND pr.name = prsi.parent
|
||||
AND pri.purchase_order IN %s
|
||||
AND pri.item_code = prsi.main_item_code
|
||||
AND pr.docstatus = 1
|
||||
GROUP BY prsi.rm_item_code, pri.purchase_order
|
||||
"""
|
||||
|
||||
backflushed_raw_materials = frappe.db.multisql({
|
||||
'mariadb': common_query.format(
|
||||
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
|
||||
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
|
||||
),
|
||||
'postgres': common_query.format(
|
||||
serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
|
||||
batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
|
||||
)
|
||||
}, (purchase_orders, ), as_dict=1)
|
||||
|
||||
backflushed_raw_materials_map = frappe._dict()
|
||||
for item in backflushed_raw_materials:
|
||||
backflushed_raw_materials_map.setdefault(item.item_key, item)
|
||||
|
||||
return backflushed_raw_materials_map
|
||||
|
||||
def get_asset_item_details(asset_items):
|
||||
asset_items_data = {}
|
||||
@@ -776,3 +877,125 @@ def validate_item_type(doc, fieldname, message):
|
||||
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message))
|
||||
|
||||
frappe.throw(error_message)
|
||||
|
||||
def get_qty_to_be_received(purchase_orders):
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
|
||||
SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
|
||||
FROM `tabPurchase Order Item` poi
|
||||
WHERE
|
||||
poi.`parent` in %s
|
||||
GROUP BY poi.`item_code`, poi.`parent`
|
||||
HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
|
||||
""", (purchase_orders)))
|
||||
|
||||
def get_non_stock_items(purchase_order, fg_item_code):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
pois.main_item_code,
|
||||
pois.rm_item_code,
|
||||
item.description,
|
||||
pois.required_qty AS qty,
|
||||
pois.rate,
|
||||
1 as non_stock_item,
|
||||
pois.stock_uom
|
||||
FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
|
||||
WHERE
|
||||
pois.`rm_item_code` = item.`name`
|
||||
AND item.is_stock_item = 0
|
||||
AND pois.`parent` = %s
|
||||
AND pois.`main_item_code` = %s
|
||||
""", (purchase_order, fg_item_code), as_dict=1)
|
||||
|
||||
|
||||
def set_serial_nos(raw_material, consumed_serial_nos, qty):
|
||||
serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
|
||||
set(get_serial_nos(consumed_serial_nos))
|
||||
if serial_nos and qty <= len(serial_nos):
|
||||
raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
|
||||
|
||||
def get_transferred_batch_qty_map(purchase_order, fg_item):
|
||||
# returns
|
||||
# {
|
||||
# (item_code, fg_code): {
|
||||
# batch1: 10, # qty
|
||||
# batch2: 16
|
||||
# },
|
||||
# }
|
||||
transferred_batch_qty_map = {}
|
||||
transferred_batches = frappe.db.sql("""
|
||||
SELECT
|
||||
sed.batch_no,
|
||||
SUM(sed.qty) AS qty,
|
||||
sed.item_code
|
||||
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||
WHERE
|
||||
se.name = sed.parent
|
||||
AND se.docstatus=1
|
||||
AND se.purpose='Send to Subcontractor'
|
||||
AND se.purchase_order = %s
|
||||
AND sed.subcontracted_item = %s
|
||||
AND sed.batch_no IS NOT NULL
|
||||
GROUP BY
|
||||
sed.batch_no,
|
||||
sed.item_code
|
||||
""", (purchase_order, fg_item), as_dict=1)
|
||||
|
||||
for batch_data in transferred_batches:
|
||||
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
||||
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
||||
|
||||
return transferred_batch_qty_map
|
||||
|
||||
def get_backflushed_batch_qty_map(purchase_order, fg_item):
|
||||
# returns
|
||||
# {
|
||||
# (item_code, fg_code): {
|
||||
# batch1: 10, # qty
|
||||
# batch2: 16
|
||||
# },
|
||||
# }
|
||||
backflushed_batch_qty_map = {}
|
||||
backflushed_batches = frappe.db.sql("""
|
||||
SELECT
|
||||
pris.batch_no,
|
||||
SUM(pris.consumed_qty) AS qty,
|
||||
pris.rm_item_code AS item_code
|
||||
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
|
||||
WHERE
|
||||
pr.name = pri.parent
|
||||
AND pri.parent = pris.parent
|
||||
AND pri.purchase_order = %s
|
||||
AND pri.item_code = pris.main_item_code
|
||||
AND pr.docstatus = 1
|
||||
AND pris.main_item_code = %s
|
||||
AND pris.batch_no IS NOT NULL
|
||||
GROUP BY
|
||||
pris.rm_item_code, pris.batch_no
|
||||
""", (purchase_order, fg_item), as_dict=1)
|
||||
|
||||
for batch_data in backflushed_batches:
|
||||
backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
||||
backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
||||
|
||||
return backflushed_batch_qty_map
|
||||
|
||||
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
|
||||
# Returns available batches to be backflushed based on requirements
|
||||
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
||||
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
|
||||
|
||||
available_batches = []
|
||||
|
||||
for (batch, transferred_qty) in transferred_batches.items():
|
||||
backflushed_qty = backflushed_batches.get(batch, 0)
|
||||
available_qty = transferred_qty - backflushed_qty
|
||||
|
||||
if available_qty >= required_qty:
|
||||
available_batches.append({'batch': batch, 'qty': required_qty})
|
||||
break
|
||||
else:
|
||||
available_batches.append({'batch': batch, 'qty': available_qty})
|
||||
required_qty -= available_qty
|
||||
|
||||
return available_batches
|
||||
@@ -552,7 +552,7 @@ class calculate_taxes_and_totals(object):
|
||||
if item.price_list_rate:
|
||||
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
||||
for d in item.pricing_rules.split(','):
|
||||
pricing_rule = frappe.get_doc('Pricing Rule', d)
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
|
||||
or (pricing_rule.margin_type == 'Percentage'):
|
||||
|
||||
0
erpnext/crm/doctype/appointment/__init__.py
Normal file
0
erpnext/crm/doctype/appointment/__init__.py
Normal file
17
erpnext/crm/doctype/appointment/appointment.js
Normal file
17
erpnext/crm/doctype/appointment/appointment.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Appointment', {
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.lead){
|
||||
frm.add_custom_button(frm.doc.lead,()=>{
|
||||
frappe.set_route("Form", "Lead", frm.doc.lead);
|
||||
});
|
||||
}
|
||||
if(frm.doc.calendar_event){
|
||||
frm.add_custom_button(__(frm.doc.calendar_event),()=>{
|
||||
frappe.set_route("Form", "Event", frm.doc.calendar_event);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
153
erpnext/crm/doctype/appointment/appointment.json
Normal file
153
erpnext/crm/doctype/appointment/appointment.json
Normal file
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"autoname": "format:APMT-{customer_name}-{####}",
|
||||
"creation": "2019-08-27 10:48:27.926283",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"scheduled_time",
|
||||
"status",
|
||||
"customer_details_section",
|
||||
"customer_name",
|
||||
"customer_phone_number",
|
||||
"customer_skype",
|
||||
"customer_email",
|
||||
"col_br_2",
|
||||
"customer_details",
|
||||
"linked_docs_section",
|
||||
"lead",
|
||||
"col_br_3",
|
||||
"calendar_event"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "customer_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Customer Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_phone_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Phone Number"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_skype",
|
||||
"fieldtype": "Data",
|
||||
"label": "Skype ID"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_details",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "scheduled_time",
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
"label": "Scheduled Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "Open\nUnverified\nClosed",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "lead",
|
||||
"fieldtype": "Link",
|
||||
"label": "Lead",
|
||||
"options": "Lead"
|
||||
},
|
||||
{
|
||||
"fieldname": "calendar_event",
|
||||
"fieldtype": "Link",
|
||||
"label": "Calendar Event",
|
||||
"options": "Event"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_br_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_email",
|
||||
"fieldtype": "Data",
|
||||
"label": "Email",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "linked_docs_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Linked Documents"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_br_3",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"modified": "2019-10-14 15:23:54.630731",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Appointment",
|
||||
"name_case": "UPPER CASE",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Guest",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
223
erpnext/crm/doctype/appointment/appointment.py
Normal file
223
erpnext/crm/doctype/appointment/appointment.py
Normal file
@@ -0,0 +1,223 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import urllib
|
||||
from collections import Counter
|
||||
from datetime import timedelta
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_url
|
||||
from frappe.utils.verified_command import verify_request, get_signed_params
|
||||
|
||||
|
||||
class Appointment(Document):
|
||||
|
||||
def find_lead_by_email(self):
|
||||
lead_list = frappe.get_list(
|
||||
'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True)
|
||||
if lead_list:
|
||||
return lead_list[0].name
|
||||
return None
|
||||
|
||||
def before_insert(self):
|
||||
number_of_appointments_in_same_slot = frappe.db.count(
|
||||
'Appointment', filters={'scheduled_time': self.scheduled_time})
|
||||
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
|
||||
if not number_of_agents == 0:
|
||||
if (number_of_appointments_in_same_slot >= number_of_agents):
|
||||
frappe.throw('Time slot is not available')
|
||||
# Link lead
|
||||
if not self.lead:
|
||||
self.lead = self.find_lead_by_email()
|
||||
|
||||
def after_insert(self):
|
||||
if self.lead:
|
||||
# Create Calendar event
|
||||
self.auto_assign()
|
||||
self.create_calendar_event()
|
||||
else:
|
||||
# Set status to unverified
|
||||
self.status = 'Unverified'
|
||||
# Send email to confirm
|
||||
self.send_confirmation_email()
|
||||
|
||||
def send_confirmation_email(self):
|
||||
verify_url = self._get_verify_url()
|
||||
template = 'confirm_appointment'
|
||||
args = {
|
||||
"link":verify_url,
|
||||
"site_url":frappe.utils.get_url(),
|
||||
"full_name":self.customer_name,
|
||||
}
|
||||
frappe.sendmail(recipients=[self.customer_email],
|
||||
template=template,
|
||||
args=args,
|
||||
subject=_('Appointment Confirmation'))
|
||||
if frappe.session.user == "Guest":
|
||||
frappe.msgprint(
|
||||
'Please check your email to confirm the appointment')
|
||||
else :
|
||||
frappe.msgprint(
|
||||
'Appointment was created. But no lead was found. Please check the email to confirm')
|
||||
|
||||
def on_change(self):
|
||||
# Sync Calendar
|
||||
if not self.calendar_event:
|
||||
return
|
||||
cal_event = frappe.get_doc('Event', self.calendar_event)
|
||||
cal_event.starts_on = self.scheduled_time
|
||||
cal_event.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def set_verified(self, email):
|
||||
if not email == self.customer_email:
|
||||
frappe.throw('Email verification failed.')
|
||||
# Create new lead
|
||||
self.create_lead_and_link()
|
||||
# Remove unverified status
|
||||
self.status = 'Open'
|
||||
# Create calender event
|
||||
self.auto_assign()
|
||||
self.create_calendar_event()
|
||||
self.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
def create_lead_and_link(self):
|
||||
# Return if already linked
|
||||
if self.lead:
|
||||
return
|
||||
lead = frappe.get_doc({
|
||||
'doctype': 'Lead',
|
||||
'lead_name': self.customer_name,
|
||||
'email_id': self.customer_email,
|
||||
'notes': self.customer_details,
|
||||
'phone': self.customer_phone_number,
|
||||
})
|
||||
lead.insert(ignore_permissions=True)
|
||||
# Link lead
|
||||
self.lead = lead.name
|
||||
|
||||
def auto_assign(self):
|
||||
from frappe.desk.form.assign_to import add as add_assignemnt
|
||||
existing_assignee = self.get_assignee_from_latest_opportunity()
|
||||
if existing_assignee:
|
||||
# If the latest opportunity is assigned to someone
|
||||
# Assign the appointment to the same
|
||||
add_assignemnt({
|
||||
'doctype': self.doctype,
|
||||
'name': self.name,
|
||||
'assign_to': existing_assignee
|
||||
})
|
||||
return
|
||||
if self._assign:
|
||||
return
|
||||
available_agents = _get_agents_sorted_by_asc_workload(
|
||||
self.scheduled_time.date())
|
||||
for agent in available_agents:
|
||||
if(_check_agent_availability(agent, self.scheduled_time)):
|
||||
agent = agent[0]
|
||||
add_assignemnt({
|
||||
'doctype': self.doctype,
|
||||
'name': self.name,
|
||||
'assign_to': agent
|
||||
})
|
||||
break
|
||||
|
||||
def get_assignee_from_latest_opportunity(self):
|
||||
if not self.lead:
|
||||
return None
|
||||
if not frappe.db.exists('Lead', self.lead):
|
||||
return None
|
||||
opporutnities = frappe.get_list(
|
||||
'Opportunity',
|
||||
filters={
|
||||
'party_name': self.lead,
|
||||
},
|
||||
ignore_permissions=True,
|
||||
order_by='creation desc')
|
||||
if not opporutnities:
|
||||
return None
|
||||
latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name )
|
||||
assignee = latest_opportunity._assign
|
||||
if not assignee:
|
||||
return None
|
||||
assignee = frappe.parse_json(assignee)[0]
|
||||
return assignee
|
||||
|
||||
def create_calendar_event(self):
|
||||
if self.calendar_event:
|
||||
return
|
||||
appointment_event = frappe.get_doc({
|
||||
'doctype': 'Event',
|
||||
'subject': ' '.join(['Appointment with', self.customer_name]),
|
||||
'starts_on': self.scheduled_time,
|
||||
'status': 'Open',
|
||||
'type': 'Public',
|
||||
'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'),
|
||||
'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)]
|
||||
})
|
||||
employee = _get_employee_from_user(self._assign)
|
||||
if employee:
|
||||
appointment_event.append('event_participants', dict(
|
||||
reference_doctype='Employee',
|
||||
reference_docname=employee.name))
|
||||
appointment_event.insert(ignore_permissions=True)
|
||||
self.calendar_event = appointment_event.name
|
||||
self.save(ignore_permissions=True)
|
||||
|
||||
def _get_verify_url(self):
|
||||
verify_route = '/book_appointment/verify'
|
||||
params = {
|
||||
'email': self.customer_email,
|
||||
'appointment': self.name
|
||||
}
|
||||
return get_url(verify_route + '?' + get_signed_params(params))
|
||||
|
||||
|
||||
def _get_agents_sorted_by_asc_workload(date):
|
||||
appointments = frappe.db.get_list('Appointment', fields='*')
|
||||
agent_list = _get_agent_list_as_strings()
|
||||
if not appointments:
|
||||
return agent_list
|
||||
appointment_counter = Counter(agent_list)
|
||||
for appointment in appointments:
|
||||
assigned_to = frappe.parse_json(appointment._assign)
|
||||
if not assigned_to:
|
||||
continue
|
||||
if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date:
|
||||
appointment_counter[assigned_to[0]] += 1
|
||||
sorted_agent_list = appointment_counter.most_common()
|
||||
sorted_agent_list.reverse()
|
||||
return sorted_agent_list
|
||||
|
||||
|
||||
def _get_agent_list_as_strings():
|
||||
agent_list_as_strings = []
|
||||
agent_list = frappe.get_doc('Appointment Booking Settings').agent_list
|
||||
for agent in agent_list:
|
||||
agent_list_as_strings.append(agent.user)
|
||||
return agent_list_as_strings
|
||||
|
||||
|
||||
def _check_agent_availability(agent_email, scheduled_time):
|
||||
appointemnts_at_scheduled_time = frappe.get_list(
|
||||
'Appointment', filters={'scheduled_time': scheduled_time})
|
||||
for appointment in appointemnts_at_scheduled_time:
|
||||
if appointment._assign == agent_email:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _get_employee_from_user(user):
|
||||
employee_docname = frappe.db.exists(
|
||||
{'doctype': 'Employee', 'user_id': user})
|
||||
if employee_docname:
|
||||
# frappe.db.exists returns a tuple of a tuple
|
||||
return frappe.get_doc('Employee', employee_docname[0][0])
|
||||
return None
|
||||
|
||||
58
erpnext/crm/doctype/appointment/test_appointment.py
Normal file
58
erpnext/crm/doctype/appointment/test_appointment.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
import datetime
|
||||
|
||||
|
||||
def create_test_lead():
|
||||
test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'})
|
||||
if test_lead:
|
||||
return frappe.get_doc('Lead', test_lead[0][0])
|
||||
test_lead = frappe.get_doc({
|
||||
'doctype': 'Lead',
|
||||
'lead_name': 'Test Lead',
|
||||
'email_id': 'test@example.com'
|
||||
})
|
||||
test_lead.insert(ignore_permissions=True)
|
||||
return test_lead
|
||||
|
||||
|
||||
def create_test_appointments():
|
||||
test_appointment = frappe.db.exists(
|
||||
{'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'})
|
||||
if test_appointment:
|
||||
return frappe.get_doc('Appointment', test_appointment[0][0])
|
||||
test_appointment = frappe.get_doc({
|
||||
'doctype': 'Appointment',
|
||||
'email': 'test@example.com',
|
||||
'status': 'Open',
|
||||
'customer_name': 'Test Lead',
|
||||
'customer_phone_number': '666',
|
||||
'customer_skype': 'test',
|
||||
'customer_email': 'test@example.com',
|
||||
'scheduled_time': datetime.datetime.now()
|
||||
})
|
||||
test_appointment.insert()
|
||||
return test_appointment
|
||||
|
||||
|
||||
class TestAppointment(unittest.TestCase):
|
||||
test_appointment = test_lead = None
|
||||
|
||||
def setUp(self):
|
||||
self.test_lead = create_test_lead()
|
||||
self.test_appointment = create_test_appointments()
|
||||
|
||||
def test_calendar_event_created(self):
|
||||
cal_event = frappe.get_doc(
|
||||
'Event', self.test_appointment.calendar_event)
|
||||
self.assertEqual(cal_event.starts_on,
|
||||
self.test_appointment.scheduled_time)
|
||||
|
||||
def test_lead_linked(self):
|
||||
lead = frappe.get_doc('Lead', self.test_lead.name)
|
||||
self.assertIsNotNone(lead)
|
||||
@@ -0,0 +1,10 @@
|
||||
frappe.ui.form.on('Appointment Booking Settings', 'validate',check_times);
|
||||
function check_times(frm) {
|
||||
$.each(frm.doc.availability_of_slots || [], function (i, d) {
|
||||
let from_time = Date.parse('01/01/2019 ' + d.from_time);
|
||||
let to_time = Date.parse('01/01/2019 ' + d.to_time);
|
||||
if (from_time > to_time) {
|
||||
frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"creation": "2019-08-27 10:56:48.309824",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enable_scheduling",
|
||||
"agent_detail_section",
|
||||
"availability_of_slots",
|
||||
"number_of_agents",
|
||||
"agent_list",
|
||||
"holiday_list",
|
||||
"appointment_details_section",
|
||||
"appointment_duration",
|
||||
"email_reminders",
|
||||
"advance_booking_days",
|
||||
"success_details",
|
||||
"success_redirect_url"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "availability_of_slots",
|
||||
"fieldtype": "Table",
|
||||
"label": "Availability Of Slots",
|
||||
"options": "Appointment Booking Slots",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "number_of_agents",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Number of Concurrent Appointments",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "holiday_list",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Holiday List",
|
||||
"options": "Holiday List",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "60",
|
||||
"fieldname": "appointment_duration",
|
||||
"fieldtype": "Int",
|
||||
"label": "Appointment Duration (In Minutes)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Notify customer and agent via email on the day of the appointment.",
|
||||
"fieldname": "email_reminders",
|
||||
"fieldtype": "Check",
|
||||
"label": "Notify Via Email"
|
||||
},
|
||||
{
|
||||
"default": "7",
|
||||
"fieldname": "advance_booking_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Number of days appointments can be booked in advance",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "agent_list",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Agents",
|
||||
"options": "Assignment Rule User",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_scheduling",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Appointment Scheduling",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "agent_detail_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Agent Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "appointment_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Appointment Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "success_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Success Settings"
|
||||
},
|
||||
{
|
||||
"description": "Leave blank for home.\nThis is relative to site URL, for example \"about\" will redirect to \"https://yoursitename.com/about\"",
|
||||
"fieldname": "success_redirect_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Success Redirect URL"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"modified": "2019-11-26 12:14:17.669366",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Appointment Booking Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Guest",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "HR Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
import datetime
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AppointmentBookingSettings(Document):
|
||||
agent_list = [] #Hack
|
||||
min_date = '01/01/1970 '
|
||||
format_string = "%d/%m/%Y %H:%M:%S"
|
||||
|
||||
def validate(self):
|
||||
self.validate_availability_of_slots()
|
||||
|
||||
def save(self):
|
||||
self.number_of_agents = len(self.agent_list)
|
||||
super(AppointmentBookingSettings, self).save()
|
||||
|
||||
def validate_availability_of_slots(self):
|
||||
for record in self.availability_of_slots:
|
||||
from_time = datetime.datetime.strptime(
|
||||
self.min_date+record.from_time, self.format_string)
|
||||
to_time = datetime.datetime.strptime(
|
||||
self.min_date+record.to_time, self.format_string)
|
||||
timedelta = to_time-from_time
|
||||
self.validate_from_and_to_time(from_time, to_time)
|
||||
self.duration_is_divisible(from_time, to_time)
|
||||
|
||||
def validate_from_and_to_time(self, from_time, to_time):
|
||||
if from_time > to_time:
|
||||
err_msg = _('<b>From Time</b> cannot be later than <b>To Time</b> for {0}').format(record.day_of_week)
|
||||
frappe.throw(_(err_msg))
|
||||
|
||||
def duration_is_divisible(self, from_time, to_time):
|
||||
timedelta = to_time - from_time
|
||||
if timedelta.total_seconds() % (self.appointment_duration * 60):
|
||||
frappe.throw(
|
||||
_('The difference between from time and To Time must be a multiple of Appointment'))
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestAppointmentBookingSettings(unittest.TestCase):
|
||||
pass
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"creation": "2019-11-19 10:49:49.494927",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"day_of_week",
|
||||
"from_time",
|
||||
"to_time"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "day_of_week",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Day Of Week",
|
||||
"options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
"label": "From Time ",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
"label": "To Time",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-11-19 10:49:49.494927",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Appointment Booking Slots",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AppointmentBookingSlots(Document):
|
||||
pass
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"creation": "2019-09-10 15:02:05.779434",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"day_of_week",
|
||||
"from_time",
|
||||
"to_time"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "day_of_week",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Day Of Week",
|
||||
"options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
"label": "From Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
"label": "To Time",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-09-10 15:05:20.406855",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Availability Of Slots",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AvailabilityOfSlots(Document):
|
||||
pass
|
||||
@@ -41,7 +41,8 @@ class EmailCampaign(Document):
|
||||
email_campaign_exists = frappe.db.exists("Email Campaign", {
|
||||
"campaign_name": self.campaign_name,
|
||||
"recipient": self.recipient,
|
||||
"status": ("in", ["In Progress", "Scheduled"])
|
||||
"status": ("in", ["In Progress", "Scheduled"]),
|
||||
"name": ("!=", self.name)
|
||||
})
|
||||
if email_campaign_exists:
|
||||
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
|
||||
@@ -78,7 +79,7 @@ def send_mail(entry, email_campaign):
|
||||
comm = make(
|
||||
doctype = "Email Campaign",
|
||||
name = email_campaign.name,
|
||||
subject = email_template.get("subject"),
|
||||
subject = frappe.render_template(email_template.get("subject"), context),
|
||||
content = frappe.render_template(email_template.get("response"), context),
|
||||
sender = sender,
|
||||
recipients = recipient,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -130,10 +130,11 @@ class Opportunity(TransactionBase):
|
||||
|
||||
def has_lost_quotation(self):
|
||||
lost_quotation = frappe.db.sql("""
|
||||
select q.name
|
||||
from `tabQuotation` q, `tabQuotation Item` qi
|
||||
where q.name = qi.parent and q.docstatus=1
|
||||
and qi.prevdoc_docname =%s and q.status = 'Lost'
|
||||
select name
|
||||
from `tabQuotation`
|
||||
where docstatus=1
|
||||
and opportunity =%s
|
||||
and status = 'Lost'
|
||||
""", self.name)
|
||||
if lost_quotation:
|
||||
if self.has_active_quotation():
|
||||
|
||||
@@ -4,11 +4,11 @@ cur_frm.add_fetch("employee", "image", "image");
|
||||
frappe.ui.form.on("Instructor", {
|
||||
employee: function(frm) {
|
||||
if(!frm.doc.employee) return;
|
||||
frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (company) => {
|
||||
frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (d) => {
|
||||
frm.set_query("department", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"company": company,
|
||||
"company": d.company,
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -16,7 +16,7 @@ frappe.ui.form.on("Instructor", {
|
||||
frm.set_query("department", "instructor_log", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"company": company,
|
||||
"company": d.company,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ class Student(Document):
|
||||
frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant))
|
||||
|
||||
def after_insert(self):
|
||||
if not frappe.get_single('Education Settings').user_creation_skip:
|
||||
if not frappe.get_single('Education Settings').get('user_creation_skip'):
|
||||
self.create_student_user()
|
||||
|
||||
def create_student_user(self):
|
||||
|
||||
@@ -182,6 +182,7 @@ standard_portal_menu_items = [
|
||||
{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"},
|
||||
{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"},
|
||||
{"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"},
|
||||
{"title": _("Appointment Booking"), "route": "/book_appointment"},
|
||||
]
|
||||
|
||||
default_roles = [
|
||||
@@ -248,10 +249,10 @@ doc_events = {
|
||||
"on_trash": "erpnext.regional.check_deletion_permission"
|
||||
},
|
||||
'Address': {
|
||||
'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code']
|
||||
'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category']
|
||||
},
|
||||
('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): {
|
||||
'validate': 'erpnext.regional.india.utils.set_place_of_supply'
|
||||
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
|
||||
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
|
||||
},
|
||||
"Contact": {
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||
@@ -301,7 +302,8 @@ scheduler_events = {
|
||||
"erpnext.quality_management.doctype.quality_review.quality_review.review",
|
||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
||||
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
|
||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
|
||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
|
||||
"erpnext.selling.doctype.quotation.quotation.set_expired_status"
|
||||
],
|
||||
"daily_long": [
|
||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||
|
||||
@@ -19,4 +19,4 @@ frappe.ui.form.on('Compensatory Leave Request', {
|
||||
frm.set_df_property('half_day_date', 'reqd', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -5,9 +5,10 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import date_diff, add_days, getdate
|
||||
from frappe.utils import date_diff, add_days, getdate, cint
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, get_holidays_for_employee
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
|
||||
get_holidays_for_employee, create_additional_leave_ledger_entry
|
||||
|
||||
class CompensatoryLeaveRequest(Document):
|
||||
|
||||
@@ -25,16 +26,14 @@ class CompensatoryLeaveRequest(Document):
|
||||
frappe.throw(_("Leave Type is madatory"))
|
||||
|
||||
def validate_attendance(self):
|
||||
query = """select attendance_date, status
|
||||
from `tabAttendance` where
|
||||
attendance_date between %(work_from_date)s and %(work_end_date)s
|
||||
and docstatus=1 and status = 'Present' and employee=%(employee)s"""
|
||||
attendance = frappe.get_all('Attendance',
|
||||
filters={
|
||||
'attendance_date': ['between', (self.work_from_date, self.work_end_date)],
|
||||
'status': 'Present',
|
||||
'docstatus': 1,
|
||||
'employee': self.employee
|
||||
}, fields=['attendance_date', 'status'])
|
||||
|
||||
attendance = frappe.db.sql(query, {
|
||||
"work_from_date": self.work_from_date,
|
||||
"work_end_date": self.work_end_date,
|
||||
"employee": self.employee
|
||||
}, as_dict=True)
|
||||
if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1:
|
||||
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
|
||||
|
||||
@@ -50,13 +49,19 @@ class CompensatoryLeaveRequest(Document):
|
||||
date_difference -= 0.5
|
||||
leave_period = get_leave_period(self.work_from_date, self.work_end_date, company)
|
||||
if leave_period:
|
||||
leave_allocation = self.exists_allocation_for_period(leave_period)
|
||||
leave_allocation = self.get_existing_allocation_for_period(leave_period)
|
||||
if leave_allocation:
|
||||
leave_allocation.new_leaves_allocated += date_difference
|
||||
leave_allocation.submit()
|
||||
leave_allocation.validate()
|
||||
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
|
||||
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
|
||||
|
||||
# generate additional ledger entry for the new compensatory leaves off
|
||||
create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1))
|
||||
|
||||
else:
|
||||
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
|
||||
self.db_set("leave_allocation", leave_allocation.name)
|
||||
self.leave_allocation=leave_allocation.name
|
||||
else:
|
||||
frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date))
|
||||
|
||||
@@ -68,11 +73,16 @@ class CompensatoryLeaveRequest(Document):
|
||||
leave_allocation = frappe.get_doc("Leave Allocation", self.leave_allocation)
|
||||
if leave_allocation:
|
||||
leave_allocation.new_leaves_allocated -= date_difference
|
||||
if leave_allocation.total_leaves_allocated - date_difference <= 0:
|
||||
leave_allocation.total_leaves_allocated = 0
|
||||
leave_allocation.submit()
|
||||
if leave_allocation.new_leaves_allocated - date_difference <= 0:
|
||||
leave_allocation.new_leaves_allocated = 0
|
||||
leave_allocation.validate()
|
||||
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
|
||||
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
|
||||
|
||||
def exists_allocation_for_period(self, leave_period):
|
||||
# create reverse entry on cancelation
|
||||
create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1))
|
||||
|
||||
def get_existing_allocation_for_period(self, leave_period):
|
||||
leave_allocation = frappe.db.sql("""
|
||||
select name
|
||||
from `tabLeave Allocation`
|
||||
@@ -95,17 +105,18 @@ class CompensatoryLeaveRequest(Document):
|
||||
|
||||
def create_leave_allocation(self, leave_period, date_difference):
|
||||
is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward")
|
||||
allocation = frappe.new_doc("Leave Allocation")
|
||||
allocation.employee = self.employee
|
||||
allocation.employee_name = self.employee_name
|
||||
allocation.leave_type = self.leave_type
|
||||
allocation.from_date = add_days(self.work_end_date, 1)
|
||||
allocation.to_date = leave_period[0].to_date
|
||||
allocation.new_leaves_allocated = date_difference
|
||||
allocation.total_leaves_allocated = date_difference
|
||||
allocation.description = self.reason
|
||||
if is_carry_forward == 1:
|
||||
allocation.carry_forward = True
|
||||
allocation.save(ignore_permissions = True)
|
||||
allocation = frappe.get_doc(dict(
|
||||
doctype="Leave Allocation",
|
||||
employee=self.employee,
|
||||
employee_name=self.employee_name,
|
||||
leave_type=self.leave_type,
|
||||
from_date=add_days(self.work_end_date, 1),
|
||||
to_date=leave_period[0].to_date,
|
||||
carry_forward=cint(is_carry_forward),
|
||||
new_leaves_allocated=date_difference,
|
||||
total_leaves_allocated=date_difference,
|
||||
description=self.reason
|
||||
))
|
||||
allocation.insert(ignore_permissions=True)
|
||||
allocation.submit()
|
||||
return allocation
|
||||
return allocation
|
||||
@@ -5,37 +5,128 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import today, add_months, add_days
|
||||
from erpnext.hr.doctype.attendance_request.test_attendance_request import get_employee
|
||||
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
|
||||
|
||||
# class TestCompensatoryLeaveRequest(unittest.TestCase):
|
||||
# def get_compensatory_leave_request(self):
|
||||
# return frappe.get_doc('Compensatory Leave Request', dict(
|
||||
# employee = employee,
|
||||
# work_from_date = today,
|
||||
# work_to_date = today,
|
||||
# reason = 'test'
|
||||
# )).insert()
|
||||
#
|
||||
# def test_creation_of_leave_allocation(self):
|
||||
# employee = get_employee()
|
||||
# today = get_today()
|
||||
#
|
||||
# compensatory_leave_request = self.get_compensatory_leave_request(today)
|
||||
#
|
||||
# before = get_leave_balance(employee, compensatory_leave_request.leave_type)
|
||||
#
|
||||
# compensatory_leave_request.submit()
|
||||
#
|
||||
# self.assertEqual(get_leave_balance(employee, compensatory_leave_request.leave_type), before + 1)
|
||||
#
|
||||
# def test_max_compensatory_leave(self):
|
||||
# employee = get_employee()
|
||||
# today = get_today()
|
||||
#
|
||||
# compensatory_leave_request = self.get_compensatory_leave_request()
|
||||
#
|
||||
# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 0)
|
||||
#
|
||||
# self.assertRaises(MaxLeavesLimitCrossed, compensatory_leave_request.submit)
|
||||
#
|
||||
# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 10)
|
||||
#
|
||||
class TestCompensatoryLeaveRequest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
|
||||
frappe.db.sql(''' delete from `tabLeave Ledger Entry`''')
|
||||
frappe.db.sql(''' delete from `tabLeave Allocation`''')
|
||||
frappe.db.sql(''' delete from `tabAttendance` where attendance_date in {0} '''.format((today(), add_days(today(), -1)))) #nosec
|
||||
create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company")
|
||||
create_holiday_list()
|
||||
|
||||
employee = get_employee()
|
||||
employee.holiday_list = "_Test Compensatory Leave"
|
||||
employee.save()
|
||||
|
||||
def test_leave_balance_on_submit(self):
|
||||
''' check creation of leave allocation on submission of compensatory leave request '''
|
||||
employee = get_employee()
|
||||
mark_attendance(employee)
|
||||
compensatory_leave_request = get_compensatory_leave_request(employee.name)
|
||||
|
||||
before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today())
|
||||
compensatory_leave_request.submit()
|
||||
|
||||
self.assertEqual(get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)), before + 1)
|
||||
|
||||
def test_leave_allocation_update_on_submit(self):
|
||||
employee = get_employee()
|
||||
mark_attendance(employee, date=add_days(today(), -1))
|
||||
compensatory_leave_request = get_compensatory_leave_request(employee.name, leave_date=add_days(today(), -1))
|
||||
compensatory_leave_request.submit()
|
||||
|
||||
# leave allocation creation on submit
|
||||
leaves_allocated = frappe.db.get_value('Leave Allocation', {
|
||||
'name': compensatory_leave_request.leave_allocation
|
||||
}, ['total_leaves_allocated'])
|
||||
self.assertEqual(leaves_allocated, 1)
|
||||
|
||||
mark_attendance(employee)
|
||||
compensatory_leave_request = get_compensatory_leave_request(employee.name)
|
||||
compensatory_leave_request.submit()
|
||||
|
||||
# leave allocation updates on submission of second compensatory leave request
|
||||
leaves_allocated = frappe.db.get_value('Leave Allocation', {
|
||||
'name': compensatory_leave_request.leave_allocation
|
||||
}, ['total_leaves_allocated'])
|
||||
self.assertEqual(leaves_allocated, 2)
|
||||
|
||||
def test_creation_of_leave_ledger_entry_on_submit(self):
|
||||
''' check creation of leave ledger entry on submission of leave request '''
|
||||
employee = get_employee()
|
||||
mark_attendance(employee)
|
||||
compensatory_leave_request = get_compensatory_leave_request(employee.name)
|
||||
compensatory_leave_request.submit()
|
||||
|
||||
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
|
||||
|
||||
self.assertEquals(len(leave_ledger_entry), 1)
|
||||
self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, 1)
|
||||
|
||||
# check reverse leave ledger entry on cancellation
|
||||
compensatory_leave_request.cancel()
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
|
||||
|
||||
self.assertEquals(len(leave_ledger_entry), 2)
|
||||
self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, -1)
|
||||
|
||||
def get_compensatory_leave_request(employee, leave_date=today()):
|
||||
prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
|
||||
dict(leave_type='Compensatory Off',
|
||||
work_from_date=leave_date,
|
||||
work_end_date=leave_date,
|
||||
employee=employee), 'name')
|
||||
if prev_comp_leave_req:
|
||||
return frappe.get_doc('Compensatory Leave Request', prev_comp_leave_req)
|
||||
|
||||
return frappe.get_doc(dict(
|
||||
doctype='Compensatory Leave Request',
|
||||
employee=employee,
|
||||
leave_type='Compensatory Off',
|
||||
work_from_date=leave_date,
|
||||
work_end_date=leave_date,
|
||||
reason='test'
|
||||
)).insert()
|
||||
|
||||
def mark_attendance(employee, date=today(), status='Present'):
|
||||
if not frappe.db.exists(dict(doctype='Attendance', employee=employee.name, attendance_date=date, status='Present')):
|
||||
attendance = frappe.get_doc({
|
||||
"doctype": "Attendance",
|
||||
"employee": employee.name,
|
||||
"attendance_date": date,
|
||||
"status": status
|
||||
})
|
||||
attendance.save()
|
||||
attendance.submit()
|
||||
|
||||
def create_holiday_list():
|
||||
if frappe.db.exists("Holiday List", "_Test Compensatory Leave"):
|
||||
return
|
||||
|
||||
holiday_list = frappe.get_doc({
|
||||
"doctype": "Holiday List",
|
||||
"from_date": add_months(today(), -3),
|
||||
"to_date": add_months(today(), 3),
|
||||
"holidays": [
|
||||
{
|
||||
"description": "Test Holiday",
|
||||
"holiday_date": today()
|
||||
},
|
||||
{
|
||||
"description": "Test Holiday 1",
|
||||
"holiday_date": add_days(today(), -1)
|
||||
}
|
||||
],
|
||||
"holiday_list_name": "_Test Compensatory Leave"
|
||||
})
|
||||
holiday_list.save()
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime
|
||||
from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr
|
||||
from frappe.model.naming import set_name_by_naming_series
|
||||
from frappe import throw, _, scrub
|
||||
from frappe.permissions import add_user_permission, remove_user_permission, \
|
||||
@@ -218,8 +218,8 @@ class Employee(NestedSet):
|
||||
|
||||
def reset_employee_emails_cache(self):
|
||||
prev_doc = self.get_doc_before_save() or {}
|
||||
cell_number = self.get('cell_number')
|
||||
prev_number = prev_doc.get('cell_number')
|
||||
cell_number = cstr(self.get('cell_number'))
|
||||
prev_number = cstr(prev_doc.get('cell_number'))
|
||||
if (cell_number != prev_number or
|
||||
self.get('user_id') != prev_doc.get('user_id')):
|
||||
frappe.cache().hdel('employees_with_number', cell_number)
|
||||
|
||||
@@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
|
||||
|
||||
class EmployeeAttendanceTool(Document):
|
||||
@@ -43,17 +44,26 @@ def get_employees(date, department = None, branch = None, company = None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def mark_employee_attendance(employee_list, status, date, leave_type=None, company=None):
|
||||
|
||||
employee_list = json.loads(employee_list)
|
||||
for employee in employee_list:
|
||||
attendance = frappe.new_doc("Attendance")
|
||||
attendance.employee = employee['employee']
|
||||
attendance.employee_name = employee['employee_name']
|
||||
attendance.attendance_date = date
|
||||
attendance.status = status
|
||||
|
||||
if status == "On Leave" and leave_type:
|
||||
attendance.leave_type = leave_type
|
||||
if company:
|
||||
attendance.company = company
|
||||
leave_type = leave_type
|
||||
else:
|
||||
attendance.company = frappe.db.get_value("Employee", employee['employee'], "Company")
|
||||
leave_type = None
|
||||
|
||||
if not company:
|
||||
company = frappe.db.get_value("Employee", employee['employee'], "Company")
|
||||
|
||||
attendance=frappe.get_doc(dict(
|
||||
doctype='Attendance',
|
||||
employee=employee.get('employee'),
|
||||
employee_name=employee.get('employee_name'),
|
||||
attendance_date=getdate(date),
|
||||
status=status,
|
||||
leave_type=leave_type,
|
||||
company=company
|
||||
))
|
||||
attendance.insert()
|
||||
attendance.submit()
|
||||
|
||||
@@ -208,6 +208,24 @@ frappe.ui.form.on("Expense Claim", {
|
||||
frm.refresh_fields();
|
||||
},
|
||||
|
||||
grand_total: function(frm) {
|
||||
frm.trigger("update_employee_advance_claimed_amount");
|
||||
},
|
||||
|
||||
update_employee_advance_claimed_amount: function(frm) {
|
||||
let amount_to_be_allocated = frm.doc.grand_total;
|
||||
$.each(frm.doc.advances || [], function(i, advance){
|
||||
if (amount_to_be_allocated >= advance.unclaimed_amount){
|
||||
frm.doc.advances[i].allocated_amount = frm.doc.advances[i].unclaimed_amount;
|
||||
amount_to_be_allocated -= advance.allocated_amount;
|
||||
} else{
|
||||
frm.doc.advances[i].allocated_amount = amount_to_be_allocated;
|
||||
amount_to_be_allocated = 0;
|
||||
}
|
||||
frm.refresh_field("advances");
|
||||
});
|
||||
},
|
||||
|
||||
make_payment_entry: function(frm) {
|
||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||
if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
|
||||
@@ -300,7 +318,7 @@ frappe.ui.form.on("Expense Claim", {
|
||||
row.advance_account = d.advance_account;
|
||||
row.advance_paid = d.paid_amount;
|
||||
row.unclaimed_amount = flt(d.paid_amount) - flt(d.claimed_amount);
|
||||
row.allocated_amount = flt(d.paid_amount) - flt(d.claimed_amount);
|
||||
row.allocated_amount = 0;
|
||||
});
|
||||
refresh_field("advances");
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ class ExpenseClaim(AccountsController):
|
||||
}[cstr(self.docstatus or 0)]
|
||||
|
||||
paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
|
||||
precision = self.precision("total_sanctioned_amount")
|
||||
precision = self.precision("grand_total")
|
||||
if (self.is_paid or (flt(self.total_sanctioned_amount) > 0
|
||||
and flt(self.total_sanctioned_amount, precision) == flt(paid_amount, precision))) \
|
||||
and flt(self.grand_total, precision) == flt(paid_amount, precision))) \
|
||||
and self.docstatus == 1 and self.approval_status == 'Approved':
|
||||
self.status = "Paid"
|
||||
elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
|
||||
|
||||
@@ -69,10 +69,14 @@ class LeaveAllocation(Document):
|
||||
|
||||
def validate_allocation_overlap(self):
|
||||
leave_allocation = frappe.db.sql("""
|
||||
select name from `tabLeave Allocation`
|
||||
where employee=%s and leave_type=%s and docstatus=1
|
||||
and to_date >= %s and from_date <= %s""",
|
||||
(self.employee, self.leave_type, self.from_date, self.to_date))
|
||||
SELECT
|
||||
name
|
||||
FROM `tabLeave Allocation`
|
||||
WHERE
|
||||
employee=%s AND leave_type=%s
|
||||
AND name <> %s AND docstatus=1
|
||||
AND to_date >= %s AND from_date <= %s""",
|
||||
(self.employee, self.leave_type, self.name, self.from_date, self.to_date))
|
||||
|
||||
if leave_allocation:
|
||||
frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}")
|
||||
|
||||
@@ -549,10 +549,10 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
|
||||
leave_days += leave_entry.leaves
|
||||
|
||||
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
|
||||
and not skip_expiry_leaves(leave_entry, to_date):
|
||||
and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date):
|
||||
leave_days += leave_entry.leaves
|
||||
|
||||
else:
|
||||
elif leave_entry.transaction_type == 'Leave Application':
|
||||
if leave_entry.from_date < getdate(from_date):
|
||||
leave_entry.from_date = from_date
|
||||
if leave_entry.to_date > getdate(to_date):
|
||||
@@ -579,14 +579,15 @@ def skip_expiry_leaves(leave_entry, date):
|
||||
def get_leave_entries(employee, leave_type, from_date, to_date):
|
||||
''' Returns leave entries between from_date and to_date '''
|
||||
return frappe.db.sql("""
|
||||
select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward, transaction_name
|
||||
from `tabLeave Ledger Entry`
|
||||
where employee=%(employee)s and leave_type=%(leave_type)s
|
||||
and docstatus=1
|
||||
and leaves<0
|
||||
and (from_date between %(from_date)s and %(to_date)s
|
||||
or to_date between %(from_date)s and %(to_date)s
|
||||
or (from_date < %(from_date)s and to_date > %(to_date)s))
|
||||
SELECT
|
||||
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type,
|
||||
is_carry_forward, is_expired
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
|
||||
AND docstatus=1 AND leaves<0
|
||||
AND (from_date between %(from_date)s AND %(to_date)s
|
||||
OR to_date between %(from_date)s AND %(to_date)s
|
||||
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
|
||||
""", {
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
@@ -773,4 +774,4 @@ def get_leave_approver(employee):
|
||||
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
|
||||
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
|
||||
|
||||
return leave_approver
|
||||
return leave_approver
|
||||
@@ -43,10 +43,18 @@ class TestLeavePeriod(unittest.TestCase):
|
||||
leave_period.grant_leave_allocation(employee=employee_doc_name)
|
||||
self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20)
|
||||
|
||||
def create_leave_period(from_date, to_date):
|
||||
def create_leave_period(from_date, to_date, company=None):
|
||||
leave_period = frappe.db.get_value('Leave Period',
|
||||
dict(company=company or erpnext.get_default_company(),
|
||||
from_date=from_date,
|
||||
to_date=to_date,
|
||||
is_active=1), 'name')
|
||||
if leave_period:
|
||||
return frappe.get_doc("Leave Period", leave_period)
|
||||
|
||||
leave_period = frappe.get_doc({
|
||||
"doctype": "Leave Period",
|
||||
"company": erpnext.get_default_company(),
|
||||
"company": company or erpnext.get_default_company(),
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"is_active": 1
|
||||
|
||||
@@ -321,11 +321,11 @@ def allocate_earned_leaves():
|
||||
if new_allocation == allocation.total_leaves_allocated:
|
||||
continue
|
||||
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
|
||||
create_earned_leave_ledger_entry(allocation, earned_leaves, today)
|
||||
create_additional_leave_ledger_entry(allocation, earned_leaves, today)
|
||||
|
||||
def create_earned_leave_ledger_entry(allocation, earned_leaves, date):
|
||||
''' Create leave ledger entry based on the earned leave frequency '''
|
||||
allocation.new_leaves_allocated = earned_leaves
|
||||
def create_additional_leave_ledger_entry(allocation, leaves, date):
|
||||
''' Create leave ledger entry for leave types '''
|
||||
allocation.new_leaves_allocated = leaves
|
||||
allocation.from_date = date
|
||||
allocation.unused_leaves = 0
|
||||
allocation.create_leave_ledger_entry()
|
||||
@@ -389,6 +389,7 @@ def get_sal_slip_total_benefit_given(employee, payroll_period, component=False):
|
||||
|
||||
def get_holidays_for_employee(employee, start_date, end_date):
|
||||
holiday_list = get_holiday_list_for_employee(employee)
|
||||
|
||||
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday`
|
||||
where
|
||||
parent=%(holiday_list)s
|
||||
@@ -437,4 +438,4 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co
|
||||
}, as_dict=True)
|
||||
if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
|
||||
total_claimed_amount = sum_of_claimed_amount[0].total_amount
|
||||
return total_claimed_amount
|
||||
return total_claimed_amount
|
||||
@@ -89,7 +89,8 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_12",
|
||||
@@ -129,7 +130,7 @@
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-10-16 13:38:32.302316",
|
||||
"modified": "2019-11-18 19:37:37.151686",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Blanket Order",
|
||||
|
||||
@@ -1,298 +1,78 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-05-24 07:20:04.255236",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"creation": "2018-05-24 07:20:04.255236",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"column_break_3",
|
||||
"qty",
|
||||
"rate",
|
||||
"ordered_qty",
|
||||
"section_break_7",
|
||||
"terms_and_conditions"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Item Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Item Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Quantity",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Quantity"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "ordered_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Ordered Quantity",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "ordered_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Ordered Quantity",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "terms_and_conditions",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Terms and Conditions",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "terms_and_conditions",
|
||||
"fieldtype": "Text",
|
||||
"label": "Terms and Conditions"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-06-14 07:04:14.050836",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Blanket Order Item",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-11-18 19:37:46.245878",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Blanket Order Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -5,6 +5,12 @@ frappe.provide("erpnext.bom");
|
||||
|
||||
frappe.ui.form.on("BOM", {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
'BOM': 'Duplicate BOM',
|
||||
'Work Order': 'Work Order',
|
||||
'Quality Inspection': 'Quality Inspection'
|
||||
};
|
||||
|
||||
frm.set_query("bom_no", "items", function() {
|
||||
return {
|
||||
filters: {
|
||||
@@ -85,9 +91,21 @@ frappe.ui.form.on("BOM", {
|
||||
}
|
||||
|
||||
if(frm.doc.docstatus!=0) {
|
||||
frm.add_custom_button(__("Duplicate"), function() {
|
||||
frm.add_custom_button(__("Duplicate BOM"), function() {
|
||||
frm.copy_doc();
|
||||
});
|
||||
}, __("Create"));
|
||||
|
||||
frm.add_custom_button(__("Work Order"), function() {
|
||||
frm.trigger("make_work_order");
|
||||
}, __("Create"));
|
||||
|
||||
if (frm.doc.inspection_required) {
|
||||
frm.add_custom_button(__("Quality Inspection"), function() {
|
||||
frm.trigger("make_quality_inspection");
|
||||
}, __("Create"));
|
||||
}
|
||||
|
||||
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
if(frm.doc.items && frm.doc.allow_alternative_item) {
|
||||
@@ -109,6 +127,41 @@ frappe.ui.form.on("BOM", {
|
||||
}
|
||||
},
|
||||
|
||||
make_work_order: function(frm) {
|
||||
const fields = [{
|
||||
fieldtype: 'Float',
|
||||
label: __('Qty To Manufacture'),
|
||||
fieldname: 'qty',
|
||||
reqd: 1,
|
||||
default: 1
|
||||
}];
|
||||
|
||||
frappe.prompt(fields, data => {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
|
||||
args: {
|
||||
item: frm.doc.item,
|
||||
qty: data.qty || 0.0,
|
||||
project: frm.doc.project
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
var doc = frappe.model.sync(r.message)[0];
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, __("Enter Value"), __("Create"));
|
||||
},
|
||||
|
||||
make_quality_inspection: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection",
|
||||
frm: frm
|
||||
})
|
||||
},
|
||||
|
||||
update_cost: function(frm) {
|
||||
return frappe.call({
|
||||
doc: frm.doc,
|
||||
|
||||
@@ -3,33 +3,36 @@
|
||||
"creation": "2013-01-22 15:11:38",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item",
|
||||
"item_name",
|
||||
"image",
|
||||
"uom",
|
||||
"quantity",
|
||||
"set_rate_of_sub_assembly_item_based_on_bom",
|
||||
"cb0",
|
||||
"is_active",
|
||||
"is_default",
|
||||
"with_operations",
|
||||
"inspection_required",
|
||||
"allow_alternative_item",
|
||||
"allow_same_item_multiple_times",
|
||||
"set_rate_of_sub_assembly_item_based_on_bom",
|
||||
"quality_inspection_template",
|
||||
"image",
|
||||
"item_name",
|
||||
"uom",
|
||||
"currency_detail",
|
||||
"company",
|
||||
"transfer_material_against",
|
||||
"project",
|
||||
"conversion_rate",
|
||||
"column_break_12",
|
||||
"currency",
|
||||
"rm_cost_as_per",
|
||||
"buying_price_list",
|
||||
"operations_section",
|
||||
"section_break_21",
|
||||
"with_operations",
|
||||
"column_break_23",
|
||||
"transfer_material_against",
|
||||
"routing",
|
||||
"operations_section",
|
||||
"operations",
|
||||
"materials_section",
|
||||
"inspection_required",
|
||||
"quality_inspection_template",
|
||||
"items",
|
||||
"scrap_section",
|
||||
"scrap_items",
|
||||
@@ -41,14 +44,9 @@
|
||||
"base_operating_cost",
|
||||
"base_raw_material_cost",
|
||||
"base_scrap_material_cost",
|
||||
"total_cost_of_bom",
|
||||
"total_cost",
|
||||
"column_break_26",
|
||||
"total_cost",
|
||||
"base_total_cost",
|
||||
"more_info_section",
|
||||
"project",
|
||||
"amended_from",
|
||||
"col_break23",
|
||||
"section_break_25",
|
||||
"description",
|
||||
"column_break_27",
|
||||
@@ -57,12 +55,14 @@
|
||||
"website_section",
|
||||
"show_in_website",
|
||||
"route",
|
||||
"column_break_52",
|
||||
"website_image",
|
||||
"thumbnail",
|
||||
"sb_web_spec",
|
||||
"web_long_description",
|
||||
"show_items",
|
||||
"show_operations"
|
||||
"show_operations",
|
||||
"web_long_description",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -152,7 +152,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "inspection_required",
|
||||
"fieldtype": "Check",
|
||||
"label": "Inspection Required"
|
||||
"label": "Quality Inspection Required"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -160,12 +160,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Alternative Item"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_same_item_multiple_times",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Same Item Multiple Times"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "1",
|
||||
@@ -193,6 +187,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Work Order",
|
||||
"fieldname": "transfer_material_against",
|
||||
"fieldtype": "Select",
|
||||
"label": "Transfer Material Against",
|
||||
@@ -235,10 +230,10 @@
|
||||
{
|
||||
"fieldname": "operations_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operations",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "with_operations",
|
||||
"fieldname": "routing",
|
||||
"fieldtype": "Link",
|
||||
"label": "Routing",
|
||||
@@ -335,10 +330,6 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_cost_of_bom",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_cost",
|
||||
"fieldtype": "Currency",
|
||||
@@ -359,10 +350,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "more_info_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
@@ -381,10 +368,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break23",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_25",
|
||||
"fieldtype": "Section Break"
|
||||
@@ -481,13 +464,26 @@
|
||||
"fieldname": "show_operations",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Operations"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_21",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operations"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_23",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_52",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-sitemap",
|
||||
"idx": 1,
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-07-30 17:00:09.665068",
|
||||
"modified": "2019-11-22 14:35:12.142150",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM",
|
||||
|
||||
@@ -65,6 +65,7 @@ class BOM(WebsiteGenerator):
|
||||
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().hdel('bom_children', self.name)
|
||||
self.check_recursion()
|
||||
self.update_stock_qty()
|
||||
self.update_exploded_items()
|
||||
@@ -96,6 +97,7 @@ class BOM(WebsiteGenerator):
|
||||
|
||||
def get_routing(self):
|
||||
if self.routing:
|
||||
self.set("operations", [])
|
||||
for d in frappe.get_all("BOM Operation", fields = ["*"],
|
||||
filters = {'parenttype': 'Routing', 'parent': self.routing}):
|
||||
child = self.append('operations', d)
|
||||
@@ -289,7 +291,7 @@ class BOM(WebsiteGenerator):
|
||||
if not valuation_rate:
|
||||
valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
|
||||
|
||||
return valuation_rate
|
||||
return flt(valuation_rate)
|
||||
|
||||
def manage_default_bom(self):
|
||||
""" Uncheck others if current one is selected as default or
|
||||
@@ -362,15 +364,9 @@ class BOM(WebsiteGenerator):
|
||||
def validate_materials(self):
|
||||
""" Validate raw material entries """
|
||||
|
||||
def get_duplicates(lst):
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
for item in lst:
|
||||
if item.item_code in seen or seen_add(item.item_code):
|
||||
yield item
|
||||
|
||||
if not self.get('items'):
|
||||
frappe.throw(_("Raw Materials cannot be blank."))
|
||||
|
||||
check_list = []
|
||||
for m in self.get('items'):
|
||||
if m.bom_no:
|
||||
@@ -379,16 +375,6 @@ class BOM(WebsiteGenerator):
|
||||
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
|
||||
check_list.append(m)
|
||||
|
||||
if not self.allow_same_item_multiple_times:
|
||||
duplicate_items = list(get_duplicates(check_list))
|
||||
if duplicate_items:
|
||||
li = []
|
||||
for i in duplicate_items:
|
||||
li.append("{0} on row {1}".format(i.item_code, i.idx))
|
||||
duplicate_list = '<br>' + '<br>'.join(li)
|
||||
|
||||
frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
|
||||
|
||||
def check_recursion(self, bom_list=[]):
|
||||
""" Check whether recursion occurs in any bom"""
|
||||
bom_list = self.traverse_tree()
|
||||
|
||||
@@ -17,11 +17,13 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'label': _('Manufacture'),
|
||||
'items': ['BOM', 'Work Order', 'Job Card', 'Production Plan']
|
||||
'items': ['BOM', 'Work Order', 'Job Card']
|
||||
},
|
||||
{
|
||||
'label': _('Purchase'),
|
||||
'label': _('Subcontract'),
|
||||
'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
|
||||
}
|
||||
]
|
||||
],
|
||||
'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
|
||||
"Purchase Invoice", "Job Card", "Stock Entry"]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ from frappe import _
|
||||
from six import string_types
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
|
||||
from frappe.model.document import Document
|
||||
import click
|
||||
|
||||
class BOMUpdateTool(Document):
|
||||
def replace_bom(self):
|
||||
@@ -17,7 +18,8 @@ class BOMUpdateTool(Document):
|
||||
frappe.cache().delete_key('bom_children')
|
||||
bom_list = self.get_parent_boms(self.new_bom)
|
||||
updated_bom = []
|
||||
|
||||
with click.progressbar(bom_list) as bom_list:
|
||||
pass
|
||||
for bom in bom_list:
|
||||
try:
|
||||
bom_obj = frappe.get_cached_doc('BOM', bom)
|
||||
|
||||
@@ -529,7 +529,6 @@ def get_material_request_items(row, sales_order,
|
||||
required_qty = ceil(required_qty)
|
||||
|
||||
if required_qty > 0:
|
||||
print(row)
|
||||
return {
|
||||
'item_code': row.item_code,
|
||||
'item_name': row.item_name,
|
||||
|
||||
@@ -581,6 +581,8 @@ erpnext.work_order = {
|
||||
description: __('Max: {0}', [max]),
|
||||
default: max
|
||||
}, data => {
|
||||
max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
|
||||
|
||||
if (data.qty > max) {
|
||||
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
|
||||
reject();
|
||||
|
||||
@@ -37,7 +37,7 @@ class WorkOrder(Document):
|
||||
ms = frappe.get_doc("Manufacturing Settings")
|
||||
self.set_onload("material_consumption", ms.material_consumption)
|
||||
self.set_onload("backflush_raw_materials_based_on", ms.backflush_raw_materials_based_on)
|
||||
|
||||
self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order)
|
||||
|
||||
def validate(self):
|
||||
self.validate_production_item()
|
||||
@@ -609,6 +609,23 @@ def get_item_details(item, project = None):
|
||||
|
||||
return res
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_work_order(item, qty=0, project=None):
|
||||
if not frappe.has_permission("Work Order", "write"):
|
||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||
|
||||
item_details = get_item_details(item, project)
|
||||
|
||||
wo_doc = frappe.new_doc("Work Order")
|
||||
wo_doc.production_item = item
|
||||
wo_doc.update(item_details)
|
||||
|
||||
if flt(qty) > 0:
|
||||
wo_doc.qty = flt(qty)
|
||||
wo_doc.get_items_and_operations_from_bom()
|
||||
|
||||
return wo_doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_if_scrap_warehouse_mandatory(bom_no):
|
||||
res = {"set_scrap_wh_mandatory": False }
|
||||
|
||||
@@ -645,4 +645,6 @@ erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
|
||||
erpnext.patches.v12_0.set_payment_entry_status
|
||||
erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields
|
||||
erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template
|
||||
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
|
||||
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
|
||||
erpnext.patches.v12_0.update_price_or_product_discount
|
||||
erpnext.patches.v12_0.add_export_type_field_in_party_master
|
||||
|
||||
@@ -15,13 +15,6 @@ def execute():
|
||||
|
||||
rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
|
||||
|
||||
if frappe.db.has_column('BOM', 'allow_same_item_multiple_times'):
|
||||
frappe.db.sql(""" UPDATE tabBOM
|
||||
SET
|
||||
allow_same_item_multiple_times = 0
|
||||
WHERE
|
||||
trim(coalesce(allow_same_item_multiple_times, '')) = '' """)
|
||||
|
||||
for doctype in ['BOM', 'Work Order']:
|
||||
frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from erpnext.regional.india.setup import make_custom_fields
|
||||
|
||||
def execute():
|
||||
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
make_custom_fields()
|
||||
|
||||
frappe.reload_doctype('Tax Category')
|
||||
frappe.reload_doctype('Sales Taxes and Charges Template')
|
||||
frappe.reload_doctype('Purchase Taxes and Charges Template')
|
||||
|
||||
# Create tax category with inter state field checked
|
||||
tax_category = frappe.db.get_value('Tax Category', {'name': 'OUT OF STATE'}, 'name')
|
||||
|
||||
if not tax_category:
|
||||
inter_state_category = frappe.get_doc({
|
||||
'doctype': 'Tax Category',
|
||||
'title': 'OUT OF STATE',
|
||||
'is_inter_state': 1
|
||||
}).insert()
|
||||
|
||||
tax_category = inter_state_category.name
|
||||
|
||||
for doctype in ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template'):
|
||||
template = frappe.db.get_value(doctype, {'is_inter_state': 1, 'disabled': 0}, ['name'])
|
||||
if template:
|
||||
frappe.db.set_value(doctype, template, 'tax_category', tax_category)
|
||||
|
||||
frappe.db.sql("""
|
||||
DELETE FROM `tabCustom Field`
|
||||
WHERE fieldname = 'is_inter_state'
|
||||
AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template')
|
||||
""")
|
||||
|
||||
|
||||
@@ -62,12 +62,12 @@ def execute():
|
||||
]
|
||||
|
||||
for dt in doctypes:
|
||||
for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item`
|
||||
for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item`
|
||||
where ifnull(item_tax_rate, '') not in ('', '{{}}')
|
||||
and item_tax_template is NULL""".format(dt), as_dict=1):
|
||||
item_tax_map = json.loads(d.item_tax_rate)
|
||||
item_tax_template_name = get_item_tax_template(item_tax_templates,
|
||||
item_tax_map, d.item_code, d.parent)
|
||||
item_tax_map, d.item_code, d.parenttype, d.parent)
|
||||
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = False
|
||||
@@ -77,7 +77,7 @@ def execute():
|
||||
settings.determine_address_tax_category_from = "Billing Address"
|
||||
settings.save()
|
||||
|
||||
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None):
|
||||
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None):
|
||||
# search for previously created item tax template by comparing tax maps
|
||||
for template, item_tax_template_map in iteritems(item_tax_templates):
|
||||
if item_tax_map == item_tax_template_map:
|
||||
@@ -88,23 +88,44 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=No
|
||||
item_tax_template.title = make_autoname("Item Tax Template-.####")
|
||||
|
||||
for tax_type, tax_rate in iteritems(item_tax_map):
|
||||
if not frappe.db.exists("Account", tax_type):
|
||||
account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1)
|
||||
if account_details:
|
||||
if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
|
||||
frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable')
|
||||
else:
|
||||
parts = tax_type.strip().split(" - ")
|
||||
account_name = " - ".join(parts[:-1])
|
||||
company = frappe.db.get_value("Company", filters={"abbr": parts[-1]})
|
||||
company = get_company(parts[-1], parenttype, parent)
|
||||
parent_account = frappe.db.get_value("Account",
|
||||
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
filters = {
|
||||
"account_name": account_name,
|
||||
"company": company,
|
||||
"account_type": "Tax",
|
||||
"parent_account": parent_account
|
||||
}).insert()
|
||||
"company": company,
|
||||
"account_type": "Tax",
|
||||
"parent_account": parent_account
|
||||
}
|
||||
tax_type = frappe.db.get_value("Account", filters)
|
||||
if not tax_type:
|
||||
account = frappe.new_doc("Account")
|
||||
account.update(filters)
|
||||
account.insert()
|
||||
tax_type = account.name
|
||||
|
||||
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
|
||||
item_tax_templates.setdefault(item_tax_template.title, {})
|
||||
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
|
||||
item_tax_template.save()
|
||||
return item_tax_template.name
|
||||
|
||||
def get_company(company_abbr, parenttype=None, parent=None):
|
||||
if parenttype and parent:
|
||||
company = frappe.get_cached_value(parenttype, parent, 'company')
|
||||
else:
|
||||
company = frappe.db.get_value("Company", filters={"abbr": company_abbr})
|
||||
|
||||
if not company:
|
||||
companies = frappe.get_all('Company')
|
||||
if len(companies) == 1:
|
||||
company = companies[0].name
|
||||
|
||||
return company
|
||||
|
||||
@@ -7,6 +7,8 @@ def execute():
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.reload_doc('accounts', 'doctype', 'Tax Category')
|
||||
|
||||
make_custom_fields()
|
||||
|
||||
for doctype in ['Sales Invoice', 'Purchase Invoice']:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("accounts", "doctype", "pricing_rule")
|
||||
|
||||
frappe.db.sql(""" UPDATE `tabPricing Rule` SET price_or_product_discount = 'Price'
|
||||
WHERE ifnull(price_or_product_discount,'') = '' """)
|
||||
@@ -302,6 +302,8 @@ def get_items(filters=None, search=None):
|
||||
if isinstance(filters, dict):
|
||||
filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()]
|
||||
|
||||
enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and')
|
||||
|
||||
show_in_website_condition = ''
|
||||
if products_settings.hide_variants:
|
||||
show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and')
|
||||
@@ -313,19 +315,32 @@ def get_items(filters=None, search=None):
|
||||
|
||||
search_condition = ''
|
||||
if search:
|
||||
# Default fields to search from
|
||||
default_fields = {'name', 'item_name', 'description', 'item_group'}
|
||||
|
||||
# Get meta search fields
|
||||
meta = frappe.get_meta("Item")
|
||||
meta_fields = set(meta.get_search_fields())
|
||||
|
||||
# Join the meta fields and default fields set
|
||||
search_fields = default_fields.union(meta_fields)
|
||||
try:
|
||||
if frappe.db.count('Item', cache=True) > 50000:
|
||||
search_fields.remove('description')
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Build or filters for query
|
||||
search = '%{}%'.format(search)
|
||||
or_filters = [
|
||||
['name', 'like', search],
|
||||
['item_name', 'like', search],
|
||||
['description', 'like', search],
|
||||
['item_group', 'like', search]
|
||||
]
|
||||
or_filters = [[field, 'like', search] for field in search_fields]
|
||||
|
||||
search_condition = get_conditions(or_filters, 'or')
|
||||
|
||||
filter_condition = get_conditions(filters, 'and')
|
||||
|
||||
where_conditions = ' and '.join(
|
||||
[condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition]
|
||||
[condition for condition in [enabled_items_filter, show_in_website_condition, \
|
||||
search_condition, filter_condition] if condition]
|
||||
)
|
||||
|
||||
left_joins = []
|
||||
|
||||
@@ -4,20 +4,16 @@ frappe.ui.form.on("Project", {
|
||||
setup(frm) {
|
||||
frm.make_methods = {
|
||||
'Timesheet': () => {
|
||||
let doctype = 'Timesheet';
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
let new_doc = frappe.model.get_new_doc(doctype);
|
||||
|
||||
// add a new row and set the project
|
||||
let time_log = frappe.model.get_new_doc('Timesheet Detail');
|
||||
time_log.project = frm.doc.name;
|
||||
time_log.parent = new_doc.name;
|
||||
time_log.parentfield = 'time_logs';
|
||||
time_log.parenttype = 'Timesheet';
|
||||
new_doc.time_logs = [time_log];
|
||||
|
||||
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
|
||||
});
|
||||
open_form(frm, "Timesheet", "Timesheet Detail", "time_logs");
|
||||
},
|
||||
'Purchase Order': () => {
|
||||
open_form(frm, "Purchase Order", "Purchase Order Item", "items");
|
||||
},
|
||||
'Purchase Receipt': () => {
|
||||
open_form(frm, "Purchase Receipt", "Purchase Receipt Item", "items");
|
||||
},
|
||||
'Purchase Invoice': () => {
|
||||
open_form(frm, "Purchase Invoice", "Purchase Invoice Item", "items");
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -80,7 +76,7 @@ frappe.ui.form.on("Project", {
|
||||
frm.events.set_status(frm, 'Cancelled');
|
||||
}, __('Set Status'));
|
||||
}
|
||||
|
||||
|
||||
if (frappe.model.can_read("Task")) {
|
||||
frm.add_custom_button(__("Gantt Chart"), function () {
|
||||
frappe.route_options = {
|
||||
@@ -123,3 +119,20 @@ frappe.ui.form.on("Project", {
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
function open_form(frm, doctype, child_doctype, parentfield) {
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
let new_doc = frappe.model.get_new_doc(doctype);
|
||||
|
||||
// add a new row and set the project
|
||||
let new_child_doc = frappe.model.get_new_doc(child_doctype);
|
||||
new_child_doc.project = frm.doc.name;
|
||||
new_child_doc.parent = new_doc.name;
|
||||
new_child_doc.parentfield = parentfield;
|
||||
new_child_doc.parenttype = doctype;
|
||||
new_doc[parentfield] = [new_child_doc];
|
||||
|
||||
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -47,11 +47,11 @@ class Task(NestedSet):
|
||||
if not self.project or frappe.flags.in_test:
|
||||
return
|
||||
|
||||
expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date"))
|
||||
expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
|
||||
|
||||
if expected_end_date:
|
||||
validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected")
|
||||
validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual")
|
||||
validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected")
|
||||
validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual")
|
||||
|
||||
def validate_status(self):
|
||||
if self.status!=self.get_db_value("status") and self.status == "Completed":
|
||||
@@ -278,4 +278,4 @@ def validate_project_dates(project_end_date, task, task_start, task_end, actual_
|
||||
frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date))
|
||||
|
||||
if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
|
||||
frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))
|
||||
frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user