Merge branch 'develop' into woocommerce-shipping

This commit is contained in:
Shivam Mishra 2020-05-16 19:40:45 +05:30 committed by GitHub
commit bdc97e6b48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 1495 additions and 1429 deletions

View File

@ -1,6 +1,5 @@
dist: trusty
language: python
dist: trusty
git:
depth: 1
@ -14,21 +13,10 @@ addons:
jobs:
include:
- name: "Python 2.7 Server Side Test"
python: 2.7
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 3.6 Server Side Test"
python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 2.7 Patch Test"
python: 2.7
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
- name: "Python 3.6 Patch Test"
python: 3.6
before_script:
@ -40,8 +28,7 @@ install:
- cd ~
- nvm install 10
- git clone https://github.com/frappe/bench --depth 1
- pip install -e ./bench
- pip install frappe-bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '12.0.0-dev'
__version__ = '13.0.0-dev'
def get_default_company(user=None):
'''Get default company for user'''

View File

@ -6,7 +6,7 @@ import frappe, json
from frappe import _
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
from frappe.utils.nestedset import get_descendants_of
@ -14,7 +14,7 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
@cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
to_date = None, timespan = None, time_interval = None):
to_date = None, timespan = None, time_interval = None, heatmap_year = None):
if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name)
else:

View File

@ -0,0 +1,127 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from erpnext import get_default_company
import frappe
import json
def get_data():
data = frappe._dict({
"dashboards": [],
"charts": []
})
company = get_company_for_dashboards()
if company:
company_doc = frappe.get_doc("Company", company)
data.dashboards = get_dashboards()
data.charts = get_charts(company_doc)
return data
def get_dashboards():
return [{
"name": "Accounts",
"dashboard_name": "Accounts",
"charts": [
{ "chart": "Outgoing Bills (Sales Invoice)" },
{ "chart": "Incoming Bills (Purchase Invoice)" },
{ "chart": "Bank Balance" },
{ "chart": "Income" },
{ "chart": "Expenses" }
]
}]
def get_charts(company):
income_account = company.default_income_account or get_account("Income Account", company.name)
expense_account = company.default_expense_account or get_account("Expense Account", company.name)
bank_account = company.default_bank_account or get_account("Bank", company.name)
return [
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"name": "Income",
"chart_name": "Income",
"timespan": "Last Year",
"color": None,
"filters_json": json.dumps({"company": company.name, "account": income_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"name": "Expenses",
"chart_name": "Expenses",
"timespan": "Last Year",
"color": None,
"filters_json": json.dumps({"company": company.name, "account": expense_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"name": "Bank Balance",
"chart_name": "Bank Balance",
"timespan": "Last Year",
"color": "#ffb868",
"filters_json": json.dumps({"company": company.name, "account": bank_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Monthly",
"name": "Incoming Bills (Purchase Invoice)",
"chart_name": "Incoming Bills (Purchase Invoice)",
"timespan": "Last Year",
"color": "#a83333",
"value_based_on": "base_grand_total",
"filters_json": json.dumps({}),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Purchase Invoice",
"type": "Bar"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Monthly",
"name": "Outgoing Bills (Sales Invoice)",
"chart_name": "Outgoing Bills (Sales Invoice)",
"timespan": "Last Year",
"color": "#7b933d",
"value_based_on": "base_grand_total",
"filters_json": json.dumps({}),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Sales Invoice",
"type": "Bar"
}
]
def get_account(account_type, company):
accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company})
if accounts:
return accounts[0].name
def get_company_for_dashboards():
company = get_default_company()
if not company:
company_list = frappe.get_list("Company")
if company_list:
company = company_list[0].name
return company

View File

@ -185,7 +185,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, end_date, project, account_currency, item.cost_center, item.name, deferred_process)
amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:
@ -222,7 +222,7 @@ def process_deferred_accounting(posting_date=today()):
doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no, deferred_process=None):
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
@ -236,12 +236,12 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": voucher_detail_no,
"voucher_detail_no": item.name,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency)
}, account_currency, item=item)
)
# GL Entry to debit the amount from the expense
gl_entries.append(
@ -251,12 +251,12 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": voucher_detail_no,
"voucher_detail_no": item.name,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
}, account_currency)
}, account_currency, item=item)
)
if gl_entries:

View File

@ -162,9 +162,9 @@ def toggle_disabling(doc):
def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item",
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"]

View File

