Merge branch 'develop' of https://github.com/frappe/erpnext into purchase-dashboard

This commit is contained in:
marination 2020-05-15 13:40:46 +05:30
commit 2a73b3ef25
37 changed files with 491 additions and 233 deletions

View File

@ -1,6 +1,5 @@
dist: trusty
language: python language: python
dist: trusty
git: git:
depth: 1 depth: 1
@ -14,21 +13,10 @@ addons:
jobs: jobs:
include: 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" - name: "Python 3.6 Server Side Test"
python: 3.6 python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage 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" - name: "Python 3.6 Patch Test"
python: 3.6 python: 3.6
before_script: before_script:
@ -40,8 +28,7 @@ install:
- cd ~ - cd ~
- nvm install 10 - nvm install 10
- git clone https://github.com/frappe/bench --depth 1 - pip install frappe-bench
- pip install -e ./bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1 - git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench - bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench

View File

@ -14,7 +14,7 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist() @frappe.whitelist()
@cache_source @cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, 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: if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name) chart = frappe.get_doc('Dashboard Chart', chart_name)
else: else:

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) total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against, 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 # 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: if frappe.flags.deferred_accounting_error:
@ -222,7 +222,7 @@ def process_deferred_accounting(posting_date=today()):
doc.submit() doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against, 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 # GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries 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": base_amount,
"credit_in_account_currency": amount, "credit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": item.name,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project, 'project': project,
'against_voucher_type': 'Process Deferred Accounting', 'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process 'against_voucher': deferred_process
}, account_currency) }, account_currency, item=item)
) )
# GL Entry to debit the amount from the expense # GL Entry to debit the amount from the expense
gl_entries.append( gl_entries.append(
@ -251,12 +251,12 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"debit": base_amount, "debit": base_amount,
"debit_in_account_currency": amount, "debit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": item.name,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project, 'project': project,
'against_voucher_type': 'Process Deferred Accounting', 'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process 'against_voucher': deferred_process
}, account_currency) }, account_currency, item=item)
) )
if gl_entries: if gl_entries:

View File

@ -162,9 +162,9 @@ def toggle_disabling(doc):
def get_doctypes_with_dimensions(): def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", 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", "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "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", "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", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"] "Subscription Plan"]

View File

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

View File

@ -86,7 +86,7 @@ class PaymentEntry(AccountsController):
self.update_payment_schedule(cancel=1) self.update_payment_schedule(cancel=1)
self.set_payment_req_status() self.set_payment_req_status()
self.set_status() self.set_status()
def set_payment_req_status(self): def set_payment_req_status(self):
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
update_payment_req_status(self, None) 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"]) 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: if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d) no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items(): 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>\ 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.") If this is undesirable please cancel the corresponding Payment Entry.")
@ -506,7 +506,7 @@ class PaymentEntry(AccountsController):
"against": against_account, "against": against_account,
"account_currency": self.party_account_currency, "account_currency": self.party_account_currency,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit" 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_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount, "credit": self.base_paid_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
if self.payment_type in ("Receive", "Internal Transfer"): if self.payment_type in ("Receive", "Internal Transfer"):
gl_entries.append( gl_entries.append(
@ -561,7 +561,7 @@ class PaymentEntry(AccountsController):
"debit_in_account_currency": self.received_amount, "debit_in_account_currency": self.received_amount,
"debit": self.base_received_amount, "debit": self.base_received_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
def add_deductions_gl_entries(self, gl_entries): def add_deductions_gl_entries(self, gl_entries):

View File

@ -4,13 +4,19 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, copy, json
from frappe import throw, _ import copy
import json
from six import string_types 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.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor 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 class MultiplePricingRuleConflict(frappe.ValidationError): pass
@ -502,18 +508,16 @@ def get_pricing_rule_items(pr_doc):
return list(set(apply_on_data)) return list(set(apply_on_data))
def validate_coupon_code(coupon_name): 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:
if coupon.valid_from > getdate(today()) : if coupon.valid_from > getdate(today()):
frappe.throw(_("Sorry,coupon code validity has not started")) frappe.throw(_("Sorry, this coupon code's validity has not started"))
elif coupon.valid_upto: elif coupon.valid_upto:
if coupon.valid_upto < getdate(today()) : if coupon.valid_upto < getdate(today()):
frappe.throw(_("Sorry,coupon code validity has expired")) frappe.throw(_("Sorry, this coupon code's validity has expired"))
elif coupon.used>=coupon.maximum_use: elif coupon.used >= coupon.maximum_use:
frappe.throw(_("Sorry,coupon code are exhausted")) frappe.throw(_("Sorry, this coupon code is no longer valid"))
else:
return
def update_coupon_code_count(coupon_name,transaction_type): def update_coupon_code_count(coupon_name,transaction_type):
coupon=frappe.get_doc("Coupon Code",coupon_name) 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": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
def make_item_gl_entries(self, gl_entries): 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": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
@ -852,7 +852,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_paid_amount \ "credit_in_account_currency": self.base_paid_amount \
if bank_account_currency==self.company_currency else self.paid_amount, if bank_account_currency==self.company_currency else self.paid_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}, bank_account_currency) }, bank_account_currency, item=self)
) )
def make_write_off_gl_entry(self, gl_entries): 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": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@ -883,7 +883,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_write_off_amount \ "credit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.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 "cost_center": self.cost_center or self.write_off_cost_center
}) }, item=self)
) )
def make_gle_for_rounding_adjustment(self, gl_entries): def make_gle_for_rounding_adjustment(self, gl_entries):
@ -902,8 +902,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.rounding_adjustment, "debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment, "debit": self.base_rounding_adjustment,
"cost_center": self.cost_center or round_off_cost_center, "cost_center": self.cost_center or round_off_cost_center,
} }, item=self))
))
def on_cancel(self): def on_cancel(self):
super(PurchaseInvoice, self).on_cancel() super(PurchaseInvoice, self).on_cancel()

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

