Merge branch 'develop' of https://github.com/frappe/erpnext into purchase-dashboard
This commit is contained in:
commit
2a73b3ef25
17
.travis.yml
17
.travis.yml
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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"]
|
||||
|
@ -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')
|
||||
|
||||
|
@ -86,7 +86,7 @@ class PaymentEntry(AccountsController):
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.set_payment_req_status()
|
||||
self.set_status()
|
||||
|
||||
|
||||
def set_payment_req_status(self):
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
|
||||
update_payment_req_status(self, None)
|
||||
@ -280,7 +280,7 @@ class PaymentEntry(AccountsController):
|
||||
outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"])
|
||||
if outstanding_amount <= 0 and not is_return:
|
||||
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
|
||||
|
||||
|
||||
for k, v in no_oustanding_refs.items():
|
||||
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
|
||||
If this is undesirable please cancel the corresponding Payment Entry.")
|
||||
@ -506,7 +506,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 +550,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 +561,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):
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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 = []
|
||||
|
@ -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)");
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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",
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class Asset(AccountsController):
|
||||
|
||||
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
|
||||
frappe.throw(_("Available-for-use Date should be after purchase date"))
|
||||
|
||||
|
||||
def validate_gross_and_purchase_amount(self):
|
||||
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
|
||||
frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
|
||||
@ -455,7 +455,7 @@ class Asset(AccountsController):
|
||||
for d in self.get('finance_books'):
|
||||
if d.finance_book == self.default_finance_book:
|
||||
return cint(d.idx) - 1
|
||||
|
||||
|
||||
def validate_make_gl_entry(self):
|
||||
purchase_document = self.get_purchase_document()
|
||||
asset_bought_with_invoice = purchase_document == self.purchase_invoice
|
||||
@ -487,14 +487,14 @@ class Asset(AccountsController):
|
||||
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
|
||||
|
||||
return purchase_document
|
||||
|
||||
|
||||
def get_asset_accounts(self):
|
||||
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
||||
asset_category = self.asset_category, company = self.company)
|
||||
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||
self.name, self.asset_category, self.company)
|
||||
|
||||
|
||||
return fixed_asset_account, cwip_account
|
||||
|
||||
def make_gl_entries(self):
|
||||
@ -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
|
||||
|
@ -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) + "/api/method/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) + "/api/method/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"
|
||||
|
@ -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",
|
||||
|
@ -31,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"
|
||||
@ -49,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)
|
||||
|
||||
@ -67,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])
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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':
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -623,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')
|
||||
@ -678,6 +678,8 @@ 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
|
||||
|
@ -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})
|
||||
|
||||
|
@ -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
|
@ -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()
|
@ -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):
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@ -62,8 +67,7 @@ def get_data(filters=None):
|
||||
territory_opportunities = list(filter(lambda x: x.territory == territory.name, opportunities))
|
||||
t_opportunity_names = []
|
||||
if territory_opportunities:
|
||||
t_opportunity_names = [t.name for t in 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))
|
||||
@ -76,7 +80,7 @@ def get_data(filters=None):
|
||||
list(filter(lambda x: x.quotation in t_quotation_names, sales_orders))
|
||||
t_order_names = []
|
||||
if territory_orders:
|
||||
t_order_names = [t.name for t in territory_orders]
|
||||
t_order_names = [t.name for t in territory_orders]
|
||||
|
||||
territory_invoices = list(filter(lambda x: x.sales_order in t_order_names, sales_invoices)) if t_order_names and sales_invoices else []
|
||||
|
||||
@ -96,12 +100,12 @@ def get_opportunities(filters):
|
||||
|
||||
if filters.get('transaction_date'):
|
||||
conditions = " WHERE transaction_date between {0} and {1}".format(
|
||||
frappe.db.escape(filters['transaction_date'][0]),
|
||||
frappe.db.escape(filters['transaction_date'][0]),
|
||||
frappe.db.escape(filters['transaction_date'][1]))
|
||||
|
||||
|
||||
if filters.company:
|
||||
if conditions:
|
||||
conditions += " AND"
|
||||
conditions += " AND"
|
||||
else:
|
||||
conditions += " WHERE"
|
||||
conditions += " company = %(company)s"
|
||||
@ -115,7 +119,7 @@ def get_opportunities(filters):
|
||||
def get_quotations(opportunities):
|
||||
if not opportunities:
|
||||
return []
|
||||
|
||||
|
||||
opportunity_names = [o.name for o in opportunities]
|
||||
|
||||
return frappe.db.sql("""
|
||||
@ -155,5 +159,5 @@ def _get_total(doclist, amount_field="base_grand_total"):
|
||||
total = 0
|
||||
for doc in doclist:
|
||||
total += doc.get(amount_field, 0)
|
||||
|
||||
|
||||
return total
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user