@ -112,8 +112,8 @@ class GLEntry(Document):
from tabAccount where name=%s""", self.account, as_dict=1)[0]
if ret.is_group==1:
frappe.throw(_("{0} {1}: Account {2} cannot be a Group")
.format(self.voucher_type, self.voucher_no, self.account))
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
transactions''').format(self.voucher_type, self.voucher_no, self.account))
if ret.docstatus==2:
frappe.throw(_("{0} {1}: Account {2} is inactive")

View File

@ -8,6 +8,7 @@ from frappe import _
from frappe.utils import flt, getdate, nowdate, add_days
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class InvoiceDiscounting(AccountsController):
def validate(self):
@ -81,10 +82,15 @@ class InvoiceDiscounting(AccountsController):
def make_gl_entries(self):
company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
gl_entries = []
invoice_fields = ["debit_to", "party_account_currency", "conversion_rate", "cost_center"]
accounting_dimensions = get_accounting_dimensions()
invoice_fields.extend(accounting_dimensions)
for d in self.invoices:
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice,
["debit_to", "party_account_currency", "conversion_rate", "cost_center"], as_dict=1)
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1)
if d.outstanding_amount:
outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate,
@ -102,7 +108,7 @@ class InvoiceDiscounting(AccountsController):
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice"
}, inv.party_account_currency))
}, inv.party_account_currency, item=inv))
gl_entries.append(self.get_gl_dict({
"account": self.accounts_receivable_credit,
@ -115,7 +121,7 @@ class InvoiceDiscounting(AccountsController):
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice"
}, ar_credit_account_currency))
}, ar_credit_account_currency, item=inv))
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')

View File

@ -451,8 +451,6 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
def set_remarks(self):
if self.remarks: return
if self.payment_type=="Internal Transfer":
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
.format(self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to)]
@ -506,7 +504,7 @@ class PaymentEntry(AccountsController):
"against": against_account,
"account_currency": self.party_account_currency,
"cost_center": self.cost_center
})
}, item=self)
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
@ -550,7 +548,7 @@ class PaymentEntry(AccountsController):
"credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount,
"cost_center": self.cost_center
})
}, item=self)
)
if self.payment_type in ("Receive", "Internal Transfer"):
gl_entries.append(
@ -561,7 +559,7 @@ class PaymentEntry(AccountsController):
"debit_in_account_currency": self.received_amount,
"debit": self.base_received_amount,
"cost_center": self.cost_center
})
}, item=self)
)
def add_deductions_gl_entries(self, gl_entries):

View File

@ -80,7 +80,7 @@ def make_journal_entry(doc, supplier, mode_of_payment=None):
paid_amt += d.amount
je.append('accounts', {
'account': doc.references[0].account,
'account': doc.account,
'credit_in_account_currency': paid_amt
})

View File

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "naming_series:",
"creation": "2015-12-15 22:23:24.745065",
"doctype": "DocType",
@ -210,13 +211,14 @@
"label": "IBAN"
},
{
"fetch_from": "bank_account.branch_code",
"fetch_from": "bank.branch_code",
"fetch_if_empty": 1,
"fieldname": "branch_code",
"fieldtype": "Read Only",
"label": "Branch Code"
},
{
"fetch_from": "bank_account.swift_number",
"fetch_from": "bank.swift_number",
"fieldname": "swift_number",
"fieldtype": "Read Only",
"label": "SWIFT Number"
@ -348,7 +350,8 @@
}
],
"is_submittable": 1,
"modified": "2020-03-28 16:07:31.960798",
"links": [],
"modified": "2020-05-08 10:23:02.815237",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",

View File

@ -99,7 +99,7 @@ class PricingRule(Document):
self.same_item = 1
def validate_max_discount(self):
if self.rate_or_discount == "Discount Percentage" and self.items:
if self.rate_or_discount == "Discount Percentage" and self.get("items"):
for d in self.items:
max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount")
if max_discount and flt(self.discount_percentage) > flt(max_discount):

View File

@ -4,13 +4,19 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe, copy, json
from frappe import throw, _
import copy
import json
from six import string_types
from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
import frappe
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
from frappe import _, throw
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
class MultiplePricingRuleConflict(frappe.ValidationError): pass
@ -502,18 +508,16 @@ def get_pricing_rule_items(pr_doc):
return list(set(apply_on_data))
def validate_coupon_code(coupon_name):
from frappe.utils import today,getdate
coupon=frappe.get_doc("Coupon Code",coupon_name)
coupon = frappe.get_doc("Coupon Code", coupon_name)
if coupon.valid_from:
if coupon.valid_from > getdate(today()) :
frappe.throw(_("Sorry,coupon code validity has not started"))
if coupon.valid_from > getdate(today()):
frappe.throw(_("Sorry, this coupon code's validity has not started"))
elif coupon.valid_upto:
if coupon.valid_upto < getdate(today()) :
frappe.throw(_("Sorry,coupon code validity has expired"))
elif coupon.used>=coupon.maximum_use:
frappe.throw(_("Sorry,coupon code are exhausted"))
else:
return
if coupon.valid_upto < getdate(today()):
frappe.throw(_("Sorry, this coupon code's validity has expired"))
elif coupon.used >= coupon.maximum_use:
frappe.throw(_("Sorry, this coupon code is no longer valid"))
def update_coupon_code_count(coupon_name,transaction_type):
coupon=frappe.get_doc("Coupon Code",coupon_name)

View File

@ -460,7 +460,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
def make_item_gl_entries(self, gl_entries):
@ -841,7 +841,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
gl_entries.append(
@ -852,7 +852,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_paid_amount \
if bank_account_currency==self.company_currency else self.paid_amount,
"cost_center": self.cost_center
}, bank_account_currency)
}, bank_account_currency, item=self)
)
def make_write_off_gl_entry(self, gl_entries):
@ -873,7 +873,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
gl_entries.append(
self.get_gl_dict({
@ -883,7 +883,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.write_off_amount,
"cost_center": self.cost_center or self.write_off_cost_center
})
}, item=self)
)
def make_gle_for_rounding_adjustment(self, gl_entries):
@ -902,8 +902,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment,
"cost_center": self.cost_center or round_off_cost_center,
}
))
}, item=self))
def on_cancel(self):
super(PurchaseInvoice, self).on_cancel()
@ -1022,6 +1021,40 @@ class PurchaseInvoice(BuyingController):
# calculate totals again after applying TDS
self.calculate_taxes_and_totals()
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
if self.get('amended_from'):
self.status = 'Draft'
return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
if not status:
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid"
#Check if outstanding amount is 0 due to debit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Debit Note Issued"
elif self.is_return == 1:
self.status = "Return"
elif outstanding_amount<=0:
self.status = "Paid"
else:
self.status = "Submitted"
else:
self.status = "Draft"
if update:
self.db_set('status', self.status, update_modified = update_modified)
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context)

View File

@ -86,6 +86,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pe.submit()
pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
pi_doc.load_from_db()
self.assertTrue(pi_doc.status, "Paid")
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
unlink_payment_on_cancel_of_invoice()
@ -203,7 +205,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.insert()
pi.submit()
pi.load_from_db()
self.assertTrue(pi.status, "Unpaid")
self.check_gle_for_pi(pi.name)
def check_gle_for_pi(self, pi):
@ -234,6 +238,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = frappe.copy_doc(test_records[0])
pi.insert()
pi.load_from_db()
self.assertTrue(pi.status, "Draft")
pi.naming_series = 'TEST-'
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
@ -248,6 +255,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.get("taxes").pop(1)
pi.insert()
pi.submit()
pi.load_from_db()
self.assertTrue(pi.status, "Unpaid")
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
@ -599,6 +608,11 @@ class TestPurchaseInvoice(unittest.TestCase):
# return entry
pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1)
pi.load_from_db()
self.assertTrue(pi.status, "Debit Note Issued")
pi1.load_from_db()
self.assertTrue(pi1.status, "Return")
actual_qty_2 = get_qty_after_transaction()
self.assertEqual(actual_qty_1 - 2, actual_qty_2)
@ -771,6 +785,8 @@ class TestPurchaseInvoice(unittest.TestCase):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount
pi = make_purchase_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
pi.load_from_db()
self.assertTrue(pi.status, "Return")
outstanding_amount = get_outstanding_amount(pi.doctype,
pi.name, "Creditors - _TC", pi.supplier, "Supplier")

View File

@ -791,7 +791,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
def make_tax_gl_entries(self, gl_entries):
@ -808,7 +808,7 @@ class SalesInvoice(SellingController):
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
"cost_center": tax.cost_center
}, account_currency)
}, account_currency, item=tax)
)
def make_item_gl_entries(self, gl_entries):
@ -828,7 +828,7 @@ class SalesInvoice(SellingController):
for gle in fixed_asset_gl_entries:
gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle))
gl_entries.append(self.get_gl_dict(gle, item=item))
asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None)
@ -866,7 +866,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
})
}, item=self)
)
gl_entries.append(
self.get_gl_dict({
@ -875,7 +875,7 @@ class SalesInvoice(SellingController):
"against": self.customer,
"debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer"
})
}, item=self)
)
def make_pos_gl_entries(self, gl_entries):
@ -896,7 +896,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
payment_mode_account_currency = get_account_currency(payment_mode.account)
@ -909,7 +909,7 @@ class SalesInvoice(SellingController):
if payment_mode_account_currency==self.company_currency \
else payment_mode.amount,
"cost_center": self.cost_center
}, payment_mode_account_currency)
}, payment_mode_account_currency, item=self)
)
def make_gle_for_change_amount(self, gl_entries):
@ -927,7 +927,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
gl_entries.append(
@ -936,7 +936,7 @@ class SalesInvoice(SellingController):
"against": self.customer,
"credit": self.base_change_amount,
"cost_center": self.cost_center
})
}, item=self)
)
else:
frappe.throw(_("Select change amount account"), title="Mandatory Field")
@ -960,7 +960,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
gl_entries.append(
self.get_gl_dict({
@ -971,7 +971,7 @@ class SalesInvoice(SellingController):
self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency
else flt(self.write_off_amount, self.precision("write_off_amount"))),
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center
}, write_off_account_currency)
}, write_off_account_currency, item=self)
)
def make_gle_for_rounding_adjustment(self, gl_entries):
@ -988,8 +988,7 @@ class SalesInvoice(SellingController):
"credit": flt(self.base_rounding_adjustment,
self.precision("base_rounding_adjustment")),
"cost_center": self.cost_center or round_off_cost_center,
}
))
}, item=self))
def update_billing_status_in_dn(self, update_modified=True):
updated_delivery_notes = []

View File

@ -58,7 +58,7 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
"rate": tax_rate_detail.tax_withholding_rate,
"threshold": tax_rate_detail.single_threshold,
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
"description": tax_withholding.category_name
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
})
def get_tax_withholding_rates(tax_withholding, fiscal_year):
@ -162,8 +162,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai
debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
supplier_credit_amount -= debit_note_amount
if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
if supplier_credit_amount >= tax_details.get('threshold', 0) or supplier_credit_amount >= tax_details.get('cumulative_threshold', 0):
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
ldc.certificate_limit):

View File

@ -297,7 +297,8 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
fields = ["*"],
filters = {
"voucher_type": voucher_type,
"voucher_no": voucher_no
"voucher_no": voucher_no,
"is_cancelled": 0
})
if gl_entries:

View File

@ -2,16 +2,19 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import datetime
from six import iteritems
import frappe
from frappe import _
from frappe.utils import flt
from frappe.utils import formatdate
from frappe.utils import flt, formatdate
from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges
from six import iteritems
from pprint import pprint
def execute(filters=None):
if not filters: filters = {}
if not filters:
filters = {}
columns = get_columns(filters)
if filters.get("budget_against_filter"):
@ -43,20 +46,25 @@ def execute(filters=None):
period_data[0] += last_total
if(filters.get("show_cumulative")):
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" :
if filters["period"] != "Yearly":
row += totals
data.append(row)
return columns, data
def get_columns(filters):
columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"]
columns = [
_(filters.get("budget_against"))
+ ":Link/%s:150" % (filters.get("budget_against")),
_("Account") + ":Link/Account:150"
]
group_months = False if filters["period"] == "Monthly" else True
@ -65,84 +73,181 @@ def get_columns(filters):
for year in fiscal_year:
for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
if filters["period"] == "Yearly":
labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Variance ") + " " + str(year[0])]
labels = [
_("Budget") + " " + str(year[0]),
_("Actual ") + " " + str(year[0]),
_("Variance ") + " " + str(year[0])
]
for label in labels:
columns.append(label+":Float:150")
columns.append(label + ":Float:150")
else:
for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]:
for label in [
_("Budget") + " (%s)" + " " + str(year[0]),
_("Actual") + " (%s)" + " " + str(year[0]),
_("Variance") + " (%s)" + " " + str(year[0])
]:
if group_months:
label = label % (formatdate(from_date, format_string="MMM") + "-" + formatdate(to_date, format_string="MMM"))
label = label % (
formatdate(from_date, format_string="MMM")
+ "-"
+ formatdate(to_date, format_string="MMM")
)
else:
label = label % formatdate(from_date, format_string="MMM")
columns.append(label+":Float:150")
columns.append(label + ":Float:150")
if filters["period"] != "Yearly" :
return columns + [_("Total Budget") + ":Float:150", _("Total Actual") + ":Float:150",
_("Total Variance") + ":Float:150"]
if filters["period"] != "Yearly":
return columns + [
_("Total Budget") + ":Float:150",
_("Total Actual") + ":Float:150",
_("Total Variance") + ":Float:150"
]
else:
return columns
def get_cost_centers(filters):
cond = "and 1=1"
order_by = ""
if filters.get("budget_against") == "Cost Center":
cond = "order by lft"
order_by = "order by lft"
if filters.get("budget_against") in ["Cost Center", "Project"]:
return frappe.db.sql_list("""select name from `tab{tab}` where company=%s
{cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company"))
return frappe.db.sql_list(
"""
select
name
from
`tab{tab}`
where
company = %s
{order_by}
""".format(tab=filters.get("budget_against"), order_by=order_by),
filters.get("company"))
else:
return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec
return frappe.db.sql_list(
"""
select
name
from
`tab{tab}`
""".format(tab=filters.get("budget_against"))) # nosec
#Get dimension & target details
# Get dimension & target details
def get_dimension_target_details(filters):
budget_against = frappe.scrub(filters.get("budget_against"))
cond = ""
if filters.get("budget_against_filter"):
cond += " and b.{budget_against} in (%s)".format(budget_against = \
frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter')))
cond += """ and b.{budget_against} in (%s)""".format(
budget_against=budget_against) % ", ".join(["%s"] * len(filters.get("budget_against_filter")))
return frappe.db.sql("""
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
from `tabBudget` b, `tabBudget Account` ba
where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s
and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year
""".format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond),
tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')),
as_dict=True)
return frappe.db.sql(
"""
select
b.{budget_against} as budget_against,
b.monthly_distribution,
ba.account,
ba.budget_amount,
b.fiscal_year
from
`tabBudget` b,
`tabBudget Account` ba
where
b.name = ba.parent
and b.docstatus = 1
and b.fiscal_year between %s and %s
and b.budget_against = %s
and b.company = %s
{cond}
order by
b.fiscal_year
""".format(
budget_against=budget_against,
cond=cond,
),
tuple(
[
filters.from_fiscal_year,
filters.to_fiscal_year,
filters.budget_against,
filters.company,
]
+ filters.get("budget_against_filter")
), as_dict=True)
#Get target distribution details of accounts of cost center
# Get target distribution details of accounts of cost center
def get_target_distribution_details(filters):
target_details = {}
for d in frappe.db.sql("""select md.name, mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
where mdp.parent=md.name and md.fiscal_year between %s and %s order by md.fiscal_year""",(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1):
target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation))
for d in frappe.db.sql(
"""
select
md.name,
mdp.month,
mdp.percentage_allocation
from
`tabMonthly Distribution Percentage` mdp,
`tabMonthly Distribution` md
where
mdp.parent = md.name
and md.fiscal_year between %s and %s
order by
md.fiscal_year
""",
(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1):
target_details.setdefault(d.name, {}).setdefault(
d.month, flt(d.percentage_allocation)
)
return target_details
#Get actual details from gl entry
# Get actual details from gl entry
def get_actual_details(name, filters):
cond = "1=1"
budget_against=filters.get("budget_against").replace(" ", "_").lower()
budget_against = frappe.scrub(filters.get("budget_against"))
cond = ""
if filters.get("budget_against") == "Cost Center":
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"])
cond = "lft>='{lft}' and rgt<='{rgt}'".format(lft = cc_lft, rgt=cc_rgt)
cond = """
and lft >= "{lft}"
and rgt <= "{rgt}"
""".format(lft=cc_lft, rgt=cc_rgt)
ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,gl.fiscal_year,
MONTHNAME(gl.posting_date) as month_name, b.{budget_against} as budget_against
from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b
where
b.name = ba.parent
and b.docstatus = 1
and ba.account=gl.account
and b.{budget_against} = gl.{budget_against}
and gl.fiscal_year between %s and %s
and b.{budget_against}=%s
and exists(select name from `tab{tab}` where name=gl.{budget_against} and {cond}) group by gl.name order by gl.fiscal_year
""".format(tab = filters.budget_against, budget_against = budget_against, cond = cond,from_year=filters.from_fiscal_year,to_year=filters.to_fiscal_year),
(filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1)
ac_details = frappe.db.sql(
"""
select
gl.account,
gl.debit,
gl.credit,
gl.fiscal_year,
MONTHNAME(gl.posting_date) as month_name,
b.{budget_against} as budget_against
from
`tabGL Entry` gl,
`tabBudget Account` ba,
`tabBudget` b
where
b.name = ba.parent
and b.docstatus = 1
and ba.account=gl.account
and b.{budget_against} = gl.{budget_against}
and gl.fiscal_year between %s and %s
and b.{budget_against} = %s
and exists(
select
name
from
`tab{tab}`
where
name = gl.{budget_against}
{cond}
)
group by
gl.name
order by gl.fiscal_year
""".format(tab=filters.budget_against, budget_against=budget_against, cond=cond),
(filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1)
cc_actual_details = {}
for d in ac_details:
@ -151,7 +256,6 @@ def get_actual_details(name, filters):
return cc_actual_details
def get_dimension_account_month_map(filters):
import datetime
dimension_target_details = get_dimension_target_details(filters)
tdd = get_target_distribution_details(filters)
@ -161,28 +265,43 @@ def get_dimension_account_month_map(filters):
actual_details = get_actual_details(ccd.budget_against, filters)
for month_id in range(1, 13):
month = datetime.date(2013, month_id, 1).strftime('%B')
cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\
.setdefault(month, frappe._dict({
"target": 0.0, "actual": 0.0
}))
month = datetime.date(2013, month_id, 1).strftime("%B")
cam_map.setdefault(ccd.budget_against, {}).setdefault(
ccd.account, {}
).setdefault(ccd.fiscal_year, {}).setdefault(
month, frappe._dict({"target": 0.0, "actual": 0.0})
)
tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month]
month_percentage = tdd.get(ccd.monthly_distribution, {}).get(month, 0) \
if ccd.monthly_distribution else 100.0/12
month_percentage = (
tdd.get(ccd.monthly_distribution, {}).get(month, 0)
if ccd.monthly_distribution
else 100.0 / 12
)
tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100
for ad in actual_details.get(ccd.account, []):
if ad.month_name == month:
tav_dict.actual += flt(ad.debit) - flt(ad.credit)
if ad.month_name == month and ad.fiscal_year == ccd.fiscal_year:
tav_dict.actual += flt(ad.debit) - flt(ad.credit)
return cam_map
def get_fiscal_years(filters):
fiscal_year = frappe.db.sql("""select name from `tabFiscal Year` where
name between %(from_fiscal_year)s and %(to_fiscal_year)s""",
{'from_fiscal_year': filters["from_fiscal_year"], 'to_fiscal_year': filters["to_fiscal_year"]})
fiscal_year = frappe.db.sql(
"""
select
name
from
`tabFiscal Year`
where
name between %(from_fiscal_year)s and %(to_fiscal_year)s
""",
{
"from_fiscal_year": filters["from_fiscal_year"],
"to_fiscal_year": filters["to_fiscal_year"]
})
return fiscal_year

View File

@ -53,7 +53,7 @@ frappe.query_reports["General Ledger"] = {
"label": __("Voucher No"),
"fieldtype": "Data",
on_change: function() {
frappe.query_report.set_filter_value('group_by', "");
frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
}
},
{

View File

@ -9,8 +9,8 @@ from erpnext.accounts.report.financial_statements import (get_period_list, get_c
import copy
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
filters.periodicity, filters.accumulated_values, filters.company)
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.period_start_date,
filters.period_end_date, filters.filter_based_on, filters.periodicity, filters.accumulated_values, filters.company)
columns, data = [], []

View File

@ -46,7 +46,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"default": frappe.defaults.get_user_default("year_end_date"),
},
{
"fieldname":"cost_center",
"fieldname": "cost_center",
"label": __("Cost Center"),
"fieldtype": "Link",
"options": "Cost Center",
@ -61,7 +61,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
}
},
{
"fieldname":"finance_book",
"fieldname": "project",
"label": __("Project"),
"fieldtype": "Link",
"options": "Project"
},
{
"fieldname": "finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
"options": "Finance Book",
@ -97,7 +103,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
}
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Trial Balance"].filters.splice(5, 0 ,{
frappe.query_reports["Trial Balance"].filters.splice(6, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",

View File

@ -69,6 +69,10 @@ def get_data(filters):
gl_entries_by_account = {}
opening_balances = get_opening_balances(filters)
#add filter inside list so that the query in financial_statements.py doesn't break
filters.project = [filters.project]
set_gl_entries_by_account(filters.company, filters.from_date,
filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry))
@ -102,6 +106,9 @@ def get_rootwise_opening_balances(filters, report_type):
additional_conditions += """ and cost_center in (select name from `tabCost Center`
where lft >= %s and rgt <= %s)""" % (lft, rgt)
if filters.project:
additional_conditions += " and project = %(project)s"
if filters.finance_book:
fb_conditions = " AND finance_book = %(finance_book)s"
if filters.include_default_book_entries:
@ -116,6 +123,7 @@ def get_rootwise_opening_balances(filters, report_type):
"from_date": filters.from_date,
"report_type": report_type,
"year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book,
"company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book')
}

View File

@ -513,7 +513,7 @@ class Asset(AccountsController):
"credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
}))
}, item=self))
gl_entries.append(self.get_gl_dict({
"account": fixed_asset_account,
@ -523,7 +523,7 @@ class Asset(AccountsController):
"debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
}))
}, item=self))
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries

View File

@ -141,7 +141,7 @@
],
"is_tree": 1,
"links": [],
"modified": "2020-03-18 18:00:08.885805",
"modified": "2020-05-08 16:11:11.375701",
"modified_by": "Administrator",
"module": "Assets",
"name": "Location",
@ -221,7 +221,6 @@
}
],
"quick_entry": 1,
"restrict_to_domain": "Agriculture",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",

View File

@ -18,6 +18,10 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
refresh: function() {
var me = this;
this._super();
if (this.frm.doc.__islocal && !this.frm.doc.valid_till) {
this.frm.set_value('valid_till', frappe.datetime.add_months(this.frm.doc.transaction_date, 1));
}
if (this.frm.doc.docstatus === 1) {
cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order,
__('Create'));

View File

@ -13,9 +13,10 @@
"supplier",
"supplier_name",
"column_break1",
"transaction_date",
"amended_from",
"company",
"transaction_date",
"valid_till",
"amended_from",
"address_section",
"supplier_address",
"contact_person",
@ -791,13 +792,18 @@
"options": "Opportunity",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "valid_till",
"fieldtype": "Date",
"label": "Valid Till"
}
],
"icon": "fa fa-shopping-cart",
"idx": 29,
"is_submittable": 1,
"links": [],
"modified": "2019-12-30 19:17:28.208693",
"modified": "2020-04-15 11:44:52.958022",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt, nowdate, add_days
from frappe.utils import flt, nowdate, add_days, getdate
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.buying_controller import BuyingController
@ -28,6 +28,7 @@ class SupplierQuotation(BuyingController):
validate_for_items(self)
self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", "qty")
self.validate_valid_till()
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
@ -52,6 +53,11 @@ class SupplierQuotation(BuyingController):
"is_child_table": True
}
})
def validate_valid_till(self):
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
frappe.throw(_("Valid till Date cannot be before Transaction Date"))
def update_rfq_supplier_status(self, include_me):
rfq_list = set([])
for item in self.items:
@ -158,3 +164,11 @@ def make_quotation(source_name, target_doc=None):
}, target_doc)
return doclist
def set_expired_status():
frappe.db.sql("""
UPDATE
`tabSupplier Quotation` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s
""", (nowdate()))

View File

@ -5,6 +5,8 @@ frappe.listview_settings['Supplier Quotation'] = {
return [__("Ordered"), "green", "status,=,Ordered"];
} else if(doc.status==="Rejected") {
return [__("Lost"), "darkgrey", "status,=,Lost"];
} else if(doc.status==="Expired") {
return [__("Expired"), "darkgrey", "status,=,Expired"];
}
}
};

View File

@ -5,20 +5,18 @@ frappe.query_reports["Quoted Item Comparison"] = {
filters: [
{
fieldtype: "Link",
label: __("Supplier Quotation"),
options: "Supplier Quotation",
fieldname: "supplier_quotation",
default: "",
get_query: () => {
return { filters: { "docstatus": ["<", 2] } }
}
label: __("Company"),
options: "Company",
fieldname: "company",
default: frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
reqd: 1,
default: "",
options: "Item",
label: __("Item"),
fieldname: "item",
fieldname: "item_code",
fieldtype: "Link",
get_query: () => {
let quote = frappe.query_report.get_filter_value('supplier_quotation');
@ -37,8 +35,37 @@ frappe.query_reports["Quoted Item Comparison"] = {
}
}
}
},
{
fieldname: "supplier",
label: __("Supplier"),
fieldtype: "MultiSelectList",
get_data: function(txt) {
return frappe.db.get_link_options('Supplier', txt);
}
},
{
fieldtype: "Link",
label: __("Supplier Quotation"),
options: "Supplier Quotation",
fieldname: "supplier_quotation",
default: "",
get_query: () => {
return { filters: { "docstatus": ["<", 2] } }
}
},
{
fieldtype: "Link",
label: __("Request for Quotation"),
options: "Request for Quotation",
fieldname: "request_for_quotation",
default: "",
get_query: () => {
return { filters: { "docstatus": ["<", 2] } }
}
}
],
onload: (report) => {
// Create a button for setting the default supplier
report.page.add_inner_button(__("Select Default Supplier"), () => {
@ -103,5 +130,3 @@ frappe.query_reports["Quoted Item Comparison"] = {
dialog.show();
}
}

View File

@ -2,103 +2,180 @@
# For license information, please see license.txt
from __future__ import unicode_literals
from erpnext.setup.utils import get_exchange_rate
from frappe.utils import flt, cint
import frappe
from frappe.utils import flt, cint
from frappe import _
from collections import defaultdict
from erpnext.setup.utils import get_exchange_rate
def execute(filters=None):
qty_list = get_quantity_list(filters.item)
data = get_quote_list(filters.item, qty_list)
columns = get_columns(qty_list)
return columns, data
if not filters:
return [], []
def get_quote_list(item, qty_list):
out = []
if not item:
conditions = get_conditions(filters)
supplier_quotation_data = get_data(filters, conditions)
columns = get_columns()
data, chart_data = prepare_data(supplier_quotation_data)
return columns, data, None, chart_data
def get_conditions(filters):
conditions = ""
if filters.get("supplier_quotation"):
conditions += " AND sqi.parent = %(supplier_quotation)s"
if filters.get("request_for_quotation"):
conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
if filters.get("supplier"):
conditions += " AND sq.supplier in %(supplier)s"
return conditions
def get_data(filters, conditions):
if not filters.get("item_code"):
return []
suppliers = []
price_data = []
supplier_quotation_data = frappe.db.sql("""SELECT
sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
sq.supplier
FROM
`tabSupplier Quotation Item` sqi,
`tabSupplier Quotation` sq
WHERE
sqi.item_code = %(item_code)s
AND sqi.parent = sq.name
AND sqi.docstatus < 2
AND sq.company = %(company)s
AND sq.status != 'Expired'
{0}""".format(conditions), filters, as_dict=1)
return supplier_quotation_data
def prepare_data(supplier_quotation_data):
out, suppliers, qty_list = [], [], []
supplier_wise_map = defaultdict(list)
supplier_qty_price_map = {}
company_currency = frappe.db.get_default("currency")
float_precision = cint(frappe.db.get_default("float_precision")) or 2
# Get the list of suppliers
for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item`
where item_code=%s and docstatus < 2""", item, as_dict=1):
for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation`
where name =%s and docstatus < 2""", root.parent, as_dict=1):
ip = frappe._dict({
"supplier": splr.supplier,
"qty": root.qty,
"parent": root.parent,
"rate": root.rate
})
price_data.append(ip)
suppliers.append(splr.supplier)
#Add a row for each supplier
for root in set(suppliers):
supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
for data in supplier_quotation_data:
supplier = data.get("supplier")
supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency")
if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
else:
exchange_rate = 1
row = frappe._dict({
"supplier_name": root
})
for col in qty_list:
# Get the quantity for this row
for item_price in price_data:
if str(item_price.qty) == col.key and item_price.supplier == root:
row[col.key] = flt(item_price.rate * exchange_rate, float_precision)
row[col.key + "QUOTE"] = item_price.parent
break
else:
row[col.key] = ""
row[col.key + "QUOTE"] = ""
out.append(row)
row = {
"quotation": data.get("parent"),
"qty": data.get("qty"),
"price": flt(data.get("rate") * exchange_rate, float_precision),
"uom": data.get("uom"),
"request_for_quotation": data.get("request_for_quotation"),
}
return out
# map for report view of form {'supplier1':[{},{},...]}
supplier_wise_map[supplier].append(row)
def get_quantity_list(item):
out = []
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
if not supplier in supplier_qty_price_map:
supplier_qty_price_map[supplier] = {}
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
if item:
qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item`
where ifnull(item_code,'')=%s and docstatus < 2 order by qty""", item, as_dict=1)
suppliers.append(supplier)
qty_list.append(data.get("qty"))
for qt in qty_list:
col = frappe._dict({
"key": str(qt.qty),
"label": "Qty: " + str(int(qt.qty))
})
out.append(col)
suppliers = list(set(suppliers))
qty_list = list(set(qty_list))
return out
# final data format for report view
for supplier in suppliers:
supplier_wise_map[supplier][0].update({"supplier_name": supplier})
for entry in supplier_wise_map[supplier]:
out.append(entry)
def get_columns(qty_list):
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
return out, chart_data
def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
data_points_map = {}
qty_list.sort()
# create qty wise values map of the form {'qty1':[value1, value2]}
for supplier in suppliers:
entry = supplier_qty_price_map[supplier]
for qty in qty_list:
if not qty in data_points_map:
data_points_map[qty] = []
if qty in entry:
data_points_map[qty].append(entry[qty])
else:
data_points_map[qty].append(None)
dataset = []
for qty in qty_list:
datapoints = {
"name": _("Price for Qty ") + str(qty),
"values": data_points_map[qty]
}
dataset.append(datapoints)
chart_data = {
"data": {
"labels": suppliers,
"datasets": dataset
},
"type": "bar"
}
return chart_data
def get_columns():
columns = [{
"fieldname": "supplier_name",
"label": "Supplier",
"label": _("Supplier"),
"fieldtype": "Link",
"options": "Supplier",
"width": 200
}]
for qty in qty_list:
columns.append({
"fieldname": qty.key,
"label": qty.label,
"fieldtype": "Currency",
"options": "currency",
"width": 80
})
columns.append({
"fieldname": qty.key + "QUOTE",
"label": "Quotation",
"fieldtype": "Link",
"options": "Supplier Quotation",
"width": 90
})
},
{
"fieldname": "quotation",
"label": _("Supplier Quotation"),
"fieldtype": "Link",
"options": "Supplier Quotation",
"width": 200
},
{
"fieldname": "qty",
"label": _("Quantity"),
"fieldtype": "Float",
"width": 80
},
{
"fieldname": "price",
"label": _("Price"),
"fieldtype": "Currency",
"options": "Company:company:default_currency",
"width": 110
},
{
"fieldname": "uom",
"label": _("UOM"),
"fieldtype": "Link",
"options": "UOM",
"width": 90
},
{
"fieldname": "request_for_quotation",
"label": _("Request for Quotation"),
"fieldtype": "Link",
"options": "Request for Quotation",
"width": 200
}
]
return columns

View File

@ -69,17 +69,6 @@ status_map = {
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
],
"Purchase Invoice": [
["Draft", None],
["Submitted", "eval:self.docstatus==1"],
["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
["Return", "eval:self.is_return==1 and self.docstatus==1"],
["Debit Note Issued",
"eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
["Cancelled", "eval:self.docstatus==2"],
],
"Material Request": [
["Draft", None],
["Stopped", "eval:self.status == 'Stopped'"],

View File

@ -153,7 +153,7 @@ class Lead(SellingController):
if not self.lead_name:
self.set_lead_name()
names = self.lead_name.split(" ")
names = self.lead_name.strip().split(" ")
if len(names) > 1:
first_name, last_name = names[0], " ".join(names[1:])
else:

View File

@ -62,6 +62,8 @@ frappe.ui.form.on('LinkedIn Settings', {
callback : function(r) {
window.location.href = r.message;
}
}).fail(function() {
frappe.dom.unfreeze();
});
}
},

View File

@ -15,7 +15,7 @@ class LinkedInSettings(Document):
params = urlencode({
"response_type":"code",
"client_id": self.consumer_key,
"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
"redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
"scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
})
@ -30,7 +30,7 @@ class LinkedInSettings(Document):
"code": code,
"client_id": self.consumer_key,
"client_secret": self.get_password(fieldname="consumer_secret"),
"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
"redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
@ -154,7 +154,7 @@ class LinkedInSettings(Document):
return response
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def callback(code=None, error=None, error_description=None):
if not error:
linkedin_settings = frappe.get_doc("LinkedIn Settings")

View File

@ -47,6 +47,8 @@ frappe.ui.form.on('Twitter Settings', {
callback : function(r) {
window.location.href = r.message;
}
}).fail(function() {
frappe.dom.unfreeze();
});
}
},

View File

@ -11,8 +11,8 @@
"consumer_key",
"column_break_5",
"consumer_secret",
"oauth_token",
"oauth_secret",
"access_token",
"access_token_secret",
"session_status"
],
"fields": [
@ -41,20 +41,6 @@
"label": "API Secret Key",
"reqd": 1
},
{
"fieldname": "oauth_token",
"fieldtype": "Data",
"hidden": 1,
"label": "OAuth Token",
"read_only": 1
},
{
"fieldname": "oauth_secret",
"fieldtype": "Password",
"hidden": 1,
"label": "OAuth Token Secret",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
@ -72,12 +58,26 @@
"label": "Session Status",
"options": "Expired\nActive",
"read_only": 1
},
{
"fieldname": "access_token",
"fieldtype": "Data",
"hidden": 1,
"label": "Access Token",
"read_only": 1
},
{
"fieldname": "access_token_secret",
"fieldtype": "Data",
"hidden": 1,
"label": "Access Token Secret",
"read_only": 1
}
],
"image_field": "profile_pic",
"issingle": 1,
"links": [],
"modified": "2020-04-21 22:06:43.726798",
"modified": "2020-05-13 17:50:47.934776",
"modified_by": "Administrator",
"module": "CRM",
"name": "Twitter Settings",

View File

@ -12,13 +12,12 @@ from tweepy.error import TweepError
class TwitterSettings(Document):
def get_authorize_url(self):
callback_url = "{0}/?cmd=erpnext.crm.doctype.twitter_settings.twitter_settings.callback".format(frappe.utils.get_url())
callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url())
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
try:
redirect_url = auth.get_authorization_url()
return redirect_url
except:
except tweepy.TweepError as e:
frappe.msgprint(_("Error! Failed to get request token."))
frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
@ -32,13 +31,13 @@ class TwitterSettings(Document):
try:
auth.get_access_token(oauth_verifier)
api = self.get_api()
api = self.get_api(auth.access_token, auth.access_token_secret)
user = api.me()
profile_pic = (user._json["profile_image_url"]).replace("_normal","")
frappe.db.set_value(self.doctype, self.name, {
"oauth_token" : auth.access_token,
"oauth_secret" : auth.access_token_secret,
"access_token" : auth.access_token,
"access_token_secret" : auth.access_token_secret,
"account_name" : user._json["screen_name"],
"profile_pic" : profile_pic,
"session_status" : "Active"
@ -50,11 +49,11 @@ class TwitterSettings(Document):
frappe.msgprint(_("Error! Failed to get access token."))
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
def get_api(self):
def get_api(self, access_token, access_token_secret):
# authentication of consumer key and secret
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
# authentication of access token and secret
auth.set_access_token(self.oauth_token, self.get_password(fieldname="oauth_secret"))
auth.set_access_token(access_token, access_token_secret)
return tweepy.API(auth)
@ -68,13 +67,13 @@ class TwitterSettings(Document):
def upload_image(self, media):
media = get_file_path(media)
api = self.get_api()
api = self.get_api(self.access_token, self.access_token_secret)
media = api.media_upload(media)
return media.media_id
def send_tweet(self, text, media_id=None):
api = self.get_api()
api = self.get_api(self.access_token, self.access_token_secret)
try:
if media_id:
response = api.update_status(status = text, media_ids = [media_id])
@ -91,8 +90,12 @@ class TwitterSettings(Document):
frappe.db.commit()
frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason))
@frappe.whitelist()
def callback(oauth_token, oauth_verifier):
twitter_settings = frappe.get_single("Twitter Settings")
twitter_settings.get_access_token(oauth_token,oauth_verifier)
frappe.db.commit()
@frappe.whitelist(allow_guest=True)
def callback(oauth_token = None, oauth_verifier = None):
if oauth_token and oauth_verifier:
twitter_settings = frappe.get_single("Twitter Settings")
twitter_settings.get_access_token(oauth_token,oauth_verifier)
frappe.db.commit()
else:
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url_to_form("Twitter Settings","Twitter Settings")

View File

@ -19,6 +19,5 @@ def update_lead_phone_numbers(contact, method):
mobile_no = primary_mobile_nos[0]
lead = frappe.get_doc("Lead", contact_lead)
lead.phone = phone
lead.mobile_no = mobile_no
lead.save()
lead.db_set("phone", phone)
lead.db_set("mobile_no", mobile_no)

View File

@ -1,790 +1,207 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 0,
"autoname": "EDU-ASP-.YYYY.-.#####",
"beta": 0,
"creation": "2015-11-12 16:34:34.658092",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"student_group",
"assessment_name",
"assessment_group",
"grading_scale",
"column_break_2",
"course",
"program",
"academic_year",
"academic_term",
"section_break_5",
"schedule_date",
"room",
"examiner",
"examiner_name",
"column_break_4",
"from_time",
"to_time",
"supervisor",
"supervisor_name",
"section_break_20",
"maximum_assessment_score",
"assessment_criteria",
"amended_from"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Student Group",
"length": 0,
"no_copy": 0,
"options": "Student Group",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "assessment_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Assessment Name",
"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
"label": "Assessment Name"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "assessment_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Assessment Group",
"length": 0,
"no_copy": 0,
"options": "Assessment Group",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "course.default_grading_scale",
"fetch_if_empty": 1,
"fieldname": "grading_scale",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Grading Scale",
"length": 0,
"no_copy": 0,
"options": "Grading Scale",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "student_group.course",
"fetch_if_empty": 1,
"fieldname": "course",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Course",
"length": 0,
"no_copy": 0,
"options": "Course",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "student_group.program",
"fieldname": "program",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"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
"options": "Program"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "student_group.academic_year",
"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": 0,
"in_standard_filter": 0,
"label": "Academic Year",
"length": 0,
"no_copy": 0,
"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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Academic Year"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "student_group.academic_term",
"fieldname": "academic_term",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Academic Term",
"length": 0,
"no_copy": 0,
"options": "Academic Term",
"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
"options": "Academic Term"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": "",
"columns": 0,
"depends_on": "",
"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": "Schedule",
"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
"label": "Schedule"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today",
"fieldname": "schedule_date",
"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": "Schedule 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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "room",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Room",
"length": 0,
"no_copy": 0,
"options": "Room",
"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
"options": "Room"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "examiner",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Examiner",
"length": 0,
"no_copy": 0,
"options": "Instructor",
"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
"options": "Instructor"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "examiner.instructor_name",
"fieldname": "examiner_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Examiner Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_time",
"fieldtype": "Time",
"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": "From Time",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_time",
"fieldtype": "Time",
"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": "To Time",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "supervisor",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supervisor",
"length": 0,
"no_copy": 0,
"options": "Instructor",
"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
"options": "Instructor"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "supervisor.instructor_name",
"fieldname": "supervisor_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supervisor Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_20",
"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": "Evaluate",
"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
"label": "Evaluate"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "maximum_assessment_score",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Maximum Assessment Score",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "assessment_criteria",
"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": "Assessment Criteria",
"length": 0,
"no_copy": 0,
"options": "Assessment Plan Criteria",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Assessment Plan",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-08-30 00:48:03.475522",
"links": [],
"modified": "2020-05-09 14:56:26.746988",
"modified_by": "Administrator",
"module": "Education",
"name": "Assessment Plan",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
@ -794,28 +211,17 @@
"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": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "assessment_name",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"title_field": "assessment_name"
}

View File

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2017-04-05 13:33:04.519313",
"doctype": "DocType",
"editable_grid": 1,
@ -42,12 +43,14 @@
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.",
"fieldname": "validate_batch",
"fieldtype": "Check",
"label": "Validate Batch for Students in Student Group"
},
{
"default": "0",
"description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.",
"fieldname": "validate_course",
"fieldtype": "Check",
@ -74,13 +77,13 @@
{
"fieldname": "web_academy_settings_section",
"fieldtype": "Section Break",
"label": "LMS Settings"
"label": "Learning Management System Settings"
},
{
"depends_on": "eval: doc.enable_lms",
"fieldname": "portal_title",
"fieldtype": "Data",
"label": "LMS Title"
"label": "Learning Management System Title"
},
{
"depends_on": "eval: doc.enable_lms",
@ -89,9 +92,10 @@
"label": "Description"
},
{
"default": "0",
"fieldname": "enable_lms",
"fieldtype": "Check",
"label": "Enable LMS"
"label": "Enable Learning Management System"
},
{
"default": "0",
@ -102,7 +106,8 @@
}
],
"issingle": 1,
"modified": "2019-05-13 18:36:13.127563",
"links": [],
"modified": "2020-05-07 19:18:10.639356",
"modified_by": "Administrator",
"module": "Education",
"name": "Education Settings",

View File

@ -98,14 +98,16 @@ class Fees(AccountsController):
"debit_in_account_currency": self.grand_total,
"against_voucher": self.name,
"against_voucher_type": self.doctype
})
}, item=self)
fee_gl_entry = self.get_gl_dict({
"account": self.income_account,
"against": self.student,
"credit": self.grand_total,
"credit_in_account_currency": self.grand_total,
"cost_center": self.cost_center
})
}, item=self)
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries([student_gl_entries, fee_gl_entry], cancel=(self.docstatus == 2),
update_outstanding="Yes", merge_entries=False)

View File

@ -209,7 +209,7 @@ def new_bank_transaction(transaction):
result.append(new_transaction.name)
except Exception:
frappe.throw(frappe.get_traceback())
frappe.throw(title=_('Bank transaction creation error'))
return result

View File

@ -0,0 +1,41 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
import json
def get_data():
return frappe._dict({
"dashboards": get_dashboards(),
"charts": get_charts(),
})
def get_dashboards():
return [{
"name": "Healthcare",
"dashboard_name": "Healthcare",
"charts": [
{ "chart": "Patient Appointments" }
]
}]
def get_charts():
return [
{
"doctype": "Dashboard Chart",
"time_interval": "Daily",
"name": "Patient Appointments",
"chart_name": "Patient Appointments",
"timespan": "Last Month",
"color": "#77ecca",
"filters_json": json.dumps({}),
"chart_type": "Count",
"timeseries": 1,
"based_on": "appointment_datetime",
"owner": "Administrator",
"document_type": "Patient Appointment",
"type": "Line",
"width": "Half"
}
]

View File

@ -308,7 +308,8 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status"
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status"
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",

View File

@ -76,25 +76,15 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-03-19 18:06:45.361830",
"modified": "2020-05-14 17:17:38.883126",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Other Income",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -104,9 +94,12 @@
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -116,9 +109,12 @@
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@ -128,6 +124,7 @@
"report": 1,
"role": "Employee",
"share": 1,
"submit": 1,
"write": 1
}
],

View File

@ -116,8 +116,9 @@ class ExpenseClaim(AccountsController):
"party_type": "Employee",
"party": self.employee,
"against_voucher_type": self.doctype,
"against_voucher": self.name
})
"against_voucher": self.name,
"cost_center": self.cost_center
}, item=self)
)
# expense entries
@ -129,7 +130,7 @@ class ExpenseClaim(AccountsController):
"debit_in_account_currency": data.sanctioned_amount,
"against": self.employee,
"cost_center": data.cost_center
})
}, item=data)
)
for data in self.advances:
@ -157,7 +158,7 @@ class ExpenseClaim(AccountsController):
"credit": self.grand_total,
"credit_in_account_currency": self.grand_total,
"against": self.employee
})
}, item=self)
)
gl_entry.append(
@ -170,7 +171,7 @@ class ExpenseClaim(AccountsController):
"debit_in_account_currency": self.grand_total,
"against_voucher": self.name,
"against_voucher_type": self.doctype,
})
}, item=self)
)
return gl_entry
@ -187,7 +188,7 @@ class ExpenseClaim(AccountsController):
"cost_center": self.cost_center,
"against_voucher_type": self.doctype,
"against_voucher": self.name
})
}, item=tax)
)
def validate_account_details(self):

View File

@ -13,9 +13,11 @@
"description",
"section_break_6",
"amount",
"cost_center",
"column_break_8",
"sanctioned_amount"
"sanctioned_amount",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
],
"fields": [
{
@ -104,12 +106,21 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2019-12-11 13:42:33.233432",
"modified": "2020-05-11 18:54:35.601592",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Detail",

View File

@ -8,14 +8,16 @@
"engine": "InnoDB",
"field_order": [
"account_head",
"cost_center",
"rate",
"col_break1",
"description",
"section_break_6",
"tax_amount",
"column_break_8",
"total"
"total",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
],
"fields": [
{
@ -91,11 +93,20 @@
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2020-03-11 13:25:06.721917",
"modified": "2020-05-11 19:01:26.611758",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Taxes and Charges",

View File

@ -191,7 +191,7 @@
"default": "Leave",
"fieldname": "payroll_based_on",
"fieldtype": "Select",
"label": "Calculate Working Days in Payroll based on",
"label": "Calculate Payroll Working Days Based On",
"options": "Leave\nAttendance"
},
{
@ -206,7 +206,7 @@
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2020-04-13 21:20:59.382394",
"modified": "2020-05-11 13:02:51.274347",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",

View File

@ -549,7 +549,7 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry):
return _get_remaining_leaves(total_leaves, allocation.to_date)
def get_leaves_for_period(employee, leave_type, from_date, to_date):
def get_leaves_for_period(employee, leave_type, from_date, to_date, do_not_skip_expired_leaves=False):
leave_entries = get_leave_entries(employee, leave_type, from_date, to_date)
leave_days = 0
@ -559,8 +559,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
leave_days += leave_entry.leaves
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date):
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \
and (do_not_skip_expired_leaves or not skip_expiry_leaves(leave_entry, to_date)):
leave_days += leave_entry.leaves
elif leave_entry.transaction_type == 'Leave Application':

View File

@ -88,32 +88,40 @@ def get_previous_expiry_ledger_entry(ledger):
}, fieldname=['name'])
def process_expired_allocation():
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry
Case 1: carry forwarded expiry period is set for the leave type,
create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
Case 2: leave type has no specific expiry period for carry forwarded leaves
and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
'''
# fetch leave type records that has carry forwarded leaves expiry
leave_type_records = frappe.db.get_values("Leave Type", filters={
'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name'])
leave_type = [record[0] for record in leave_type_records]
leave_type = [record[0] for record in leave_type_records] or ['']
expired_allocation = frappe.db.sql_list("""SELECT name
FROM `tabLeave Ledger Entry`
WHERE
`transaction_type`='Leave Allocation'
AND `is_expired`=1""")
expire_allocation = frappe.get_all("Leave Ledger Entry",
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
filters={
'to_date': ("<", today()),
'transaction_type': 'Leave Allocation',
'transaction_name': ('not in', expired_allocation)
},
or_filters={
'is_carry_forward': 0,
'leave_type': ('in', leave_type)
})
# fetch non expired leave ledger entry of transaction_type allocation
expire_allocation = frappe.db.sql("""
SELECT
leaves, to_date, employee, leave_type,
is_carry_forward, transaction_name as name, transaction_type
FROM `tabLeave Ledger Entry` l
WHERE (NOT EXISTS
(SELECT name
FROM `tabLeave Ledger Entry`
WHERE
transaction_name = l.transaction_name
AND transaction_type = 'Leave Allocation'
AND name<>l.name
AND docstatus = 1
AND (
is_carry_forward=l.is_carry_forward
OR (is_carry_forward = 0 AND leave_type not in %s)
)))
AND transaction_type = 'Leave Allocation'
AND to_date < %s""", (leave_type, today()), as_dict=1)
if expire_allocation:
create_expiry_ledger_entry(expire_allocation)
@ -133,6 +141,7 @@ def get_remaining_leaves(allocation):
'employee': allocation.employee,
'leave_type': allocation.leave_type,
'to_date': ('<=', allocation.to_date),
'docstatus': 1
}, fieldname=['SUM(leaves)'])
@frappe.whitelist()
@ -159,7 +168,8 @@ def expire_allocation(allocation, expiry_date=None):
def expire_carried_forward_allocation(allocation):
''' Expires remaining leaves in the on carried forward allocation '''
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type,
allocation.from_date, allocation.to_date, do_not_skip_expired_leaves=True)
leaves = flt(allocation.leaves) + flt(leaves_taken)
# allow expired leaves entry to be created

View File

@ -145,7 +145,7 @@ def import_attendances(rows):
def remove_holidays(rows):
rows = [ row for row in rows if row[4] != "Holiday"]
return
return rows
from frappe.modules import scrub

View File

@ -215,7 +215,7 @@ def get_conditions(filters):
def get_employee_details(group_by, company):
emp_map = {}
query = """select name, employee_name, designation, department, branch, company,
holiday_list from `tabEmployee` where company = '%s' """ % frappe.db.escape(company)
holiday_list from `tabEmployee` where company = %s """ % frappe.db.escape(company)
if group_by:
group_by = group_by.lower()