View File

@ -53,7 +53,7 @@ frappe.query_reports["General Ledger"] = {
"label": __("Voucher No"), "label": __("Voucher No"),
"fieldtype": "Data", "fieldtype": "Data",
on_change: function() { 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

@ -46,7 +46,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"default": frappe.defaults.get_user_default("year_end_date"), "default": frappe.defaults.get_user_default("year_end_date"),
}, },
{ {
"fieldname":"cost_center", "fieldname": "cost_center",
"label": __("Cost Center"), "label": __("Cost Center"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Cost Center", "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"), "label": __("Finance Book"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Finance Book", "options": "Finance Book",
@ -97,7 +103,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
} }
erpnext.dimension_filters.forEach((dimension) => { 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"], "fieldname": dimension["fieldname"],
"label": __(dimension["label"]), "label": __(dimension["label"]),
"fieldtype": "Link", "fieldtype": "Link",

View File

@ -69,6 +69,10 @@ def get_data(filters):
gl_entries_by_account = {} gl_entries_by_account = {}
opening_balances = get_opening_balances(filters) 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, 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)) 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` additional_conditions += """ and cost_center in (select name from `tabCost Center`
where lft >= %s and rgt <= %s)""" % (lft, rgt) where lft >= %s and rgt <= %s)""" % (lft, rgt)
if filters.project:
additional_conditions += " and project = %(project)s"
if filters.finance_book: if filters.finance_book:
fb_conditions = " AND finance_book = %(finance_book)s" fb_conditions = " AND finance_book = %(finance_book)s"
if filters.include_default_book_entries: if filters.include_default_book_entries:
@ -116,6 +123,7 @@ def get_rootwise_opening_balances(filters, report_type):
"from_date": filters.from_date, "from_date": filters.from_date,
"report_type": report_type, "report_type": report_type,
"year_start_date": filters.year_start_date, "year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book, "finance_book": filters.finance_book,
"company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book') "company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book')
} }

View File

@ -125,7 +125,7 @@ class Asset(AccountsController):
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): 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")) frappe.throw(_("Available-for-use Date should be after purchase date"))
def validate_gross_and_purchase_amount(self): def validate_gross_and_purchase_amount(self):
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: 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. {}\ 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'): for d in self.get('finance_books'):
if d.finance_book == self.default_finance_book: if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1 return cint(d.idx) - 1
def validate_make_gl_entry(self): def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document() purchase_document = self.get_purchase_document()
asset_bought_with_invoice = purchase_document == self.purchase_invoice 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 purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
return purchase_document return purchase_document
def get_asset_accounts(self): def get_asset_accounts(self):
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company) asset_category = self.asset_category, company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account", cwip_account = get_asset_account("capital_work_in_progress_account",
self.name, self.asset_category, self.company) self.name, self.asset_category, self.company)
return fixed_asset_account, cwip_account return fixed_asset_account, cwip_account
def make_gl_entries(self): def make_gl_entries(self):
@ -513,7 +513,7 @@ class Asset(AccountsController):
"credit": self.purchase_receipt_amount, "credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount, "credit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
})) }, item=self))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": fixed_asset_account, "account": fixed_asset_account,
@ -523,7 +523,7 @@ class Asset(AccountsController):
"debit": self.purchase_receipt_amount, "debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount, "debit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
})) }, item=self))
if gl_entries: if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries

View File

@ -15,7 +15,7 @@ class LinkedInSettings(Document):
params = urlencode({ params = urlencode({
"response_type":"code", "response_type":"code",
"client_id": self.consumer_key, "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" "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, "code": code,
"client_id": self.consumer_key, "client_id": self.consumer_key,
"client_secret": self.get_password(fieldname="consumer_secret"), "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 = { headers = {
"Content-Type": "application/x-www-form-urlencoded" "Content-Type": "application/x-www-form-urlencoded"

View File

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

View File

@ -31,13 +31,13 @@ class TwitterSettings(Document):
try: try:
auth.get_access_token(oauth_verifier) auth.get_access_token(oauth_verifier)
api = self.get_api() api = self.get_api(auth.access_token, auth.access_token_secret)
user = api.me() user = api.me()
profile_pic = (user._json["profile_image_url"]).replace("_normal","") profile_pic = (user._json["profile_image_url"]).replace("_normal","")
frappe.db.set_value(self.doctype, self.name, { frappe.db.set_value(self.doctype, self.name, {
"oauth_token" : auth.access_token, "access_token" : auth.access_token,
"oauth_secret" : auth.access_token_secret, "access_token_secret" : auth.access_token_secret,
"account_name" : user._json["screen_name"], "account_name" : user._json["screen_name"],
"profile_pic" : profile_pic, "profile_pic" : profile_pic,
"session_status" : "Active" "session_status" : "Active"
@ -49,11 +49,11 @@ class TwitterSettings(Document):
frappe.msgprint(_("Error! Failed to get access token.")) frappe.msgprint(_("Error! Failed to get access token."))
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key')) 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 # authentication of consumer key and secret
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
# authentication of access token and 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) return tweepy.API(auth)
@ -67,13 +67,13 @@ class TwitterSettings(Document):
def upload_image(self, media): def upload_image(self, media):
media = get_file_path(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) media = api.media_upload(media)
return media.media_id return media.media_id
def send_tweet(self, text, media_id=None): def send_tweet(self, text, media_id=None):
api = self.get_api() api = self.get_api(self.access_token, self.access_token_secret)
try: try:
if media_id: if media_id:
response = api.update_status(status = text, media_ids = [media_id]) response = api.update_status(status = text, media_ids = [media_id])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -549,7 +549,7 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry):
return _get_remaining_leaves(total_leaves, allocation.to_date) 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_entries = get_leave_entries(employee, leave_type, from_date, to_date)
leave_days = 0 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': if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
leave_days += leave_entry.leaves leave_days += leave_entry.leaves
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \ elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \
and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date): and (do_not_skip_expired_leaves or not skip_expiry_leaves(leave_entry, to_date)):
leave_days += leave_entry.leaves leave_days += leave_entry.leaves
elif leave_entry.transaction_type == 'Leave Application': elif leave_entry.transaction_type == 'Leave Application':

View File

@ -88,32 +88,40 @@ def get_previous_expiry_ledger_entry(ledger):
}, fieldname=['name']) }, fieldname=['name'])
def process_expired_allocation(): 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 # fetch leave type records that has carry forwarded leaves expiry
leave_type_records = frappe.db.get_values("Leave Type", filters={ leave_type_records = frappe.db.get_values("Leave Type", filters={
'expire_carry_forwarded_leaves_after_days': (">", 0) 'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name']) }, 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 # fetch non expired leave ledger entry of transaction_type allocation
FROM `tabLeave Ledger Entry` expire_allocation = frappe.db.sql("""
WHERE SELECT
`transaction_type`='Leave Allocation' leaves, to_date, employee, leave_type,
AND `is_expired`=1""") is_carry_forward, transaction_name as name, transaction_type
FROM `tabLeave Ledger Entry` l
expire_allocation = frappe.get_all("Leave Ledger Entry", WHERE (NOT EXISTS
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], (SELECT name
filters={ FROM `tabLeave Ledger Entry`
'to_date': ("<", today()), WHERE
'transaction_type': 'Leave Allocation', transaction_name = l.transaction_name
'transaction_name': ('not in', expired_allocation) AND transaction_type = 'Leave Allocation'
}, AND name<>l.name
or_filters={ AND docstatus = 1
'is_carry_forward': 0, AND (
'leave_type': ('in', leave_type) 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: if expire_allocation:
create_expiry_ledger_entry(expire_allocation) create_expiry_ledger_entry(expire_allocation)
@ -133,6 +141,7 @@ def get_remaining_leaves(allocation):
'employee': allocation.employee, 'employee': allocation.employee,
'leave_type': allocation.leave_type, 'leave_type': allocation.leave_type,
'to_date': ('<=', allocation.to_date), 'to_date': ('<=', allocation.to_date),
'docstatus': 1
}, fieldname=['SUM(leaves)']) }, fieldname=['SUM(leaves)'])
@frappe.whitelist() @frappe.whitelist()
@ -159,7 +168,8 @@ def expire_allocation(allocation, expiry_date=None):
def expire_carried_forward_allocation(allocation): def expire_carried_forward_allocation(allocation):
''' Expires remaining leaves in the on carried forward allocation ''' ''' Expires remaining leaves in the on carried forward allocation '''
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period 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) leaves = flt(allocation.leaves) + flt(leaves_taken)
# allow expired leaves entry to be created # allow expired leaves entry to be created

View File

@ -38,7 +38,7 @@ class LoanSecurityPledge(Document):
for pledge in self.securities: for pledge in self.securities:
if not pledge.qty and not pledge.amount: 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): if not (self.loan_application and pledge.loan_security_price):
pledge.loan_security_price = get_loan_security_price(pledge.loan_security) pledge.loan_security_price = get_loan_security_price(pledge.loan_security)

View File

@ -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.update_due_date_in_gle
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position 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.v11_1.set_status_for_material_request_type_manufacture
erpnext.patches.v12_0.move_plaid_settings_to_doctype erpnext.patches.v12_0.move_plaid_settings_to_doctype
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link') 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.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype 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 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.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price

View File

@ -20,7 +20,8 @@ def execute():
else: else:
insert_after_field = 'accounting_dimensions_section' 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}) 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

@ -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

@ -165,6 +165,10 @@ class Customer(TransactionBase):
contact.mobile_no = lead.mobile_no contact.mobile_no = lead.mobile_no
contact.is_primary_contact = 1 contact.is_primary_contact = 1
contact.append('links', dict(link_doctype='Customer', link_name=self.name)) 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.flags.ignore_permissions = self.flags.ignore_permissions
contact.autoname() contact.autoname()
if not frappe.db.exists("Contact", contact.name): if not frappe.db.exists("Contact", contact.name):

View File

@ -3,6 +3,14 @@
frappe.query_reports["Customer Acquisition and Loyalty"] = { frappe.query_reports["Customer Acquisition and Loyalty"] = {
"filters": [ "filters": [
{
"fieldname": "view_type",
"label": __("View Type"),
"fieldtype": "Select",
"options": ["Monthly", "Territory Wise"],
"default": "Monthly",
"reqd": 1
},
{ {
"fieldname":"company", "fieldname":"company",
"label": __("Company"), "label": __("Company"),
@ -24,6 +32,13 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
"fieldtype": "Date", "fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_end_date"), "default": frappe.defaults.get_user_default("year_end_date"),
"reqd": 1 "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 # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import calendar
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import getdate, cint, cstr from frappe.utils import cint, cstr
import calendar
def execute(filters=None): def execute(filters=None):
# key yyyy-mm common_columns = [
new_customers_in = {} {
repeat_customers_in = {} 'label': _('New Customers'),
customers = [] 'fieldname': 'new_customers',
company_condition = "" '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"): def get_data_by_time(filters, common_columns):
company_condition = ' and company=%(company)s' # 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` customers_in = get_customer_stats(filters)
where docstatus=1 and posting_date <= %(to_date)s
{company_condition} order by posting_date""".format(company_condition=company_condition),
filters, as_dict=1):
key = si.posting_date.strftime("%Y-%m") # time series
if not si.customer in customers: from_year, from_month, temp = filters.get('from_date').split('-')
new_customers_in.setdefault(key, [0, 0.0]) to_year, to_month, temp = filters.get('to_date').split('-')
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, to_year, to_month = \
from_year, from_month, temp = filters.get("from_date").split("-") cint(from_year), cint(from_month), cint(to_year), cint(to_month)
to_year, to_month, temp = filters.get("to_date").split("-")
from_year, from_month, to_year, to_month = \ out = []
cint(from_year), cint(from_month), cint(to_year), cint(to_month) 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 = [] def get_data_by_territory(filters, common_columns):
for year in range(from_year, to_year+1): columns = [{
for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13): 'label': 'Territory',
key = "{year}-{month:02d}".format(year=year, month=month) 'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
'width': 150
}]
columns += common_columns
new = new_customers_in.get(key, [0,0.0]) customers_in = get_customer_stats(filters, tree_view=True)
repeat = repeat_customers_in.get(key, [0,0.0])
out.append([cstr(year), calendar.month_name[month], territory_dict = {}
new[0], repeat[0], new[0] + repeat[0], for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
new[1], repeat[1], new[1] + repeat[1]]) territory_dict.update({
t.name: {
'parent': t.parent_territory,
'is_group': t.is_group
}
})
return [ depth_map = frappe._dict()
_("Year") + "::100", for name, info in territory_dict.items():
_("Month") + "::100", default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
_("New Customers") + ":Int:100", depth_map.setdefault(name, default)
_("Repeat Customers") + ":Int:100",
_("Total") + ":Int:100",
_("New Customer Revenue") + ":Currency:150",
_("Repeat Customer Revenue") + ":Currency:150",
_("Total Revenue") + ":Currency:150"
], out
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

@ -20,31 +20,36 @@ def get_columns():
"label": _("Territory"), "label": _("Territory"),
"fieldname": "territory", "fieldname": "territory",
"fieldtype": "Link", "fieldtype": "Link",
"options": "Territory" "options": "Territory",
"width": 150
}, },
{ {
"label": _("Opportunity Amount"), "label": _("Opportunity Amount"),
"fieldname": "opportunity_amount", "fieldname": "opportunity_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": currency "options": currency,
"width": 150
}, },
{ {
"label": _("Quotation Amount"), "label": _("Quotation Amount"),
"fieldname": "quotation_amount", "fieldname": "quotation_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": currency "options": currency,
"width": 150
}, },
{ {
"label": _("Order Amount"), "label": _("Order Amount"),
"fieldname": "order_amount", "fieldname": "order_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"options": currency "options": currency,
"width": 150
}, },
{ {
"label": _("Billing Amount"), "label": _("Billing Amount"),
"fieldname": "billing_amount", "fieldname": "billing_amount",
"fieldtype": "Currency", "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)) territory_opportunities = list(filter(lambda x: x.territory == territory.name, opportunities))
t_opportunity_names = [] t_opportunity_names = []
if territory_opportunities: 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 = [] territory_quotations = []
if t_opportunity_names and quotations: if t_opportunity_names and quotations:
territory_quotations = list(filter(lambda x: x.opportunity in t_opportunity_names, 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)) list(filter(lambda x: x.quotation in t_quotation_names, sales_orders))
t_order_names = [] t_order_names = []
if territory_orders: 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 [] 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'): if filters.get('transaction_date'):
conditions = " WHERE transaction_date between {0} and {1}".format( 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])) frappe.db.escape(filters['transaction_date'][1]))
if filters.company: if filters.company:
if conditions: if conditions:
conditions += " AND" conditions += " AND"
else: else:
conditions += " WHERE" conditions += " WHERE"
conditions += " company = %(company)s" conditions += " company = %(company)s"
@ -115,7 +119,7 @@ def get_opportunities(filters):
def get_quotations(opportunities): def get_quotations(opportunities):
if not opportunities: if not opportunities:
return [] return []
opportunity_names = [o.name for o in opportunities] opportunity_names = [o.name for o in opportunities]
return frappe.db.sql(""" return frappe.db.sql("""
@ -155,5 +159,5 @@ def _get_total(doclist, amount_field="base_grand_total"):
total = 0 total = 0
for doc in doclist: for doc in doclist:
total += doc.get(amount_field, 0) total += doc.get(amount_field, 0)
return total return total

View File

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

View File

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

View File

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

View File

@ -69,3 +69,10 @@ class ItemPrice(Document):
self.reference = self.customer self.reference = self.customer
if self.buying: if self.buying:
self.reference = self.supplier 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

@ -630,7 +630,7 @@ def get_item_price(args, item_code, ignore_party=False):
elif args.get("supplier"): elif args.get("supplier"):
conditions += " and supplier=%(supplier)s" conditions += " and supplier=%(supplier)s"
else: 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'): if args.get('transaction_date'):
conditions += """ and %(transaction_date)s between conditions += """ and %(transaction_date)s between