Merge branch 'develop' of https://github.com/frappe/erpnext into project-link-for-all-accounts
This commit is contained in:
commit
3c63f5b76b
14
.github/workflows/docker-release.yml
vendored
Normal file
14
.github/workflows/docker-release.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: Trigger Docker build on release
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
jobs:
|
||||
curl:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: alpine:latest
|
||||
steps:
|
||||
- name: curl
|
||||
run: |
|
||||
apk add curl bash
|
||||
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
|
21
CODEOWNERS
21
CODEOWNERS
@ -3,17 +3,16 @@
|
||||
# These owners will be the default owners for everything in
|
||||
# the repo. Unless a later match takes precedence,
|
||||
|
||||
* @nabinhait
|
||||
manufacturing/ @rohitwaghchaure
|
||||
manufacturing/ @rohitwaghchaure @marination
|
||||
accounts/ @deepeshgarg007 @nextchamp-saqib
|
||||
loan_management/ @deepeshgarg007
|
||||
pos* @nextchamp-saqib
|
||||
assets/ @nextchamp-saqib
|
||||
loan_management/ @deepeshgarg007 @rohitwaghchaure
|
||||
pos* @nextchamp-saqib @rohitwaghchaure
|
||||
assets/ @nextchamp-saqib @deepeshgarg007
|
||||
stock/ @marination @rohitwaghchaure
|
||||
buying/ @marination @rohitwaghchaure
|
||||
hr/ @Anurag810
|
||||
projects/ @hrwX
|
||||
support/ @hrwX
|
||||
healthcare/ @ruchamahabal
|
||||
erpnext_integrations/ @Mangesh-Khairnar
|
||||
buying/ @marination @deepeshgarg007
|
||||
hr/ @Anurag810 @rohitwaghchaure
|
||||
projects/ @hrwX @nextchamp-saqib
|
||||
support/ @hrwX @marination
|
||||
healthcare/ @ruchamahabal @marination
|
||||
erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib
|
||||
requirements.txt @gavindsouza
|
||||
|
@ -5,7 +5,22 @@ import frappe
|
||||
import json
|
||||
from frappe.utils import nowdate, add_months, get_date_str
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_fiscal_year, get_account_name
|
||||
from erpnext.accounts.utils import get_fiscal_year, get_account_name, FiscalYearError
|
||||
|
||||
def _get_fiscal_year(date=None):
|
||||
try:
|
||||
fiscal_year = get_fiscal_year(date=nowdate(), as_dict=True)
|
||||
return fiscal_year
|
||||
|
||||
except FiscalYearError:
|
||||
#if no fiscal year for current date then get default fiscal year
|
||||
try:
|
||||
fiscal_year = get_fiscal_year(as_dict=True)
|
||||
return fiscal_year
|
||||
|
||||
except FiscalYearError:
|
||||
#if still no fiscal year found then no accounting data created, return
|
||||
return None
|
||||
|
||||
def get_company_for_dashboards():
|
||||
company = frappe.defaults.get_defaults().company
|
||||
@ -18,10 +33,16 @@ def get_company_for_dashboards():
|
||||
return None
|
||||
|
||||
def get_data():
|
||||
|
||||
fiscal_year = _get_fiscal_year(nowdate())
|
||||
|
||||
if not fiscal_year:
|
||||
return frappe._dict()
|
||||
|
||||
return frappe._dict({
|
||||
"dashboards": get_dashboards(),
|
||||
"charts": get_charts(),
|
||||
"number_cards": get_number_cards()
|
||||
"charts": get_charts(fiscal_year),
|
||||
"number_cards": get_number_cards(fiscal_year)
|
||||
})
|
||||
|
||||
def get_dashboards():
|
||||
@ -46,10 +67,9 @@ def get_dashboards():
|
||||
]
|
||||
}]
|
||||
|
||||
def get_charts():
|
||||
def get_charts(fiscal_year):
|
||||
company = frappe.get_doc("Company", get_company_for_dashboards())
|
||||
bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
|
||||
fiscal_year = get_fiscal_year(date=nowdate())
|
||||
default_cost_center = company.cost_center
|
||||
|
||||
return [
|
||||
@ -61,8 +81,8 @@ def get_charts():
|
||||
"filters_json": json.dumps({
|
||||
"company": company.name,
|
||||
"filter_based_on": "Fiscal Year",
|
||||
"from_fiscal_year": fiscal_year[0],
|
||||
"to_fiscal_year": fiscal_year[0],
|
||||
"from_fiscal_year": fiscal_year.get('name'),
|
||||
"to_fiscal_year": fiscal_year.get('name'),
|
||||
"periodicity": "Monthly",
|
||||
"include_default_book_entries": 1
|
||||
}),
|
||||
@ -158,8 +178,8 @@ def get_charts():
|
||||
"report_name": "Budget Variance Report",
|
||||
"filters_json": json.dumps({
|
||||
"company": company.name,
|
||||
"from_fiscal_year": fiscal_year[0],
|
||||
"to_fiscal_year": fiscal_year[0],
|
||||
"from_fiscal_year": fiscal_year.get('name'),
|
||||
"to_fiscal_year": fiscal_year.get('name'),
|
||||
"period": "Monthly",
|
||||
"budget_against": "Cost Center"
|
||||
}),
|
||||
@ -190,10 +210,10 @@ def get_charts():
|
||||
},
|
||||
]
|
||||
|
||||
def get_number_cards():
|
||||
fiscal_year = get_fiscal_year(date=nowdate())
|
||||
year_start_date = get_date_str(fiscal_year[1])
|
||||
year_end_date = get_date_str(fiscal_year[2])
|
||||
def get_number_cards(fiscal_year):
|
||||
|
||||
year_start_date = get_date_str(fiscal_year.get("year_start_date"))
|
||||
year_end_date = get_date_str(fiscal_year.get("year_end_date"))
|
||||
return [
|
||||
{
|
||||
"doctype": "Number Card",
|
||||
|
@ -14,6 +14,9 @@ frappe.treeview_settings["Account"] = {
|
||||
on_change: function() {
|
||||
var me = frappe.treeview_settings['Account'].treeview;
|
||||
var company = me.page.fields_dict.company.get_value();
|
||||
if (!company) {
|
||||
frappe.throw(__("Please set a Company"));
|
||||
}
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.account.account.get_root_company",
|
||||
args: {
|
||||
|
@ -72,7 +72,11 @@ def make_dimension_in_accounting_doctypes(doc):
|
||||
if doctype == "Budget":
|
||||
add_dimension_to_budget_doctype(df, doc)
|
||||
else:
|
||||
create_custom_field(doctype, df)
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
fieldnames = [d.fieldname for d in meta.get("fields")]
|
||||
|
||||
if df['fieldname'] not in fieldnames:
|
||||
create_custom_field(doctype, df)
|
||||
|
||||
count += 1
|
||||
|
||||
|
@ -14,7 +14,18 @@ frappe.ui.form.on('Cost Center', {
|
||||
is_group: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
frm.set_query("cost_center", "distributed_cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
enable_distributed_cost_center: 0,
|
||||
name: ['!=', frm.doc.name]
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if (!frm.is_new()) {
|
||||
|
@ -16,6 +16,9 @@
|
||||
"cb0",
|
||||
"is_group",
|
||||
"disabled",
|
||||
"section_break_9",
|
||||
"enable_distributed_cost_center",
|
||||
"distributed_cost_center",
|
||||
"lft",
|
||||
"rgt",
|
||||
"old_parent"
|
||||
@ -119,13 +122,31 @@
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_distributed_cost_center",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Distributed Cost Center"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_group==0",
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_distributed_cost_center",
|
||||
"fieldname": "distributed_cost_center",
|
||||
"fieldtype": "Table",
|
||||
"label": "Distributed Cost Center",
|
||||
"options": "Distributed Cost Center"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-29 16:09:30.025214",
|
||||
"modified": "2020-06-17 16:09:30.025214",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cost Center",
|
||||
|
@ -19,6 +19,24 @@ class CostCenter(NestedSet):
|
||||
def validate(self):
|
||||
self.validate_mandatory()
|
||||
self.validate_parent_cost_center()
|
||||
self.validate_distributed_cost_center()
|
||||
|
||||
def validate_distributed_cost_center(self):
|
||||
if cint(self.enable_distributed_cost_center):
|
||||
if not self.distributed_cost_center:
|
||||
frappe.throw(_("Please enter distributed cost center"))
|
||||
if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100:
|
||||
frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100"))
|
||||
if not self.get('__islocal'):
|
||||
if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \
|
||||
and self.check_if_part_of_distributed_cost_center():
|
||||
frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"))
|
||||
if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False):
|
||||
frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center"))
|
||||
if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)):
|
||||
frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."))
|
||||
else:
|
||||
self.distributed_cost_center = []
|
||||
|
||||
def validate_mandatory(self):
|
||||
if self.cost_center_name != self.company and not self.parent_cost_center:
|
||||
@ -43,12 +61,15 @@ class CostCenter(NestedSet):
|
||||
return 1
|
||||
|
||||
def convert_ledger_to_group(self):
|
||||
if cint(self.enable_distributed_cost_center):
|
||||
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
|
||||
if self.check_if_part_of_distributed_cost_center():
|
||||
frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group"))
|
||||
if self.check_gle_exists():
|
||||
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
|
||||
else:
|
||||
self.is_group = 1
|
||||
self.save()
|
||||
return 1
|
||||
self.is_group = 1
|
||||
self.save()
|
||||
return 1
|
||||
|
||||
def check_gle_exists(self):
|
||||
return frappe.db.get_value("GL Entry", {"cost_center": self.name})
|
||||
@ -57,6 +78,9 @@ class CostCenter(NestedSet):
|
||||
return frappe.db.sql("select name from `tabCost Center` where \
|
||||
parent_cost_center = %s and docstatus != 2", self.name)
|
||||
|
||||
def check_if_part_of_distributed_cost_center(self):
|
||||
return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name})
|
||||
|
||||
def before_rename(self, olddn, newdn, merge=False):
|
||||
# Add company abbr if not provided
|
||||
from erpnext.setup.doctype.company.company import get_name_with_abbr
|
||||
@ -100,3 +124,7 @@ def get_name_with_number(new_account, account_number):
|
||||
if account_number and not new_account[0].isdigit():
|
||||
new_account = account_number + " - " + new_account
|
||||
return new_account
|
||||
|
||||
def check_if_distributed_cost_center_enabled(cost_center_list):
|
||||
value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
|
||||
return next((True for x in value_list if x[0]), False)
|
@ -22,6 +22,33 @@ class TestCostCenter(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, cost_center.save)
|
||||
|
||||
def test_validate_distributed_cost_center(self):
|
||||
|
||||
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}):
|
||||
frappe.get_doc(test_records[0]).insert()
|
||||
|
||||
if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
|
||||
frappe.get_doc(test_records[1]).insert()
|
||||
|
||||
invalid_distributed_cost_center = frappe.get_doc({
|
||||
"company": "_Test Company",
|
||||
"cost_center_name": "_Test Distributed Cost Center",
|
||||
"doctype": "Cost Center",
|
||||
"is_group": 0,
|
||||
"parent_cost_center": "_Test Company - _TC",
|
||||
"enable_distributed_cost_center": 1,
|
||||
"distributed_cost_center": [{
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"percentage_allocation": 40
|
||||
}, {
|
||||
"cost_center": "_Test Cost Center 2 - _TC",
|
||||
"percentage_allocation": 50
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save)
|
||||
|
||||
def create_cost_center(**args):
|
||||
args = frappe._dict(args)
|
||||
if args.cost_center_name:
|
||||
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-03-19 12:34:01.500390",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"cost_center",
|
||||
"percentage_allocation"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "percentage_allocation",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Percentage Allocation",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-19 12:54:43.674655",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Distributed Cost Center",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, 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 DistributedCostCenter(Document):
|
||||
pass
|
@ -191,6 +191,7 @@
|
||||
{
|
||||
"fieldname": "total_debit",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Debit",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "total_debit",
|
||||
@ -252,7 +253,6 @@
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Total Amount",
|
||||
"no_copy": 1,
|
||||
"options": "total_amount_currency",
|
||||
@ -503,7 +503,7 @@
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-29 10:55:28.240916",
|
||||
"modified": "2020-06-02 18:15:46.955697",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"currency_section",
|
||||
"account_currency",
|
||||
"column_break_10",
|
||||
@ -32,7 +33,6 @@
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"reference_due_date",
|
||||
"project",
|
||||
"col_break3",
|
||||
"is_advance",
|
||||
"user_remark",
|
||||
@ -273,7 +273,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-25 01:47:49.060128",
|
||||
"modified": "2020-06-18 14:06:54.833738",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
@ -319,7 +319,7 @@ class PaymentEntry(AccountsController):
|
||||
invoice_payment_amount_map.setdefault(key, 0.0)
|
||||
invoice_payment_amount_map[key] += reference.allocated_amount
|
||||
|
||||
if not invoice_paid_amount_map.get(reference.reference_name):
|
||||
if not invoice_paid_amount_map.get(key):
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term'])
|
||||
for term in payment_schedule:
|
||||
@ -332,12 +332,14 @@ class PaymentEntry(AccountsController):
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
else:
|
||||
outstanding = invoice_paid_amount_map.get(key)['outstanding']
|
||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||
|
||||
if amount > outstanding:
|
||||
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
|
||||
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
if amount and outstanding:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
@ -1091,17 +1093,20 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||
references = []
|
||||
for payment_term in payment_schedule:
|
||||
references.append({
|
||||
'reference_doctype': dt,
|
||||
'reference_name': dn,
|
||||
'bill_no': doc.get('bill_no'),
|
||||
'due_date': doc.get('due_date'),
|
||||
'total_amount': grand_total,
|
||||
'outstanding_amount': outstanding_amount,
|
||||
'payment_term': payment_term.payment_term,
|
||||
'allocated_amount': flt(payment_term.payment_amount - payment_term.paid_amount,
|
||||
payment_term_outstanding = flt(payment_term.payment_amount - payment_term.paid_amount,
|
||||
payment_term.precision('payment_amount'))
|
||||
})
|
||||
|
||||
if payment_term_outstanding:
|
||||
references.append({
|
||||
'reference_doctype': dt,
|
||||
'reference_name': dn,
|
||||
'bill_no': doc.get('bill_no'),
|
||||
'due_date': doc.get('due_date'),
|
||||
'total_amount': grand_total,
|
||||
'outstanding_amount': outstanding_amount,
|
||||
'payment_term': payment_term.payment_term,
|
||||
'allocated_amount': payment_term_outstanding
|
||||
})
|
||||
|
||||
return references
|
||||
|
||||
|
@ -349,9 +349,10 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-08 10:23:02.815237",
|
||||
"modified": "2020-05-29 17:38:49.392713",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -582,14 +582,14 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def validate_item_code(self):
|
||||
for d in self.get('items'):
|
||||
if not d.item_code:
|
||||
if not d.item_code and self.is_opening == "No":
|
||||
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
|
||||
|
||||
def validate_warehouse(self):
|
||||
super(SalesInvoice, self).validate_warehouse()
|
||||
|
||||
for d in self.get_item_list():
|
||||
if not d.warehouse and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||
if not d.warehouse and d.item_code and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
|
||||
|
||||
def validate_delivery_note(self):
|
||||
@ -1454,11 +1454,17 @@ def get_inter_company_details(doc, doctype):
|
||||
parties = frappe.db.get_all("Supplier", fields=["name"], filters={"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company})
|
||||
company = frappe.get_cached_value("Customer", doc.customer, "represents_company")
|
||||
|
||||
if not parties:
|
||||
frappe.throw(_('No Supplier found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
|
||||
|
||||
party = get_internal_party(parties, "Supplier", doc)
|
||||
else:
|
||||
parties = frappe.db.get_all("Customer", fields=["name"], filters={"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company})
|
||||
company = frappe.get_cached_value("Supplier", doc.supplier, "represents_company")
|
||||
|
||||
if not parties:
|
||||
frappe.throw(_('No Customer found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
|
||||
|
||||
party = get_internal_party(parties, "Customer", doc)
|
||||
|
||||
return {
|
||||
|
@ -1772,53 +1772,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
|
||||
|
||||
def test_deferred_error_email(self):
|
||||
deferred_account = create_account(account_name="Deferred Revenue",
|
||||
parent_account="Current Liabilities - _TC", company="_Test Company")
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item.enable_deferred_revenue = 1
|
||||
item.deferred_revenue_account = deferred_account
|
||||
item.no_of_months = 12
|
||||
item.save()
|
||||
|
||||
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
|
||||
si.items[0].enable_deferred_revenue = 1
|
||||
si.items[0].service_start_date = "2019-01-10"
|
||||
si.items[0].service_end_date = "2019-03-15"
|
||||
si.items[0].deferred_revenue_account = deferred_account
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
|
||||
|
||||
acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
|
||||
acc_settings.acc_frozen_upto = '2019-01-31'
|
||||
acc_settings.save()
|
||||
|
||||
pda = frappe.get_doc(dict(
|
||||
doctype='Process Deferred Accounting',
|
||||
posting_date=nowdate(),
|
||||
start_date="2019-01-01",
|
||||
end_date="2019-03-31",
|
||||
type="Income",
|
||||
company="_Test Company"
|
||||
))
|
||||
|
||||
pda.insert()
|
||||
pda.submit()
|
||||
|
||||
email = frappe.db.sql(""" select name from `tabEmail Queue`
|
||||
where message like %(txt)s """, {
|
||||
'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name)
|
||||
})
|
||||
|
||||
self.assertTrue(email)
|
||||
|
||||
acc_settings.load_from_db()
|
||||
acc_settings.acc_frozen_upto = None
|
||||
acc_settings.save()
|
||||
|
||||
def test_inter_company_transaction(self):
|
||||
|
||||
if not frappe.db.exists("Customer", "_Test Internal Customer"):
|
||||
|
@ -45,7 +45,9 @@ class ShippingRule(Document):
|
||||
shipping_amount = 0.0
|
||||
by_value = False
|
||||
|
||||
self.validate_countries(doc)
|
||||
if doc.get_shipping_address():
|
||||
# validate country only if there is address
|
||||
self.validate_countries(doc)
|
||||
|
||||
if self.calculate_based_on == 'Net Total':
|
||||
value = doc.base_net_total
|
||||
|
@ -169,9 +169,11 @@ class ReceivablePayableReport(object):
|
||||
|
||||
def append_subtotal_row(self, party):
|
||||
sub_total_row = self.total_row_map.get(party)
|
||||
self.data.append(sub_total_row)
|
||||
self.data.append({})
|
||||
self.update_sub_total_row(sub_total_row, 'Total')
|
||||
|
||||
if sub_total_row:
|
||||
self.data.append(sub_total_row)
|
||||
self.data.append({})
|
||||
self.update_sub_total_row(sub_total_row, 'Total')
|
||||
|
||||
def get_voucher_balance(self, gle):
|
||||
if self.filters.get("sales_person"):
|
||||
@ -232,7 +234,8 @@ class ReceivablePayableReport(object):
|
||||
|
||||
if self.filters.get('group_by_party'):
|
||||
self.append_subtotal_row(self.previous_party)
|
||||
self.data.append(self.total_row_map.get('Total'))
|
||||
if self.data:
|
||||
self.data.append(self.total_row_map.get('Total'))
|
||||
|
||||
def append_row(self, row):
|
||||
self.allocate_future_payments(row)
|
||||
|
@ -29,37 +29,60 @@ def execute(filters=None):
|
||||
for dimension in dimensions:
|
||||
dimension_items = cam_map.get(dimension)
|
||||
if dimension_items:
|
||||
for account, monthwise_data in iteritems(dimension_items):
|
||||
row = [dimension, account]
|
||||
totals = [0, 0, 0]
|
||||
for year in get_fiscal_years(filters):
|
||||
last_total = 0
|
||||
for relevant_months in period_month_ranges:
|
||||
period_data = [0, 0, 0]
|
||||
for month in relevant_months:
|
||||
if monthwise_data.get(year[0]):
|
||||
month_data = monthwise_data.get(year[0]).get(month, {})
|
||||
for i, fieldname in enumerate(["target", "actual", "variance"]):
|
||||
value = flt(month_data.get(fieldname))
|
||||
period_data[i] += value
|
||||
totals[i] += value
|
||||
|
||||
period_data[0] += last_total
|
||||
|
||||
if filters.get("show_cumulative"):
|
||||
last_total = period_data[0] - period_data[1]
|
||||
|
||||
period_data[2] = period_data[0] - period_data[1]
|
||||
row += period_data
|
||||
totals[2] = totals[0] - totals[1]
|
||||
if filters["period"] != "Yearly":
|
||||
row += totals
|
||||
data.append(row)
|
||||
data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0)
|
||||
else:
|
||||
DCC_allocation = frappe.db.sql('''SELECT parent, sum(percentage_allocation) as percentage_allocation
|
||||
FROM `tabDistributed Cost Center`
|
||||
WHERE cost_center IN %(dimension)s
|
||||
AND parent NOT IN %(dimension)s
|
||||
GROUP BY parent''',{'dimension':[dimension]})
|
||||
if DCC_allocation:
|
||||
filters['budget_against_filter'] = [DCC_allocation[0][0]]
|
||||
cam_map = get_dimension_account_month_map(filters)
|
||||
dimension_items = cam_map.get(DCC_allocation[0][0])
|
||||
if dimension_items:
|
||||
data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1])
|
||||
|
||||
chart = get_chart_data(filters, columns, data)
|
||||
|
||||
return columns, data, None, chart
|
||||
|
||||
def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation):
|
||||
|
||||
for account, monthwise_data in iteritems(dimension_items):
|
||||
row = [dimension, account]
|
||||
totals = [0, 0, 0]
|
||||
for year in get_fiscal_years(filters):
|
||||
last_total = 0
|
||||
for relevant_months in period_month_ranges:
|
||||
period_data = [0, 0, 0]
|
||||
for month in relevant_months:
|
||||
if monthwise_data.get(year[0]):
|
||||
month_data = monthwise_data.get(year[0]).get(month, {})
|
||||
for i, fieldname in enumerate(["target", "actual", "variance"]):
|
||||
value = flt(month_data.get(fieldname))
|
||||
period_data[i] += value
|
||||
totals[i] += value
|
||||
|
||||
period_data[0] += last_total
|
||||
|
||||
if DCC_allocation:
|
||||
period_data[0] = period_data[0]*(DCC_allocation/100)
|
||||
period_data[1] = period_data[1]*(DCC_allocation/100)
|
||||
|
||||
if(filters.get("show_cumulative")):
|
||||
last_total = period_data[0] - period_data[1]
|
||||
|
||||
period_data[2] = period_data[0] - period_data[1]
|
||||
row += period_data
|
||||
totals[2] = totals[0] - totals[1]
|
||||
if filters["period"] != "Yearly" :
|
||||
row += totals
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
{
|
||||
@ -366,7 +389,7 @@ def get_chart_data(filters, columns, data):
|
||||
budget_values[i] += values[index]
|
||||
actual_values[i] += values[index+1]
|
||||
index += 3
|
||||
|
||||
|
||||
return {
|
||||
'data': {
|
||||
'labels': labels,
|
||||
|
@ -56,9 +56,8 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
|
||||
to_date = add_months(start_date, months_to_add)
|
||||
start_date = to_date
|
||||
|
||||
if to_date == get_first_day(to_date):
|
||||
# if to_date is the first day, get the last day of previous month
|
||||
to_date = add_days(to_date, -1)
|
||||
# Subtract one day from to_date, as it may be first day in next fiscal year or month
|
||||
to_date = add_days(to_date, -1)
|
||||
|
||||
if to_date <= year_end_date:
|
||||
# the normal case
|
||||
@ -387,11 +386,43 @@ def set_gl_entries_by_account(
|
||||
key: value
|
||||
})
|
||||
|
||||
distributed_cost_center_query = ""
|
||||
if filters and filters.get('cost_center'):
|
||||
distributed_cost_center_query = """
|
||||
UNION ALL
|
||||
SELECT posting_date,
|
||||
account,
|
||||
debit*(DCC_allocation.percentage_allocation/100) as debit,
|
||||
credit*(DCC_allocation.percentage_allocation/100) as credit,
|
||||
is_opening,
|
||||
fiscal_year,
|
||||
debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
|
||||
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency,
|
||||
account_currency
|
||||
FROM `tabGL Entry`,
|
||||
(
|
||||
SELECT parent, sum(percentage_allocation) as percentage_allocation
|
||||
FROM `tabDistributed Cost Center`
|
||||
WHERE cost_center IN %(cost_center)s
|
||||
AND parent NOT IN %(cost_center)s
|
||||
AND is_cancelled = 0
|
||||
GROUP BY parent
|
||||
) as DCC_allocation
|
||||
WHERE company=%(company)s
|
||||
{additional_conditions}
|
||||
AND posting_date <= %(to_date)s
|
||||
AND cost_center = DCC_allocation.parent
|
||||
""".format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", ''))
|
||||
|
||||
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
{additional_conditions}
|
||||
and posting_date <= %(to_date)s
|
||||
order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec
|
||||
and is_cancelled = 0
|
||||
{distributed_cost_center_query}
|
||||
order by account, posting_date""".format(
|
||||
additional_conditions=additional_conditions,
|
||||
distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
|
||||
|
||||
if filters and filters.get('presentation_currency'):
|
||||
convert_to_presentation_currency(gl_entries, get_currency(filters))
|
||||
@ -489,4 +520,4 @@ def get_columns(periodicity, period_list, accumulated_values=1, company=None):
|
||||
"width": 150
|
||||
})
|
||||
|
||||
return columns
|
||||
return columns
|
@ -128,18 +128,53 @@ def get_gl_entries(filters):
|
||||
filters['company_fb'] = frappe.db.get_value("Company",
|
||||
filters.get("company"), 'default_finance_book')
|
||||
|
||||
distributed_cost_center_query = ""
|
||||
if filters and filters.get('cost_center'):
|
||||
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
|
||||
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
|
||||
|
||||
distributed_cost_center_query = """
|
||||
UNION ALL
|
||||
SELECT name as gl_entry,
|
||||
posting_date,
|
||||
account,
|
||||
party_type,
|
||||
party,
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
cost_center, project,
|
||||
against_voucher_type,
|
||||
against_voucher,
|
||||
account_currency,
|
||||
remarks, against,
|
||||
is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
|
||||
FROM `tabGL Entry`,
|
||||
(
|
||||
SELECT parent, sum(percentage_allocation) as percentage_allocation
|
||||
FROM `tabDistributed Cost Center`
|
||||
WHERE cost_center IN %(cost_center)s
|
||||
AND parent NOT IN %(cost_center)s
|
||||
GROUP BY parent
|
||||
) as DCC_allocation
|
||||
WHERE company=%(company)s
|
||||
{conditions}
|
||||
AND posting_date <= %(to_date)s
|
||||
AND cost_center = DCC_allocation.parent
|
||||
""".format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name as gl_entry, posting_date, account, party_type, party,
|
||||
voucher_type, voucher_no, cost_center, project,
|
||||
against_voucher_type, against_voucher, account_currency,
|
||||
remarks, against, is_opening {select_fields}
|
||||
remarks, against, is_opening, creation {select_fields}
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s {conditions}
|
||||
{distributed_cost_center_query}
|
||||
{order_by_statement}
|
||||
""".format(
|
||||
select_fields=select_fields, conditions=get_conditions(filters),
|
||||
select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
|
||||
order_by_statement=order_by_statement
|
||||
),
|
||||
filters, as_dict=1)
|
||||
|
@ -265,13 +265,6 @@ def get_columns(additional_table_columns, filters):
|
||||
'fieldtype': 'Currency',
|
||||
'options': 'currency',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'fieldname': 'currency',
|
||||
'label': _('Currency'),
|
||||
'fieldtype': 'Currency',
|
||||
'width': 80,
|
||||
'hidden': 1
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -223,7 +223,7 @@ def get_columns(additional_table_columns, filters):
|
||||
}
|
||||
]
|
||||
|
||||
if filters.get('group_by') != 'Terriotory':
|
||||
if filters.get('group_by') != 'Territory':
|
||||
columns.extend([
|
||||
{
|
||||
'label': _("Territory"),
|
||||
@ -304,13 +304,6 @@ def get_columns(additional_table_columns, filters):
|
||||
'fieldtype': 'Currency',
|
||||
'options': 'currency',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'fieldname': 'currency',
|
||||
'label': _('Currency'),
|
||||
'fieldtype': 'Currency',
|
||||
'width': 80,
|
||||
'hidden': 1
|
||||
}
|
||||
]
|
||||
|
||||
@ -536,6 +529,13 @@ def get_tax_accounts(item_list, columns, company_currency,
|
||||
'fieldtype': 'Currency',
|
||||
'options': 'currency',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'fieldname': 'currency',
|
||||
'label': _('Currency'),
|
||||
'fieldtype': 'Currency',
|
||||
'width': 80,
|
||||
'hidden': 1
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -105,6 +105,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name):
|
||||
|
||||
def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
|
||||
data = []
|
||||
new_accounts = accounts
|
||||
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
|
||||
|
||||
for d in accounts:
|
||||
@ -118,6 +119,19 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
|
||||
"currency": company_currency,
|
||||
"based_on": based_on
|
||||
}
|
||||
if based_on == 'cost_center':
|
||||
cost_center_doc = frappe.get_doc("Cost Center",d.name)
|
||||
if not cost_center_doc.enable_distributed_cost_center:
|
||||
DCC_allocation = frappe.db.sql("""SELECT parent, sum(percentage_allocation) as percentage_allocation
|
||||
FROM `tabDistributed Cost Center`
|
||||
WHERE cost_center IN %(cost_center)s
|
||||
AND parent NOT IN %(cost_center)s
|
||||
GROUP BY parent""",{'cost_center': [d.name]})
|
||||
if DCC_allocation:
|
||||
for account in new_accounts:
|
||||
if account['name'] == DCC_allocation[0][0]:
|
||||
for value in value_fields:
|
||||
d[value] += account[value]*(DCC_allocation[0][1]/100)
|
||||
|
||||
for key in value_fields:
|
||||
row[key] = flt(d.get(key, 0.0), 3)
|
||||
|
@ -111,7 +111,7 @@ def get_gle_map(filters):
|
||||
# {"purchase_invoice": list of dict of all gle created for this invoice}
|
||||
gle_map = {}
|
||||
gle = frappe.db.get_all('GL Entry',\
|
||||
{"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]]},
|
||||
{"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0},
|
||||
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
|
||||
|
||||
for d in gle:
|
||||
|
@ -57,6 +57,9 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
|
||||
|
||||
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
||||
|
||||
if not transaction_date and not fiscal_year:
|
||||
return fiscal_years
|
||||
|
||||
if transaction_date:
|
||||
transaction_date = getdate(transaction_date)
|
||||
|
||||
@ -79,6 +82,23 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
|
||||
if verbose==1: frappe.msgprint(error_msg)
|
||||
raise FiscalYearError(error_msg)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_fiscal_year_filter_field(company=None):
|
||||
field = {
|
||||
"fieldtype": "Select",
|
||||
"options": [],
|
||||
"operator": "Between",
|
||||
"query_value": True
|
||||
}
|
||||
fiscal_years = get_fiscal_years(company=company)
|
||||
for fiscal_year in fiscal_years:
|
||||
field["options"].append({
|
||||
"label": fiscal_year.name,
|
||||
"value": fiscal_year.name,
|
||||
"query_value": [fiscal_year.year_start_date.strftime("%Y-%m-%d"), fiscal_year.year_end_date.strftime("%Y-%m-%d")]
|
||||
})
|
||||
return field
|
||||
|
||||
def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
|
||||
years = [f[0] for f in get_fiscal_years(date, label=_(label), company=company)]
|
||||
if fiscal_year not in years:
|
||||
@ -935,4 +955,4 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
|
||||
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
|
||||
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
|
||||
|
||||
return gl_entries
|
||||
return gl_entries
|
||||
|
@ -5,14 +5,23 @@ import frappe
|
||||
import json
|
||||
from frappe.utils import nowdate, add_months, get_date_str
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
|
||||
from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
|
||||
|
||||
def get_data():
|
||||
|
||||
fiscal_year = _get_fiscal_year(nowdate())
|
||||
|
||||
if not fiscal_year:
|
||||
return frappe._dict()
|
||||
|
||||
year_start_date = get_date_str(fiscal_year.get('year_start_date'))
|
||||
year_end_date = get_date_str(fiscal_year.get('year_end_date'))
|
||||
|
||||
return frappe._dict({
|
||||
"dashboards": get_dashboards(),
|
||||
"charts": get_charts(),
|
||||
"number_cards": get_number_cards(),
|
||||
"charts": get_charts(fiscal_year, year_start_date, year_end_date),
|
||||
"number_cards": get_number_cards(fiscal_year, year_start_date, year_end_date),
|
||||
})
|
||||
|
||||
def get_dashboards():
|
||||
@ -31,12 +40,7 @@ def get_dashboards():
|
||||
]
|
||||
}]
|
||||
|
||||
fiscal_year = get_fiscal_year(date=nowdate())
|
||||
year_start_date = get_date_str(fiscal_year[1])
|
||||
year_end_date = get_date_str(fiscal_year[2])
|
||||
|
||||
|
||||
def get_charts():
|
||||
def get_charts(fiscal_year, year_start_date, year_end_date):
|
||||
company = get_company_for_dashboards()
|
||||
return [
|
||||
{
|
||||
@ -55,8 +59,8 @@ def get_charts():
|
||||
"company": company,
|
||||
"status": "In Location",
|
||||
"filter_based_on": "Fiscal Year",
|
||||
"from_fiscal_year": fiscal_year[0],
|
||||
"to_fiscal_year": fiscal_year[0],
|
||||
"from_fiscal_year": fiscal_year.get('name'),
|
||||
"to_fiscal_year": fiscal_year.get('name'),
|
||||
"period_start_date": year_start_date,
|
||||
"period_end_date": year_end_date,
|
||||
"date_based_on": "Purchase Date",
|
||||
@ -134,7 +138,7 @@ def get_charts():
|
||||
}
|
||||
]
|
||||
|
||||
def get_number_cards():
|
||||
def get_number_cards(fiscal_year, year_start_date, year_end_date):
|
||||
return [
|
||||
{
|
||||
"name": "Total Assets",
|
||||
@ -172,14 +176,4 @@ def get_number_cards():
|
||||
"filters_json": "[]",
|
||||
"doctype": "Number Card"
|
||||
}
|
||||
]
|
||||
|
||||
def get_company_for_dashboards():
|
||||
company = frappe.defaults.get_defaults().company
|
||||
if company:
|
||||
return company
|
||||
else:
|
||||
company_list = frappe.get_list("Company")
|
||||
if company_list:
|
||||
return company_list[0].name
|
||||
return None
|
||||
]
|
@ -41,7 +41,7 @@ def assign_tasks(asset_maintenance_name, assign_to_member, maintenance_task, nex
|
||||
team_member = frappe.db.get_value('User', assign_to_member, "email")
|
||||
args = {
|
||||
'doctype' : 'Asset Maintenance',
|
||||
'assign_to' : team_member,
|
||||
'assign_to' : [team_member],
|
||||
'name' : asset_maintenance_name,
|
||||
'description' : maintenance_task,
|
||||
'date' : next_due_date
|
||||
|
@ -5,13 +5,24 @@ import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
|
||||
|
||||
def get_data():
|
||||
|
||||
fiscal_year = _get_fiscal_year(nowdate())
|
||||
|
||||
if not fiscal_year:
|
||||
return frappe._dict()
|
||||
|
||||
company = frappe.get_doc("Company", get_company_for_dashboards())
|
||||
fiscal_year_name = fiscal_year.get("name")
|
||||
start_date = str(fiscal_year.get("year_start_date"))
|
||||
end_date = str(fiscal_year.get("year_end_date"))
|
||||
|
||||
return frappe._dict({
|
||||
"dashboards": get_dashboards(),
|
||||
"charts": get_charts(),
|
||||
"number_cards": get_number_cards(),
|
||||
"charts": get_charts(company, fiscal_year_name, start_date, end_date),
|
||||
"number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
|
||||
})
|
||||
|
||||
def get_company_for_dashboards():
|
||||
@ -24,12 +35,6 @@ def get_company_for_dashboards():
|
||||
return company_list[0].name
|
||||
return None
|
||||
|
||||
company = frappe.get_doc("Company", get_company_for_dashboards())
|
||||
fiscal_year = get_fiscal_year(nowdate(), as_dict=1)
|
||||
fiscal_year_name = fiscal_year.get("name")
|
||||
start_date = str(fiscal_year.get("year_start_date"))
|
||||
end_date = str(fiscal_year.get("year_end_date"))
|
||||
|
||||
def get_dashboards():
|
||||
return [{
|
||||
"name": "Buying",
|
||||
@ -48,7 +53,7 @@ def get_dashboards():
|
||||
]
|
||||
}]
|
||||
|
||||
def get_charts():
|
||||
def get_charts(company, fiscal_year_name, start_date, end_date):
|
||||
return [
|
||||
{
|
||||
"name": "Purchase Order Analysis",
|
||||
@ -139,7 +144,7 @@ def get_charts():
|
||||
}
|
||||
]
|
||||
|
||||
def get_number_cards():
|
||||
def get_number_cards(company, fiscal_year_name, start_date, end_date):
|
||||
return [
|
||||
{
|
||||
"name": "Annual Purchase",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -71,6 +71,15 @@ class PurchaseOrder(BuyingController):
|
||||
"compare_fields": [["project", "="], ["item_code", "="],
|
||||
["uom", "="], ["conversion_factor", "="]],
|
||||
"is_child_table": True
|
||||
},
|
||||
"Material Request": {
|
||||
"ref_dn_field": "material_request",
|
||||
"compare_fields": [["company", "="]],
|
||||
},
|
||||
"Material Request Item": {
|
||||
"ref_dn_field": "material_request_item",
|
||||
"compare_fields": [["project", "="], ["item_code", "="]],
|
||||
"is_child_table": True
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -118,7 +118,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertEqual(po.get("items")[0].amount, 1400)
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
|
||||
|
||||
|
||||
|
||||
def test_add_new_item_in_update_child_qty_rate(self):
|
||||
po = create_purchase_order(do_not_save=1)
|
||||
po.items[0].qty = 4
|
||||
@ -144,7 +144,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertEquals(len(po.get('items')), 2)
|
||||
self.assertEqual(po.status, 'To Receive and Bill')
|
||||
|
||||
|
||||
|
||||
def test_remove_item_in_update_child_qty_rate(self):
|
||||
po = create_purchase_order(do_not_save=1)
|
||||
po.items[0].qty = 4
|
||||
@ -185,6 +185,23 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertEquals(len(po.get('items')), 1)
|
||||
self.assertEqual(po.status, 'To Receive and Bill')
|
||||
|
||||
def test_update_child_qty_rate_perm(self):
|
||||
po = create_purchase_order(item_code= "_Test Item", qty=4)
|
||||
|
||||
user = 'test@example.com'
|
||||
test_user = frappe.get_doc('User', user)
|
||||
test_user.add_roles("Accounts User")
|
||||
frappe.set_user(user)
|
||||
|
||||
# update qty
|
||||
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}])
|
||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
|
||||
|
||||
# add new item
|
||||
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
|
||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_update_qty(self):
|
||||
po = create_purchase_order()
|
||||
|
||||
@ -689,7 +706,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
po.save()
|
||||
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
|
||||
|
||||
|
||||
|
||||
def test_po_optional_blanket_order(self):
|
||||
"""
|
||||
Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
|
||||
|
@ -25,6 +25,7 @@ class RequestforQuotation(BuyingController):
|
||||
self.validate_duplicate_supplier()
|
||||
self.validate_supplier_list()
|
||||
validate_for_items(self)
|
||||
super(RequestforQuotation, self).set_qty_as_per_stock_uom()
|
||||
self.update_email_id()
|
||||
|
||||
def validate_duplicate_supplier(self):
|
||||
@ -278,6 +279,7 @@ def create_rfq_items(sq_doc, supplier, data):
|
||||
"description": data.description,
|
||||
"qty": data.qty,
|
||||
"rate": data.rate,
|
||||
"conversion_factor": data.conversion_factor if data.conversion_factor else None,
|
||||
"supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"),
|
||||
"warehouse": data.warehouse or '',
|
||||
"request_for_quotation_item": data.name,
|
||||
|
@ -6,12 +6,14 @@ from __future__ import unicode_literals
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
|
||||
|
||||
class TestRequestforQuotation(unittest.TestCase):
|
||||
def test_quote_status(self):
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
rfq = make_request_for_quotation()
|
||||
|
||||
self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Pending')
|
||||
@ -31,7 +33,6 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'No Quote')
|
||||
|
||||
def test_make_supplier_quotation(self):
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
rfq = make_request_for_quotation()
|
||||
|
||||
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
|
||||
@ -51,15 +52,13 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
self.assertEqual(sq1.get('items')[0].qty, 5)
|
||||
|
||||
def test_make_supplier_quotation_with_special_characters(self):
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
|
||||
frappe.delete_doc_if_exists("Supplier", "_Test Supplier '1", force=1)
|
||||
supplier = frappe.new_doc("Supplier")
|
||||
supplier.supplier_name = "_Test Supplier '1"
|
||||
supplier.supplier_group = "_Test Supplier Group"
|
||||
supplier.insert()
|
||||
|
||||
rfq = make_request_for_quotation(supplier_wt_appos)
|
||||
rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
|
||||
|
||||
sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier"))
|
||||
sq.submit()
|
||||
@ -76,7 +75,6 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
frappe.form_dict.name = None
|
||||
|
||||
def test_make_supplier_quotation_from_portal(self):
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
|
||||
rfq = make_request_for_quotation()
|
||||
rfq.get('items')[0].rate = 100
|
||||
rfq.supplier = rfq.suppliers[0].supplier
|
||||
@ -90,12 +88,34 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
self.assertEqual(supplier_quotation_doc.get('items')[0].qty, 5)
|
||||
self.assertEqual(supplier_quotation_doc.get('items')[0].amount, 500)
|
||||
|
||||
def test_make_multi_uom_supplier_quotation(self):
|
||||
item_code = "_Test Multi UOM RFQ Item"
|
||||
if not frappe.db.exists('Item', item_code):
|
||||
item = make_item(item_code, {'stock_uom': '_Test UOM'})
|
||||
row = item.append('uoms', {
|
||||
'uom': 'Kg',
|
||||
'conversion_factor': 2
|
||||
})
|
||||
row.db_update()
|
||||
|
||||
def make_request_for_quotation(supplier_data=None):
|
||||
rfq = make_request_for_quotation(item_code="_Test Multi UOM RFQ Item", uom="Kg", conversion_factor=2)
|
||||
rfq.get('items')[0].rate = 100
|
||||
rfq.supplier = rfq.suppliers[0].supplier
|
||||
|
||||
self.assertEqual(rfq.items[0].stock_qty, 10)
|
||||
|
||||
supplier_quotation_name = create_supplier_quotation(rfq)
|
||||
supplier_quotation = frappe.get_doc('Supplier Quotation', supplier_quotation_name)
|
||||
|
||||
self.assertEqual(supplier_quotation.items[0].qty, 5)
|
||||
self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
|
||||
|
||||
def make_request_for_quotation(**args):
|
||||
"""
|
||||
:param supplier_data: List containing supplier data
|
||||
"""
|
||||
supplier_data = supplier_data if supplier_data else get_supplier_data()
|
||||
args = frappe._dict(args)
|
||||
supplier_data = args.get("supplier_data") if args.get("supplier_data") else get_supplier_data()
|
||||
rfq = frappe.new_doc('Request for Quotation')
|
||||
rfq.transaction_date = nowdate()
|
||||
rfq.status = 'Draft'
|
||||
@ -106,11 +126,13 @@ def make_request_for_quotation(supplier_data=None):
|
||||
rfq.append('suppliers', data)
|
||||
|
||||
rfq.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"item_code": args.item_code or "_Test Item",
|
||||
"description": "_Test Item",
|
||||
"uom": "_Test UOM",
|
||||
"qty": 5,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"uom": args.uom or "_Test UOM",
|
||||
"stock_uom": args.stock_uom or "_Test UOM",
|
||||
"qty": args.qty or 5,
|
||||
"conversion_factor": args.conversion_factor or 1.0,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"schedule_date": nowdate()
|
||||
})
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2016-02-25 08:04:02.452958",
|
||||
"doctype": "DocType",
|
||||
@ -9,6 +10,7 @@
|
||||
"supplier_part_no",
|
||||
"column_break_3",
|
||||
"item_name",
|
||||
"schedule_date",
|
||||
"section_break_5",
|
||||
"description",
|
||||
"item_group",
|
||||
@ -18,9 +20,11 @@
|
||||
"image_view",
|
||||
"quantity",
|
||||
"qty",
|
||||
"stock_uom",
|
||||
"col_break2",
|
||||
"schedule_date",
|
||||
"uom",
|
||||
"conversion_factor",
|
||||
"stock_qty",
|
||||
"warehouse_and_reference",
|
||||
"warehouse",
|
||||
"project_name",
|
||||
@ -33,7 +37,7 @@
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 3,
|
||||
"columns": 2,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -98,7 +102,7 @@
|
||||
{
|
||||
"fieldname": "quantity",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Quantity"
|
||||
"label": "Quantity & Stock"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@ -129,12 +133,12 @@
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "UOM",
|
||||
"oldfieldname": "uom",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "UOM",
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"width": "100px"
|
||||
},
|
||||
@ -144,7 +148,7 @@
|
||||
"label": "Warehouse and Reference"
|
||||
},
|
||||
{
|
||||
"columns": 3,
|
||||
"columns": 2,
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -202,6 +206,7 @@
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"fieldname": "page_break",
|
||||
"fieldtype": "Check",
|
||||
"label": "Page Break",
|
||||
@ -219,10 +224,36 @@
|
||||
{
|
||||
"fieldname": "section_break_23",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
"options": "UOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "conversion_factor",
|
||||
"fieldtype": "Float",
|
||||
"label": "UOM Conversion Factor",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty as per Stock UOM",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-05-01 17:50:23.703801",
|
||||
"links": [],
|
||||
"modified": "2020-06-12 19:10:36.333441",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation Item",
|
||||
|
@ -4,6 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns(filters)
|
||||
@ -54,15 +55,16 @@ def get_columns(filters):
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"label": _("Description"),
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
"label": _("Item"),
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Quantity"),
|
||||
"fieldname": "quantity",
|
||||
"fieldtype": "Int",
|
||||
"fieldtype": "Float",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
@ -118,7 +120,7 @@ def get_columns(filters):
|
||||
},
|
||||
{
|
||||
"label": _("Purchase Order Amount(Company Currency)"),
|
||||
"fieldname": "purchase_order_amt_usd",
|
||||
"fieldname": "purchase_order_amt_in_company_currency",
|
||||
"fieldtype": "Float",
|
||||
"width": 140
|
||||
},
|
||||
@ -175,17 +177,17 @@ def get_data(filters):
|
||||
"requesting_site": po.warehouse,
|
||||
"requestor": po.owner,
|
||||
"material_request_no": po.material_request,
|
||||
"description": po.description,
|
||||
"quantity": po.qty,
|
||||
"item_code": po.item_code,
|
||||
"quantity": flt(po.qty),
|
||||
"unit_of_measurement": po.stock_uom,
|
||||
"status": po.status,
|
||||
"purchase_order_date": po.transaction_date,
|
||||
"purchase_order": po.parent,
|
||||
"supplier": po.supplier,
|
||||
"estimated_cost": mr_record.get('amount'),
|
||||
"actual_cost": pi_records.get(po.name),
|
||||
"purchase_order_amt": po.amount,
|
||||
"purchase_order_amt_in_company_currency": po.base_amount,
|
||||
"estimated_cost": flt(mr_record.get('amount')),
|
||||
"actual_cost": flt(pi_records.get(po.name)),
|
||||
"purchase_order_amt": flt(po.amount),
|
||||
"purchase_order_amt_in_company_currency": flt(po.base_amount),
|
||||
"expected_delivery_date": po.schedule_date,
|
||||
"actual_delivery_date": pr_records.get(po.name)
|
||||
}
|
||||
@ -198,9 +200,14 @@ def get_mapped_mr_details(conditions):
|
||||
SELECT
|
||||
par.transaction_date,
|
||||
par.per_ordered,
|
||||
par.owner,
|
||||
child.name,
|
||||
child.parent,
|
||||
child.amount
|
||||
child.amount,
|
||||
child.qty,
|
||||
child.item_code,
|
||||
child.uom,
|
||||
par.status
|
||||
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
|
||||
WHERE
|
||||
par.per_ordered>=0
|
||||
@ -217,7 +224,15 @@ def get_mapped_mr_details(conditions):
|
||||
procurement_record_details = dict(
|
||||
material_request_date=record.transaction_date,
|
||||
material_request_no=record.parent,
|
||||
estimated_cost=record.amount
|
||||
requestor=record.owner,
|
||||
item_code=record.item_code,
|
||||
estimated_cost=flt(record.amount),
|
||||
quantity=flt(record.qty),
|
||||
unit_of_measurement=record.uom,
|
||||
status=record.status,
|
||||
actual_cost=0,
|
||||
purchase_order_amt=0,
|
||||
purchase_order_amt_in_company_currency=0
|
||||
)
|
||||
procurement_record_against_mr.append(procurement_record_details)
|
||||
return mr_records, procurement_record_against_mr
|
||||
@ -259,7 +274,7 @@ def get_po_entries(conditions):
|
||||
child.warehouse,
|
||||
child.material_request,
|
||||
child.material_request_item,
|
||||
child.description,
|
||||
child.item_code,
|
||||
child.stock_uom,
|
||||
child.qty,
|
||||
child.amount,
|
||||
|
@ -37,8 +37,6 @@ class TestProcurementTracker(unittest.TestCase):
|
||||
po = make_purchase_order(mr.name)
|
||||
po.supplier = "_Test Supplier"
|
||||
po.get("items")[0].cost_center = "Main - _TPC"
|
||||
po.items[0].rate = 500.0
|
||||
po.save()
|
||||
po.submit()
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.get("items")[0].cost_center = "Main - _TPC"
|
||||
@ -55,7 +53,7 @@ class TestProcurementTracker(unittest.TestCase):
|
||||
"requesting_site": "_Test Procurement Warehouse - _TPC",
|
||||
"requestor": "Administrator",
|
||||
"material_request_no": mr.name,
|
||||
"description": '_Test Item 1',
|
||||
"item_code": '_Test Item',
|
||||
"quantity": 10.0,
|
||||
"unit_of_measurement": "_Test UOM",
|
||||
"status": "To Bill",
|
||||
@ -63,7 +61,7 @@ class TestProcurementTracker(unittest.TestCase):
|
||||
"purchase_order": po.name,
|
||||
"supplier": "_Test Supplier",
|
||||
"estimated_cost": 0.0,
|
||||
"actual_cost": None,
|
||||
"actual_cost": 0.0,
|
||||
"purchase_order_amt": po.net_total,
|
||||
"purchase_order_amt_in_company_currency": po.base_net_total,
|
||||
"expected_delivery_date": date_obj,
|
||||
|
@ -1137,8 +1137,8 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
|
||||
child_item.item_name = item.item_name
|
||||
child_item.description = item.description
|
||||
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
|
||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
|
||||
child_item.uom = item.stock_uom
|
||||
child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
|
||||
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
||||
if not child_item.warehouse:
|
||||
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
||||
@ -1157,8 +1157,8 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
|
||||
child_item.item_name = item.item_name
|
||||
child_item.description = item.description
|
||||
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
|
||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
|
||||
child_item.uom = item.stock_uom
|
||||
child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
|
||||
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
||||
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
||||
return child_item
|
||||
@ -1190,6 +1190,26 @@ def check_and_delete_children(parent, data):
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
||||
def check_permissions(doc, perm_type='create'):
|
||||
try:
|
||||
doc.check_permission(perm_type)
|
||||
except:
|
||||
action = "add" if perm_type == 'create' else "update"
|
||||
frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
|
||||
|
||||
def get_new_child_item(item_row):
|
||||
if parent_doctype == "Sales Order":
|
||||
return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
|
||||
if parent_doctype == "Purchase Order":
|
||||
return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
|
||||
|
||||
def validate_quantity(child_item, d):
|
||||
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
|
||||
frappe.throw(_("Cannot set quantity less than delivered quantity"))
|
||||
|
||||
if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
|
||||
frappe.throw(_("Cannot set quantity less than received quantity"))
|
||||
|
||||
data = json.loads(trans_items)
|
||||
|
||||
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
|
||||
@ -1201,20 +1221,29 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
new_child_flag = False
|
||||
if not d.get("docname"):
|
||||
new_child_flag = True
|
||||
if parent_doctype == "Sales Order":
|
||||
child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
|
||||
if parent_doctype == "Purchase Order":
|
||||
child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
|
||||
check_permissions(parent, 'create')
|
||||
child_item = get_new_child_item(d)
|
||||
else:
|
||||
check_permissions(parent, 'write')
|
||||
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
|
||||
if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")):
|
||||
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
|
||||
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
|
||||
prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
|
||||
|
||||
if parent_doctype == 'Sales Order':
|
||||
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
|
||||
elif parent_doctype == 'Purchase Order':
|
||||
prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
|
||||
|
||||
rate_unchanged = prev_rate == new_rate
|
||||
qty_unchanged = prev_qty == new_qty
|
||||
conversion_factor_unchanged = prev_con_fac == new_con_fac
|
||||
date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
|
||||
if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged:
|
||||
continue
|
||||
|
||||
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
|
||||
frappe.throw(_("Cannot set quantity less than delivered quantity"))
|
||||
|
||||
if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
|
||||
frappe.throw(_("Cannot set quantity less than received quantity"))
|
||||
validate_quantity(child_item, d)
|
||||
|
||||
child_item.qty = flt(d.get("qty"))
|
||||
precision = child_item.precision("rate") or 2
|
||||
@ -1225,6 +1254,18 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
else:
|
||||
child_item.rate = flt(d.get("rate"))
|
||||
|
||||
if d.get("conversion_factor"):
|
||||
if child_item.stock_uom == child_item.uom:
|
||||
child_item.conversion_factor = 1
|
||||
else:
|
||||
child_item.conversion_factor = flt(d.get('conversion_factor'))
|
||||
|
||||
if d.get("delivery_date") and parent_doctype == 'Sales Order':
|
||||
child_item.delivery_date = d.get('delivery_date')
|
||||
|
||||
if d.get("schedule_date") and parent_doctype == 'Purchase Order':
|
||||
child_item.schedule_date = d.get('schedule_date')
|
||||
|
||||
if flt(child_item.price_list_rate):
|
||||
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
||||
# if rate is greater than price_list_rate, set margin
|
||||
|
@ -349,7 +349,7 @@ class BuyingController(StockController):
|
||||
})
|
||||
|
||||
if not rm.rate:
|
||||
rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
|
||||
rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse,
|
||||
self.doctype, self.name, currency=self.company_currency, company=self.company)
|
||||
|
||||
rm.amount = qty * flt(rm.rate)
|
||||
|
@ -70,7 +70,7 @@ def validate_item_variant_attributes(item, args=None):
|
||||
|
||||
else:
|
||||
attributes_list = attribute_values.get(attribute.lower(), [])
|
||||
validate_item_attribute_value(attributes_list, attribute, value, item.name)
|
||||
validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
|
||||
|
||||
def validate_is_incremental(numeric_attribute, attribute, value, item):
|
||||
from_range = numeric_attribute.from_range
|
||||
@ -93,13 +93,20 @@ def validate_is_incremental(numeric_attribute, attribute, value, item):
|
||||
.format(attribute, from_range, to_range, increment, item),
|
||||
InvalidItemAttributeValueError, title=_('Invalid Attribute'))
|
||||
|
||||
def validate_item_attribute_value(attributes_list, attribute, attribute_value, item):
|
||||
def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
|
||||
allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
|
||||
if allow_rename_attribute_value:
|
||||
pass
|
||||
elif attribute_value not in attributes_list:
|
||||
frappe.throw(_("The value {0} is already assigned to an exisiting Item {2}.").format(
|
||||
attribute_value, attribute, item), InvalidItemAttributeValueError, title=_('Rename Not Allowed'))
|
||||
if from_variant:
|
||||
frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
|
||||
frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
|
||||
else:
|
||||
msg = _("The value {0} is already assigned to an existing Item {1}.").format(
|
||||
frappe.bold(attribute_value), frappe.bold(item))
|
||||
msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
|
||||
|
||||
frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
|
||||
|
||||
def get_attribute_values(item):
|
||||
if not frappe.flags.attribute_values:
|
||||
|
@ -19,7 +19,8 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError): pass
|
||||
class StockController(AccountsController):
|
||||
def validate(self):
|
||||
super(StockController, self).validate()
|
||||
self.validate_inspection()
|
||||
if not self.get('is_return'):
|
||||
self.validate_inspection()
|
||||
self.validate_serialized_batch()
|
||||
self.validate_customer_provided_item()
|
||||
|
||||
@ -228,7 +229,9 @@ class StockController(AccountsController):
|
||||
|
||||
def check_expense_account(self, item):
|
||||
if not item.get("expense_account"):
|
||||
frappe.throw(_("Expense Account not set for Item {0}. Please set an Expense Account for the item in the Items table").format(item.item_code))
|
||||
frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
|
||||
Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
|
||||
title=_("Expense Account Missing"))
|
||||
|
||||
else:
|
||||
is_expense_account = frappe.db.get_value("Account",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"creation": "2013-04-10 11:45:37",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"email_append_to": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"organization_lead",
|
||||
@ -448,7 +449,7 @@
|
||||
"idx": 5,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-05-11 20:27:45.868960",
|
||||
"modified": "2020-06-18 14:39:41.835416",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead",
|
||||
@ -508,8 +509,10 @@
|
||||
}
|
||||
],
|
||||
"search_fields": "lead_name,lead_owner,status",
|
||||
"sender_field": "email_id",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"subject_field": "title",
|
||||
"title_field": "title"
|
||||
}
|
@ -3,11 +3,7 @@ from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'prevdoc_docname',
|
||||
'non_standard_fieldnames': {
|
||||
'Supplier Quotation': 'opportunity',
|
||||
'Quotation': 'opportunity'
|
||||
},
|
||||
'fieldname': 'opportunity',
|
||||
'transactions': [
|
||||
{
|
||||
'items': ['Quotation', 'Supplier Quotation']
|
||||
|
@ -30,24 +30,32 @@
|
||||
"fieldname": "text",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Tweet",
|
||||
"mandatory_depends_on": "eval:doc.twitter ==1"
|
||||
"mandatory_depends_on": "eval:doc.twitter ==1",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image"
|
||||
"label": "Image",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "twitter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Twitter"
|
||||
"label": "Twitter",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "linkedin",
|
||||
"fieldtype": "Check",
|
||||
"label": "LinkedIn"
|
||||
"label": "LinkedIn",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@ -56,13 +64,17 @@
|
||||
"no_copy": 1,
|
||||
"options": "Social Media Post",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.twitter ==1",
|
||||
"fieldname": "content",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Twitter"
|
||||
"label": "Twitter",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -70,7 +82,9 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Post Status",
|
||||
"options": "\nScheduled\nPosted\nError",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -78,7 +92,9 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Twitter Post Id",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -86,68 +102,89 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "LinkedIn Post Id",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "campaign_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Campaign",
|
||||
"options": "Campaign"
|
||||
"options": "Campaign",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"label": "Share On"
|
||||
"label": "Share On",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tweet_preview",
|
||||
"fieldtype": "HTML"
|
||||
"fieldtype": "HTML",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:doc.linkedin==1",
|
||||
"fieldname": "linkedin_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "LinkedIn"
|
||||
"label": "LinkedIn",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "attachments_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Attachments"
|
||||
"label": "Attachments",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "linkedin_post",
|
||||
"fieldtype": "Text",
|
||||
"label": "Post",
|
||||
"mandatory_depends_on": "eval:doc.linkedin ==1"
|
||||
"mandatory_depends_on": "eval:doc.linkedin ==1",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "scheduled_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Scheduled Time",
|
||||
"read_only_depends_on": "eval:doc.post_status == \"Posted\""
|
||||
"read_only_depends_on": "eval:doc.post_status == \"Posted\"",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-21 15:10:04.953713",
|
||||
"modified": "2020-06-14 10:31:33.961381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Social Media Post",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@ -157,6 +194,35 @@
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
|
@ -9,6 +9,14 @@ frappe.ui.form.on('Fee Structure', {
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.set_query("academic_term", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"academic_year": frm.doc.academic_year
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("receivable_account", function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
@ -11,8 +12,8 @@
|
||||
"program",
|
||||
"student_category",
|
||||
"column_break_2",
|
||||
"academic_term",
|
||||
"academic_year",
|
||||
"academic_term",
|
||||
"section_break_4",
|
||||
"components",
|
||||
"section_break_6",
|
||||
@ -157,7 +158,8 @@
|
||||
],
|
||||
"icon": "fa fa-flag",
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-05-26 09:04:17.765758",
|
||||
"links": [],
|
||||
"modified": "2020-06-16 15:34:57.295010",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Fee Structure",
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2015-09-07 14:37:01.886859",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@ -16,26 +17,33 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "Course",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
{
|
||||
"fetch_from": "course.course_name",
|
||||
"fieldname": "course_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Course Name",
|
||||
"fetch_from": "course.course_name",
|
||||
"read_only":1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "required",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Mandatory"
|
||||
"label": "Mandatory",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-06-12 12:42:12.845972",
|
||||
"links": [],
|
||||
"modified": "2020-06-09 18:56:10.213241",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Program Course",
|
||||
@ -45,4 +53,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -1,398 +1,119 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 1,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2016-09-13 03:05:27.154713",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"allow_guest_to_view": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2016-09-13 03:05:27.154713",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"route",
|
||||
"column_break_3",
|
||||
"academic_year",
|
||||
"admission_start_date",
|
||||
"admission_end_date",
|
||||
"published",
|
||||
"enable_admission_application",
|
||||
"section_break_5",
|
||||
"program_details",
|
||||
"introduction"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 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,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "route",
|
||||
"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": "Route",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"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,
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"label": "Route",
|
||||
"no_copy": 1,
|
||||
"unique": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "application_form_route",
|
||||
"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": "Application Form Route",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Academic Year",
|
||||
"no_copy": 1,
|
||||
"options": "Academic Year",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_year",
|
||||
"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": 1,
|
||||
"label": "Academic Year",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Academic Year",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "admission_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Admission Start Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "admission_start_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Admission Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "admission_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Admission End Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "admission_end_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Admission End Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "published",
|
||||
"fieldtype": "Check",
|
||||
"label": "Publish on website"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "published",
|
||||
"fieldtype": "Check",
|
||||
"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": "Publish on website",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Eligibility and Details"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Eligibility and Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "program_details",
|
||||
"fieldtype": "Table",
|
||||
"label": "Eligibility and Details",
|
||||
"options": "Student Admission Program"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "program_details",
|
||||
"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": "Eligibility and Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Admission Program",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "introduction",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Introduction"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "introduction",
|
||||
"fieldtype": "Text Editor",
|
||||
"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": "Introduction",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"default": "0",
|
||||
"fieldname": "enable_admission_application",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Admission Application"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_published_field": "published",
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-11-10 18:57:34.570376",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2020-06-15 20:18:38.591626",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 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": "Academics User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Academics User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"route": "admissions",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
],
|
||||
"restrict_to_domain": "Education",
|
||||
"route": "admissions",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title"
|
||||
}
|
@ -43,8 +43,8 @@
|
||||
<thead>
|
||||
<tr class="active">
|
||||
<th style="width: 90px">Program/Std.</th>
|
||||
<th style="width: 170px">Minumum Age(DOB)</th>
|
||||
<th style="width: 170px">Maximum Age(DOB)</th>
|
||||
<th style="width: 170px">Minumum Age</th>
|
||||
<th style="width: 170px">Maximum Age</th>
|
||||
<th style="width: 100px">Application Fee</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -52,8 +52,8 @@
|
||||
{% for row in program_details %}
|
||||
<tr>
|
||||
<td>{{ row.program }}</td>
|
||||
<td>{{ row.minimum_age }}</td>
|
||||
<td>{{ row.maximum_age }}</td>
|
||||
<td>{{ row.min_age }}</td>
|
||||
<td>{{ row.max_age }}</td>
|
||||
<td>{{ row.application_fee }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -61,12 +61,11 @@
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- if application_form_route -%}
|
||||
{%- if doc.enable_admission_application -%}
|
||||
<br>
|
||||
<p>
|
||||
<a class='btn btn-primary'
|
||||
href='/{{ doc.application_form_route }}?new=1'>
|
||||
href='/student-applicant?new=1&student_admission={{doc.name}}'>
|
||||
{{ _("Apply Now") }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
@ -11,7 +11,7 @@ QUnit.test('Test: Student Admission', function(assert) {
|
||||
{admission_start_date: '2016-04-20'},
|
||||
{admission_end_date: '2016-05-31'},
|
||||
{title: '2016-17 Admissions'},
|
||||
{application_form_route: 'student-applicant'},
|
||||
{enable_admission_application: 1},
|
||||
{introduction: 'Test intro'},
|
||||
{program_details: [
|
||||
[
|
||||
@ -28,7 +28,7 @@ QUnit.test('Test: Student Admission', function(assert) {
|
||||
assert.ok(cur_frm.doc.admission_start_date == '2016-04-20');
|
||||
assert.ok(cur_frm.doc.admission_end_date == '2016-05-31');
|
||||
assert.ok(cur_frm.doc.title == '2016-17 Admissions');
|
||||
assert.ok(cur_frm.doc.application_form_route == 'student-applicant');
|
||||
assert.ok(cur_frm.doc.enable_admission_application == 1);
|
||||
assert.ok(cur_frm.doc.introduction == 'Test intro');
|
||||
assert.ok(cur_frm.doc.program_details[0].program == 'Standard Test', 'Program correctly selected');
|
||||
assert.ok(cur_frm.doc.program_details[0].application_fee == 1000);
|
||||
|
@ -1,237 +1,77 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2017-09-15 12:59:43.207923",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2017-09-15 12:59:43.207923",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"program",
|
||||
"min_age",
|
||||
"max_age",
|
||||
"column_break_4",
|
||||
"application_fee",
|
||||
"applicant_naming_series"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "program",
|
||||
"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": "Program",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program",
|
||||
"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": "program",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Program",
|
||||
"options": "Program",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "minimum_age",
|
||||
"fieldtype": "Date",
|
||||
"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": "Minimum Age",
|
||||
"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",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "maximum_age",
|
||||
"fieldtype": "Date",
|
||||
"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": "Maximum Age",
|
||||
"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": "application_fee",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Application Fee",
|
||||
"show_days": 1,
|
||||
"show_seconds": 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": "applicant_naming_series",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Naming Series (for Student Applicant)",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "application_fee",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Application Fee",
|
||||
"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": "min_age",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Minimum Age",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "applicant_naming_series",
|
||||
"fieldtype": "Data",
|
||||
"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": "Naming Series (for Student Applicant)",
|
||||
"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": "max_age",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Maximum Age",
|
||||
"show_days": 1,
|
||||
"show_seconds": 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": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-11-04 03:37:17.408427",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission Program",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-10 23:06:30.037404",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission Program",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"restrict_to_domain": "Education",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -6,7 +6,7 @@ from __future__ import print_function, unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import getdate, add_years, nowdate, date_diff
|
||||
|
||||
class StudentApplicant(Document):
|
||||
def autoname(self):
|
||||
@ -31,6 +31,7 @@ class StudentApplicant(Document):
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
|
||||
|
||||
if self.student_admission and self.program and self.date_of_birth:
|
||||
self.validation_from_student_admission()
|
||||
|
||||
@ -48,16 +49,16 @@ class StudentApplicant(Document):
|
||||
frappe.throw(_("Please select Student Admission which is mandatory for the paid student applicant"))
|
||||
|
||||
def validation_from_student_admission(self):
|
||||
|
||||
student_admission = get_student_admission_data(self.student_admission, self.program)
|
||||
|
||||
# different validation for minimum and maximum age so that either min/max can also work independently.
|
||||
if student_admission and student_admission.minimum_age and \
|
||||
getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
|
||||
frappe.throw(_("Not eligible for the admission in this program as per DOB"))
|
||||
if student_admission and student_admission.min_age and \
|
||||
date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.min_age)) < 0:
|
||||
frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
|
||||
|
||||
if student_admission and student_admission.maximum_age and \
|
||||
getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
|
||||
frappe.throw(_("Not eligible for the admission in this program as per DOB"))
|
||||
if student_admission and student_admission.max_age and \
|
||||
date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.max_age)) > 0:
|
||||
frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
|
||||
|
||||
|
||||
def on_payment_authorized(self, *args, **kwargs):
|
||||
@ -65,10 +66,12 @@ class StudentApplicant(Document):
|
||||
|
||||
|
||||
def get_student_admission_data(student_admission, program):
|
||||
|
||||
student_admission = frappe.db.sql("""select sa.admission_start_date, sa.admission_end_date,
|
||||
sap.program, sap.minimum_age, sap.maximum_age, sap.applicant_naming_series
|
||||
sap.program, sap.min_age, sap.max_age, sap.applicant_naming_series
|
||||
from `tabStudent Admission` sa, `tabStudent Admission Program` sap
|
||||
where sa.name = sap.parent and sa.name = %s and sap.program = %s""", (student_admission, program), as_dict=1)
|
||||
|
||||
if student_admission:
|
||||
return student_admission[0]
|
||||
else:
|
||||
|
@ -1,200 +1,248 @@
|
||||
{
|
||||
"accept_payment": 0,
|
||||
"allow_comments": 0,
|
||||
"allow_delete": 0,
|
||||
"allow_edit": 1,
|
||||
"allow_incomplete": 0,
|
||||
"allow_multiple": 1,
|
||||
"allow_print": 0,
|
||||
"amount": 0.0,
|
||||
"amount_based_on_field": 0,
|
||||
"creation": "2016-09-22 13:10:10.792735",
|
||||
"doc_type": "Student Applicant",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2017-02-21 05:44:46.022738",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "student-applicant",
|
||||
"owner": "Administrator",
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "student-applicant",
|
||||
"show_sidebar": 1,
|
||||
"sidebar_items": [],
|
||||
"success_url": "/student-applicant",
|
||||
"title": "Student Applicant",
|
||||
"accept_payment": 0,
|
||||
"allow_comments": 0,
|
||||
"allow_delete": 0,
|
||||
"allow_edit": 1,
|
||||
"allow_incomplete": 0,
|
||||
"allow_multiple": 1,
|
||||
"allow_print": 0,
|
||||
"amount": 0.0,
|
||||
"amount_based_on_field": 0,
|
||||
"creation": "2016-09-22 13:10:10.792735",
|
||||
"doc_type": "Student Applicant",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2020-06-11 22:53:45.875310",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "student-applicant",
|
||||
"owner": "Administrator",
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "student-applicant",
|
||||
"route_to_success_link": 0,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_sidebar": 1,
|
||||
"sidebar_items": [],
|
||||
"success_url": "/student-applicant",
|
||||
"title": "Student Applicant",
|
||||
"web_form_fields": [
|
||||
{
|
||||
"fieldname": "first_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "First Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "first_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "First Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "middle_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Middle Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "middle_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Middle Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "last_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Last Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "last_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Last Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Image",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Image",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Program",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Program",
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Program",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Program",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Academic Year",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Academic Year",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Academic Year",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Academic Year",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "date_of_birth",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"label": "Date of Birth",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "date_of_birth",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"label": "Date of Birth",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "blood_group",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Blood Group",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "blood_group",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Blood Group",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "student_email_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Student Email ID",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "student_email_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Student Email ID",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "student_mobile_number",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Student Mobile Number",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "student_mobile_number",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Student Mobile Number",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"default": "INDIAN",
|
||||
"fieldname": "nationality",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Nationality",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"default": "INDIAN",
|
||||
"fieldname": "nationality",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Nationality",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "address_line_1",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Address Line 1",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "address_line_1",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Address Line 1",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "address_line_2",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Address Line 2",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "address_line_2",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Address Line 2",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "pincode",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Pincode",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "pincode",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Pincode",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "guardians",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Guardians",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Student Guardian",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "guardians",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Guardians",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Student Guardian",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "siblings",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Siblings",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Student Sibling",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "siblings",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Siblings",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Student Sibling",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "student_admission",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Student Admission",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Student Admission",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
@ -241,14 +241,17 @@ def get_order_taxes(shopify_order, shopify_settings):
|
||||
return taxes
|
||||
|
||||
def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
|
||||
"""Shipping lines represents the shipping details,
|
||||
each such shipping detail consists of a list of tax_lines"""
|
||||
for shipping_charge in shipping_lines:
|
||||
taxes.append({
|
||||
"charge_type": _("Actual"),
|
||||
"account_head": get_tax_account_head(shipping_charge),
|
||||
"description": shipping_charge["title"],
|
||||
"tax_amount": shipping_charge["price"],
|
||||
"cost_center": shopify_settings.cost_center
|
||||
})
|
||||
for tax in shipping_charge.get("tax_lines"):
|
||||
taxes.append({
|
||||
"charge_type": _("Actual"),
|
||||
"account_head": get_tax_account_head(tax),
|
||||
"description": tax["title"],
|
||||
"tax_amount": tax["price"],
|
||||
"cost_center": shopify_settings.cost_center
|
||||
})
|
||||
|
||||
return taxes
|
||||
|
||||
|
@ -73,10 +73,16 @@ def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name
|
||||
|
||||
if customer_exists:
|
||||
frappe.rename_doc("Customer", old_name, customer_name)
|
||||
billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"})
|
||||
shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"})
|
||||
rename_address(billing_address, customer)
|
||||
rename_address(shipping_address, customer)
|
||||
for address_type in ("Billing", "Shipping",):
|
||||
try:
|
||||
address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": address_type})
|
||||
rename_address(address, customer)
|
||||
except (
|
||||
frappe.DoesNotExistError,
|
||||
frappe.DuplicateEntryError,
|
||||
frappe.ValidationError,
|
||||
):
|
||||
pass
|
||||
else:
|
||||
create_address(raw_billing_data, customer, "Billing")
|
||||
create_address(raw_shipping_data, customer, "Shipping")
|
||||
|
@ -8,6 +8,7 @@ import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_request_session
|
||||
from requests.exceptions import HTTPError
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from erpnext.erpnext_integrations.utils import get_webhook_address
|
||||
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
|
||||
@ -29,19 +30,24 @@ class ShopifySettings(Document):
|
||||
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
|
||||
# url = get_shopify_url('admin/webhooks.json', self)
|
||||
created_webhooks = [d.method for d in self.webhooks]
|
||||
url = get_shopify_url('admin/api/2019-04/webhooks.json', self)
|
||||
url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
|
||||
for method in webhooks:
|
||||
session = get_request_session()
|
||||
try:
|
||||
d = session.post(url, data=json.dumps({
|
||||
res = session.post(url, data=json.dumps({
|
||||
"webhook": {
|
||||
"topic": method,
|
||||
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
|
||||
"format": "json"
|
||||
}
|
||||
}), headers=get_header(self))
|
||||
d.raise_for_status()
|
||||
self.update_webhook_table(method, d.json())
|
||||
res.raise_for_status()
|
||||
self.update_webhook_table(method, res.json())
|
||||
|
||||
except HTTPError as e:
|
||||
error_message = res.json().get('errors', e)
|
||||
make_shopify_log(status="Warning", exception=error_message, rollback=True)
|
||||
|
||||
except Exception as e:
|
||||
make_shopify_log(status="Warning", exception=e, rollback=True)
|
||||
|
||||
@ -50,13 +56,18 @@ class ShopifySettings(Document):
|
||||
deleted_webhooks = []
|
||||
|
||||
for d in self.webhooks:
|
||||
url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self)
|
||||
url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
|
||||
try:
|
||||
res = session.delete(url, headers=get_header(self))
|
||||
res.raise_for_status()
|
||||
deleted_webhooks.append(d)
|
||||
|
||||
except HTTPError as e:
|
||||
error_message = res.json().get('errors', e)
|
||||
make_shopify_log(status="Warning", exception=error_message, rollback=True)
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error(message=frappe.get_traceback(), title=e)
|
||||
frappe.log_error(message=e, title='Shopify Webhooks Issue')
|
||||
|
||||
for d in deleted_webhooks:
|
||||
self.remove(d)
|
||||
@ -125,4 +136,3 @@ def setup_custom_fields():
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields)
|
||||
|
||||
|
@ -8,7 +8,7 @@ from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings impo
|
||||
shopify_variants_attr_list = ["option1", "option2", "option3"]
|
||||
|
||||
def sync_item_from_shopify(shopify_settings, item):
|
||||
url = get_shopify_url("admin/api/2019-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
|
||||
url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
|
||||
session = get_request_session()
|
||||
|
||||
try:
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Tally Migration', {
|
||||
frappe.provide("erpnext.tally_migration");
|
||||
|
||||
frappe.ui.form.on("Tally Migration", {
|
||||
onload: function (frm) {
|
||||
let reload_status = true;
|
||||
frappe.realtime.on("tally_migration_progress_update", function (data) {
|
||||
@ -35,7 +37,17 @@ frappe.ui.form.on('Tally Migration', {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.trigger("show_logs_preview");
|
||||
erpnext.tally_migration.failed_import_log = JSON.parse(frm.doc.failed_import_log);
|
||||
erpnext.tally_migration.fixed_errors_log = JSON.parse(frm.doc.fixed_errors_log);
|
||||
|
||||
["default_round_off_account", "default_warehouse", "default_cost_center"].forEach(account => {
|
||||
frm.toggle_reqd(account, frm.doc.is_master_data_imported === 1)
|
||||
frm.toggle_enable(account, frm.doc.is_day_book_data_processed != 1)
|
||||
})
|
||||
|
||||
if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
|
||||
if (frm.doc.is_master_data_processed) {
|
||||
if (frm.doc.status != "Importing Master Data") {
|
||||
@ -47,6 +59,7 @@ frappe.ui.form.on('Tally Migration', {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.doc.day_book_data && !frm.doc.is_day_book_data_imported) {
|
||||
if (frm.doc.is_day_book_data_processed) {
|
||||
if (frm.doc.status != "Importing Day Book Data") {
|
||||
@ -59,6 +72,17 @@ frappe.ui.form.on('Tally Migration', {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
erpnext_company: function (frm) {
|
||||
frappe.db.exists("Company", frm.doc.erpnext_company).then(exists => {
|
||||
if (exists) {
|
||||
frappe.msgprint(
|
||||
__("Company {0} already exists. Continuing will overwrite the Company and Chart of Accounts", [frm.doc.erpnext_company]),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
add_button: function (frm, label, method) {
|
||||
frm.add_custom_button(
|
||||
label,
|
||||
@ -71,5 +95,255 @@ frappe.ui.form.on('Tally Migration', {
|
||||
frm.reload_doc();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
render_html_table(frm, shown_logs, hidden_logs, field) {
|
||||
if (shown_logs && shown_logs.length > 0) {
|
||||
frm.toggle_display(field, true);
|
||||
} else {
|
||||
frm.toggle_display(field, false);
|
||||
return
|
||||
}
|
||||
let rows = erpnext.tally_migration.get_html_rows(shown_logs, field);
|
||||
let rows_head, table_caption;
|
||||
|
||||
let table_footer = (hidden_logs && (hidden_logs.length > 0)) ? `<tr class="text-muted">
|
||||
<td colspan="4">And ${hidden_logs.length} more others</td>
|
||||
</tr>`: "";
|
||||
|
||||
if (field === "fixed_error_log_preview") {
|
||||
rows_head = `<th width="75%">${__("Meta Data")}</th>
|
||||
<th width="10%">${__("Unresolve")}</th>`
|
||||
table_caption = "Resolved Issues"
|
||||
} else {
|
||||
rows_head = `<th width="75%">${__("Error Message")}</th>
|
||||
<th width="10%">${__("Create")}</th>`
|
||||
table_caption = "Error Log"
|
||||
}
|
||||
|
||||
frm.get_field(field).$wrapper.html(`
|
||||
<table class="table table-bordered">
|
||||
<caption>${table_caption}</caption>
|
||||
<tr class="text-muted">
|
||||
<th width="5%">${__("#")}</th>
|
||||
<th width="10%">${__("DocType")}</th>
|
||||
${rows_head}
|
||||
</tr>
|
||||
${rows}
|
||||
${table_footer}
|
||||
</table>
|
||||
`);
|
||||
},
|
||||
|
||||
show_error_summary(frm) {
|
||||
let summary = erpnext.tally_migration.failed_import_log.reduce((summary, row) => {
|
||||
if (row.doc) {
|
||||
if (summary[row.doc.doctype]) {
|
||||
summary[row.doc.doctype] += 1;
|
||||
} else {
|
||||
summary[row.doc.doctype] = 1;
|
||||
}
|
||||
}
|
||||
return summary
|
||||
}, {});
|
||||
console.table(summary);
|
||||
},
|
||||
|
||||
show_logs_preview(frm) {
|
||||
let empty = "[]";
|
||||
let import_log = frm.doc.failed_import_log || empty;
|
||||
let completed_log = frm.doc.fixed_errors_log || empty;
|
||||
let render_section = !(import_log === completed_log && import_log === empty);
|
||||
|
||||
frm.toggle_display("import_log_section", render_section);
|
||||
if (render_section) {
|
||||
frm.trigger("show_error_summary");
|
||||
frm.trigger("show_errored_import_log");
|
||||
frm.trigger("show_fixed_errors_log");
|
||||
}
|
||||
},
|
||||
|
||||
show_errored_import_log(frm) {
|
||||
let import_log = erpnext.tally_migration.failed_import_log;
|
||||
let logs = import_log.slice(0, 20);
|
||||
let hidden_logs = import_log.slice(20);
|
||||
|
||||
frm.events.render_html_table(frm, logs, hidden_logs, "failed_import_preview");
|
||||
},
|
||||
|
||||
show_fixed_errors_log(frm) {
|
||||
let completed_log = erpnext.tally_migration.fixed_errors_log;
|
||||
let logs = completed_log.slice(0, 20);
|
||||
let hidden_logs = completed_log.slice(20);
|
||||
|
||||
frm.events.render_html_table(frm, logs, hidden_logs, "fixed_error_log_preview");
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.tally_migration.getError = (traceback) => {
|
||||
/* Extracts the Error Message from the Python Traceback or Solved error */
|
||||
let is_multiline = traceback.trim().indexOf("\n") != -1;
|
||||
let message;
|
||||
|
||||
if (is_multiline) {
|
||||
let exc_error_idx = traceback.trim().lastIndexOf("\n") + 1
|
||||
let error_line = traceback.substr(exc_error_idx)
|
||||
let split_str_idx = (error_line.indexOf(':') > 0) ? error_line.indexOf(':') + 1 : 0;
|
||||
message = error_line.slice(split_str_idx).trim();
|
||||
} else {
|
||||
message = traceback;
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
erpnext.tally_migration.cleanDoc = (obj) => {
|
||||
/* Strips all null and empty values of your JSON object */
|
||||
let temp = obj;
|
||||
$.each(temp, function(key, value){
|
||||
if (value === "" || value === null){
|
||||
delete obj[key];
|
||||
} else if (Object.prototype.toString.call(value) === '[object Object]') {
|
||||
erpnext.tally_migration.cleanDoc(value);
|
||||
} else if ($.isArray(value)) {
|
||||
$.each(value, function (k,v) { erpnext.tally_migration.cleanDoc(v); });
|
||||
}
|
||||
});
|
||||
return temp;
|
||||
}
|
||||
|
||||
erpnext.tally_migration.unresolve = (document) => {
|
||||
/* Mark document migration as unresolved ie. move to failed error log */
|
||||
let frm = cur_frm;
|
||||
let failed_log = erpnext.tally_migration.failed_import_log;
|
||||
let fixed_log = erpnext.tally_migration.fixed_errors_log;
|
||||
|
||||
let modified_fixed_log = fixed_log.filter(row => {
|
||||
if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
|
||||
return row
|
||||
}
|
||||
});
|
||||
|
||||
failed_log.push({ doc: document, exc: `Marked unresolved on ${Date()}` });
|
||||
|
||||
frm.doc.failed_import_log = JSON.stringify(failed_log);
|
||||
frm.doc.fixed_errors_log = JSON.stringify(modified_fixed_log);
|
||||
|
||||
frm.dirty();
|
||||
frm.save();
|
||||
}
|
||||
|
||||
erpnext.tally_migration.resolve = (document) => {
|
||||
/* Mark document migration as resolved ie. move to fixed error log */
|
||||
let frm = cur_frm;
|
||||
let failed_log = erpnext.tally_migration.failed_import_log;
|
||||
let fixed_log = erpnext.tally_migration.fixed_errors_log;
|
||||
|
||||
let modified_failed_log = failed_log.filter(row => {
|
||||
if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
|
||||
return row
|
||||
}
|
||||
});
|
||||
fixed_log.push({ doc: document, exc: `Solved on ${Date()}` });
|
||||
|
||||
frm.doc.failed_import_log = JSON.stringify(modified_failed_log);
|
||||
frm.doc.fixed_errors_log = JSON.stringify(fixed_log);
|
||||
|
||||
frm.dirty();
|
||||
frm.save();
|
||||
}
|
||||
|
||||
erpnext.tally_migration.create_new_doc = (document) => {
|
||||
/* Mark as resolved and create new document */
|
||||
erpnext.tally_migration.resolve(document);
|
||||
return frappe.call({
|
||||
type: "POST",
|
||||
method: 'erpnext.erpnext_integrations.doctype.tally_migration.tally_migration.new_doc',
|
||||
args: {
|
||||
document
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
frappe.model.sync(r.message);
|
||||
frappe.get_doc(r.message.doctype, r.message.name).__run_link_triggers = true;
|
||||
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
erpnext.tally_migration.get_html_rows = (logs, field) => {
|
||||
let index = 0;
|
||||
let rows = logs
|
||||
.map(({ doc, exc }) => {
|
||||
let id = frappe.dom.get_unique_id();
|
||||
let traceback = exc;
|
||||
|
||||
let error_message = erpnext.tally_migration.getError(traceback);
|
||||
index++;
|
||||
|
||||
let show_traceback = `
|
||||
<button class="btn btn-default btn-xs m-3" type="button" data-toggle="collapse" data-target="#${id}-traceback" aria-expanded="false" aria-controls="${id}-traceback">
|
||||
${__("Show Traceback")}
|
||||
</button>
|
||||
<div class="collapse margin-top" id="${id}-traceback">
|
||||
<div class="well">
|
||||
<pre style="font-size: smaller;">${traceback}</pre>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
let show_doc = `
|
||||
<button class='btn btn-default btn-xs m-3' type='button' data-toggle='collapse' data-target='#${id}-doc' aria-expanded='false' aria-controls='${id}-doc'>
|
||||
${__("Show Document")}
|
||||
</button>
|
||||
<div class="collapse margin-top" id="${id}-doc">
|
||||
<div class="well">
|
||||
<pre style="font-size: smaller;">${JSON.stringify(erpnext.tally_migration.cleanDoc(doc), null, 1)}</pre>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
let create_button = `
|
||||
<button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.create_new_doc(${JSON.stringify(doc)})'>
|
||||
${__("Create Document")}
|
||||
</button>`
|
||||
|
||||
let mark_as_unresolved = `
|
||||
<button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.unresolve(${JSON.stringify(doc)})'>
|
||||
${__("Mark as unresolved")}
|
||||
</button>`
|
||||
|
||||
if (field === "fixed_error_log_preview") {
|
||||
return `<tr>
|
||||
<td>${index}</td>
|
||||
<td>
|
||||
<div>${doc.doctype}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>${error_message}</div>
|
||||
<div>${show_doc}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>${mark_as_unresolved}</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
} else {
|
||||
return `<tr>
|
||||
<td>${index}</td>
|
||||
<td>
|
||||
<div>${doc.doctype}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>${error_message}</div>
|
||||
<div>${show_traceback}</div>
|
||||
<div>${show_doc}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>${create_button}</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
}).join("");
|
||||
|
||||
return rows
|
||||
}
|
@ -28,14 +28,19 @@
|
||||
"vouchers",
|
||||
"accounts_section",
|
||||
"default_warehouse",
|
||||
"round_off_account",
|
||||
"default_round_off_account",
|
||||
"column_break_21",
|
||||
"default_cost_center",
|
||||
"day_book_section",
|
||||
"day_book_data",
|
||||
"column_break_27",
|
||||
"is_day_book_data_processed",
|
||||
"is_day_book_data_imported"
|
||||
"is_day_book_data_imported",
|
||||
"import_log_section",
|
||||
"failed_import_log",
|
||||
"fixed_errors_log",
|
||||
"failed_import_preview",
|
||||
"fixed_error_log_preview"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -57,6 +62,7 @@
|
||||
"fieldname": "tally_creditors_account",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tally Creditors Account",
|
||||
"read_only_depends_on": "eval:doc.is_master_data_processed==1",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -69,6 +75,7 @@
|
||||
"fieldname": "tally_debtors_account",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tally Debtors Account",
|
||||
"read_only_depends_on": "eval:doc.is_master_data_processed==1",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -92,7 +99,7 @@
|
||||
"fieldname": "erpnext_company",
|
||||
"fieldtype": "Data",
|
||||
"label": "ERPNext Company",
|
||||
"read_only_depends_on": "eval:doc.is_master_data_processed == 1"
|
||||
"read_only_depends_on": "eval:doc.is_master_data_processed==1"
|
||||
},
|
||||
{
|
||||
"fieldname": "processed_files_section",
|
||||
@ -136,6 +143,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "is_master_data_imported",
|
||||
"description": "The accounts are set by the system automatically but do confirm these defaults",
|
||||
"fieldname": "accounts_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounts"
|
||||
@ -146,12 +154,6 @@
|
||||
"label": "Default Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "round_off_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Round Off Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break"
|
||||
@ -212,11 +214,47 @@
|
||||
"fieldname": "default_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default UOM",
|
||||
"options": "UOM"
|
||||
"options": "UOM",
|
||||
"read_only_depends_on": "eval:doc.is_master_data_imported==1"
|
||||
},
|
||||
{
|
||||
"default": "[]",
|
||||
"fieldname": "failed_import_log",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 1,
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "failed_import_preview",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Failed Import Log"
|
||||
},
|
||||
{
|
||||
"fieldname": "import_log_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Import Log"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_round_off_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Round Off Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"default": "[]",
|
||||
"fieldname": "fixed_errors_log",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 1,
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "fixed_error_log_preview",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Fixed Error Log"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-16 13:03:28.894919",
|
||||
"modified": "2020-04-28 00:29:18.039826",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Tally Migration",
|
||||
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import zipfile
|
||||
from decimal import Decimal
|
||||
@ -15,18 +16,34 @@ from bs4 import BeautifulSoup as bs
|
||||
import frappe
|
||||
from erpnext import encode_company_abbr
|
||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
|
||||
from erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer import unset_existing_data
|
||||
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import getseries, revert_series_if_last
|
||||
from frappe.utils.data import format_datetime
|
||||
|
||||
|
||||
PRIMARY_ACCOUNT = "Primary"
|
||||
VOUCHER_CHUNK_SIZE = 500
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def new_doc(document):
|
||||
document = json.loads(document)
|
||||
doctype = document.pop("doctype")
|
||||
document.pop("name", None)
|
||||
doc = frappe.new_doc(doctype)
|
||||
doc.update(document)
|
||||
|
||||
return doc
|
||||
|
||||
class TallyMigration(Document):
|
||||
def validate(self):
|
||||
failed_import_log = json.loads(self.failed_import_log)
|
||||
sorted_failed_import_log = sorted(failed_import_log, key=lambda row: row["doc"]["creation"])
|
||||
self.failed_import_log = json.dumps(sorted_failed_import_log)
|
||||
|
||||
def autoname(self):
|
||||
if not self.name:
|
||||
self.name = "Tally Migration on " + format_datetime(self.creation)
|
||||
@ -65,9 +82,17 @@ class TallyMigration(Document):
|
||||
"attached_to_name": self.name,
|
||||
"content": json.dumps(value),
|
||||
"is_private": True
|
||||
}).insert()
|
||||
})
|
||||
try:
|
||||
f.insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
setattr(self, key, f.file_url)
|
||||
|
||||
def set_account_defaults(self):
|
||||
self.default_cost_center, self.default_round_off_account = frappe.db.get_value("Company", self.erpnext_company, ["cost_center", "round_off_account"])
|
||||
self.default_warehouse = frappe.db.get_value("Stock Settings", "Stock Settings", "default_warehouse")
|
||||
|
||||
def _process_master_data(self):
|
||||
def get_company_name(collection):
|
||||
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
|
||||
@ -84,7 +109,11 @@ class TallyMigration(Document):
|
||||
children, parents = get_children_and_parent_dict(accounts)
|
||||
group_set = [acc[1] for acc in accounts if acc[2]]
|
||||
children, customers, suppliers = remove_parties(parents, children, group_set)
|
||||
coa = traverse({}, children, roots, roots, group_set)
|
||||
|
||||
try:
|
||||
coa = traverse({}, children, roots, roots, group_set)
|
||||
except RecursionError:
|
||||
self.log(_("Error occured while parsing Chart of Accounts: Please make sure that no two accounts have the same name"))
|
||||
|
||||
for account in coa:
|
||||
coa[account]["root_type"] = root_type_map[account]
|
||||
@ -126,14 +155,18 @@ class TallyMigration(Document):
|
||||
def remove_parties(parents, children, group_set):
|
||||
customers, suppliers = set(), set()
|
||||
for account in parents:
|
||||
found = False
|
||||
if self.tally_creditors_account in parents[account]:
|
||||
children.pop(account, None)
|
||||
found = True
|
||||
if account not in group_set:
|
||||
suppliers.add(account)
|
||||
elif self.tally_debtors_account in parents[account]:
|
||||
children.pop(account, None)
|
||||
if self.tally_debtors_account in parents[account]:
|
||||
found = True
|
||||
if account not in group_set:
|
||||
customers.add(account)
|
||||
if found:
|
||||
children.pop(account, None)
|
||||
|
||||
return children, customers, suppliers
|
||||
|
||||
def traverse(tree, children, accounts, roots, group_set):
|
||||
@ -151,6 +184,7 @@ class TallyMigration(Document):
|
||||
parties, addresses = [], []
|
||||
for account in collection.find_all("LEDGER"):
|
||||
party_type = None
|
||||
links = []
|
||||
if account.NAME.string.strip() in customers:
|
||||
party_type = "Customer"
|
||||
parties.append({
|
||||
@ -161,7 +195,9 @@ class TallyMigration(Document):
|
||||
"territory": "All Territories",
|
||||
"customer_type": "Individual",
|
||||
})
|
||||
elif account.NAME.string.strip() in suppliers:
|
||||
links.append({"link_doctype": party_type, "link_name": account["NAME"]})
|
||||
|
||||
if account.NAME.string.strip() in suppliers:
|
||||
party_type = "Supplier"
|
||||
parties.append({
|
||||
"doctype": party_type,
|
||||
@ -170,6 +206,8 @@ class TallyMigration(Document):
|
||||
"supplier_group": "All Supplier Groups",
|
||||
"supplier_type": "Individual",
|
||||
})
|
||||
links.append({"link_doctype": party_type, "link_name": account["NAME"]})
|
||||
|
||||
if party_type:
|
||||
address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
|
||||
addresses.append({
|
||||
@ -183,7 +221,7 @@ class TallyMigration(Document):
|
||||
"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
|
||||
"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
|
||||
"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
|
||||
"links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
|
||||
"links": links
|
||||
})
|
||||
return parties, addresses
|
||||
|
||||
@ -242,12 +280,18 @@ class TallyMigration(Document):
|
||||
def create_company_and_coa(coa_file_url):
|
||||
coa_file = frappe.get_doc("File", {"file_url": coa_file_url})
|
||||
frappe.local.flags.ignore_chart_of_accounts = True
|
||||
company = frappe.get_doc({
|
||||
"doctype": "Company",
|
||||
"company_name": self.erpnext_company,
|
||||
"default_currency": "INR",
|
||||
"enable_perpetual_inventory": 0,
|
||||
}).insert()
|
||||
|
||||
try:
|
||||
company = frappe.get_doc({
|
||||
"doctype": "Company",
|
||||
"company_name": self.erpnext_company,
|
||||
"default_currency": "INR",
|
||||
"enable_perpetual_inventory": 0,
|
||||
}).insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
company = frappe.get_doc("Company", self.erpnext_company)
|
||||
unset_existing_data(self.erpnext_company)
|
||||
|
||||
frappe.local.flags.ignore_chart_of_accounts = False
|
||||
create_charts(company.name, custom_chart=json.loads(coa_file.get_content()))
|
||||
company.create_default_warehouses()
|
||||
@ -256,36 +300,35 @@ class TallyMigration(Document):
|
||||
parties_file = frappe.get_doc("File", {"file_url": parties_file_url})
|
||||
for party in json.loads(parties_file.get_content()):
|
||||
try:
|
||||
frappe.get_doc(party).insert()
|
||||
party_doc = frappe.get_doc(party)
|
||||
party_doc.insert()
|
||||
except:
|
||||
self.log(party)
|
||||
self.log(party_doc)
|
||||
addresses_file = frappe.get_doc("File", {"file_url": addresses_file_url})
|
||||
for address in json.loads(addresses_file.get_content()):
|
||||
try:
|
||||
frappe.get_doc(address).insert(ignore_mandatory=True)
|
||||
address_doc = frappe.get_doc(address)
|
||||
address_doc.insert(ignore_mandatory=True)
|
||||
except:
|
||||
try:
|
||||
gstin = address.pop("gstin", None)
|
||||
frappe.get_doc(address).insert(ignore_mandatory=True)
|
||||
self.log({"address": address, "message": "Invalid GSTIN: {}. Address was created without GSTIN".format(gstin)})
|
||||
except:
|
||||
self.log(address)
|
||||
self.log(address_doc)
|
||||
|
||||
def create_items_uoms(items_file_url, uoms_file_url):
|
||||
uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
|
||||
for uom in json.loads(uoms_file.get_content()):
|
||||
if not frappe.db.exists(uom):
|
||||
try:
|
||||
frappe.get_doc(uom).insert()
|
||||
uom_doc = frappe.get_doc(uom)
|
||||
uom_doc.insert()
|
||||
except:
|
||||
self.log(uom)
|
||||
self.log(uom_doc)
|
||||
|
||||
items_file = frappe.get_doc("File", {"file_url": items_file_url})
|
||||
for item in json.loads(items_file.get_content()):
|
||||
try:
|
||||
frappe.get_doc(item).insert()
|
||||
item_doc = frappe.get_doc(item)
|
||||
item_doc.insert()
|
||||
except:
|
||||
self.log(item)
|
||||
self.log(item_doc)
|
||||
|
||||
try:
|
||||
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
|
||||
@ -299,10 +342,13 @@ class TallyMigration(Document):
|
||||
|
||||
self.publish("Import Master Data", _("Done"), 4, 4)
|
||||
|
||||
self.set_account_defaults()
|
||||
self.is_master_data_imported = 1
|
||||
frappe.db.commit()
|
||||
|
||||
except:
|
||||
self.publish("Import Master Data", _("Process Failed"), -1, 5)
|
||||
frappe.db.rollback()
|
||||
self.log()
|
||||
|
||||
finally:
|
||||
@ -323,7 +369,9 @@ class TallyMigration(Document):
|
||||
processed_voucher = function(voucher)
|
||||
if processed_voucher:
|
||||
vouchers.append(processed_voucher)
|
||||
frappe.db.commit()
|
||||
except:
|
||||
frappe.db.rollback()
|
||||
self.log(voucher)
|
||||
return vouchers
|
||||
|
||||
@ -349,6 +397,7 @@ class TallyMigration(Document):
|
||||
journal_entry = {
|
||||
"doctype": "Journal Entry",
|
||||
"tally_guid": voucher.GUID.string.strip(),
|
||||
"tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
|
||||
"posting_date": voucher.DATE.string.strip(),
|
||||
"company": self.erpnext_company,
|
||||
"accounts": accounts,
|
||||
@ -377,6 +426,7 @@ class TallyMigration(Document):
|
||||
"doctype": doctype,
|
||||
party_field: voucher.PARTYNAME.string.strip(),
|
||||
"tally_guid": voucher.GUID.string.strip(),
|
||||
"tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
|
||||
"posting_date": voucher.DATE.string.strip(),
|
||||
"due_date": voucher.DATE.string.strip(),
|
||||
"items": get_voucher_items(voucher, doctype),
|
||||
@ -468,14 +518,21 @@ class TallyMigration(Document):
|
||||
oldest_year = new_year
|
||||
|
||||
def create_custom_fields(doctypes):
|
||||
for doctype in doctypes:
|
||||
df = {
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "tally_guid",
|
||||
"read_only": 1,
|
||||
"label": "Tally GUID"
|
||||
}
|
||||
create_custom_field(doctype, df)
|
||||
tally_guid_df = {
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "tally_guid",
|
||||
"read_only": 1,
|
||||
"label": "Tally GUID"
|
||||
}
|
||||
tally_voucher_no_df = {
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "tally_voucher_no",
|
||||
"read_only": 1,
|
||||
"label": "Tally Voucher Number"
|
||||
}
|
||||
for df in [tally_guid_df, tally_voucher_no_df]:
|
||||
for doctype in doctypes:
|
||||
create_custom_field(doctype, df)
|
||||
|
||||
def create_price_list():
|
||||
frappe.get_doc({
|
||||
@ -490,7 +547,7 @@ class TallyMigration(Document):
|
||||
try:
|
||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
|
||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
|
||||
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
|
||||
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.default_round_off_account)
|
||||
|
||||
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
|
||||
vouchers = json.loads(vouchers_file.get_content())
|
||||
@ -521,11 +578,14 @@ class TallyMigration(Document):
|
||||
|
||||
for index, voucher in enumerate(chunk, start=start):
|
||||
try:
|
||||
doc = frappe.get_doc(voucher).insert()
|
||||
doc.submit()
|
||||
voucher_doc = frappe.get_doc(voucher)
|
||||
voucher_doc.insert()
|
||||
voucher_doc.submit()
|
||||
self.publish("Importing Vouchers", _("{} of {}").format(index, total), index, total)
|
||||
frappe.db.commit()
|
||||
except:
|
||||
self.log(voucher)
|
||||
frappe.db.rollback()
|
||||
self.log(voucher_doc)
|
||||
|
||||
if is_last:
|
||||
self.status = ""
|
||||
@ -551,9 +611,22 @@ class TallyMigration(Document):
|
||||
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
|
||||
|
||||
def log(self, data=None):
|
||||
data = data or self.status
|
||||
message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
|
||||
return frappe.log_error(title="Tally Migration Error", message=message)
|
||||
if isinstance(data, frappe.model.document.Document):
|
||||
if sys.exc_info()[1].__class__ != frappe.DuplicateEntryError:
|
||||
failed_import_log = json.loads(self.failed_import_log)
|
||||
doc = data.as_dict()
|
||||
failed_import_log.append({
|
||||
"doc": doc,
|
||||
"exc": traceback.format_exc()
|
||||
})
|
||||
self.failed_import_log = json.dumps(failed_import_log, separators=(',', ':'))
|
||||
self.save()
|
||||
frappe.db.commit()
|
||||
|
||||
else:
|
||||
data = data or self.status
|
||||
message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
|
||||
return frappe.log_error(title="Tally Migration Error", message=message)
|
||||
|
||||
def set_status(self, status=""):
|
||||
self.status = status
|
||||
|
@ -70,6 +70,7 @@ def validate_service_item(item, msg):
|
||||
if frappe.db.get_value('Item', item, 'is_stock_item'):
|
||||
frappe.throw(_(msg))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
|
||||
fields = ['name', 'practitioner_name', 'mobile_phone']
|
||||
|
||||
|
@ -15,6 +15,7 @@ class TestInpatientRecord(unittest.TestCase):
|
||||
patient = create_patient()
|
||||
# Schedule Admission
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||
self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||
@ -26,7 +27,7 @@ class TestInpatientRecord(unittest.TestCase):
|
||||
self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||
|
||||
# Discharge
|
||||
schedule_discharge(patient=patient)
|
||||
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||
|
||||
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
@ -44,8 +45,10 @@ class TestInpatientRecord(unittest.TestCase):
|
||||
patient = create_patient()
|
||||
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
ip_record_new = create_inpatient(patient)
|
||||
ip_record_new.expected_length_of_stay = 0
|
||||
self.assertRaises(frappe.ValidationError, ip_record_new.save)
|
||||
|
||||
service_unit = get_healthcare_service_unit()
|
||||
|
@ -41,7 +41,7 @@ boot_session = "erpnext.startup.boot.boot_session"
|
||||
notification_config = "erpnext.startup.notifications.get_notification_config"
|
||||
get_help_messages = "erpnext.utilities.activation.get_help_messages"
|
||||
leaderboards = "erpnext.startup.leaderboard.get_leaderboards"
|
||||
|
||||
filters_config = "erpnext.startup.filters.get_filters_config"
|
||||
|
||||
on_session_creation = [
|
||||
"erpnext.portal.utils.create_customer_or_supplier",
|
||||
@ -238,6 +238,9 @@ doc_events = {
|
||||
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
|
||||
"on_trash": "erpnext.regional.check_deletion_permission"
|
||||
},
|
||||
"Purchase Invoice": {
|
||||
"on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries"
|
||||
},
|
||||
"Payment Entry": {
|
||||
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"],
|
||||
"on_trash": "erpnext.regional.check_deletion_permission"
|
||||
@ -320,8 +323,7 @@ scheduler_events = {
|
||||
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans"
|
||||
],
|
||||
"monthly_long": [
|
||||
"erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income",
|
||||
"erpnext.accounts.deferred_revenue.convert_deferred_expense_to_expense",
|
||||
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
|
||||
"erpnext.hr.utils.allocate_earned_leaves",
|
||||
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans"
|
||||
]
|
||||
|
@ -18,7 +18,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Leaves",
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Application\",\n \"name\": \"Leave Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Allocation\",\n \"name\": \"Leave Allocation\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Type\"\n ],\n \"label\": \"Leave Policy\",\n \"name\": \"Leave Policy\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Period\",\n \"name\": \"Leave Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Type\",\n \"name\": \"Leave Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Holiday List\",\n \"name\": \"Holiday List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Compensatory Leave Request\",\n \"name\": \"Compensatory Leave Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Encashment\",\n \"name\": \"Leave Encashment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Block List\",\n \"name\": \"Leave Block List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Application\"\n ],\n \"doctype\": \"Leave Application\",\n \"is_query_report\": true,\n \"label\": \"Employee Leave Balance\",\n \"name\": \"Employee Leave Balance\",\n \"type\": \"report\"\n }\n]"
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Application\",\n \"name\": \"Leave Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Allocation\",\n \"name\": \"Leave Allocation\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Type\"\n ],\n \"label\": \"Leave Policy\",\n \"name\": \"Leave Policy\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Period\",\n \"name\": \"Leave Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Type\",\n \"name\": \"Leave Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Holiday List\",\n \"name\": \"Holiday List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Compensatory Leave Request\",\n \"name\": \"Compensatory Leave Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Encashment\",\n \"name\": \"Leave Encashment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Block List\",\n \"name\": \"Leave Block List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Application\"\n ],\n \"doctype\": \"Leave Application\",\n \"is_query_report\": true,\n \"label\": \"Employee Leave Balance\",\n \"name\": \"Employee Leave Balance\",\n \"type\": \"report\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -93,7 +93,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "HR",
|
||||
"modified": "2020-05-28 13:36:07.710600",
|
||||
"modified": "2020-06-16 19:20:50.976045",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR",
|
||||
@ -126,7 +126,7 @@
|
||||
},
|
||||
{
|
||||
"label": "Salary Structure",
|
||||
"link_to": "Payroll Entry",
|
||||
"link_to": "Salary Structure",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
|
@ -19,11 +19,15 @@
|
||||
"attendance_date",
|
||||
"company",
|
||||
"department",
|
||||
"shift",
|
||||
"attendance_request",
|
||||
"amended_from",
|
||||
"details_section",
|
||||
"shift",
|
||||
"in_time",
|
||||
"out_time",
|
||||
"column_break_18",
|
||||
"late_entry",
|
||||
"early_exit"
|
||||
"early_exit",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -172,13 +176,36 @@
|
||||
"fieldname": "early_exit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Early Exit"
|
||||
},
|
||||
{
|
||||
"fieldname": "details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "shift",
|
||||
"fieldname": "in_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "In Time",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "shift",
|
||||
"fieldname": "out_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Out Time",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-ok",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-11 11:40:14.319496",
|
||||
"modified": "2020-05-29 13:51:37.177231",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Attendance",
|
||||
|
@ -19,7 +19,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
||||
approvers = []
|
||||
department_details = {}
|
||||
department_list = []
|
||||
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
|
||||
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver"], as_dict=True)
|
||||
|
||||
employee_department = filters.get("department") or employee.department
|
||||
if employee_department:
|
||||
@ -33,10 +33,16 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("doctype") == "Leave Application" and employee.leave_approver:
|
||||
approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']))
|
||||
|
||||
if filters.get("doctype") == "Expense Claim" and employee.expense_approver:
|
||||
approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name']))
|
||||
|
||||
|
||||
if filters.get("doctype") == "Leave Application":
|
||||
parentfield = "leave_approvers"
|
||||
field_name = "Leave Approver"
|
||||
else:
|
||||
parentfield = "expense_approvers"
|
||||
field_name = "Expense Approver"
|
||||
if department_list:
|
||||
for d in department_list:
|
||||
approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
|
||||
@ -46,4 +52,12 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
||||
and approver.parentfield = %s
|
||||
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
|
||||
|
||||
if len(approvers) == 0:
|
||||
frappe.throw(_("Please set {0} for the Employee or for Department: {1}").
|
||||
format(
|
||||
field_name, frappe.bold(employee_department),
|
||||
frappe.bold(employee.name)
|
||||
),
|
||||
title=_(field_name + " Missing"))
|
||||
|
||||
return set(tuple(approver) for approver in approvers)
|
||||
|
@ -62,6 +62,7 @@
|
||||
"salary_mode",
|
||||
"payroll_cost_center",
|
||||
"column_break_52",
|
||||
"expense_approver",
|
||||
"bank_name",
|
||||
"bank_ac_no",
|
||||
"health_insurance_section",
|
||||
@ -205,7 +206,7 @@
|
||||
"label": "Status",
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nActive\nLeft",
|
||||
"options": "Active\nLeft",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@ -667,6 +668,7 @@
|
||||
"oldfieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.status == \"Left\"",
|
||||
"fieldname": "relieving_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Relieving Date",
|
||||
@ -797,13 +799,21 @@
|
||||
{
|
||||
"fieldname": "column_break_52",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "expense_approver",
|
||||
"fieldtype": "Link",
|
||||
"label": "Expense Approver",
|
||||
"options": "User",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-05-05 18:51:03.152503",
|
||||
"modified": "2020-06-18 18:01:27.223535",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
|
@ -139,13 +139,13 @@ frappe.ui.form.on('Employee Advance', {
|
||||
employee: function (frm) {
|
||||
if (frm.doc.employee) {
|
||||
return frappe.call({
|
||||
method: "erpnext.hr.doctype.employee_advance.employee_advance.get_due_advance_amount",
|
||||
method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
|
||||
args: {
|
||||
"employee": frm.doc.employee,
|
||||
"posting_date": frm.doc.posting_date
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("due_advance_amount",r.message);
|
||||
frm.set_value("pending_amount",r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
"column_break_11",
|
||||
"advance_amount",
|
||||
"paid_amount",
|
||||
"due_advance_amount",
|
||||
"pending_amount",
|
||||
"claimed_amount",
|
||||
"return_amount",
|
||||
"section_break_7",
|
||||
@ -102,14 +102,6 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:cur_frm.doc.employee",
|
||||
"fieldname": "due_advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Due Advance Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "claimed_amount",
|
||||
"fieldtype": "Currency",
|
||||
@ -177,11 +169,19 @@
|
||||
"fieldname": "repay_unclaimed_amount_from_salary",
|
||||
"fieldtype": "Check",
|
||||
"label": "Repay unclaimed amount from salary"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:cur_frm.doc.employee",
|
||||
"fieldname": "pending_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Pending Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-06 15:11:33.747535",
|
||||
"modified": "2020-06-12 12:42:39.833818",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Advance",
|
||||
|
@ -95,7 +95,7 @@ class EmployeeAdvance(Document):
|
||||
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_due_advance_amount(employee, posting_date):
|
||||
def get_pending_amount(employee, posting_date):
|
||||
employee_due_amount = frappe.get_all("Employee Advance", \
|
||||
filters = {"employee":employee, "docstatus":1, "posting_date":("<=", posting_date)}, \
|
||||
fields = ["advance_amount", "paid_amount"])
|
||||
|
@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
|
||||
return doc
|
||||
|
||||
|
||||
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None):
|
||||
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, in_time=None, out_time=None, shift=None):
|
||||
"""Creates an attendance and links the attendance to the Employee Checkin.
|
||||
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
|
||||
|
||||
@ -100,7 +100,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
|
||||
'company': employee_doc.company,
|
||||
'shift': shift,
|
||||
'late_entry': late_entry,
|
||||
'early_exit': early_exit
|
||||
'early_exit': early_exit,
|
||||
'in_time': in_time,
|
||||
'out_time': out_time
|
||||
}
|
||||
attendance = frappe.get_doc(doc_dict).insert()
|
||||
attendance.submit()
|
||||
|
@ -243,7 +243,6 @@ frappe.ui.form.on("Expense Claim", {
|
||||
},
|
||||
|
||||
update_employee_advance_claimed_amount: function(frm) {
|
||||
console.log("update_employee_advance_claimed_amount")
|
||||
let amount_to_be_allocated = frm.doc.grand_total;
|
||||
$.each(frm.doc.advances || [], function(i, advance){
|
||||
if (amount_to_be_allocated >= advance.unclaimed_amount){
|
||||
@ -295,6 +294,16 @@ frappe.ui.form.on("Expense Claim", {
|
||||
frm.events.get_advances(frm);
|
||||
},
|
||||
|
||||
cost_center: function(frm) {
|
||||
frm.events.set_child_cost_center(frm);
|
||||
},
|
||||
set_child_cost_center: function(frm){
|
||||
(frm.doc.expenses || []).forEach(function(d) {
|
||||
if (!d.cost_center){
|
||||
d.cost_center = frm.doc.cost_center;
|
||||
}
|
||||
});
|
||||
},
|
||||
get_taxes: function(frm) {
|
||||
if(frm.doc.taxes) {
|
||||
frappe.call({
|
||||
@ -338,8 +347,7 @@ frappe.ui.form.on("Expense Claim", {
|
||||
|
||||
frappe.ui.form.on("Expense Claim Detail", {
|
||||
expenses_add: function(frm, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
frm.script_manager.copy_from_first_row("expenses", row, ["cost_center"]);
|
||||
frm.events.set_child_cost_center(frm);
|
||||
},
|
||||
amount: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
|
@ -66,6 +66,7 @@
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "From Employee",
|
||||
"oldfieldname": "employee",
|
||||
"oldfieldtype": "Link",
|
||||
@ -164,6 +165,7 @@
|
||||
"default": "Today",
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Posting Date",
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
@ -236,6 +238,7 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
@ -368,7 +371,7 @@
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2019-12-14 23:52:05.388458",
|
||||
"modified": "2020-06-15 12:43:04.099803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Claim",
|
||||
|
@ -38,11 +38,15 @@ frappe.ui.form.on("Leave Application", {
|
||||
},
|
||||
|
||||
validate: function(frm) {
|
||||
if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){
|
||||
frm.doc.half_day_date = frm.doc.from_date;
|
||||
}
|
||||
frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
|
||||
},
|
||||
|
||||
make_dashboard: function(frm) {
|
||||
var leave_details;
|
||||
let lwps;
|
||||
if (frm.doc.employee) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details",
|
||||
@ -58,6 +62,7 @@ frappe.ui.form.on("Leave Application", {
|
||||
if (!r.exc && r.message['leave_approver']) {
|
||||
frm.set_value('leave_approver', r.message['leave_approver']);
|
||||
}
|
||||
lwps = r.message["lwps"];
|
||||
}
|
||||
});
|
||||
$("div").remove(".form-dashboard-section.custom");
|
||||
@ -67,6 +72,18 @@ frappe.ui.form.on("Leave Application", {
|
||||
})
|
||||
);
|
||||
frm.dashboard.show();
|
||||
let allowed_leave_types = Object.keys(leave_details);
|
||||
|
||||
// lwps should be allowed, lwps don't have any allocation
|
||||
allowed_leave_types = allowed_leave_types.concat(lwps);
|
||||
|
||||
frm.set_query('leave_type', function(){
|
||||
return {
|
||||
filters : [
|
||||
['leave_type_name', 'in', allowed_leave_types]
|
||||
]
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -19,7 +19,6 @@ class NotAnOptionalHoliday(frappe.ValidationError): pass
|
||||
|
||||
from frappe.model.document import Document
|
||||
class LeaveApplication(Document):
|
||||
|
||||
def get_feed(self):
|
||||
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
|
||||
|
||||
@ -33,6 +32,7 @@ class LeaveApplication(Document):
|
||||
self.validate_block_days()
|
||||
self.validate_salary_processed_days()
|
||||
self.validate_attendance()
|
||||
self.set_half_day_date()
|
||||
if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'):
|
||||
self.validate_optional_leave()
|
||||
self.validate_applicable_after()
|
||||
@ -131,8 +131,6 @@ class LeaveApplication(Document):
|
||||
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
|
||||
date = dt.strftime("%Y-%m-%d")
|
||||
status = "Half Day" if getdate(date) == getdate(self.half_day_date) else "On Leave"
|
||||
print("-------->>>", status)
|
||||
# frappe.throw("Hello")
|
||||
|
||||
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
|
||||
attendance_date = date, docstatus = ('!=', 2)))
|
||||
@ -292,6 +290,10 @@ class LeaveApplication(Document):
|
||||
frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday)
|
||||
day = add_days(day, 1)
|
||||
|
||||
def set_half_day_date(self):
|
||||
if self.from_date == self.to_date and self.half_day == 1:
|
||||
self.half_day_date = self.from_date
|
||||
|
||||
def notify_employee(self):
|
||||
employee = frappe.get_doc("Employee", self.employee)
|
||||
if not employee.user_id:
|
||||
@ -442,6 +444,7 @@ def get_leave_details(employee, date):
|
||||
total_allocated_leaves = frappe.db.get_value('Leave Allocation', {
|
||||
'from_date': ('<=', date),
|
||||
'to_date': ('>=', date),
|
||||
'employee': employee,
|
||||
'leave_type': allocation.leave_type,
|
||||
}, 'SUM(total_leaves_allocated)') or 0
|
||||
|
||||
@ -459,9 +462,14 @@ def get_leave_details(employee, date):
|
||||
"pending_leaves": leaves_pending,
|
||||
"remaining_leaves": remaining_leaves}
|
||||
|
||||
#is used in set query
|
||||
lwps = frappe.get_list("Leave Type", filters = {"is_lwp": 1})
|
||||
lwps = [lwp.name for lwp in lwps]
|
||||
|
||||
ret = {
|
||||
'leave_allocation': leave_allocation,
|
||||
'leave_approver': get_leave_approver(employee)
|
||||
'leave_approver': get_leave_approver(employee),
|
||||
'lwps': lwps
|
||||
}
|
||||
|
||||
return ret
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
{% if data %}
|
||||
{% if not jQuery.isEmptyObject(data) %}
|
||||
<h5 style="margin-top: 20px;"> {{ __("Allocated Leaves") }} </h5>
|
||||
<table class="table table-bordered small">
|
||||
<thead>
|
||||
@ -11,7 +11,6 @@
|
||||
<th style="width: 16%" class="text-right">{{ __("Pending Leaves") }}</th>
|
||||
<th style="width: 16%" class="text-right">{{ __("Available Leaves") }}</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for(const [key, value] of Object.entries(data)) { %}
|
||||
@ -26,6 +25,6 @@
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% } else { %}
|
||||
{% else %}
|
||||
<p style="margin-top: 30px;"> No Leaves have been allocated. </p>
|
||||
{% } %}
|
||||
{% endif %}
|
@ -28,13 +28,14 @@ class ShiftType(Document):
|
||||
logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
|
||||
for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
|
||||
single_shift_logs = list(group)
|
||||
attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs)
|
||||
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name)
|
||||
attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs)
|
||||
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name)
|
||||
for employee in self.get_assigned_employee(self.process_attendance_after, True):
|
||||
self.mark_absent_for_dates_with_no_attendance(employee)
|
||||
|
||||
def get_attendance(self, logs):
|
||||
"""Return attendance_status, working_hours for a set of logs belonging to a single shift.
|
||||
"""Return attendance_status, working_hours, late_entry, early_exit, in_time, out_time
|
||||
for a set of logs belonging to a single shift.
|
||||
Assumtion:
|
||||
1. These logs belongs to an single shift, single employee and is not in a holiday date.
|
||||
2. Logs are in chronological order
|
||||
@ -48,10 +49,10 @@ class ShiftType(Document):
|
||||
early_exit = True
|
||||
|
||||
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
|
||||
return 'Absent', total_working_hours, late_entry, early_exit
|
||||
return 'Absent', total_working_hours, late_entry, early_exit, in_time, out_time
|
||||
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
|
||||
return 'Half Day', total_working_hours, late_entry, early_exit
|
||||
return 'Present', total_working_hours, late_entry, early_exit
|
||||
return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time
|
||||
return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time
|
||||
|
||||
def mark_absent_for_dates_with_no_attendance(self, employee):
|
||||
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.
|
||||
|
@ -73,11 +73,11 @@ class EmployeeBoardingController(Document):
|
||||
def assign_task_to_users(self, task, users):
|
||||
for user in users:
|
||||
args = {
|
||||
'assign_to' : user,
|
||||
'doctype' : task.doctype,
|
||||
'name' : task.name,
|
||||
'description' : task.description or task.subject,
|
||||
'notify': self.notify_users_by_email
|
||||
'assign_to': [user],
|
||||
'doctype': task.doctype,
|
||||
'name': task.name,
|
||||
'description': task.description or task.subject,
|
||||
'notify': self.notify_users_by_email
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Reports",
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Loan Repayment\"\n ],\n \"doctype\": \"Loan Repayment\",\n \"incomplete_dependencies\": [\n \"Loan Repayment\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"doctype\": \"Loan Security Pledge\",\n \"incomplete_dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]"
|
||||
"links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]"
|
||||
}
|
||||
],
|
||||
"category": "Modules",
|
||||
@ -34,11 +34,10 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends_another_page": 0,
|
||||
"hide_custom": 0,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Loan",
|
||||
"modified": "2020-05-28 13:37:42.017709",
|
||||
"modified": "2020-06-07 19:42:14.947902",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan",
|
||||
|
@ -27,6 +27,7 @@ class LoanDisbursement(AccountsController):
|
||||
|
||||
def on_cancel(self):
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.ignore_linked_doctypes = ['GL Entry']
|
||||
|
||||
def set_missing_values(self):
|
||||
if not self.disbursement_date:
|
||||
|
@ -31,6 +31,7 @@ class LoanInterestAccrual(AccountsController):
|
||||
self.update_is_accrued()
|
||||
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.ignore_linked_doctypes = ['GL Entry']
|
||||
|
||||
def update_is_accrued(self):
|
||||
frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0)
|
||||
@ -176,21 +177,23 @@ def get_term_loans(date, term_loan=None, loan_type=None):
|
||||
return term_loans
|
||||
|
||||
def make_loan_interest_accrual_entry(args):
|
||||
loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
|
||||
loan_interest_accrual.loan = args.loan
|
||||
loan_interest_accrual.applicant_type = args.applicant_type
|
||||
loan_interest_accrual.applicant = args.applicant
|
||||
loan_interest_accrual.interest_income_account = args.interest_income_account
|
||||
loan_interest_accrual.loan_account = args.loan_account
|
||||
loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, 2)
|
||||
loan_interest_accrual.interest_amount = flt(args.interest_amount, 2)
|
||||
loan_interest_accrual.posting_date = args.posting_date or nowdate()
|
||||
loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
|
||||
loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
|
||||
loan_interest_accrual.payable_principal_amount = args.payable_principal
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
loan_interest_accrual.save()
|
||||
loan_interest_accrual.submit()
|
||||
loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
|
||||
loan_interest_accrual.loan = args.loan
|
||||
loan_interest_accrual.applicant_type = args.applicant_type
|
||||
loan_interest_accrual.applicant = args.applicant
|
||||
loan_interest_accrual.interest_income_account = args.interest_income_account
|
||||
loan_interest_accrual.loan_account = args.loan_account
|
||||
loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
|
||||
loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
|
||||
loan_interest_accrual.posting_date = args.posting_date or nowdate()
|
||||
loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
|
||||
loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
|
||||
loan_interest_accrual.payable_principal_amount = args.payable_principal
|
||||
|
||||
loan_interest_accrual.save()
|
||||
loan_interest_accrual.submit()
|
||||
|
||||
|
||||
def get_no_of_days_for_interest_accural(loan, posting_date):
|
||||
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.utils import flt, getdate
|
||||
from frappe.utils import flt, getdate, cint
|
||||
from six import iteritems
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime
|
||||
@ -29,8 +29,11 @@ class LoanRepayment(AccountsController):
|
||||
def on_cancel(self):
|
||||
self.mark_as_unpaid()
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.ignore_linked_doctypes = ['GL Entry']
|
||||
|
||||
def set_missing_values(self, amounts):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
if not self.posting_date:
|
||||
self.posting_date = get_datetime()
|
||||
|
||||
@ -38,24 +41,26 @@ class LoanRepayment(AccountsController):
|
||||
self.cost_center = erpnext.get_default_cost_center(self.company)
|
||||
|
||||
if not self.interest_payable:
|
||||
self.interest_payable = flt(amounts['interest_amount'], 2)
|
||||
self.interest_payable = flt(amounts['interest_amount'], precision)
|
||||
|
||||
if not self.penalty_amount:
|
||||
self.penalty_amount = flt(amounts['penalty_amount'], 2)
|
||||
self.penalty_amount = flt(amounts['penalty_amount'], precision)
|
||||
|
||||
if not self.pending_principal_amount:
|
||||
self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2)
|
||||
self.pending_principal_amount = flt(amounts['pending_principal_amount'], precision)
|
||||
|
||||
if not self.payable_principal_amount and self.is_term_loan:
|
||||
self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2)
|
||||
self.payable_principal_amount = flt(amounts['payable_principal_amount'], precision)
|
||||
|
||||
if not self.payable_amount:
|
||||
self.payable_amount = flt(amounts['payable_amount'], 2)
|
||||
self.payable_amount = flt(amounts['payable_amount'], precision)
|
||||
|
||||
if amounts.get('due_date'):
|
||||
self.due_date = amounts.get('due_date')
|
||||
|
||||
def validate_amount(self):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
if not self.amount_paid:
|
||||
frappe.throw(_("Amount paid cannot be zero"))
|
||||
|
||||
@ -63,11 +68,13 @@ class LoanRepayment(AccountsController):
|
||||
msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
|
||||
frappe.throw(msg)
|
||||
|
||||
if self.payment_type == "Loan Closure" and flt(self.amount_paid, 2) < flt(self.payable_amount, 2):
|
||||
if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision):
|
||||
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
|
||||
frappe.throw(msg)
|
||||
|
||||
def update_paid_amount(self):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
loan = frappe.get_doc("Loan", self.against_loan)
|
||||
|
||||
for payment in self.repayment_details:
|
||||
@ -75,9 +82,9 @@ class LoanRepayment(AccountsController):
|
||||
SET paid_principal_amount = `paid_principal_amount` + %s,
|
||||
paid_interest_amount = `paid_interest_amount` + %s
|
||||
WHERE name = %s""",
|
||||
(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
|
||||
(flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual))
|
||||
|
||||
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
||||
if flt(loan.total_principal_paid + self.principal_amount_paid, precision) >= flt(loan.total_payment, precision):
|
||||
if loan.is_secured_loan:
|
||||
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
||||
else:
|
||||
@ -253,6 +260,7 @@ def get_accrued_interest_entries(against_loan):
|
||||
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
|
||||
|
||||
def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
against_loan_doc = frappe.get_doc("Loan", against_loan)
|
||||
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
|
||||
@ -282,8 +290,8 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
payable_principal_amount += entry.payable_principal_amount
|
||||
|
||||
pending_accrual_entries.setdefault(entry.name, {
|
||||
'interest_amount': flt(entry.interest_amount),
|
||||
'payable_principal_amount': flt(entry.payable_principal_amount)
|
||||
'interest_amount': flt(entry.interest_amount, precision),
|
||||
'payable_principal_amount': flt(entry.payable_principal_amount, precision)
|
||||
})
|
||||
|
||||
if not final_due_date:
|
||||
@ -301,11 +309,11 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365
|
||||
total_pending_interest += (pending_days * per_day_interest)
|
||||
|
||||
amounts["pending_principal_amount"] = pending_principal_amount
|
||||
amounts["payable_principal_amount"] = payable_principal_amount
|
||||
amounts["interest_amount"] = total_pending_interest
|
||||
amounts["penalty_amount"] = penalty_amount
|
||||
amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
|
||||
amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
|
||||
amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
|
||||
amounts["interest_amount"] = flt(total_pending_interest, precision)
|
||||
amounts["penalty_amount"] = flt(penalty_amount, precision)
|
||||
amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
|
||||
amounts["pending_accrual_entries"] = pending_accrual_entries
|
||||
|
||||
if final_due_date:
|
||||
|
@ -41,6 +41,7 @@
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "rate_of_interest",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Rate of Interest (%) Yearly",
|
||||
@ -143,7 +144,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-15 00:24:43.259963",
|
||||
"modified": "2020-06-07 18:55:59.346292",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Type",
|
||||
|
@ -76,7 +76,8 @@ def get_columns(filters):
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "currency",
|
||||
"options": "Currency",
|
||||
"width": 50
|
||||
"width": 50,
|
||||
"hidden": 1
|
||||
}
|
||||
]
|
||||
|
||||
@ -84,17 +85,13 @@ def get_columns(filters):
|
||||
|
||||
def get_data(filters):
|
||||
|
||||
loan_security_price_map = frappe._dict(frappe.get_all("Loan Security",
|
||||
fields=["name", "loan_security_price"], as_list=1
|
||||
))
|
||||
|
||||
data = []
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
loan_security_pledges = frappe.db.sql("""
|
||||
SELECT
|
||||
p.name, p.applicant, p.loan, p.status, p.pledge_time,
|
||||
c.loan_security, c.qty
|
||||
c.loan_security, c.qty, c.loan_security_price, c.amount
|
||||
FROM
|
||||
`tabLoan Security Pledge` p, `tabPledge` c
|
||||
WHERE
|
||||
@ -115,8 +112,8 @@ def get_data(filters):
|
||||
row["pledge_time"] = pledge.pledge_time
|
||||
row["loan_security"] = pledge.loan_security
|
||||
row["qty"] = pledge.qty
|
||||
row["loan_security_price"] = loan_security_price_map.get(pledge.loan_security)
|
||||
row["loan_security_value"] = row["loan_security_price"] * pledge.qty
|
||||
row["loan_security_price"] = pledge.loan_security_price
|
||||
row["loan_security_value"] = pledge.amount
|
||||
row["currency"] = default_currency
|
||||
|
||||
data.append(row)
|
||||
|
@ -4,7 +4,6 @@
|
||||
import frappe, erpnext, json
|
||||
from frappe import _
|
||||
from frappe.utils import nowdate, get_first_day, get_last_day, add_months
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
def get_data():
|
||||
return frappe._dict({
|
||||
|
@ -112,8 +112,16 @@ class BOM(WebsiteGenerator):
|
||||
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)
|
||||
filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
|
||||
child = self.append('operations', {
|
||||
"operation": d.operation,
|
||||
"workstation": d.workstation,
|
||||
"description": d.description,
|
||||
"time_in_mins": d.time_in_mins,
|
||||
"batch_size": d.batch_size,
|
||||
"operating_cost": d.operating_cost,
|
||||
"idx": d.idx
|
||||
})
|
||||
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
|
||||
|
||||
def set_bom_material_details(self):
|
||||
|
@ -78,6 +78,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.doctype == 'BOM'",
|
||||
"fieldname": "base_hour_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Base Hour Rate(Company Currency)",
|
||||
@ -87,6 +88,7 @@
|
||||
},
|
||||
{
|
||||
"default": "5",
|
||||
"depends_on": "eval:parent.doctype == 'BOM'",
|
||||
"fieldname": "base_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Operating Cost(Company Currency)",
|
||||
@ -108,12 +110,12 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-07-16 22:35:55.374037",
|
||||
"modified_by": "govindsmenokee@gmail.com",
|
||||
"modified": "2020-06-16 17:01:11.128420",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Operation",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
@ -44,7 +44,6 @@ frappe.ui.form.on('BOM Operation', {
|
||||
name: d.workstation
|
||||
},
|
||||
callback: function (data) {
|
||||
frappe.model.set_value(d.doctype, d.name, "base_hour_rate", data.message.hour_rate);
|
||||
frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
|
||||
frm.events.calculate_operating_cost(frm, d);
|
||||
}
|
||||
|
@ -217,6 +217,8 @@ class ForecastingReport(ExponentialSmoothingForecast):
|
||||
}
|
||||
|
||||
def get_summary_data(self):
|
||||
if not self.data: return
|
||||
|
||||
return [
|
||||
{
|
||||
"value": sum(self.total_demand),
|
||||
|
@ -77,6 +77,7 @@ def create_customer(user_details):
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = user_details.fullname
|
||||
customer.customer_type = "Individual"
|
||||
customer.flags.ignore_mandatory = True
|
||||
customer.insert(ignore_permissions=True)
|
||||
|
||||
try:
|
||||
@ -91,7 +92,11 @@ def create_customer(user_details):
|
||||
"link_name": customer.name
|
||||
})
|
||||
|
||||
contact.insert()
|
||||
contact.save()
|
||||
|
||||
except frappe.DuplicateEntryError:
|
||||
return customer.name
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed"))
|
||||
pass
|
||||
|
@ -62,11 +62,26 @@ def get_member_based_on_subscription(subscription_id, email):
|
||||
'subscription_id': subscription_id,
|
||||
'email_id': email
|
||||
}, order_by="creation desc")
|
||||
return frappe.get_doc("Member", members[0]['name'])
|
||||
try:
|
||||
return frappe.get_doc("Member", members[0]['name'])
|
||||
except:
|
||||
return None
|
||||
|
||||
def verify_signature(data):
|
||||
signature = frappe.request.headers.get('X-Razorpay-Signature')
|
||||
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
key = settings.get_webhook_secret()
|
||||
|
||||
controller = frappe.get_doc("Razorpay Settings")
|
||||
|
||||
controller.verify_signature(data, signature, key)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def trigger_razorpay_subscription(*args, **kwargs):
|
||||
data = frappe.request.get_data()
|
||||
data = frappe.request.get_data(as_text=True)
|
||||
verify_signature(data)
|
||||
|
||||
if isinstance(data, six.string_types):
|
||||
data = json.loads(data)
|
||||
@ -84,7 +99,10 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
except Exception as e:
|
||||
error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed"))
|
||||
notify_failure(error_log)
|
||||
raise e
|
||||
return False
|
||||
|
||||
if not member:
|
||||
return False
|
||||
|
||||
if data.event == "subscription.activated":
|
||||
member.customer_id = payment.customer_id
|
||||
@ -113,7 +131,6 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def notify_failure(log):
|
||||
try:
|
||||
content = """Dear System Manager,
|
||||
|
@ -1,8 +1,30 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Membership Settings', {
|
||||
frappe.ui.form.on("Membership Settings", {
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.webhook_secret) {
|
||||
frm.add_custom_button(__("Revoke <Key></Key>"), () => {
|
||||
frm.call("revoke_key").then(() => {
|
||||
frm.refresh();
|
||||
})
|
||||
});
|
||||
}
|
||||
frm.trigger("add_generate_button");
|
||||
},
|
||||
|
||||
}
|
||||
add_generate_button: function(frm) {
|
||||
let label;
|
||||
|
||||
if (frm.doc.webhook_secret) {
|
||||
label = __("Regenerate Webhook Secret");
|
||||
} else {
|
||||
label = __("Generate Webhook Secret");
|
||||
}
|
||||
frm.add_custom_button(label, () => {
|
||||
frm.call("generate_webhook_key").then(() => {
|
||||
frm.refresh();
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -8,7 +8,8 @@
|
||||
"enable_razorpay",
|
||||
"razorpay_settings_section",
|
||||
"billing_cycle",
|
||||
"billing_frequency"
|
||||
"billing_frequency",
|
||||
"webhook_secret"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -34,11 +35,17 @@
|
||||
"fieldname": "billing_frequency",
|
||||
"fieldtype": "Int",
|
||||
"label": "Billing Frequency"
|
||||
},
|
||||
{
|
||||
"fieldname": "webhook_secret",
|
||||
"fieldtype": "Password",
|
||||
"label": "Webhook Secret",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-07 18:42:51.496807",
|
||||
"modified": "2020-05-22 12:38:27.103759",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Membership Settings",
|
||||
|
@ -4,11 +4,27 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.integrations.utils import get_payment_gateway_controller
|
||||
from frappe.model.document import Document
|
||||
|
||||
class MembershipSettings(Document):
|
||||
pass
|
||||
def generate_webhook_key(self):
|
||||
key = frappe.generate_hash(length=20)
|
||||
self.webhook_secret = key
|
||||
self.save()
|
||||
|
||||
frappe.msgprint(
|
||||
_("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key,
|
||||
_("Webhook Secret")
|
||||
);
|
||||
|
||||
def revoke_key(self):
|
||||
self.webhook_secret = None;
|
||||
self.save()
|
||||
|
||||
def get_webhook_secret(self):
|
||||
return self.get_password(fieldname="webhook_secret", raise_exception=False)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_plans_for_membership(*args, **kwargs):
|
||||
|
@ -680,6 +680,8 @@ erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
|
||||
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
|
||||
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
|
||||
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
|
||||
execute:frappe.reload_doc("HR", "doctype", "Employee Advance")
|
||||
erpnext.patches.v12_0.move_due_advance_amount_to_pending_amount
|
||||
execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
|
||||
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
|
||||
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
|
||||
@ -695,4 +697,7 @@ execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
|
||||
erpnext.patches.v12_0.update_uom_conversion_factor
|
||||
erpnext.patches.v13_0.delete_old_purchase_reports
|
||||
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
|
||||
erpnext.patches.v12_0.unhide_cost_center_field
|
||||
erpnext.patches.v12_0.unhide_cost_center_field
|
||||
erpnext.patches.v13_0.update_sla_enhancements
|
||||
erpnext.patches.v12_0.update_address_template_for_india
|
||||
erpnext.patches.v12_0.set_multi_uom_in_rfq
|
||||
|
@ -4,7 +4,7 @@ import frappe
|
||||
def execute():
|
||||
frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
|
||||
|
||||
if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account'):
|
||||
if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'):
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabBank` b, `tabBank Account` ba
|
||||
SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code
|
||||
@ -12,4 +12,4 @@ def execute():
|
||||
""")
|
||||
|
||||
frappe.reload_doc('accounts', 'doctype', 'bank_account')
|
||||
frappe.reload_doc('accounts', 'doctype', 'payment_request')
|
||||
frappe.reload_doc('accounts', 'doctype', 'payment_request')
|
||||
|
@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
''' Move from due_advance_amount to pending_amount '''
|
||||
|
||||
if frappe.db.has_column("Employee Advance", "due_advance_amount"):
|
||||
frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user