View File

@ -248,8 +248,7 @@ def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_d
for loan_security in loan_security_pledge_details:
unpledge_request.append('securities', {
"loan_security": loan_security.loan_security,
"qty": loan_security.qty,
"against_pledge": loan_security.parent
"qty": loan_security.qty
})
if as_dict:

View File

@ -15,6 +15,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
class TestLoan(unittest.TestCase):
def setUp(self):
@ -152,7 +153,7 @@ class TestLoan(unittest.TestCase):
repayment_entry.save()
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year))
self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
@ -305,7 +306,7 @@ class TestLoan(unittest.TestCase):
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
repayment_entry.submit()
@ -319,13 +320,12 @@ class TestLoan(unittest.TestCase):
unpledge_request.submit()
unpledge_request.status = 'Approved'
unpledge_request.save()
loan_security_pledge.load_from_db()
loan.load_from_db()
pledged_qty = get_pledged_security_qty(loan.name)
self.assertEqual(loan.status, 'Closed')
for security in loan_security_pledge.securities:
self.assertEquals(security.qty, 0)
self.assertEquals(sum(pledged_qty.values()), 0)
def create_loan_accounts():

View File

@ -264,6 +264,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
penalty_amount = 0
payable_principal_amount = 0
final_due_date = ''
due_date = ''
for entry in accrued_interest_entries:
# Loan repayment due date is one day after the loan interest is accrued
@ -272,7 +273,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
due_date = add_days(entry.posting_date, 1)
no_of_late_days = date_diff(posting_date,
add_days(due_date, loan_type_details.grace_period_in_days)) + 1
add_days(due_date, loan_type_details.grace_period_in_days))
if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary):
penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365
@ -290,9 +291,9 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
if payment_type == "Loan Closure" and not payable_principal_amount:
if final_due_date:
pending_days = date_diff(posting_date, final_due_date)
if payment_type == "Loan Closure":
if due_date:
pending_days = date_diff(posting_date, due_date) + 1
else:
pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1

