Merge branch 'develop' of https://github.com/frappe/erpnext into develop
This commit is contained in:
commit
529a5f84bb
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '12.1.8'
|
||||
__version__ = '12.2.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -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)
|
||||
|
@ -237,7 +237,7 @@ class PurchaseInvoice(BuyingController):
|
||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||
else:
|
||||
item.expense_account = stock_not_billed_account
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, asset_category):
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||
item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
||||
company = self.company)
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
@ -357,7 +357,7 @@ class PurchaseInvoice(BuyingController):
|
||||
return
|
||||
if not gl_entries:
|
||||
gl_entries = self.get_gl_entries()
|
||||
|
||||
|
||||
if gl_entries:
|
||||
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
|
||||
|
||||
@ -408,7 +408,7 @@ class PurchaseInvoice(BuyingController):
|
||||
for item in self.get("items"):
|
||||
if item.item_code and item.is_fixed_asset:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
if is_cwip_accounting_enabled(self.company, asset_category):
|
||||
if is_cwip_accounting_enabled(asset_category):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@ -452,6 +452,10 @@ class PurchaseInvoice(BuyingController):
|
||||
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
|
||||
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
|
||||
|
||||
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
|
||||
if d.category in ('Valuation', 'Total and Valuation')
|
||||
and flt(d.base_tax_amount_after_discount_amount)]
|
||||
|
||||
for item in self.get("items"):
|
||||
if flt(item.base_net_amount):
|
||||
account_currency = get_account_currency(item.expense_account)
|
||||
@ -500,11 +504,10 @@ class PurchaseInvoice(BuyingController):
|
||||
"credit": flt(item.rm_supp_cost)
|
||||
}, warehouse_account[self.supplier_warehouse]["account_currency"], item=item))
|
||||
|
||||
elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(self.company,
|
||||
asset_category)):
|
||||
elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)):
|
||||
expense_account = (item.expense_account
|
||||
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
|
||||
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
amount = flt(item.base_net_amount, item.precision("base_net_amount"))
|
||||
else:
|
||||
@ -517,7 +520,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project
|
||||
}, account_currency, item=item))
|
||||
|
||||
|
||||
# If asset is bought through this document and not linked to PR
|
||||
if self.update_stock and item.landed_cost_voucher_amount:
|
||||
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
@ -539,9 +542,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project
|
||||
}, item=item))
|
||||
|
||||
|
||||
# update gross amount of asset bought through this document
|
||||
assets = frappe.db.get_all('Asset',
|
||||
assets = frappe.db.get_all('Asset',
|
||||
filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
|
||||
)
|
||||
for asset in assets:
|
||||
@ -551,10 +554,10 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.auto_accounting_for_stock and self.is_opening == "No" and \
|
||||
item.item_code in stock_items and item.item_tax_amount:
|
||||
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
|
||||
if item.purchase_receipt:
|
||||
if item.purchase_receipt and valuation_tax_accounts:
|
||||
negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry`
|
||||
where voucher_type='Purchase Receipt' and voucher_no=%s and account=%s""",
|
||||
(item.purchase_receipt, self.expenses_included_in_valuation))
|
||||
where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""",
|
||||
(item.purchase_receipt, valuation_tax_accounts))
|
||||
|
||||
if not negative_expense_booked_in_pr:
|
||||
gl_entries.append(
|
||||
@ -633,7 +636,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if asset_eiiav_currency == self.company_currency else
|
||||
item.item_tax_amount / self.conversion_rate)
|
||||
}, item=item))
|
||||
|
||||
|
||||
# When update stock is checked
|
||||
# Assets are bought through this document then it will be linked to this document
|
||||
if self.update_stock:
|
||||
@ -655,9 +658,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project
|
||||
}, item=item))
|
||||
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all('Asset',
|
||||
assets = frappe.db.get_all('Asset',
|
||||
filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
|
||||
)
|
||||
for asset in assets:
|
||||
|
@ -204,7 +204,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
|
||||
self.check_gle_for_pi_against_pr(pi.name)
|
||||
self.check_gle_for_pi(pi.name)
|
||||
|
||||
def check_gle_for_pi(self, pi):
|
||||
gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit
|
||||
@ -225,26 +225,6 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def check_gle_for_pi_against_pr(self, pi):
|
||||
gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit
|
||||
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||
group by account""", pi, as_dict=1)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = dict((d[0], d) for d in [
|
||||
["Creditors - TCP1", 0, 720],
|
||||
["Stock Received But Not Billed - TCP1", 750.0, 0],
|
||||
["_Test Account Shipping Charges - TCP1", 100.0, 100.0],
|
||||
["_Test Account VAT - TCP1", 120.0, 0],
|
||||
["_Test Account Customs Duty - TCP1", 0, 150]
|
||||
])
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account][0], gle.account)
|
||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_purchase_invoice_change_naming_series(self):
|
||||
pi = frappe.copy_doc(test_records[1])
|
||||
pi.insert()
|
||||
|
@ -117,6 +117,7 @@
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
@ -192,7 +193,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.stock_uom",
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "UOM",
|
||||
@ -766,7 +766,7 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-11-03 13:43:23.782877",
|
||||
"modified": "2019-11-21 16:27:52.043744",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -135,7 +135,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
|
||||
validate_loyalty_points(self, self.loyalty_points)
|
||||
|
||||
|
||||
def validate_fixed_asset(self):
|
||||
for d in self.get("items"):
|
||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
||||
|
@ -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
|
||||
}
|
@ -163,23 +163,35 @@ def validate_account_for_perpetual_inventory(gl_map):
|
||||
.format(account), StockAccountInvalidTransaction)
|
||||
|
||||
elif account_bal != stock_bal:
|
||||
error_reason = _("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and it's linked warehouses.").format(
|
||||
account_bal, stock_bal, frappe.bold(account))
|
||||
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(stock_bal - account_bal))
|
||||
button_text = _("Make Adjustment Entry")
|
||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
|
||||
|
||||
frappe.throw("""{0}<br></br>{1}<br></br>
|
||||
<div style="text-align:right;">
|
||||
<button class="btn btn-primary" onclick="frappe.new_doc('Journal Entry')">{2}</button>
|
||||
</div>""".format(error_reason, error_resolution, button_text),
|
||||
StockValueAndAccountBalanceOutOfSync, title=_('Account Balance Out Of Sync'))
|
||||
diff = flt(stock_bal - account_bal, precision)
|
||||
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
|
||||
stock_bal, account_bal, frappe.bold(account))
|
||||
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
|
||||
stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
|
||||
|
||||
db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
|
||||
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
|
||||
|
||||
journal_entry_args = {
|
||||
'accounts':[
|
||||
{'account': account, db_or_cr_warehouse_account : abs(diff)},
|
||||
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
|
||||
}
|
||||
|
||||
frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
||||
raise_exception=StockValueAndAccountBalanceOutOfSync,
|
||||
title=_('Values Out Of Sync'),
|
||||
primary_action={
|
||||
'label': _('Make Journal Entry'),
|
||||
'client_action': 'erpnext.route_to_adjustment_jv',
|
||||
'args': journal_entry_args
|
||||
})
|
||||
|
||||
def validate_cwip_accounts(gl_map):
|
||||
cwip_enabled = cint(frappe.get_cached_value("Company",
|
||||
gl_map[0].company, "enable_cwip_accounting"))
|
||||
|
||||
if not cwip_enabled:
|
||||
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
||||
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
||||
|
||||
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
|
||||
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||
|
@ -630,7 +630,7 @@ def get_held_invoices(party_type, party):
|
||||
'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
|
||||
as_dict=1
|
||||
)
|
||||
held_invoices = [d['name'] for d in held_invoices]
|
||||
held_invoices = set([d['name'] for d in held_invoices])
|
||||
|
||||
return held_invoices
|
||||
|
||||
@ -639,14 +639,19 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
|
||||
outstanding_invoices = []
|
||||
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
|
||||
|
||||
if erpnext.get_party_account_type(party_type) == 'Receivable':
|
||||
if account:
|
||||
root_type = frappe.get_cached_value("Account", account, "root_type")
|
||||
party_account_type = "Receivable" if root_type == "Asset" else "Payable"
|
||||
else:
|
||||
party_account_type = erpnext.get_party_account_type(party_type)
|
||||
|
||||
if party_account_type == 'Receivable':
|
||||
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
|
||||
payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
|
||||
else:
|
||||
dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
|
||||
payment_dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
|
||||
|
||||
invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice'
|
||||
held_invoices = get_held_invoices(party_type, party)
|
||||
|
||||
invoice_list = frappe.db.sql("""
|
||||
@ -665,7 +670,6 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
|
||||
group by voucher_type, voucher_no
|
||||
order by posting_date, name""".format(
|
||||
dr_or_cr=dr_or_cr,
|
||||
invoice = invoice,
|
||||
condition=condition or ""
|
||||
), {
|
||||
"party_type": party_type,
|
||||
|
@ -42,6 +42,24 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
setup: function(frm) {
|
||||
frm.make_methods = {
|
||||
'Asset Movement': () => {
|
||||
frappe.call({
|
||||
method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
|
||||
freeze: true,
|
||||
args:{
|
||||
"assets": [{ name: cur_frm.doc.name }]
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
var doc = frappe.model.sync(r.message)[0];
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
frm.set_query("purchase_receipt", (doc) => {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_purchase_receipts",
|
||||
@ -487,92 +505,19 @@ erpnext.asset.restore_asset = function(frm) {
|
||||
})
|
||||
};
|
||||
|
||||
erpnext.asset.transfer_asset = function(frm) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Transfer Asset"),
|
||||
fields: [
|
||||
{
|
||||
"label": __("Target Location"),
|
||||
"fieldname": "target_location",
|
||||
"fieldtype": "Link",
|
||||
"options": "Location",
|
||||
"get_query": function () {
|
||||
return {
|
||||
filters: [
|
||||
["Location", "is_group", "=", 0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"label": __("Select Serial No"),
|
||||
"fieldname": "serial_nos",
|
||||
"fieldtype": "Link",
|
||||
"options": "Serial No",
|
||||
"get_query": function () {
|
||||
return {
|
||||
filters: {
|
||||
'asset': frm.doc.name
|
||||
}
|
||||
}
|
||||
},
|
||||
"onchange": function() {
|
||||
let val = this.get_value();
|
||||
if (val) {
|
||||
let serial_nos = dialog.get_value("serial_no") || val;
|
||||
if (serial_nos) {
|
||||
serial_nos = serial_nos.split('\n');
|
||||
serial_nos.push(val);
|
||||
|
||||
const unique_sn = serial_nos.filter(function(elem, index, self) {
|
||||
return index === self.indexOf(elem);
|
||||
});
|
||||
|
||||
dialog.set_value("serial_no", unique_sn.join('\n'));
|
||||
dialog.set_value("serial_nos", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": __("Serial No"),
|
||||
"fieldname": "serial_no",
|
||||
"read_only": 1,
|
||||
"fieldtype": "Small Text"
|
||||
},
|
||||
{
|
||||
"label": __("Date"),
|
||||
"fieldname": "transfer_date",
|
||||
"fieldtype": "Datetime",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.now_datetime()
|
||||
erpnext.asset.transfer_asset = function() {
|
||||
frappe.call({
|
||||
method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
|
||||
freeze: true,
|
||||
args:{
|
||||
"assets": [{ name: cur_frm.doc.name }],
|
||||
"purpose": "Transfer"
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
var doc = frappe.model.sync(r.message)[0];
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
dialog.set_primary_action(__("Transfer"), function() {
|
||||
var args = dialog.get_values();
|
||||
if(!args) return;
|
||||
dialog.hide();
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.assets.doctype.asset.asset.transfer_asset",
|
||||
args: {
|
||||
args: {
|
||||
"asset": frm.doc.name,
|
||||
"transaction_date": args.transfer_date,
|
||||
"source_location": frm.doc.location,
|
||||
"target_location": args.target_location,
|
||||
"serial_no": args.serial_no,
|
||||
"company": frm.doc.company
|
||||
}
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
cur_frm.reload_doc();
|
||||
}
|
||||
})
|
||||
});
|
||||
dialog.show();
|
||||
};
|
||||
|
@ -31,10 +31,9 @@ class Asset(AccountsController):
|
||||
self.validate_in_use_date()
|
||||
self.set_status()
|
||||
self.make_asset_movement()
|
||||
if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.company,
|
||||
self.asset_category):
|
||||
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()
|
||||
|
||||
@ -44,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'
|
||||
@ -52,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))
|
||||
|
||||
@ -99,7 +98,7 @@ class Asset(AccountsController):
|
||||
if not flt(self.gross_purchase_amount):
|
||||
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
|
||||
|
||||
if is_cwip_accounting_enabled(self.company, self.asset_category):
|
||||
if is_cwip_accounting_enabled(self.asset_category):
|
||||
if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
|
||||
frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}").
|
||||
format(self.item_code))
|
||||
@ -126,15 +125,17 @@ class Asset(AccountsController):
|
||||
frappe.throw(_("Available-for-use Date should be after purchase date"))
|
||||
|
||||
def cancel_auto_gen_movement(self):
|
||||
reference_docname = self.purchase_invoice or self.purchase_receipt
|
||||
movement = frappe.db.get_all('Asset Movement', filters={ 'reference_name': reference_docname, 'docstatus': 1 })
|
||||
if len(movement) > 1:
|
||||
movements = frappe.db.sql(
|
||||
"""SELECT asm.name, asm.docstatus
|
||||
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
|
||||
WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""", self.name, as_dict=1)
|
||||
if len(movements) > 1:
|
||||
frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \
|
||||
cancelled manually to cancel this asset.'))
|
||||
movement = frappe.get_doc('Asset Movement', movement[0].get('name'))
|
||||
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
|
||||
@ -203,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)
|
||||
@ -261,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,
|
||||
@ -295,7 +296,9 @@ class Asset(AccountsController):
|
||||
.format(row.idx))
|
||||
|
||||
if not row.depreciation_start_date:
|
||||
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
||||
if not self.available_for_use_date:
|
||||
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
||||
row.depreciation_start_date = self.available_for_use_date
|
||||
|
||||
if not self.is_existing_asset:
|
||||
self.opening_accumulated_depreciation = 0
|
||||
@ -514,7 +517,7 @@ def update_maintenance_status():
|
||||
asset.set_status('Out of Order')
|
||||
|
||||
def make_post_gl_entry():
|
||||
if not is_cwip_accounting_enabled(self.company, self.asset_category):
|
||||
if not is_cwip_accounting_enabled(self.asset_category):
|
||||
return
|
||||
|
||||
assets = frappe.db.sql_list(""" select name from `tabAsset`
|
||||
@ -646,34 +649,21 @@ def make_journal_entry(asset_name):
|
||||
return je
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_asset_movement(assets):
|
||||
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.'))
|
||||
|
||||
asset_movement = frappe.new_doc("Asset Movement")
|
||||
asset_movement.quantity = len(assets)
|
||||
prev_reference_docname = ''
|
||||
|
||||
for asset in assets:
|
||||
asset = frappe.get_doc('Asset', asset.get('name'))
|
||||
# get PR/PI linked with asset
|
||||
reference_docname = asset.get('purchase_receipt') if asset.get('purchase_receipt') \
|
||||
else asset.get('purchase_invoice')
|
||||
# checks if all the assets are linked with a single PR/PI
|
||||
if prev_reference_docname == '':
|
||||
prev_reference_docname = reference_docname
|
||||
elif prev_reference_docname != reference_docname:
|
||||
frappe.throw(_('Assets selected should belong to same reference document.'))
|
||||
|
||||
asset_movement.company = asset.get('company')
|
||||
asset_movement.reference_doctype = 'Purchase Receipt' if asset.get('purchase_receipt') else 'Purchase Invoice'
|
||||
asset_movement.reference_name = prev_reference_docname
|
||||
asset_movement.append("assets", {
|
||||
'asset': asset.get('name'),
|
||||
'source_location': asset.get('location'),
|
||||
@ -683,12 +673,7 @@ def make_asset_movement(assets):
|
||||
if asset_movement.get('assets'):
|
||||
return asset_movement.as_dict()
|
||||
|
||||
def is_cwip_accounting_enabled(company, asset_category=None):
|
||||
enable_cwip_in_company = cint(frappe.db.get_value("Company", company, "enable_cwip_accounting"))
|
||||
|
||||
if enable_cwip_in_company or not asset_category:
|
||||
return enable_cwip_in_company
|
||||
|
||||
def is_cwip_accounting_enabled(asset_category):
|
||||
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
|
||||
|
||||
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
|
||||
|
@ -37,6 +37,7 @@ frappe.listview_settings['Asset'] = {
|
||||
const assets = me.get_checked_items();
|
||||
frappe.call({
|
||||
method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
|
||||
freeze: true,
|
||||
args:{
|
||||
"assets": assets
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,6 @@ from frappe.model.document import Document
|
||||
class AssetCategory(Document):
|
||||
def validate(self):
|
||||
self.validate_finance_books()
|
||||
self.validate_enable_cwip_accounting()
|
||||
|
||||
def validate_finance_books(self):
|
||||
for d in self.finance_books:
|
||||
@ -19,15 +18,6 @@ class AssetCategory(Document):
|
||||
if cint(d.get(frappe.scrub(field)))<1:
|
||||
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
|
||||
|
||||
def validate_enable_cwip_accounting(self):
|
||||
if self.enable_cwip_accounting :
|
||||
for d in self.accounts:
|
||||
cwip = frappe.db.get_value("Company",d.company_name,"enable_cwip_accounting")
|
||||
if cwip:
|
||||
frappe.throw(_
|
||||
("CWIP is enabled globally in Company {1}. To enable it in Asset Category, first disable it in {1} ").format(
|
||||
frappe.bold(d.idx), frappe.bold(d.company_name)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):
|
||||
if item and frappe.db.get_value("Item", item, "is_fixed_asset"):
|
||||
|
@ -31,6 +31,13 @@ frappe.ui.form.on('Asset Movement', {
|
||||
name: ["in", ["Purchase Receipt", "Purchase Invoice"]]
|
||||
}
|
||||
};
|
||||
}),
|
||||
frm.set_query("asset", "assets", () => {
|
||||
return {
|
||||
filters: {
|
||||
status: ["not in", ["Draft"]]
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@ -76,50 +83,6 @@ frappe.ui.form.on('Asset Movement', {
|
||||
});
|
||||
});
|
||||
frm.refresh_field('assets');
|
||||
},
|
||||
|
||||
reference_name: function(frm) {
|
||||
if (frm.doc.reference_name && frm.doc.reference_doctype) {
|
||||
const reference_doctype = frm.doc.reference_doctype === 'Purchase Invoice' ? 'purchase_invoice' : 'purchase_receipt';
|
||||
// On selection of reference name,
|
||||
// sets query to display assets linked to that reference doc
|
||||
frm.set_query('asset', 'assets', function() {
|
||||
return {
|
||||
filters: {
|
||||
[reference_doctype] : frm.doc.reference_name
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// fetches linked asset & adds to the assets table
|
||||
frappe.db.get_list('Asset', {
|
||||
fields: ['name', 'location', 'custodian'],
|
||||
filters: {
|
||||
[reference_doctype] : frm.doc.reference_name
|
||||
}
|
||||
}).then((docs) => {
|
||||
if (docs.length == 0) {
|
||||
frappe.msgprint(frappe._(`Please select ${frm.doc.reference_doctype} which has assets.`));
|
||||
frm.doc.reference_name = '';
|
||||
frm.refresh_field('reference_name');
|
||||
return;
|
||||
}
|
||||
frm.doc.assets = [];
|
||||
docs.forEach(doc => {
|
||||
frm.add_child('assets', {
|
||||
asset: doc.name,
|
||||
source_location: doc.location,
|
||||
from_employee: doc.custodian
|
||||
});
|
||||
frm.refresh_field('assets');
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.log(err); // eslint-disable-line
|
||||
});
|
||||
} else {
|
||||
// if reference is deleted then remove query
|
||||
frm.set_query('asset', 'assets', () => ({ filters: {} }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -132,7 +95,7 @@ frappe.ui.form.on('Asset Movement Item', {
|
||||
if(asset_doc.location) frappe.model.set_value(cdt, cdn, 'source_location', asset_doc.location);
|
||||
if(asset_doc.custodian) frappe.model.set_value(cdt, cdn, 'from_employee', asset_doc.custodian);
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
console.log(err); // eslint-disable-line
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,12 @@
|
||||
"purpose",
|
||||
"column_break_4",
|
||||
"transaction_date",
|
||||
"section_break_10",
|
||||
"assets",
|
||||
"reference",
|
||||
"reference_doctype",
|
||||
"column_break_9",
|
||||
"reference_name",
|
||||
"section_break_10",
|
||||
"assets",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@ -47,6 +47,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "reference",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reference"
|
||||
@ -54,18 +55,16 @@
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference DocType",
|
||||
"label": "Reference Document Type",
|
||||
"no_copy": 1,
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Reference Name",
|
||||
"label": "Reference Document Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_doctype",
|
||||
"reqd": 1
|
||||
"options": "reference_doctype"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@ -93,7 +92,7 @@
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-11-13 15:37:48.870147",
|
||||
"modified": "2019-11-23 13:28:47.256935",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Movement",
|
||||
|
@ -22,7 +22,7 @@ class AssetMovement(Document):
|
||||
if company != self.company:
|
||||
frappe.throw(_("Asset {0} does not belong to company {1}").format(d.asset, self.company))
|
||||
|
||||
if not(d.source_location or d.target_location or d.from_employee or d.to_employee):
|
||||
if not (d.source_location or d.target_location or d.from_employee or d.to_employee):
|
||||
frappe.throw(_("Either location or employee must be required"))
|
||||
|
||||
def validate_location(self):
|
||||
|
@ -60,7 +60,8 @@
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Date"
|
||||
"label": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_asset_value",
|
||||
@ -110,7 +111,7 @@
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-05-26 09:46:23.613412",
|
||||
"modified": "2019-11-22 14:09:25.800375",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Value Adjustment",
|
||||
|
@ -5,12 +5,13 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, getdate, cint, date_diff
|
||||
from frappe.utils import flt, getdate, cint, date_diff, formatdate
|
||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AssetValueAdjustment(Document):
|
||||
def validate(self):
|
||||
self.validate_date()
|
||||
self.set_difference_amount()
|
||||
self.set_current_asset_value()
|
||||
|
||||
@ -23,6 +24,12 @@ class AssetValueAdjustment(Document):
|
||||
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
|
||||
|
||||
self.reschedule_depreciations(self.current_asset_value)
|
||||
|
||||
def validate_date(self):
|
||||
asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
|
||||
if getdate(self.date) < getdate(asset_purchase_date):
|
||||
frappe.throw(_("Asset Value Adjustment cannot be posted before Asset's purchase date <b>{0}</b>.")
|
||||
.format(formatdate(asset_purchase_date)), title="Incorrect Date")
|
||||
|
||||
def set_difference_amount(self):
|
||||
self.difference_amount = flt(self.current_asset_value - self.new_asset_value)
|
||||
|
@ -313,7 +313,7 @@ def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=
|
||||
|
||||
last_purchase_details = get_last_purchase_details(item_code, name)
|
||||
if last_purchase_details:
|
||||
last_purchase_rate = (last_purchase_details['base_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate
|
||||
last_purchase_rate = (last_purchase_details['base_net_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate
|
||||
return last_purchase_rate
|
||||
else:
|
||||
item_last_purchase_rate = frappe.get_cached_value("Item", item_code, "last_purchase_rate")
|
||||
|
@ -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
|
||||
}
|
@ -24,12 +24,12 @@ def update_last_purchase_rate(doc, is_submit):
|
||||
last_purchase_rate = None
|
||||
if last_purchase_details and \
|
||||
(last_purchase_details.purchase_date > this_purchase_date):
|
||||
last_purchase_rate = last_purchase_details['base_rate']
|
||||
last_purchase_rate = last_purchase_details['base_net_rate']
|
||||
elif is_submit == 1:
|
||||
# even if this transaction is the latest one, it should be submitted
|
||||
# for it to be considered for latest purchase rate
|
||||
if flt(d.conversion_factor):
|
||||
last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor)
|
||||
last_purchase_rate = flt(d.base_net_rate) / flt(d.conversion_factor)
|
||||
# Check if item code is present
|
||||
# Conversion factor should not be mandatory for non itemized items
|
||||
elif d.item_code:
|
||||
|
14
erpnext/change_log/v12/v12_2_0.md
Normal file
14
erpnext/change_log/v12/v12_2_0.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Version 12.2.0 Release Notes
|
||||
|
||||
### Accounting
|
||||
|
||||
1. Fixed Asset
|
||||
- "Enable CWIP" options moved to Asset Category from Asset Settings
|
||||
- Removed Asset link from Purchase Receipt Item table
|
||||
- Enhanced Asset master
|
||||
- Asset Movement now handles movement of multiple assets
|
||||
- Introduced monthly depreciation
|
||||
2. GL Entries for Landed Cost Voucher now posted directly against individual Charges account
|
||||
3. Optimization of BOM Update Tool
|
||||
4. Syncing of Stock and Account balance is enforced, in case of perpetual inventory
|
||||
5. Rendered email template in Email Campaign
|
@ -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",
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -577,6 +577,7 @@ class BuyingController(StockController):
|
||||
|
||||
def auto_make_assets(self, asset_items):
|
||||
items_data = get_asset_item_details(asset_items)
|
||||
messages = []
|
||||
|
||||
for d in self.items:
|
||||
if d.is_fixed_asset:
|
||||
@ -589,12 +590,16 @@ class BuyingController(StockController):
|
||||
for qty in range(cint(d.qty)):
|
||||
self.make_asset(d)
|
||||
is_plural = 's' if cint(d.qty) != 1 else ''
|
||||
frappe.msgprint(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural))
|
||||
messages.append(_('{0} Asset{2} Created for <b>{1}</b>').format(cint(d.qty), d.item_code, is_plural))
|
||||
else:
|
||||
frappe.throw(_("Asset Naming Series is mandatory for the auto creation for item {0}").format(d.item_code))
|
||||
frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}")
|
||||
.format(d.item_code, d.idx))
|
||||
else:
|
||||
frappe.msgprint(_("Assets not created. You will have to create asset manually."))
|
||||
|
||||
messages.append(_("Assets not created for <b>{0}</b>. You will have to create asset manually.")
|
||||
.format(d.item_code))
|
||||
|
||||
for message in messages:
|
||||
frappe.msgprint(message, title="Success")
|
||||
|
||||
def make_asset(self, row):
|
||||
if not row.asset_location:
|
||||
@ -636,7 +641,10 @@ class BuyingController(StockController):
|
||||
asset = frappe.get_doc('Asset', asset.name)
|
||||
if delete_asset and is_auto_create_enabled:
|
||||
# need to delete movements to delete assets otherwise throws link exists error
|
||||
movements = frappe.db.get_all('Asset Movement', filters={ 'reference_name': self.name })
|
||||
movements = frappe.db.sql(
|
||||
"""SELECT asm.name
|
||||
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
|
||||
WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1)
|
||||
for movement in movements:
|
||||
frappe.delete_doc('Asset Movement', movement.name, force=1)
|
||||
frappe.delete_doc("Asset", asset.name, force=1)
|
||||
@ -647,8 +655,12 @@ class BuyingController(StockController):
|
||||
asset.purchase_date = self.posting_date
|
||||
asset.supplier = self.supplier
|
||||
elif self.docstatus == 2:
|
||||
asset.set(field, None)
|
||||
asset.supplier = None
|
||||
if asset.docstatus == 0:
|
||||
asset.set(field, None)
|
||||
asset.supplier = None
|
||||
if asset.docstatus == 1 and delete_asset:
|
||||
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\
|
||||
Please cancel the it to continue.').format(asset.name))
|
||||
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.flags.ignore_mandatory = True
|
||||
|
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
|
File diff suppressed because it is too large
Load Diff
@ -301,7 +301,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.set_expired_status"
|
||||
],
|
||||
"daily_long": [
|
||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -140,32 +140,6 @@ class ExpenseClaim(AccountsController):
|
||||
"against": ",".join([d.default_account for d in self.expenses]),
|
||||
"party_type": "Employee",
|
||||
"party": self.employee,
|
||||
"against_voucher_type": self.doctype,
|
||||
"against_voucher": self.name
|
||||
})
|
||||
)
|
||||
|
||||
gl_entry.append(
|
||||
self.get_gl_dict({
|
||||
"account": data.advance_account,
|
||||
"debit": data.allocated_amount,
|
||||
"debit_in_account_currency": data.allocated_amount,
|
||||
"against": self.payable_account,
|
||||
"party_type": "Employee",
|
||||
"party": self.employee,
|
||||
"against_voucher_type": self.doctype,
|
||||
"against_voucher": self.name
|
||||
})
|
||||
)
|
||||
|
||||
gl_entry.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.payable_account,
|
||||
"credit": data.allocated_amount,
|
||||
"credit_in_account_currency": data.allocated_amount,
|
||||
"against": data.advance_account,
|
||||
"party_type": "Employee",
|
||||
"party": self.employee,
|
||||
"against_voucher_type": "Employee Advance",
|
||||
"against_voucher": data.employee_advance
|
||||
})
|
||||
|
@ -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",
|
||||
|
@ -96,6 +96,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 +290,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 +363,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 +374,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
@ -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,
|
||||
|
@ -609,6 +609,22 @@ 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 qty > 0:
|
||||
wo_doc.qty = 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,5 @@ 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
|
@ -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))
|
||||
|
||||
|
@ -7,15 +7,11 @@ def execute():
|
||||
'''Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field
|
||||
in Company, delete Asset Settings '''
|
||||
|
||||
if frappe.db.exists("DocType","Asset Settings"):
|
||||
frappe.reload_doctype("Company")
|
||||
cwip_value = frappe.db.get_single_value("Asset Settings","disable_cwip_accounting")
|
||||
if frappe.db.exists("DocType", "Asset Settings"):
|
||||
frappe.reload_doctype("Asset Category")
|
||||
cwip_value = frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")
|
||||
|
||||
frappe.db.sql("""UPDATE `tabAsset Category` SET enable_cwip_accounting = %s""", cint(cwip_value))
|
||||
|
||||
companies = [x['name'] for x in frappe.get_all("Company", "name")]
|
||||
for company in companies:
|
||||
enable_cwip_accounting = cint(not cint(cwip_value))
|
||||
frappe.db.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting)
|
||||
|
||||
frappe.db.sql(
|
||||
""" DELETE FROM `tabSingles` where doctype = 'Asset Settings' """)
|
||||
frappe.delete_doc_if_exists("DocType","Asset Settings")
|
||||
frappe.db.sql("""DELETE FROM `tabSingles` where doctype = 'Asset Settings'""")
|
||||
frappe.delete_doc_if_exists("DocType", "Asset Settings")
|
@ -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,'') = '' """)
|
@ -7,7 +7,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, throw
|
||||
from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate
|
||||
from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
from frappe.desk.form.assign_to import close_all_assignments, clear
|
||||
from frappe.utils import date_diff
|
||||
@ -212,8 +212,11 @@ def set_multiple_status(names, status):
|
||||
task.save()
|
||||
|
||||
def set_tasks_as_overdue():
|
||||
tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Completed']]})
|
||||
tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]})
|
||||
for task in tasks:
|
||||
if frappe.db.get_value("Task", task.name, "status") in 'Pending Review':
|
||||
if getdate(frappe.db.get_value("Task", task.name, "review_date")) < getdate(today()):
|
||||
continue
|
||||
frappe.get_doc("Task", task.name).update_status()
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -64,7 +64,7 @@ frappe.ui.form.on(cur_frm.doctype, {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Sales Invoice Payment', {
|
||||
@ -355,4 +355,4 @@ cur_frm.pformat.taxes= function(doc){
|
||||
out += '</table></td></tr></table></div>';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
@ -74,6 +74,22 @@ $.extend(erpnext, {
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
route_to_adjustment_jv: (args) => {
|
||||
frappe.model.with_doctype('Journal Entry', () => {
|
||||
// route to adjustment Journal Entry to handle Account Balance and Stock Value mismatch
|
||||
let journal_entry = frappe.model.get_new_doc('Journal Entry');
|
||||
|
||||
args.accounts.forEach((je_account) => {
|
||||
let child_row = frappe.model.add_child(journal_entry, "accounts");
|
||||
child_row.account = je_account.account;
|
||||
child_row.debit_in_account_currency = je_account.debit_in_account_currency;
|
||||
child_row.credit_in_account_currency = je_account.credit_in_account_currency;
|
||||
child_row.party_type = "" ;
|
||||
});
|
||||
frappe.set_route('Form','Journal Entry', journal_entry.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -205,7 +205,9 @@
|
||||
{%- endif %}
|
||||
<ImponibileImporto>{{ format_float(data.taxable_amount, item_meta.get_field("tax_amount").precision) }}</ImponibileImporto>
|
||||
<Imposta>{{ format_float(data.tax_amount, item_meta.get_field("tax_amount").precision) }}</Imposta>
|
||||
<EsigibilitaIVA>{{ doc.vat_collectability.split("-")[0] }}</EsigibilitaIVA>
|
||||
{%- if data.vat_collectability %}
|
||||
<EsigibilitaIVA>{{ doc.vat_collectability.split("-")[0] }}</EsigibilitaIVA>
|
||||
{%- endif %}
|
||||
{%- if data.tax_exemption_law %}
|
||||
<RiferimentoNormativo>{{ data.tax_exemption_law }}</RiferimentoNormativo>
|
||||
{%- endif %}
|
||||
|
@ -49,9 +49,9 @@ frappe.ui.form.on("Customer", {
|
||||
})
|
||||
frm.set_query('customer_primary_address', function(doc) {
|
||||
return {
|
||||
query: "erpnext.selling.doctype.customer.customer.get_customer_primary_address",
|
||||
filters: {
|
||||
'customer': doc.name
|
||||
'link_doctype': 'Customer',
|
||||
'link_name': doc.name
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -397,15 +397,3 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil
|
||||
'customer': customer,
|
||||
'txt': '%%%s%%' % txt
|
||||
})
|
||||
|
||||
def get_customer_primary_address(doctype, txt, searchfield, start, page_len, filters):
|
||||
customer = frappe.db.escape(filters.get('customer'))
|
||||
return frappe.db.sql("""
|
||||
select `tabAddress`.name from `tabAddress`, `tabDynamic Link`
|
||||
where `tabAddress`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
|
||||
and `tabDynamic Link`.link_doctype = 'Customer'
|
||||
and `tabAddress`.name like %(txt)s
|
||||
""", {
|
||||
'customer': customer,
|
||||
'txt': '%%%s%%' % txt
|
||||
})
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -185,6 +185,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||
|
||||
return doclist
|
||||
|
||||
def set_expired_status():
|
||||
frappe.db.sql("""UPDATE `tabQuotation` SET `status` = 'Expired'
|
||||
WHERE `status` != "Expired" AND `valid_till` < %s""", (nowdate()))
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_invoice(source_name, target_doc=None):
|
||||
return _make_sales_invoice(source_name, target_doc)
|
||||
|
@ -14,15 +14,13 @@ frappe.listview_settings['Quotation'] = {
|
||||
|
||||
get_indicator: function(doc) {
|
||||
if(doc.status==="Open") {
|
||||
if (doc.valid_till && doc.valid_till < frappe.datetime.nowdate()) {
|
||||
return [__("Expired"), "darkgrey", "valid_till,<," + frappe.datetime.nowdate()];
|
||||
} else {
|
||||
return [__("Open"), "orange", "status,=,Open"];
|
||||
}
|
||||
return [__("Open"), "orange", "status,=,Open"];
|
||||
} else if(doc.status==="Ordered") {
|
||||
return [__("Ordered"), "green", "status,=,Ordered"];
|
||||
} else if(doc.status==="Lost") {
|
||||
return [__("Lost"), "darkgrey", "status,=,Lost"];
|
||||
} else if(doc.status==="Expired") {
|
||||
return [__("Expired"), "darkgrey", "status,=,Expired"];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -201,6 +201,28 @@ class TestQuotation(unittest.TestCase):
|
||||
sec_qo = make_quotation(item_list=qo_item2, do_not_submit=True)
|
||||
sec_qo.submit()
|
||||
|
||||
def test_quotation_expiry(self):
|
||||
from erpnext.selling.doctype.quotation.quotation import set_expired_status
|
||||
|
||||
quotation_item = [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"warehouse":"",
|
||||
"qty": 1,
|
||||
"rate": 500
|
||||
}
|
||||
]
|
||||
|
||||
yesterday = add_days(nowdate(), -1)
|
||||
expired_quotation = make_quotation(item_list=quotation_item, transaction_date=yesterday, do_not_submit=True)
|
||||
expired_quotation.valid_till = yesterday
|
||||
expired_quotation.save()
|
||||
expired_quotation.submit()
|
||||
set_expired_status()
|
||||
expired_quotation.reload()
|
||||
self.assertEqual(expired_quotation.status, "Expired")
|
||||
|
||||
|
||||
test_records = frappe.get_test_records('Quotation')
|
||||
|
||||
def get_quotation_dict(party_name=None, item_code=None):
|
||||
@ -258,3 +280,5 @@ def make_quotation(**args):
|
||||
qo.submit()
|
||||
|
||||
return qo
|
||||
|
||||
|
||||
|
@ -29,7 +29,7 @@ frappe.ui.form.on("Company", {
|
||||
|
||||
company_name: function(frm) {
|
||||
if(frm.doc.__islocal) {
|
||||
# add missing " " arg in split method
|
||||
// add missing " " arg in split method
|
||||
let parts = frm.doc.company_name.split(" ");
|
||||
let abbr = $.map(parts, function (p) {
|
||||
return p? p.substr(0, 1) : null;
|
||||
|
@ -72,7 +72,6 @@
|
||||
"stock_received_but_not_billed",
|
||||
"expenses_included_in_valuation",
|
||||
"fixed_asset_depreciation_settings",
|
||||
"enable_cwip_accounting",
|
||||
"accumulated_depreciation_account",
|
||||
"depreciation_expense_account",
|
||||
"series_for_depreciation_entry",
|
||||
@ -721,18 +720,12 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Buying Terms",
|
||||
"options": "Terms and Conditions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_cwip_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Capital Work in Progress Accounting"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-building",
|
||||
"idx": 1,
|
||||
"image_field": "company_logo",
|
||||
"modified": "2019-10-09 14:42:04.440974",
|
||||
"modified": "2019-11-22 13:04:47.470768",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
@ -49,7 +49,7 @@ frappe.ui.form.on("Item", {
|
||||
if (!frm.doc.is_fixed_asset) {
|
||||
erpnext.item.make_dashboard(frm);
|
||||
}
|
||||
|
||||
|
||||
if (frm.doc.is_fixed_asset) {
|
||||
frm.trigger('is_fixed_asset');
|
||||
frm.trigger('auto_create_assets');
|
||||
@ -140,6 +140,7 @@ frappe.ui.form.on("Item", {
|
||||
// set serial no to false & toggles its visibility
|
||||
frm.set_value('has_serial_no', 0);
|
||||
frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
|
||||
frm.toggle_reqd(['asset_category'], frm.doc.is_fixed_asset);
|
||||
frm.toggle_display(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
|
||||
|
||||
frm.call({
|
||||
@ -150,6 +151,8 @@ frappe.ui.form.on("Item", {
|
||||
frm.trigger("set_asset_naming_series");
|
||||
}
|
||||
});
|
||||
|
||||
frm.trigger('auto_create_assets');
|
||||
},
|
||||
|
||||
set_asset_naming_series: function(frm) {
|
||||
@ -159,8 +162,8 @@ frappe.ui.form.on("Item", {
|
||||
},
|
||||
|
||||
auto_create_assets: function(frm) {
|
||||
frm.toggle_reqd(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets);
|
||||
frm.toggle_display(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets);
|
||||
frm.toggle_reqd(['asset_naming_series'], frm.doc.auto_create_assets);
|
||||
frm.toggle_display(['asset_naming_series'], frm.doc.auto_create_assets);
|
||||
},
|
||||
|
||||
page_name: frappe.utils.warn_page_name_change,
|
||||
|
@ -645,7 +645,7 @@ class Item(WebsiteGenerator):
|
||||
json.dumps(item_wise_tax_detail), update_modified=False)
|
||||
|
||||
def set_last_purchase_rate(self, new_name):
|
||||
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
|
||||
last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0)
|
||||
frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate)
|
||||
|
||||
def recalculate_bin_qty(self, new_name):
|
||||
@ -942,7 +942,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
last_purchase_order = frappe.db.sql("""\
|
||||
select po.name, po.transaction_date, po.conversion_rate,
|
||||
po_item.conversion_factor, po_item.base_price_list_rate,
|
||||
po_item.discount_percentage, po_item.base_rate
|
||||
po_item.discount_percentage, po_item.base_rate, po_item.base_net_rate
|
||||
from `tabPurchase Order` po, `tabPurchase Order Item` po_item
|
||||
where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and
|
||||
po.name = po_item.parent
|
||||
@ -953,7 +953,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
last_purchase_receipt = frappe.db.sql("""\
|
||||
select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
|
||||
pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage,
|
||||
pr_item.base_rate
|
||||
pr_item.base_rate, pr_item.base_net_rate
|
||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
|
||||
where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and
|
||||
pr.name = pr_item.parent
|
||||
@ -984,6 +984,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
out = frappe._dict({
|
||||
"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
|
||||
"base_rate": flt(last_purchase.base_rate) / conversion_factor,
|
||||
"base_net_rate": flt(last_purchase.net_rate) / conversion_factor,
|
||||
"discount_percentage": flt(last_purchase.discount_percentage),
|
||||
"purchase_date": purchase_date
|
||||
})
|
||||
@ -992,7 +993,8 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
out.update({
|
||||
"price_list_rate": out.base_price_list_rate / conversion_rate,
|
||||
"rate": out.base_rate / conversion_rate,
|
||||
"base_rate": out.base_rate
|
||||
"base_rate": out.base_rate,
|
||||
"base_net_rate": out.base_net_rate
|
||||
})
|
||||
|
||||
return out
|
||||
|
@ -8,11 +8,11 @@
|
||||
"naming_series",
|
||||
"company",
|
||||
"purchase_receipts",
|
||||
"sec_break1",
|
||||
"taxes",
|
||||
"purchase_receipt_items",
|
||||
"get_items_from_purchase_receipts",
|
||||
"items",
|
||||
"sec_break1",
|
||||
"taxes",
|
||||
"section_break_9",
|
||||
"total_taxes_and_charges",
|
||||
"col_break1",
|
||||
@ -123,7 +123,7 @@
|
||||
],
|
||||
"icon": "icon-usd",
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-10-09 13:39:36.082777",
|
||||
"modified": "2019-11-21 15:34:10.846093",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Landed Cost Voucher",
|
||||
|
@ -138,8 +138,8 @@ class LandedCostVoucher(Document):
|
||||
if item.is_fixed_asset:
|
||||
receipt_document_type = 'purchase_invoice' if item.receipt_document_type == 'Purchase Invoice' \
|
||||
else 'purchase_receipt'
|
||||
docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document },
|
||||
fields=['name', 'docstatus'])
|
||||
docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document,
|
||||
'item_code': item.item_code }, fields=['name', 'docstatus'])
|
||||
if not docs or len(docs) != item.qty:
|
||||
frappe.throw(_('There are not enough asset created or linked to {0}. \
|
||||
Please create or link {1} Assets with respective document.').format(item.receipt_document, item.qty))
|
||||
@ -148,8 +148,7 @@ class LandedCostVoucher(Document):
|
||||
if d.docstatus == 1:
|
||||
frappe.throw(_('{2} <b>{0}</b> has submitted Assets.\
|
||||
Remove Item <b>{1}</b> from table to continue.').format(
|
||||
item.receipt_document, item.item_code, item.receipt_document_type)
|
||||
)
|
||||
item.receipt_document, item.item_code, item.receipt_document_type))
|
||||
|
||||
def update_rate_in_serial_no_for_non_asset_items(self, receipt_document):
|
||||
for item in receipt_document.get("items"):
|
||||
|
@ -82,11 +82,21 @@ class PurchaseReceipt(BuyingController):
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_cwip_accounts()
|
||||
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
throw(_("Posting Date cannot be future date"))
|
||||
|
||||
def validate_cwip_accounts(self):
|
||||
for item in self.get('items'):
|
||||
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
|
||||
# check cwip accounts before making auto assets
|
||||
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
|
||||
break
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(PurchaseReceipt, self).validate_with_previous_doc({
|
||||
@ -343,7 +353,7 @@ class PurchaseReceipt(BuyingController):
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
if is_cwip_accounting_enabled(self.company, item.asset_category):
|
||||
if is_cwip_accounting_enabled(item.asset_category):
|
||||
self.add_asset_gl_entries(item, gl_entries)
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
self.add_lcv_gl_entries(item, gl_entries)
|
||||
@ -386,7 +396,7 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
def add_lcv_gl_entries(self, item, gl_entries):
|
||||
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
if not is_cwip_accounting_enabled(self.company, item.asset_category):
|
||||
if not is_cwip_accounting_enabled(item.asset_category):
|
||||
asset_account = get_asset_category_account(asset_category=item.asset_category, \
|
||||
fieldname='fixed_asset_account', company=self.company)
|
||||
else:
|
||||
|
@ -6,6 +6,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \
|
||||
import get_template_details
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
class QualityInspection(Document):
|
||||
def validate(self):
|
||||
@ -84,3 +85,37 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
parent=filters.get('parent'), cond = cond, mcond = mcond, start = start,
|
||||
page_len = page_len, qi_condition = qi_condition),
|
||||
{'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
|
||||
|
||||
def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.get_all('Quality Inspection',
|
||||
limit_start=start,
|
||||
limit_page_length=page_len,
|
||||
filters = {
|
||||
'docstatus': 1,
|
||||
'name': ('like', '%%%s%%' % txt),
|
||||
'item_code': filters.get("item_code"),
|
||||
'reference_name': ('in', [filters.get("reference_name", ''), ''])
|
||||
}, as_list=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_quality_inspection(source_name, target_doc=None):
|
||||
def postprocess(source, doc):
|
||||
doc.inspected_by = frappe.session.user
|
||||
doc.get_quality_inspection_template()
|
||||
|
||||
doc = get_mapped_doc("BOM", source_name, {
|
||||
'BOM': {
|
||||
"doctype": "Quality Inspection",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
},
|
||||
"field_map": {
|
||||
"name": "bom_no",
|
||||
"item": "item_code",
|
||||
"stock_uom": "uom",
|
||||
"stock_qty": "qty"
|
||||
},
|
||||
}
|
||||
}, target_doc, postprocess)
|
||||
|
||||
return doc
|
@ -102,11 +102,12 @@ frappe.ui.form.on('Stock Entry', {
|
||||
|
||||
frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
|
||||
return {
|
||||
query:"erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query",
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
item_code: d.item_code,
|
||||
reference_name: doc.name
|
||||
'item_code': d.item_code,
|
||||
'reference_name': doc.name
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -91,6 +91,7 @@ class StockEntry(StockController):
|
||||
self.update_cost_in_project()
|
||||
self.validate_reserved_serial_no_consumption()
|
||||
self.update_transferred_qty()
|
||||
self.update_quality_inspection()
|
||||
if self.work_order and self.purpose == "Manufacture":
|
||||
self.update_so_in_serial_number()
|
||||
|
||||
@ -108,6 +109,7 @@ class StockEntry(StockController):
|
||||
self.make_gl_entries_on_cancel()
|
||||
self.update_cost_in_project()
|
||||
self.update_transferred_qty()
|
||||
self.update_quality_inspection()
|
||||
|
||||
def set_job_card_data(self):
|
||||
if self.job_card and not self.work_order:
|
||||
@ -1285,6 +1287,20 @@ class StockEntry(StockController):
|
||||
|
||||
self._update_percent_field_in_targets(args, update_modified=True)
|
||||
|
||||
def update_quality_inspection(self):
|
||||
if self.inspection_required:
|
||||
reference_type = reference_name = ''
|
||||
if self.docstatus == 1:
|
||||
reference_name = self.name
|
||||
reference_type = 'Stock Entry'
|
||||
|
||||
for d in self.items:
|
||||
if d.quality_inspection:
|
||||
frappe.db.set_value("Quality Inspection", d.quality_inspection, {
|
||||
'reference_type': reference_type,
|
||||
'reference_name': reference_name
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def move_sample_to_retention_warehouse(company, items):
|
||||
if isinstance(items, string_types):
|
||||
|
@ -292,7 +292,7 @@ def validate_filters(filters):
|
||||
if not (filters.get("item_code") or filters.get("warehouse")):
|
||||
sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0])
|
||||
if sle_count > 500000:
|
||||
frappe.throw(_("Please set filter based on Item or Warehouse"))
|
||||
frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries."))
|
||||
|
||||
def get_variants_attributes():
|
||||
'''Return all item variant attributes.'''
|
||||
|
10
erpnext/templates/emails/confirm_appointment.html
Normal file
10
erpnext/templates/emails/confirm_appointment.html
Normal file
@ -0,0 +1,10 @@
|
||||
<p>{{_("Dear")}} {{ full_name }}{% if last_name %} {{ last_name}}{% endif %},</p>
|
||||
<p>{{_("A new appointment has been created for you with {0}").format(site_url)}}.</p>
|
||||
<p>{{_("Click on the link below to verify your email and confirm the appointment")}}.</p>
|
||||
|
||||
<p style="margin: 30px 0px;">
|
||||
<a href="{{ link }}" rel="nofollow" style="padding: 8px 20px; background-color: #7575ff; color: #fff; border-radius: 4px; text-decoration: none; line-height: 1; border-bottom: 3px solid rgba(0, 0, 0, 0.2); font-size: 14px; font-weight: 200;">{{ _("Verify Email") }}</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<p style="font-size: 85%;">{{_("You can also copy-paste this link in your browser")}} <a href="{{ link }}">{{ link }}</a></p>
|
53
erpnext/www/book-appointment/index.css
Normal file
53
erpnext/www/book-appointment/index.css
Normal file
@ -0,0 +1,53 @@
|
||||
.time-slot {
|
||||
margin-bottom: 2em;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
border-radius: 0.4em;
|
||||
cursor: pointer;
|
||||
border: 0.5px solid #cccccc;
|
||||
min-height: 75px;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#submit-button-area {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"submit"
|
||||
"back";
|
||||
}
|
||||
}
|
||||
#customer-form{
|
||||
border-color: black;
|
||||
}
|
||||
#customer-form ::placeholder{
|
||||
color: #ddd;
|
||||
}
|
||||
#timeslot-container{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.time-slot:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.time-slot.unavailable {
|
||||
background: #CBD5E0;
|
||||
cursor: not-allowed;
|
||||
color: #718096
|
||||
}
|
||||
|
||||
.time-slot.unavailable .text-muted {
|
||||
color: #718096
|
||||
}
|
||||
|
||||
.time-slot.selected {
|
||||
color: white;
|
||||
background: #5e64ff;
|
||||
}
|
||||
|
||||
.time-slot.selected .text-muted {
|
||||
color: #EDF2F7 !important;
|
||||
}
|
66
erpnext/www/book-appointment/index.html
Normal file
66
erpnext/www/book-appointment/index.html
Normal file
@ -0,0 +1,66 @@
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %}{{ _("Book Appointment") }}{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script src="assets/js/moment-bundle.min.js"></script>
|
||||
<script src="book-appointment/index.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="container">
|
||||
<!-- title: Book an appointment -->
|
||||
<div id="select-date-time">
|
||||
<div class="text-center mt-5">
|
||||
<h3>Book an appointment</h3>
|
||||
<p class="lead text-muted" id="lead-text">Select the date and your timezone</p>
|
||||
</div>
|
||||
<div class="row justify-content-center mt-3">
|
||||
<div class="col-md-6 align-self-center ">
|
||||
<div class="row">
|
||||
<input type="date" oninput="on_date_or_timezone_select()" name="appointment-date"
|
||||
id="appointment-date" class="form-control mt-3 col-md m-3">
|
||||
<select name="appointment-timezone" oninput="on_date_or_timezone_select()" id="appointment-timezone"
|
||||
class="form-control m-3 col-md">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3" id="timeslot-container">
|
||||
|
||||
</div>
|
||||
<div class="row justify-content-center mt-3">
|
||||
<div class="col-md-4 mb-3">
|
||||
<button class="btn btn-primary form-control" id="next-button">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--Enter Details-->
|
||||
<div id="enter-details" class="mb-5">
|
||||
<div class="text-center mt-5">
|
||||
<h3>Add details</h3>
|
||||
<p class="lead text-muted">Selected date is <span class="date-span"></span> at <span class="time-span">
|
||||
</span></p>
|
||||
</div>
|
||||
<div class="row justify-content-center mt-3">
|
||||
<div class="col-md-4 align-items-center">
|
||||
<form id="customer-form" action='#'>
|
||||
<input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name (required)" required>
|
||||
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number" placeholder="+910000000000">
|
||||
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype">
|
||||
<input class="form-control mt-3"type="email" name="customer_email" id="customer_email" placeholder="Email Address (required)" required>
|
||||
|
||||
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
|
||||
placeholder="Notes"></textarea>
|
||||
</form>
|
||||
<div class="row mt-3 " id="submit-button-area">
|
||||
<div class="col-md mt-3" style="grid-area: back;"><button class="btn btn-dark form-control" onclick="initialise_select_date()">Go back</button></div>
|
||||
<div class="col-md mt-3" style="grid-area: submit;"><button class="btn btn-primary form-control " onclick="submit()" id="submit-button">Submit</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
236
erpnext/www/book-appointment/index.js
Normal file
236
erpnext/www/book-appointment/index.js
Normal file
@ -0,0 +1,236 @@
|
||||
frappe.ready(async () => {
|
||||
initialise_select_date();
|
||||
})
|
||||
|
||||
window.holiday_list = [];
|
||||
|
||||
async function initialise_select_date() {
|
||||
navigate_to_page(1);
|
||||
await get_global_variables();
|
||||
setup_date_picker();
|
||||
setup_timezone_selector();
|
||||
hide_next_button();
|
||||
}
|
||||
|
||||
async function get_global_variables() {
|
||||
// Using await through this file instead of then.
|
||||
window.appointment_settings = (await frappe.call({
|
||||
method: 'erpnext.www.book-appointment.index.get_appointment_settings'
|
||||
})).message;
|
||||
window.timezones = (await frappe.call({
|
||||
method:'erpnext.www.book-appointment.index.get_timezones'
|
||||
})).message;
|
||||
window.holiday_list = window.appointment_settings.holiday_list;
|
||||
}
|
||||
|
||||
function setup_timezone_selector() {
|
||||
/**
|
||||
* window.timezones is a dictionary with the following structure
|
||||
* { IANA name: Pretty name}
|
||||
* For example : { Asia/Kolkata : "India Time - Asia/Kolkata"}
|
||||
*/
|
||||
let timezones_element = document.getElementById('appointment-timezone');
|
||||
let offset = new Date().getTimezoneOffset();
|
||||
Object.keys(window.timezones).forEach((timezone) => {
|
||||
let opt = document.createElement('option');
|
||||
opt.value = timezone;
|
||||
if (timezone == moment.tz.guess()) {
|
||||
opt.selected = true;
|
||||
}
|
||||
opt.innerHTML = window.timezones[timezone]
|
||||
timezones_element.appendChild(opt)
|
||||
});
|
||||
}
|
||||
|
||||
function setup_date_picker() {
|
||||
let date_picker = document.getElementById('appointment-date');
|
||||
let today = new Date();
|
||||
date_picker.min = today.toISOString().substr(0, 10);
|
||||
today.setDate(today.getDate() + window.appointment_settings.advance_booking_days);
|
||||
date_picker.max = today.toISOString().substr(0, 10);
|
||||
}
|
||||
|
||||
function hide_next_button() {
|
||||
let next_button = document.getElementById('next-button');
|
||||
next_button.disabled = true;
|
||||
next_button.onclick = () => frappe.msgprint("Please select a date and time");
|
||||
}
|
||||
|
||||
function show_next_button() {
|
||||
let next_button = document.getElementById('next-button');
|
||||
next_button.disabled = false;
|
||||
next_button.onclick = setup_details_page;
|
||||
}
|
||||
|
||||
function on_date_or_timezone_select() {
|
||||
let date_picker = document.getElementById('appointment-date');
|
||||
let timezone = document.getElementById('appointment-timezone');
|
||||
if (date_picker.value === '') {
|
||||
clear_time_slots();
|
||||
hide_next_button();
|
||||
frappe.throw('Please select a date');
|
||||
}
|
||||
window.selected_date = date_picker.value;
|
||||
window.selected_timezone = timezone.value;
|
||||
update_time_slots(date_picker.value, timezone.value);
|
||||
let lead_text = document.getElementById('lead-text');
|
||||
lead_text.innerHTML = "Select Time"
|
||||
}
|
||||
|
||||
async function get_time_slots(date, timezone) {
|
||||
let slots = (await frappe.call({
|
||||
method: 'erpnext.www.book-appointment.index.get_appointment_slots',
|
||||
args: {
|
||||
date: date,
|
||||
timezone: timezone
|
||||
}
|
||||
})).message;
|
||||
return slots;
|
||||
}
|
||||
|
||||
async function update_time_slots(selected_date, selected_timezone) {
|
||||
let timeslot_container = document.getElementById('timeslot-container');
|
||||
window.slots = await get_time_slots(selected_date, selected_timezone);
|
||||
clear_time_slots();
|
||||
if (window.slots.length <= 0) {
|
||||
let message_div = document.createElement('p');
|
||||
message_div.innerHTML = "There are no slots available on this date";
|
||||
timeslot_container.appendChild(message_div);
|
||||
return
|
||||
}
|
||||
window.slots.forEach((slot, index) => {
|
||||
// Get and append timeslot div
|
||||
let timeslot_div = get_timeslot_div_layout(slot)
|
||||
timeslot_container.appendChild(timeslot_div);
|
||||
});
|
||||
set_default_timeslot();
|
||||
}
|
||||
|
||||
function get_timeslot_div_layout(timeslot) {
|
||||
let start_time = new Date(timeslot.time)
|
||||
let timeslot_div = document.createElement('div');
|
||||
timeslot_div.classList.add('time-slot');
|
||||
if (!timeslot.availability) {
|
||||
timeslot_div.classList.add('unavailable')
|
||||
}
|
||||
timeslot_div.innerHTML = get_slot_layout(start_time);
|
||||
timeslot_div.id = timeslot.time.substr(11, 20);
|
||||
timeslot_div.addEventListener('click', select_time);
|
||||
return timeslot_div
|
||||
}
|
||||
|
||||
function clear_time_slots() {
|
||||
// Clear any existing divs in timeslot container
|
||||
let timeslot_container = document.getElementById('timeslot-container');
|
||||
while (timeslot_container.firstChild) {
|
||||
timeslot_container.removeChild(timeslot_container.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function get_slot_layout(time) {
|
||||
let timezone = document.getElementById("appointment-timezone").value;
|
||||
time = new Date(time);
|
||||
let start_time_string = moment(time).tz(timezone).format("LT");
|
||||
let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes');
|
||||
let end_time_string = end_time.format("LT");
|
||||
return `<span style="font-size: 1.2em;">${start_time_string}</span><br><span class="text-muted small">to ${end_time_string}</span>`;
|
||||
}
|
||||
|
||||
function select_time() {
|
||||
if (this.classList.contains('unavailable')) {
|
||||
return;
|
||||
}
|
||||
let selected_element = document.getElementsByClassName('selected');
|
||||
if (!(selected_element.length > 0)) {
|
||||
this.classList.add('selected');
|
||||
show_next_button();
|
||||
return;
|
||||
}
|
||||
selected_element = selected_element[0]
|
||||
window.selected_time = this.id;
|
||||
selected_element.classList.remove('selected');
|
||||
this.classList.add('selected');
|
||||
show_next_button();
|
||||
}
|
||||
|
||||
function set_default_timeslot() {
|
||||
let timeslots = document.getElementsByClassName('time-slot')
|
||||
// Can't use a forEach here since, we need to break the loop after a timeslot is selected
|
||||
for (let i = 0; i < timeslots.length; i++) {
|
||||
const timeslot = timeslots[i];
|
||||
if (!timeslot.classList.contains('unavailable')) {
|
||||
timeslot.classList.add('selected');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function navigate_to_page(page_number) {
|
||||
let page1 = document.getElementById('select-date-time');
|
||||
let page2 = document.getElementById('enter-details');
|
||||
switch (page_number) {
|
||||
case 1:
|
||||
page1.style.display = 'block';
|
||||
page2.style.display = 'none';
|
||||
break;
|
||||
case 2:
|
||||
page1.style.display = 'none';
|
||||
page2.style.display = 'block';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setup_details_page() {
|
||||
navigate_to_page(2)
|
||||
let date_container = document.getElementsByClassName('date-span')[0];
|
||||
let time_container = document.getElementsByClassName('time-span')[0];
|
||||
date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY");
|
||||
time_container.innerHTML = moment(window.selected_time, "HH:mm:ss").format("LT");
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
let button = document.getElementById('submit-button');
|
||||
button.disabled = true;
|
||||
let form = document.querySelector('#customer-form');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
button.disabled = false;
|
||||
return;
|
||||
}
|
||||
let contact = get_form_data();
|
||||
let appointment = frappe.call({
|
||||
method: 'erpnext.www.book-appointment.index.create_appointment',
|
||||
args: {
|
||||
'date': window.selected_date,
|
||||
'time': window.selected_time,
|
||||
'contact': contact,
|
||||
'tz':window.selected_timezone
|
||||
},
|
||||
callback: (response)=>{
|
||||
if (response.message.status == "Unverified") {
|
||||
frappe.show_alert("Please check your email to confirm the appointment")
|
||||
} else {
|
||||
frappe.show_alert("Appointment Created Successfully");
|
||||
}
|
||||
setTimeout(()=>{
|
||||
let redirect_url = "/";
|
||||
if (window.appointment_settings.success_redirect_url){
|
||||
redirect_url += window.appointment_settings.success_redirect_url;
|
||||
}
|
||||
window.location.href = redirect_url;},5000)
|
||||
},
|
||||
error: (err)=>{
|
||||
frappe.show_alert("Something went wrong please try again");
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function get_form_data() {
|
||||
contact = {};
|
||||
let inputs = ['name', 'skype', 'number', 'notes', 'email'];
|
||||
inputs.forEach((id) => contact[id] = document.getElementById(`customer_${id}`).value)
|
||||
return contact
|
||||
}
|
159
erpnext/www/book-appointment/index.py
Normal file
159
erpnext/www/book-appointment/index.py
Normal file
@ -0,0 +1,159 @@
|
||||
import frappe
|
||||
import datetime
|
||||
import json
|
||||
import pytz
|
||||
|
||||
|
||||
WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
||||
|
||||
no_cache = 1
|
||||
|
||||
|
||||
def get_context(context):
|
||||
is_enabled = frappe.db.get_single_value('Appointment Booking Settings', 'enable_scheduling')
|
||||
if is_enabled:
|
||||
return context
|
||||
else:
|
||||
frappe.local.flags.redirect_location = '/404'
|
||||
raise frappe.Redirect
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_appointment_settings():
|
||||
settings = frappe.get_doc('Appointment Booking Settings')
|
||||
settings.holiday_list = frappe.get_doc('Holiday List', settings.holiday_list)
|
||||
return settings
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_timezones():
|
||||
from babel.dates import get_timezone, get_timezone_name, Locale
|
||||
from frappe.utils.momentjs import get_all_timezones
|
||||
|
||||
translated_dict = {}
|
||||
locale = Locale.parse(frappe.local.lang, sep="-")
|
||||
|
||||
for tz in get_all_timezones():
|
||||
timezone_name = get_timezone_name(get_timezone(tz), locale=locale, width='short')
|
||||
if timezone_name:
|
||||
translated_dict[tz] = timezone_name + ' - ' + tz
|
||||
|
||||
return translated_dict
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_appointment_slots(date, timezone):
|
||||
# Convert query to local timezones
|
||||
format_string = '%Y-%m-%d %H:%M:%S'
|
||||
query_start_time = datetime.datetime.strptime(date + ' 00:00:00', format_string)
|
||||
query_end_time = datetime.datetime.strptime(date + ' 23:59:59', format_string)
|
||||
query_start_time = convert_to_system_timezone(timezone, query_start_time)
|
||||
query_end_time = convert_to_system_timezone(timezone, query_end_time)
|
||||
now = convert_to_guest_timezone(timezone, datetime.datetime.now())
|
||||
|
||||
# Database queries
|
||||
settings = frappe.get_doc('Appointment Booking Settings')
|
||||
holiday_list = frappe.get_doc('Holiday List', settings.holiday_list)
|
||||
timeslots = get_available_slots_between(query_start_time, query_end_time, settings)
|
||||
|
||||
# Filter and convert timeslots
|
||||
converted_timeslots = []
|
||||
for timeslot in timeslots:
|
||||
converted_timeslot = convert_to_guest_timezone(timezone, timeslot)
|
||||
# Check if holiday
|
||||
if _is_holiday(converted_timeslot.date(), holiday_list):
|
||||
converted_timeslots.append(dict(time=converted_timeslot, availability=False))
|
||||
continue
|
||||
# Check availability
|
||||
if check_availabilty(timeslot, settings) and converted_timeslot >= now:
|
||||
converted_timeslots.append(dict(time=converted_timeslot, availability=True))
|
||||
else:
|
||||
converted_timeslots.append(dict(time=converted_timeslot, availability=False))
|
||||
date_required = datetime.datetime.strptime(date + ' 00:00:00', format_string).date()
|
||||
converted_timeslots = filter_timeslots(date_required, converted_timeslots)
|
||||
return converted_timeslots
|
||||
|
||||
def get_available_slots_between(query_start_time, query_end_time, settings):
|
||||
records = _get_records(query_start_time, query_end_time, settings)
|
||||
timeslots = []
|
||||
appointment_duration = datetime.timedelta(
|
||||
minutes=settings.appointment_duration)
|
||||
for record in records:
|
||||
if record.day_of_week == WEEKDAYS[query_start_time.weekday()]:
|
||||
current_time = _deltatime_to_datetime(query_start_time, record.from_time)
|
||||
end_time = _deltatime_to_datetime(query_start_time, record.to_time)
|
||||
else:
|
||||
current_time = _deltatime_to_datetime(query_end_time, record.from_time)
|
||||
end_time = _deltatime_to_datetime(query_end_time, record.to_time)
|
||||
while current_time + appointment_duration <= end_time:
|
||||
timeslots.append(current_time)
|
||||
current_time += appointment_duration
|
||||
return timeslots
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def create_appointment(date, time, tz, contact):
|
||||
format_string = '%Y-%m-%d %H:%M:%S%z'
|
||||
scheduled_time = datetime.datetime.strptime(date + " " + time, format_string)
|
||||
# Strip tzinfo from datetime objects since it's handled by the doctype
|
||||
scheduled_time = scheduled_time.replace(tzinfo = None)
|
||||
scheduled_time = convert_to_system_timezone(tz, scheduled_time)
|
||||
scheduled_time = scheduled_time.replace(tzinfo = None)
|
||||
# Create a appointment document from form
|
||||
appointment = frappe.new_doc('Appointment')
|
||||
appointment.scheduled_time = scheduled_time
|
||||
contact = json.loads(contact)
|
||||
appointment.customer_name = contact.get('name', None)
|
||||
appointment.customer_phone_number = contact.get('number', None)
|
||||
appointment.customer_skype = contact.get('skype', None)
|
||||
appointment.customer_details = contact.get('notes', None)
|
||||
appointment.customer_email = contact.get('email', None)
|
||||
appointment.status = 'Open'
|
||||
appointment.insert()
|
||||
return appointment
|
||||
|
||||
# Helper Functions
|
||||
def filter_timeslots(date, timeslots):
|
||||
filtered_timeslots = []
|
||||
for timeslot in timeslots:
|
||||
if(timeslot['time'].date() == date):
|
||||
filtered_timeslots.append(timeslot)
|
||||
return filtered_timeslots
|
||||
|
||||
def convert_to_guest_timezone(guest_tz, datetimeobject):
|
||||
guest_tz = pytz.timezone(guest_tz)
|
||||
local_timezone = pytz.timezone(frappe.utils.get_time_zone())
|
||||
datetimeobject = local_timezone.localize(datetimeobject)
|
||||
datetimeobject = datetimeobject.astimezone(guest_tz)
|
||||
return datetimeobject
|
||||
|
||||
def convert_to_system_timezone(guest_tz,datetimeobject):
|
||||
guest_tz = pytz.timezone(guest_tz)
|
||||
datetimeobject = guest_tz.localize(datetimeobject)
|
||||
system_tz = pytz.timezone(frappe.utils.get_time_zone())
|
||||
datetimeobject = datetimeobject.astimezone(system_tz)
|
||||
return datetimeobject
|
||||
|
||||
def check_availabilty(timeslot, settings):
|
||||
return frappe.db.count('Appointment', {'scheduled_time': timeslot}) < settings.number_of_agents
|
||||
|
||||
def _is_holiday(date, holiday_list):
|
||||
for holiday in holiday_list.holidays:
|
||||
if holiday.holiday_date == date:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_records(start_time, end_time, settings):
|
||||
records = []
|
||||
for record in settings.availability_of_slots:
|
||||
if record.day_of_week == WEEKDAYS[start_time.weekday()] or record.day_of_week == WEEKDAYS[end_time.weekday()]:
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
|
||||
def _deltatime_to_datetime(date, deltatime):
|
||||
time = (datetime.datetime.min + deltatime).time()
|
||||
return datetime.datetime.combine(date.date(), time)
|
||||
|
||||
|
||||
def _datetime_to_deltatime(date_time):
|
||||
midnight = datetime.datetime.combine(date_time.date(), datetime.time.min)
|
||||
return (date_time-midnight)
|
18
erpnext/www/book-appointment/verify/index.html
Normal file
18
erpnext/www/book-appointment/verify/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ _("Verify Email") }}
|
||||
{% endblock%}
|
||||
|
||||
{% block page_content %}
|
||||
|
||||
{% if success==True %}
|
||||
<div class="alert alert-success">
|
||||
Your email has been verified and your appointment has been scheduled
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
Verification failed please check the link
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock%}
|
20
erpnext/www/book-appointment/verify/index.py
Normal file
20
erpnext/www/book-appointment/verify/index.py
Normal file
@ -0,0 +1,20 @@
|
||||
import frappe
|
||||
|
||||
from frappe.utils.verified_command import verify_request
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_context(context):
|
||||
if not verify_request():
|
||||
context.success = False
|
||||
return context
|
||||
|
||||
email = frappe.form_dict['email']
|
||||
appointment_name = frappe.form_dict['appointment']
|
||||
|
||||
if email and appointment_name:
|
||||
appointment = frappe.get_doc('Appointment',appointment_name)
|
||||
appointment.set_verified(email)
|
||||
context.success = True
|
||||
return context
|
||||
else:
|
||||
context.success = False
|
||||
return context
|
Loading…
Reference in New Issue
Block a user