View File

@ -38,7 +38,7 @@ class LoanSecurityPledge(Document):
for pledge in self.securities:
if not pledge.qty and not pledge.amount:
frappe.throw(_("Qty or Amount is mandatroy for loan security"))
frappe.throw(_("Qty or Amount is mandatory for loan security!"))
if not (self.loan_application and pledge.loan_security_price):
pledge.loan_security_price = get_loan_security_price(pledge.loan_security)

View File

@ -69,7 +69,7 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100)
for loan, value in iteritems(loan_security_map):
if (value["security_value"]/value["loan_amount"]) < ltv_ratio:
if (value["loan_amount"]/value['security_value'] * 100) > ltv_ratio:
create_loan_security_shortfall(loan, value, process_loan_security_shortfall)
def create_loan_security_shortfall(loan, value, process_loan_security_shortfall):

View File

@ -4,10 +4,8 @@
frappe.ui.form.on('Loan Security Unpledge', {
refresh: function(frm) {
frm.set_query("against_pledge", "securities", () => {
return {
filters : [["status", "in", ["Pledged", "Partially Pledged"]]]
};
});
if (frm.doc.docstatus == 1 && frm.doc.status == 'Approved') {
frm.set_df_property('status', 'read_only', 1);
}
}
});

View File

@ -8,12 +8,13 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils import get_datetime, flt
import json
from six import iteritems
from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price
class LoanSecurityUnpledge(Document):
def validate(self):
self.validate_pledges()
self.validate_duplicate_securities()
self.validate_unpledge_qty()
def on_cancel(self):
self.update_loan_security_pledge(cancel=1)
@ -23,80 +24,52 @@ class LoanSecurityUnpledge(Document):
def validate_duplicate_securities(self):
security_list = []
for d in self.securities:
security = [d.loan_security, d.against_pledge]
if security not in security_list:
security_list.append(security)
if d.loan_security not in security_list:
security_list.append(d.loan_security)
else:
frappe.throw(_("Row {0}: Loan Security {1} against Loan Security Pledge {2} added multiple times").format(
d.idx, frappe.bold(d.loan_security), frappe.bold(d.against_pledge)))
frappe.throw(_("Row {0}: Loan Security {1} added multiple times").format(
d.idx, frappe.bold(d.loan_security)))
def validate_pledges(self):
pledge_qty_map = self.get_pledge_details()
loan = frappe.get_doc("Loan", self.loan)
def validate_unpledge_qty(self):
pledge_qty_map = get_pledged_security_qty(self.loan)
remaining_qty = 0
unpledge_value = 0
ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
fields=["name", "loan_to_value_ratio"], as_list=1))
loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price",
fields=["loan_security", "loan_security_price"],
filters = {
"valid_from": ("<=", get_datetime()),
"valid_upto": (">=", get_datetime())
}, as_list=1))
loan_amount, principal_paid = frappe.get_value("Loan", self.loan, ['loan_amount', 'total_principal_paid'])
pending_principal_amount = loan_amount - principal_paid
security_value = 0
for security in self.securities:
pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0)
if not pledged_qty:
frappe.throw(_("Zero qty of {0} pledged against loan {1}").format(frappe.bold(security.loan_security),
frappe.bold(self.loan)))
pledged_qty = pledge_qty_map.get(security.loan_security)
unpledge_qty = pledged_qty - security.qty
security_price = security.qty * get_loan_security_price(security.loan_security)
if security.qty > pledged_qty:
frappe.throw(_("""Row {0}: {1} {2} of {3} is pledged against Loan {4}.
You are trying to unpledge more""").format(security.idx, pledged_qty, security.uom,
frappe.bold(security.loan_security), frappe.bold(self.loan)))
if unpledge_qty < 0:
frappe.throw(_("""Row {0}: Cannot unpledge more than {1} qty of {2} against
Loan Security Pledge {3}""").format(security.idx, frappe.bold(pledged_qty),
frappe.bold(security.loan_security), frappe.bold(security.against_pledge)))
qty_after_unpledge = pledged_qty - security.qty
ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
remaining_qty += unpledge_qty
unpledge_value += security_price - flt(security_price * security.haircut/100)
security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security)
if unpledge_value > loan.total_principal_paid:
frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount"))
if not security_value and pending_principal_amount > 0:
frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
def get_pledge_details(self):
pledge_qty_map = {}
pledge_details = frappe.db.sql("""
SELECT p.parent, p.loan_security, p.qty FROM
`tabLoan Security Pledge` lsp,
`tabPledge` p
WHERE
p.parent = lsp.name
AND lsp.loan = %s
AND lsp.docstatus = 1
AND lsp.status in ('Pledged', 'Partially Pledged')
""", (self.loan), as_dict=1)
for pledge in pledge_details:
pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty)
return pledge_qty_map
if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio:
frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
def on_update_after_submit(self):
if self.status == "Approved":
self.update_loan_security_pledge()
self.update_loan_status()
def update_loan_security_pledge(self, cancel=0):
if cancel:
new_qty = 'p.qty + u.qty'
else:
new_qty = 'p.qty - u.qty'
frappe.db.sql("""
UPDATE
`tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp, `tabLoan Security Unpledge` lsu
SET p.qty = {new_qty}
WHERE
lsp.loan = %s
AND p.parent = u.against_pledge
AND p.parent = lsp.name
AND lsp.docstatus = 1
AND p.loan_security = u.loan_security""".format(new_qty=new_qty),(self.loan))
self.db_set('unpledge_time', get_datetime())
def update_loan_status(self, cancel=0):
if cancel:
@ -104,10 +77,45 @@ class LoanSecurityUnpledge(Document):
if loan_status == 'Closed':
frappe.db.set_value('Loan', self.loan, 'status', 'Loan Closure Requested')
else:
pledge_qty = frappe.db.sql("""SELECT SUM(c.qty)
FROM `tabLoan Security Pledge` p, `tabPledge` c
WHERE p.loan = %s AND c.parent = p.name""", (self.loan))[0][0]
pledged_qty = 0
current_pledges = get_pledged_security_qty(self.loan)
if not pledge_qty:
for security, qty in iteritems(current_pledges):
pledged_qty += qty
if not pledged_qty:
frappe.db.set_value('Loan', self.loan, 'status', 'Closed')
@frappe.whitelist()
def get_pledged_security_qty(loan):
current_pledges = {}
unpledges = frappe._dict(frappe.db.sql("""
SELECT u.loan_security, sum(u.qty) as qty
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
WHERE up.loan = %s
AND u.parent = up.name
AND up.status = 'Approved'
GROUP BY u.loan_security
""", (loan)))
pledges = frappe._dict(frappe.db.sql("""
SELECT p.loan_security, sum(p.qty) as qty
FROM `tabLoan Security Pledge` lp, `tabPledge`p
WHERE lp.loan = %s
AND p.parent = lp.name
AND lp.status = 'Pledged'
GROUP BY p.loan_security
""", (loan)))
for security, qty in iteritems(pledges):
current_pledges.setdefault(security, qty)
current_pledges[security] -= unpledges.get(security, 0.0)
return current_pledges

View File

@ -1,11 +1,11 @@
{
"actions": [],
"creation": "2019-09-21 13:22:19.793797",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"loan_security",
"against_pledge",
"loan_security_type",
"loan_security_code",
"haircut",
@ -54,14 +54,6 @@
"label": "Quantity",
"reqd": 1
},
{
"fieldname": "against_pledge",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Against Pledge",
"options": "Loan Security Pledge",
"reqd": 1
},
{
"fetch_from": "loan_security.haircut",
"fieldname": "haircut",
@ -71,7 +63,8 @@
}
],
"istable": 1,
"modified": "2019-10-02 12:48:18.588236",
"links": [],
"modified": "2020-05-06 10:50:18.448552",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Unpledge",

View File

@ -206,30 +206,31 @@ class JobCard(Document):
for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], []
field = "operation_id" if self.operation_id else "operation"
data = frappe.get_all('Job Card',
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
filters = {"docstatus": 1, "work_order": self.work_order,
"workstation": self.workstation, "operation": self.operation})
"workstation": self.workstation, field: self.get(field)})
if data and len(data) > 0:
for_quantity = data[0].completed_qty
time_in_mins = data[0].time_in_mins
if for_quantity:
if self.get(field):
time_data = frappe.db.sql("""
SELECT
min(from_time) as start_time, max(to_time) as end_time
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s
and jc.workstation = %s and jc.operation = %s and jc.docstatus = 1
""", (self.work_order, self.workstation, self.operation), as_dict=1)
and jc.workstation = %s and jc.{0} = %s and jc.docstatus = 1
""".format(field), (self.work_order, self.workstation, self.get(field)), as_dict=1)
wo = frappe.get_doc('Work Order', self.work_order)
work_order_field = "name" if field == "operation_id" else field
for data in wo.operations:
if data.workstation == self.workstation and data.operation == self.operation:
if data.get(work_order_field) == self.get(field) and data.workstation == self.workstation:
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None

View File

@ -421,6 +421,9 @@ class WorkOrder(Document):
return holidays[holiday_list]
def update_operation_status(self):
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order"))
max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage/100 * flt(self.qty))
for d in self.get("operations"):
if not d.completed_qty:
d.status = "Pending"
@ -428,6 +431,8 @@ class WorkOrder(Document):
d.status = "Work in Progress"
elif flt(d.completed_qty) == flt(self.qty):
d.status = "Completed"
elif flt(d.completed_qty) <= max_allowed_qty_for_wo:
d.status = "Completed"
else:
frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))

View File

@ -495,6 +495,7 @@ erpnext.patches.v10_0.rename_offer_letter_to_job_offer
execute:frappe.delete_doc('DocType', 'Production Planning Tool', ignore_missing=True)
erpnext.patches.v10_0.migrate_daily_work_summary_settings_to_daily_work_summary_group # 24-12-2018
erpnext.patches.v10_0.add_default_cash_flow_mappers
erpnext.patches.v11_0.rename_duplicate_item_code_values
erpnext.patches.v11_0.make_quality_inspection_template
erpnext.patches.v10_0.update_status_for_multiple_source_in_po
erpnext.patches.v10_0.set_auto_created_serial_no_in_stock_entry
@ -622,7 +623,7 @@ erpnext.patches.v11_1.update_default_supplier_in_item_defaults
erpnext.patches.v12_0.update_due_date_in_gle
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes #2020-05-11
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
erpnext.patches.v12_0.move_plaid_settings_to_doctype
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link')
@ -630,7 +631,6 @@ execute:frappe.reload_doc('desk', 'doctype', 'dashboard')
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_source')
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart')
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_field')
erpnext.patches.v12_0.add_default_dashboards # 2020-04-05
erpnext.patches.v12_0.remove_bank_remittance_custom_fields
erpnext.patches.v12_0.generate_leave_ledger_entries
execute:frappe.delete_doc_if_exists("Report", "Loan Repayment")
@ -678,4 +678,9 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
erpnext.patches.v12_0.fix_quotation_expired_status
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
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
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
erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation

View File

@ -0,0 +1,8 @@
import frappe
def execute():
items = []
items = frappe.db.sql("""select item_code from `tabItem` group by item_code having count(*) > 1""", as_dict=True)
if items:
for item in items:
frappe.db.sql("""update `tabItem` set item_code=name where item_code = %s""", (item.item_code))

View File

@ -1,8 +1,9 @@
from __future__ import unicode_literals
from frappe import _
import frappe
def execute():
hr_settings = frappe.get_single("HR Settings")
hr_settings.leave_approval_notification_template = "Leave Approval Notification"
hr_settings.leave_status_notification_template = "Leave Status Notification"
hr_settings.leave_approval_notification_template = _("Leave Approval Notification")
hr_settings.leave_status_notification_template = _("Leave Status Notification")
hr_settings.save()

View File

@ -1,10 +0,0 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from erpnext.setup.setup_wizard.operations.install_fixtures import add_dashboards
def execute():
frappe.reload_doc("desk", "doctype", "number_card_link")
frappe.reload_doc("healthcare", "doctype", "patient_appointment")
add_dashboards()

View File

@ -20,7 +20,8 @@ def execute():
else:
insert_after_field = 'accounting_dimensions_section'
for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item"]:
for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item",
"Expense Claim Detail", "Expense Taxes and Charges"]:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})

View File

@ -0,0 +1,44 @@
# Copyright (c) 2018, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
"""Delete duplicate leave ledger entries of type allocation created."""
if not frappe.db.a_row_exists("Leave Ledger Entry"):
return
duplicate_records_list = get_duplicate_records()
delete_duplicate_ledger_entries(duplicate_records_list)
def get_duplicate_records():
"""Fetch all but one duplicate records from the list of expired leave allocation."""
return frappe.db.sql_list("""
WITH duplicate_records AS
(SELECT
name, transaction_name, is_carry_forward,
ROW_NUMBER() over(partition by transaction_name order by creation)as row
FROM `tabLeave Ledger Entry` l
WHERE (EXISTS
(SELECT name
FROM `tabLeave Ledger Entry`
WHERE
transaction_name = l.transaction_name
AND transaction_type = 'Leave Allocation'
AND name <> l.name
AND employee = l.employee
AND docstatus = 1
AND leave_type = l.leave_type
AND is_carry_forward=l.is_carry_forward
AND to_date = l.to_date
AND from_date = l.from_date
AND is_expired = 1
)))
SELECT name FROM duplicate_records WHERE row > 1
""")
def delete_duplicate_ledger_entries(duplicate_records_list):
"""Delete duplicate leave ledger entries."""
if duplicate_records_list:
frappe.db.sql(''' DELETE FROM `tabLeave Ledger Entry` WHERE name in {0}'''.format(tuple(duplicate_records_list))) #nosec

View File

@ -3,6 +3,10 @@ import frappe
from collections import defaultdict
def execute():
frappe.reload_doc('stock', 'doctype', 'delivery_note_item', force=True)
frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item', force=True)
def map_rows(doc_row, return_doc_row, detail_field, doctype):
"""Map rows after identifying similar ones."""

View File

@ -0,0 +1,8 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("buying", "doctype", "supplier_quotation")
frappe.db.sql("""UPDATE `tabSupplier Quotation`
SET valid_till = DATE_ADD(transaction_date , INTERVAL 1 MONTH)
WHERE docstatus < 2""")

View File

@ -0,0 +1,15 @@
from __future__ import unicode_literals
import frappe
def execute():
invalid_selling_item_price = frappe.db.sql(
"""SELECT name FROM `tabItem Price` WHERE selling = 1 and buying = 0 and (supplier IS NOT NULL or supplier = '')"""
)
invalid_buying_item_price = frappe.db.sql(
"""SELECT name FROM `tabItem Price` WHERE selling = 0 and buying = 1 and (customer IS NOT NULL or customer = '')"""
)
docs_to_modify = invalid_buying_item_price + invalid_selling_item_price
for d in docs_to_modify:
# saving the doc will auto reset invalid customer/supplier field
doc = frappe.get_doc("Item Price", d[0])
doc.save()

View File

@ -7,7 +7,7 @@ import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
if not frappe.db.table_exists("Payroll Period"):
if not (frappe.db.table_exists("Payroll Period") and frappe.db.table_exists("Taxable Salary Slab")):
return
for doctype in ("income_tax_slab", "salary_structure_assignment", "employee_other_income", "income_tax_slab_other_charges"):
@ -60,6 +60,9 @@ def execute():
""", (income_tax_slab.name, company.name, period.start_date))
# move other incomes to separate document
if not frappe.db.table_exists("Employee Tax Exemption Proof Submission"):
return
migrated = []
proofs = frappe.get_all("Employee Tax Exemption Proof Submission",
filters = {'docstatus': 1},
@ -79,6 +82,9 @@ def execute():
except:
pass
if not frappe.db.table_exists("Employee Tax Exemption Declaration"):
return
declerations = frappe.get_all("Employee Tax Exemption Declaration",
filters = {'docstatus': 1},
fields =['payroll_period', 'employee', 'company', 'income_from_other_sources']

View File

@ -0,0 +1,4 @@
{{ country }}<br>{% if pincode %}{{ pincode }}<br>{% endif -%}{{ county }}{{ city }}{{ address_line1 }}{% if address_line2 %}{{ address_line2 }}{% endif -%}
{% if phone %}<br>Phone: {{ phone }}{% endif -%}
{% if fax %}<br>Fax: {{ fax }}{% endif -%}
{% if email_id %}<br>Email: {{ email_id }}{% endif -%}

View File

@ -251,8 +251,7 @@ def get_tax_template_for_sez(party_details, master_doctype, company, party_type)
def calculate_annual_eligible_hra_exemption(doc):
basic_component = frappe.get_cached_value('Company', doc.company, "basic_component")
hra_component = frappe.get_cached_value('Company', doc.company, "hra_component")
basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"])
if not (basic_component and hra_component):
frappe.throw(_("Please mention Basic and HRA component in Company"))
annual_exemption, monthly_exemption, hra_amount = 0, 0, 0

View File

@ -165,6 +165,10 @@ class Customer(TransactionBase):
contact.mobile_no = lead.mobile_no
contact.is_primary_contact = 1
contact.append('links', dict(link_doctype='Customer', link_name=self.name))
if lead.email_id:
contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1))
if lead.mobile_no:
contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1))
contact.flags.ignore_permissions = self.flags.ignore_permissions
contact.autoname()
if not frappe.db.exists("Contact", contact.name):

View File

@ -3,6 +3,14 @@
frappe.query_reports["Customer Acquisition and Loyalty"] = {
"filters": [
{
"fieldname": "view_type",
"label": __("View Type"),
"fieldtype": "Select",
"options": ["Monthly", "Territory Wise"],
"default": "Monthly",
"reqd": 1
},
{
"fieldname":"company",
"label": __("Company"),
@ -24,6 +32,13 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
"fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_end_date"),
"reqd": 1
},
]
}
],
'formatter': function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
}
}

View File

@ -2,65 +2,186 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import calendar
import frappe
from frappe import _
from frappe.utils import getdate, cint, cstr
import calendar
from frappe.utils import cint, cstr
def execute(filters=None):
# key yyyy-mm
new_customers_in = {}
repeat_customers_in = {}
customers = []
company_condition = ""
common_columns = [
{
'label': _('New Customers'),
'fieldname': 'new_customers',
'fieldtype': 'Int',
'default': 0,
'width': 125
},
{
'label': _('Repeat Customers'),
'fieldname': 'repeat_customers',
'fieldtype': 'Int',
'default': 0,
'width': 125
},
{
'label': _('Total'),
'fieldname': 'total',
'fieldtype': 'Int',
'default': 0,
'width': 100
},
{
'label': _('New Customer Revenue'),
'fieldname': 'new_customer_revenue',
'fieldtype': 'Currency',
'default': 0.0,
'width': 175
},
{
'label': _('Repeat Customer Revenue'),
'fieldname': 'repeat_customer_revenue',
'fieldtype': 'Currency',
'default': 0.0,
'width': 175
},
{
'label': _('Total Revenue'),
'fieldname': 'total_revenue',
'fieldtype': 'Currency',
'default': 0.0,
'width': 175
}
]
if filters.get('view_type') == 'Monthly':
return get_data_by_time(filters, common_columns)
else:
return get_data_by_territory(filters, common_columns)
if filters.get("company"):
company_condition = ' and company=%(company)s'
def get_data_by_time(filters, common_columns):
# key yyyy-mm
columns = [
{
'label': _('Year'),
'fieldname': 'year',
'fieldtype': 'Data',
'width': 100
},
{
'label': _('Month'),
'fieldname': 'month',
'fieldtype': 'Data',
'width': 100
},
]
columns += common_columns
for si in frappe.db.sql("""select posting_date, customer, base_grand_total from `tabSales Invoice`
where docstatus=1 and posting_date <= %(to_date)s
{company_condition} order by posting_date""".format(company_condition=company_condition),
filters, as_dict=1):
customers_in = get_customer_stats(filters)
key = si.posting_date.strftime("%Y-%m")
if not si.customer in customers:
new_customers_in.setdefault(key, [0, 0.0])
new_customers_in[key][0] += 1
new_customers_in[key][1] += si.base_grand_total
customers.append(si.customer)
else:
repeat_customers_in.setdefault(key, [0, 0.0])
repeat_customers_in[key][0] += 1
repeat_customers_in[key][1] += si.base_grand_total
# time series
from_year, from_month, temp = filters.get('from_date').split('-')
to_year, to_month, temp = filters.get('to_date').split('-')
# time series
from_year, from_month, temp = filters.get("from_date").split("-")
to_year, to_month, temp = filters.get("to_date").split("-")
from_year, from_month, to_year, to_month = \
cint(from_year), cint(from_month), cint(to_year), cint(to_month)
from_year, from_month, to_year, to_month = \
cint(from_year), cint(from_month), cint(to_year), cint(to_month)
out = []
for year in range(from_year, to_year+1):
for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
key = '{year}-{month:02d}'.format(year=year, month=month)
data = customers_in.get(key)
new = data['new'] if data else [0, 0.0]
repeat = data['repeat'] if data else [0, 0.0]
out.append({
'year': cstr(year),
'month': calendar.month_name[month],
'new_customers': new[0],
'repeat_customers': repeat[0],
'total': new[0] + repeat[0],
'new_customer_revenue': new[1],
'repeat_customer_revenue': repeat[1],
'total_revenue': new[1] + repeat[1]
})
return columns, out
out = []
for year in range(from_year, to_year+1):
for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
key = "{year}-{month:02d}".format(year=year, month=month)
def get_data_by_territory(filters, common_columns):
columns = [{
'label': 'Territory',
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
'width': 150
}]
columns += common_columns
new = new_customers_in.get(key, [0,0.0])
repeat = repeat_customers_in.get(key, [0,0.0])
customers_in = get_customer_stats(filters, tree_view=True)
out.append([cstr(year), calendar.month_name[month],
new[0], repeat[0], new[0] + repeat[0],
new[1], repeat[1], new[1] + repeat[1]])
territory_dict = {}
for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
territory_dict.update({
t.name: {
'parent': t.parent_territory,
'is_group': t.is_group
}
})
return [
_("Year") + "::100",
_("Month") + "::100",
_("New Customers") + ":Int:100",
_("Repeat Customers") + ":Int:100",
_("Total") + ":Int:100",
_("New Customer Revenue") + ":Currency:150",
_("Repeat Customer Revenue") + ":Currency:150",
_("Total Revenue") + ":Currency:150"
], out
depth_map = frappe._dict()
for name, info in territory_dict.items():
default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
depth_map.setdefault(name, default)
data = []
for name, indent in depth_map.items():
condition = customers_in.get(name)
new = customers_in[name]['new'] if condition else [0, 0.0]
repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
temp = {
'territory': name,
'parent_territory': territory_dict[name]['parent'],
'indent': indent,
'new_customers': new[0],
'repeat_customers': repeat[0],
'total': new[0] + repeat[0],
'new_customer_revenue': new[1],
'repeat_customer_revenue': repeat[1],
'total_revenue': new[1] + repeat[1],
'bold': 0 if indent else 1
}
data.append(temp)
loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
for ld in loop_data:
if ld['parent_territory']:
parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
for key in parent_data.keys():
if key not in ['indent', 'territory', 'parent_territory', 'bold']:
parent_data[key] += ld[key]
return columns, data, None, None, None, 1
def get_customer_stats(filters, tree_view=False):
""" Calculates number of new and repeated customers. """
company_condition = ''
if filters.get('company'):
company_condition = ' and company=%(company)s'
customers = []
customers_in = {}
for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
where docstatus=1 and posting_date <= %(to_date)s and posting_date >= %(from_date)s
{company_condition} order by posting_date'''.format(company_condition=company_condition),
filters, as_dict=1):
key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
if not si.customer in customers:
customers_in[key]['new'][0] += 1
customers_in[key]['new'][1] += si.base_grand_total
customers.append(si.customer)
else:
customers_in[key]['repeat'][0] += 1
customers_in[key]['repeat'][1] += si.base_grand_total
return customers_in

View File

@ -7,7 +7,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-05-24 05:37:02.866139",
"modified": "2020-04-30 19:49:02.303320",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Analytics",

View File

@ -194,6 +194,9 @@ class Analytics(object):
def get_rows(self):
self.data = []
self.get_periodic_data()
total_row = {
"entity": "Total",
}
for entity, period_data in iteritems(self.entity_periodic_data):
row = {
@ -207,6 +210,9 @@ class Analytics(object):
row[scrub(period)] = amount
total += amount
if not total_row.get(scrub(period)): total_row[scrub(period)] = 0
total_row[scrub(period)] += amount
row["total"] = total
if self.filters.tree_type == "Item":
@ -214,6 +220,8 @@ class Analytics(object):
self.data.append(row)
self.data.append(total_row)
def get_rows_by_group(self):
self.get_periodic_data()
out = []
@ -232,8 +240,10 @@ class Analytics(object):
self.entity_periodic_data.setdefault(d.parent, frappe._dict()).setdefault(period, 0.0)
self.entity_periodic_data[d.parent][period] += amount
total += amount
row["total"] = total
out = [row] + out
self.data = out
def get_periodic_data(self):

View File

@ -33,6 +33,21 @@ class TestAnalytics(unittest.TestCase):
report = execute(filters)
expected_data = [
{
'entity': 'Total',
'apr_2017': 0.0,
'may_2017': 0.0,
'jun_2017': 2000.0,
'jul_2017': 1000.0,
'aug_2017': 0.0,
'sep_2017': 1500.0,
'oct_2017': 1000.0,
'nov_2017': 0.0,
'dec_2017': 0.0,
'jan_2018': 0.0,
'feb_2018': 2000.0,
'mar_2018': 0.0
},
{
"entity": "_Test Customer 1",
"entity_name": "_Test Customer 1",
@ -134,6 +149,21 @@ class TestAnalytics(unittest.TestCase):
report = execute(filters)
expected_data = [
{
'entity': 'Total',
'apr_2017': 0.0,
'may_2017': 0.0,
'jun_2017': 20.0,
'jul_2017': 10.0,
'aug_2017': 0.0,
'sep_2017': 15.0,
'oct_2017': 10.0,
'nov_2017': 0.0,
'dec_2017': 0.0,
'jan_2018': 0.0,
'feb_2018': 20.0,
'mar_2018': 0.0
},
{
"entity": "_Test Customer 1",
"entity_name": "_Test Customer 1",

View File

@ -20,31 +20,36 @@ def get_columns():
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory"
"options": "Territory",
"width": 150
},
{
"label": _("Opportunity Amount"),
"fieldname": "opportunity_amount",
"fieldtype": "Currency",
"options": currency
"options": currency,
"width": 150
},
{
"label": _("Quotation Amount"),
"fieldname": "quotation_amount",
"fieldtype": "Currency",
"options": currency
"options": currency,
"width": 150
},
{
"label": _("Order Amount"),
"fieldname": "order_amount",
"fieldtype": "Currency",
"options": currency
"options": currency,
"width": 150
},
{
"label": _("Billing Amount"),
"fieldname": "billing_amount",
"fieldtype": "Currency",
"options": currency
"options": currency,
"width": 150
}
]
@ -63,7 +68,6 @@ def get_data(filters=None):
t_opportunity_names = []
if territory_opportunities:
t_opportunity_names = [t.name for t in territory_opportunities]
territory_quotations = []
if t_opportunity_names and quotations:
territory_quotations = list(filter(lambda x: x.opportunity in t_opportunity_names, quotations))

View File

@ -47,26 +47,20 @@
}
],
"category": "Modules",
"charts": [
{
"chart_name": "Bank Balance",
"label": "Bank Balance"
}
],
"charts": [],
"creation": "2020-01-23 13:46:38.833076",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"icon": "",
"idx": 0,
"is_standard": 1,
"label": "Getting Started",
"modified": "2020-04-01 11:30:19.763099",
"label": "Home",
"modified": "2020-05-11 10:20:37.358701",
"modified_by": "Administrator",
"module": "Setup",
"name": "Getting Started",
"name": "Home",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 1,

View File

@ -47,9 +47,7 @@ class TestCompany(unittest.TestCase):
frappe.delete_doc("Company", "COA from Existing Company")
def test_coa_based_on_country_template(self):
countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France",
"Guatemala", "Indonesia", "Italy", "Mexico", "Nicaragua", "Netherlands", "Singapore",
"Brazil", "Argentina", "Hungary", "Taiwan"]
countries = ["Canada", "Germany", "France"]
for country in countries:
templates = get_charts_for_country(country)

View File

@ -3,8 +3,6 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import flt
from frappe import _
@ -14,6 +12,7 @@ class Territory(NestedSet):
nsm_parent_field = 'parent_territory'
def validate(self):
for d in self.get('targets') or []:
if not flt(d.target_qty) and not flt(d.target_amount):
frappe.throw(_("Either target qty or target amount is mandatory"))

View File

@ -1,133 +0,0 @@
from __future__ import unicode_literals
from frappe import _
import frappe
import json
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
def get_default_dashboards():
company = frappe.get_doc("Company", get_company_for_dashboards())
income_account = company.default_income_account or get_account("Income Account", company.name)
expense_account = company.default_expense_account or get_account("Expense Account", company.name)
bank_account = company.default_bank_account or get_account("Bank", company.name)
return {
"Dashboards": [
{
"doctype": "Dashboard",
"dashboard_name": "Accounts",
"charts": [
{ "chart": "Outgoing Bills (Sales Invoice)" },
{ "chart": "Incoming Bills (Purchase Invoice)" },
{ "chart": "Bank Balance" },
{ "chart": "Income" },
{ "chart": "Expenses" },
{ "chart": "Patient Appointments" }
]
}
],
"Charts": [
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"chart_name": "Income",
"timespan": "Last Year",
"color": None,
"filters_json": json.dumps({"company": company.name, "account": income_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"chart_name": "Expenses",
"timespan": "Last Year",
"color": None,
"filters_json": json.dumps({"company": company.name, "account": expense_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"chart_name": "Bank Balance",
"timespan": "Last Year",
"color": "#ffb868",
"filters_json": json.dumps({"company": company.name, "account": bank_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Monthly",
"chart_name": "Incoming Bills (Purchase Invoice)",
"timespan": "Last Year",
"color": "#a83333",
"value_based_on": "base_grand_total",
"filters_json": json.dumps({}),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Purchase Invoice",
"type": "Bar",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Monthly",
"chart_name": "Outgoing Bills (Sales Invoice)",
"timespan": "Last Year",
"color": "#7b933d",
"value_based_on": "base_grand_total",
"filters_json": json.dumps({}),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Sales Invoice",
"type": "Bar",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Daily",
"chart_name": "Patient Appointments",
"timespan": "Last Month",
"color": "#77ecca",
"filters_json": json.dumps({}),
"chart_type": "Count",
"timeseries": 1,
"based_on": "appointment_datetime",
"owner": "Administrator",
"document_type": "Patient Appointment",
"type": "Line",
"width": "Half"
}
]
}
def get_account(account_type, company):
accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company})
if accounts:
return accounts[0].name

View File

@ -485,8 +485,6 @@ def install_defaults(args=None):
# bank account same as a CoA entry
pass
add_dashboards()
# Now, with fixtures out of the way, onto concrete stuff
records = [
@ -504,27 +502,6 @@ def install_defaults(args=None):
make_records(records)
def add_dashboards():
from erpnext.setup.setup_wizard.data.dashboard_charts import get_company_for_dashboards
if not get_company_for_dashboards():
return
from erpnext.setup.setup_wizard.data.dashboard_charts import get_default_dashboards
from frappe.modules.import_file import import_file_by_path
dashboard_data = get_default_dashboards()
# create account balance timeline before creating dashbaord charts
doctype = "dashboard_chart_source"
docname = "account_balance_timeline"
folder = os.path.dirname(frappe.get_module("erpnext.accounts").__file__)
doc_path = os.path.join(folder, doctype, docname, docname) + ".json"
import_file_by_path(doc_path, force=0, for_sync=True)
make_records(dashboard_data["Charts"])
make_records(dashboard_data["Dashboards"])
def get_fy_details(fy_start_date, fy_end_date):
start_year = getdate(fy_start_date).year

View File

@ -541,27 +541,31 @@ def show_terms(doc):
return doc.tc_name
@frappe.whitelist(allow_guest=True)
def apply_coupon_code(applied_code,applied_referral_sales_partner):
def apply_coupon_code(applied_code, applied_referral_sales_partner):
quotation = True
if applied_code:
coupon_list=frappe.get_all('Coupon Code', filters={"docstatus": ("<", "2"), 'coupon_code':applied_code }, fields=['name'])
if coupon_list:
coupon_name=coupon_list[0].name
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
validate_coupon_code(coupon_name)
quotation = _get_cart_quotation()
quotation.coupon_code=coupon_name
if not applied_code:
frappe.throw(_("Please enter a coupon code"))
coupon_list = frappe.get_all('Coupon Code', filters={'coupon_code': applied_code})
if not coupon_list:
frappe.throw(_("Please enter a valid coupon code"))
coupon_name = coupon_list[0].name
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
validate_coupon_code(coupon_name)
quotation = _get_cart_quotation()
quotation.coupon_code = coupon_name
quotation.flags.ignore_permissions = True
quotation.save()
if applied_referral_sales_partner:
sales_partner_list = frappe.get_all('Sales Partner', filters={'referral_code': applied_referral_sales_partner})
if sales_partner_list:
sales_partner_name = sales_partner_list[0].name
quotation.referral_sales_partner = sales_partner_name
quotation.flags.ignore_permissions = True
quotation.save()
if applied_referral_sales_partner:
sales_partner_list=frappe.get_all('Sales Partner', filters={'docstatus': 0, 'referral_code':applied_referral_sales_partner }, fields=['name'])
if sales_partner_list:
sales_partner_name=sales_partner_list[0].name
quotation.referral_sales_partner=sales_partner_name
quotation.flags.ignore_permissions = True
quotation.save()
else:
frappe.throw(_("Please enter valid coupon code !!"))
else:
frappe.throw(_("Please enter coupon code !!"))
return quotation

View File

@ -10,14 +10,16 @@ from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings
from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status
@frappe.whitelist(allow_guest=True)
def get_product_info_for_website(item_code):
def get_product_info_for_website(item_code, skip_quotation_creation=False):
"""get product price / stock info for website"""
cart_settings = get_shopping_cart_settings()
if not cart_settings.enabled:
return frappe._dict()
cart_quotation = _get_cart_quotation()
cart_quotation = frappe._dict()
if not skip_quotation_creation:
cart_quotation = _get_cart_quotation()
price = get_price(
item_code,
@ -51,7 +53,7 @@ def get_product_info_for_website(item_code):
def set_product_info_for_website(item):
"""set product price uom for website"""
product_info = get_product_info_for_website(item.item_code)
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True)
if product_info:
item.update(product_info)

View File

@ -7,7 +7,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.naming import make_autoname, revert_series_if_last
from frappe.utils import flt, cint
from frappe.utils import flt, cint, get_link_to_form
from frappe.utils.jinja import render_template
from frappe.utils.data import add_days
from six import string_types
@ -124,7 +124,7 @@ class Batch(Document):
if has_expiry_date and not self.expiry_date:
frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \
.format(frappe.bold("Shelf Life in Days"),
frappe.utils.get_link_to_form("Item", self.item),
get_link_to_form("Item", self.item),
frappe.bold("Batch Expiry Date")),
title=_("Expiry Date Mandatory"))
@ -264,16 +264,20 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
cond = ''
if serial_no:
if serial_no and frappe.get_cached_value('Item', item_code, 'has_batch_no'):
serial_nos = get_serial_nos(serial_no)
batch = frappe.get_all("Serial No",
fields = ["distinct batch_no"],
filters= {
"item_code": item_code,
"warehouse": warehouse,
"name": ("in", get_serial_nos(serial_no))
"name": ("in", serial_nos)
}
)
if not batch:
validate_serial_no_with_batch(serial_nos, item_code)
if batch and len(batch) > 1:
return []
@ -289,3 +293,14 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
group by batch_id
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
""".format(cond), (item_code, warehouse), as_dict=True)
def validate_serial_no_with_batch(serial_nos, item_code):
if frappe.get_cached_value("Serial No", serial_nos[0], "item_code") != item_code:
frappe.throw(_("The serial no {0} does not belong to item {1}")
.format(get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code)))
serial_no_link = ','.join([get_link_to_form("Serial No", sn) for sn in serial_nos])
message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
frappe.throw(_("There is no batch found against the {0}: {1}")
.format(message, serial_no_link))

View File

@ -467,7 +467,7 @@ class Item(WebsiteGenerator):
def set_shopping_cart_data(self, context):
from erpnext.shopping_cart.product_info import get_product_info_for_website
context.shopping_cart = get_product_info_for_website(self.name)
context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True)
def add_default_uom_in_conversion_factor_table(self):
uom_conv_list = [d.uom for d in self.get("uoms")]
@ -572,6 +572,13 @@ class Item(WebsiteGenerator):
frappe.throw(_("Barcode {0} is not a valid {1} code").format(
item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
if item_barcode.barcode != item_barcode.name:
# if barcode is getting updated , the row name has to reset.
# Delete previous old row doc and re-enter row as if new to reset name in db.
item_barcode.set("__islocal", True)
item_barcode.name = None
frappe.delete_doc("Item Barcode", item_barcode.name)
def validate_warehouse_for_reorder(self):
'''Validate Reorder level table for duplicate and conditional mandatory'''
warehouse = []

View File

@ -69,3 +69,10 @@ class ItemPrice(Document):
self.reference = self.customer
if self.buying:
self.reference = self.supplier
if self.selling and not self.buying:
# if only selling then remove supplier
self.supplier = None
if self.buying and not self.selling:
# if only buying then remove customer
self.customer = None

View File

@ -181,7 +181,7 @@ class StockEntry(StockController):
stock_items = self.get_stock_items()
serialized_items = self.get_serialized_items()
for item in self.get("items"):
if item.qty and item.qty < 0:
if flt(item.qty) and flt(item.qty) < 0:
frappe.throw(_("Row {0}: The item {1}, quantity must be positive number")
.format(item.idx, frappe.bold(item.item_code)))
@ -470,7 +470,7 @@ class StockEntry(StockController):
"qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
"serial_no": item.serial_no,
"voucher_type": self.doctype,
"voucher_no": item.name,
"voucher_no": self.name,
"company": self.company,
"allow_zero_valuation": item.allow_zero_valuation_rate,
})

View File

@ -177,7 +177,7 @@ def convert_to_group_or_ledger():
return frappe.get_doc("Warehouse", args.docname).convert_to_group_or_ledger()
def get_child_warehouses(warehouse):
lft, rgt = frappe.get_cached_value("Warehouse", warehouse, [lft, rgt])
lft, rgt = frappe.get_cached_value("Warehouse", warehouse, ["lft", "rgt"])
return frappe.db.sql_list("""select name from `tabWarehouse`
where lft >= %s and rgt <= %s""", (lft, rgt))

View File

@ -630,7 +630,7 @@ def get_item_price(args, item_code, ignore_party=False):
elif args.get("supplier"):
conditions += " and supplier=%(supplier)s"
else:
conditions += " and (customer is null or customer = '') and (supplier is null or supplier = '')"
conditions += "and (customer is null or customer = '') and (supplier is null or supplier = '')"
if args.get('transaction_date'):
conditions += """ and %(transaction_date)s between

View File

@ -548,7 +548,16 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)):
frappe.local.message_log = []
frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting / cancelling this entry.")
.format(item_code, voucher_type, voucher_no))
form_link = frappe.utils.get_link_to_form("Item", item_code)
message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no)
message += "<br><br>" + _(" Here are the options to proceed:")
solutions = "<li>" + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "</li>"
solutions += "<li>" + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "</li>"
sub_solutions = "<ul><li>" + _("Create an incoming stock transaction for the Item.") + "</li>"
sub_solutions += "<li>" + _("Mention Valuation Rate in the Item master.") + "</li></ul>"
msg = message + solutions + sub_solutions + "</li>"
frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
return valuation_rate