Customer's credit days based on fixed days / last day of the next month

This commit is contained in:
Nabin Hait 2015-07-09 16:35:46 +05:30
parent ccb9117ba5
commit 4770a1abb5
11 changed files with 456 additions and 340 deletions

View File

@ -193,7 +193,7 @@
"icon": "icon-list", "icon": "icon-list",
"idx": 1, "idx": 1,
"in_create": 1, "in_create": 1,
"modified": "2015-06-14 20:57:19.800276", "modified": "2015-07-09 15:51:04.986518",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "GL Entry", "name": "GL Entry",
@ -225,6 +225,19 @@
"role": "Accounts Manager", "role": "Accounts Manager",
"submit": 0, "submit": 0,
"write": 0 "write": 0
},
{
"create": 0,
"delete": 0,
"email": 0,
"export": 1,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 1,
"role": "Auditor",
"share": 0,
"write": 0
} }
], ],
"search_fields": "voucher_no,account,posting_date,against_voucher", "search_fields": "voucher_no,account,posting_date,against_voucher",

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cstr, flt, fmt_money, formatdate, getdate from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, date_diff
from frappe import msgprint, _, scrub from frappe import msgprint, _, scrub
from erpnext.setup.utils import get_company_currency from erpnext.setup.utils import get_company_currency
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
@ -35,7 +35,7 @@ class JournalEntry(AccountsController):
self.set_print_format_fields() self.set_print_format_fields()
self.validate_against_sales_order() self.validate_against_sales_order()
self.validate_against_purchase_order() self.validate_against_purchase_order()
self.check_credit_days() self.check_due_date()
self.validate_expense_claim() self.validate_expense_claim()
self.validate_credit_debit_note() self.validate_credit_debit_note()
self.validate_empty_accounts_table() self.validate_empty_accounts_table()
@ -88,23 +88,21 @@ class JournalEntry(AccountsController):
for customer in customers: for customer in customers:
check_credit_limit(customer, self.company) check_credit_limit(customer, self.company)
def check_credit_days(self): def check_due_date(self):
from erpnext.accounts.party import get_credit_days
posting_date = None
if self.cheque_date: if self.cheque_date:
for d in self.get("accounts"): for d in self.get("accounts"):
if d.party_type and d.party and d.get("credit" if d.party_type=="Customer" else "debit") > 0: if d.party_type and d.party and d.get("credit" if d.party_type=="Customer" else "debit") > 0:
due_date = None
if d.against_invoice: if d.against_invoice:
posting_date = frappe.db.get_value("Sales Invoice", d.against_invoice, "posting_date") due_date = frappe.db.get_value("Sales Invoice", d.against_invoice, "due_date")
elif d.against_voucher: elif d.against_voucher:
posting_date = frappe.db.get_value("Purchase Invoice", d.against_voucher, "posting_date") due_date = frappe.db.get_value("Purchase Invoice", d.against_voucher, "due_date")
credit_days = get_credit_days(d.party_type, d.party, self.company) if due_date and getdate(self.cheque_date) > getdate(due_date):
if posting_date and credit_days: diff = date_diff(self.cheque_date, due_date)
date_diff = (getdate(self.cheque_date) - getdate(posting_date)).days if diff > 0:
if date_diff > flt(credit_days): msgprint(_("Note: Reference Date exceeds invoice due date by {0} days for {1} {2}")
msgprint(_("Note: Reference Date exceeds allowed credit days by {0} days for {1} {2}") .format(diff, d.party_type, d.party))
.format(date_diff - flt(credit_days), d.party_type, d.party))
def validate_cheque_info(self): def validate_cheque_info(self):
if self.voucher_type in ['Bank Entry']: if self.voucher_type in ['Bank Entry']:

View File

@ -69,6 +69,32 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
} }
}, },
posting_date: function() {
var me = this;
if (this.frm.doc.posting_date) {
if (this.frm.doc.supplier) {
return frappe.call({
method: "erpnext.accounts.party.get_due_date",
args: {
"posting_date": me.frm.doc.posting_date,
"party_type": "Supplier",
"party": me.frm.doc.supplier,
"company": me.frm.doc.company
},
callback: function(r, rt) {
if(r.message) {
me.frm.set_value("due_date", r.message);
}
erpnext.get_fiscal_year(me.frm.doc.company, me.frm.doc.posting_date);
}
})
} else {
erpnext.get_fiscal_year(me.frm.doc.company, me.frm.doc.posting_date);
}
}
},
supplier: function() { supplier: function() {
var me = this; var me = this;
if(this.frm.updating_party_details) if(this.frm.updating_party_details)

View File

@ -147,6 +147,33 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
} }
}, },
posting_date: function() {
var me = this;
if (this.frm.doc.posting_date) {
if (this.frm.doc.customer) {
return frappe.call({
method: "erpnext.accounts.party.get_due_date",
args: {
"posting_date": me.frm.doc.posting_date,
"party_type": "Customer",
"party": me.frm.doc.customer,
"company": me.frm.doc.company
},
callback: function(r, rt) {
if(r.message) {
me.frm.set_value("due_date", r.message);
}
erpnext.get_fiscal_year(me.frm.doc.company, me.frm.doc.posting_date);
}
})
} else {
erpnext.get_fiscal_year(me.frm.doc.company, me.frm.doc.posting_date);
}
}
},
customer: function() { customer: function() {
var me = this; var me = this;
if(this.frm.updating_party_details) return; if(this.frm.updating_party_details) return;

View File

@ -138,7 +138,7 @@ class SalesInvoice(SellingController):
if not self.debit_to: if not self.debit_to:
self.debit_to = get_party_account(self.company, self.customer, "Customer") self.debit_to = get_party_account(self.company, self.customer, "Customer")
if not self.due_date: if not self.due_date and self.customer:
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company) self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
super(SalesInvoice, self).set_missing_values(for_validate) super(SalesInvoice, self).set_missing_values(for_validate)

View File

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest, copy import unittest, copy
import time import time
from frappe.utils import nowdate, add_days
from erpnext.accounts.utils import get_stock_and_account_difference from erpnext.accounts.utils import get_stock_and_account_difference
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.projects.doctype.time_log_batch.test_time_log_batch import * from erpnext.projects.doctype.time_log_batch.test_time_log_batch import *
@ -757,13 +758,27 @@ class TestSalesInvoice(unittest.TestCase):
# hack! because stock ledger entires are already inserted and are not rolled back! # hack! because stock ledger entires are already inserted and are not rolled back!
self.assertRaises(SerialNoDuplicateError, si.cancel) self.assertRaises(SerialNoDuplicateError, si.cancel)
def test_invoice_due_date_against_customers_credit_days(self):
si = create_sales_invoice()
# set customer's credit days
frappe.db.set_value("Customer", "_Test Customer", "credit_days_based_on", "Fixed Days")
frappe.db.set_value("Customer", "_Test Customer", "credit_days", 10)
si.validate()
self.assertEqual(si.due_date, add_days(nowdate(), 10))
# set customer's credit days is last day of the next month
frappe.db.set_value("Customer", "_Test Customer", "credit_days_based_on", "Last Day of the Next Month")
si1 = create_sales_invoice(posting_date="2015-07-05")
self.assertEqual(si1.due_date, "2015-08-31")
def create_sales_invoice(**args): def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice") si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args) args = frappe._dict(args)
if args.posting_date: if args.posting_date:
si.posting_date = args.posting_date si.posting_date = args.posting_date or nowdate()
if args.posting_time:
si.posting_time = args.posting_time
si.company = args.company or "_Test Company" si.company = args.company or "_Test Company"
si.customer = args.customer or "_Test Customer" si.customer = args.customer or "_Test Customer"

View File

@ -4,9 +4,10 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import datetime
from frappe import _, msgprint, scrub from frappe import _, msgprint, scrub
from frappe.defaults import get_user_permissions from frappe.defaults import get_user_permissions
from frappe.utils import add_days, getdate, formatdate, flt from frappe.utils import add_days, getdate, formatdate, flt, get_first_day, date_diff, nowdate
from erpnext.utilities.doctype.address.address import get_address_display from erpnext.utilities.doctype.address.address import get_address_display
from erpnext.utilities.doctype.contact.contact import get_contact_details from erpnext.utilities.doctype.contact.contact import get_contact_details
@ -158,43 +159,54 @@ def get_party_account(company, party, party_type):
return account return account
@frappe.whitelist()
def get_due_date(posting_date, party_type, party, company): def get_due_date(posting_date, party_type, party, company):
"""Set Due Date = Posting Date + Credit Days""" """Set Due Date = Posting Date + Credit Days"""
due_date = None due_date = None
if posting_date: if posting_date and party:
credit_days = get_credit_days(party_type, party, company) due_date = nowdate()
due_date = add_days(posting_date, credit_days) if credit_days else posting_date if party_type=="Customer":
credit_days_based_on, credit_days = get_credit_days(party_type, party, company)
if credit_days_based_on == "Fixed Days" and credit_days:
due_date = add_days(posting_date, credit_days)
elif credit_days_based_on == "Last Day of the Next Month":
due_date = (get_first_day(posting_date, 0, 2) + datetime.timedelta(-1)).strftime("%Y-%m-%d")
else:
credit_days = get_credit_days(party_type, party, company)
if credit_days:
due_date = add_days(posting_date, credit_days)
return due_date return due_date
def get_credit_days(party_type, party, company): def get_credit_days(party_type, party, company):
if not party: if party_type and party:
return None if party_type == "Customer":
credit_days_based_on, credit_days, customer_group = \
frappe.db.get_value(party_type, party, ["credit_days_based_on", "credit_days", "customer_group"])
party_group_doctype = "Customer Group" if party_type=="Customer" else "Supplier Type" if not credit_days_based_on:
credit_days, party_group = frappe.db.get_value(party_type, party, ["credit_days", frappe.scrub(party_group_doctype)]) credit_days_based_on, credit_days = \
frappe.db.get_value("Customer Group", customer_group, ["credit_days_based_on", "credit_days"]) \
or frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"])
if not credit_days: return credit_days_based_on, credit_days
credit_days = frappe.db.get_value(party_group_doctype, party_group, "credit_days") or \ else:
frappe.db.get_value("Company", company, "credit_days") credit_days, supplier_type = frappe.db.get_value(party_type, party, ["credit_days", "supplier_type"])
if not credit_days:
credit_days = frappe.db.get_value("Supplier Type", supplier_type, "credit_days") \
or frappe.db.get_value("Company", company, "credit_days")
return credit_days return credit_days
def validate_due_date(posting_date, due_date, party_type, party, company): def validate_due_date(posting_date, due_date, party_type, party, company):
credit_days = get_credit_days(party_type, party, company) if getdate(due_date) < getdate(posting_date):
posting_date, due_date = getdate(posting_date), getdate(due_date)
diff = (due_date - posting_date).days
if diff < 0:
frappe.throw(_("Due Date cannot be before Posting Date")) frappe.throw(_("Due Date cannot be before Posting Date"))
elif credit_days is not None and diff > flt(credit_days): else:
is_credit_controller = frappe.db.get_value("Accounts Settings", None, default_due_date = get_due_date(posting_date, party_type, party, company)
"credit_controller") in frappe.get_roles() if getdate(due_date) > getdate(default_due_date):
is_credit_controller = frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
if is_credit_controller: if is_credit_controller:
msgprint(_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)") msgprint(_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)")
.format(diff - flt(credit_days))) .format(date_diff(due_date, default_due_date)))
else: else:
max_due_date = formatdate(add_days(posting_date, credit_days)) frappe.throw(_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date)))
frappe.throw(_("Due / Reference Date cannot be after {0}").format(max_due_date))

View File

@ -14,7 +14,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
$.each({ $.each({
posting_date: today, posting_date: today,
due_date: today,
transaction_date: today, transaction_date: today,
currency: currency, currency: currency,
price_list_currency: currency, price_list_currency: currency,

View File

@ -200,6 +200,15 @@
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "credit_days_based_on",
"fieldtype": "Select",
"label": "Credit Days Based On",
"options": "\nFixed Days\nLast Day of the Next Month",
"permlevel": 0,
"precision": ""
},
{
"depends_on": "eval:doc.credit_days_based_on=='Fixed Days'",
"fieldname": "credit_days", "fieldname": "credit_days",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Credit Days", "label": "Credit Days",
@ -269,7 +278,7 @@
], ],
"icon": "icon-user", "icon": "icon-user",
"idx": 1, "idx": 1,
"modified": "2015-02-24 17:32:36.065248", "modified": "2015-07-09 12:41:31.037121",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Customer", "name": "Customer",

View File

@ -246,7 +246,15 @@
"permlevel": 0 "permlevel": 0
}, },
{ {
"depends_on": "eval:!doc.__islocal", "fieldname": "credit_days_based_on",
"fieldtype": "Select",
"label": "Credit Days Based On",
"options": "\nFixed Days\nLast Day of the Next Month",
"permlevel": 0,
"precision": ""
},
{
"depends_on": "eval:(!doc.__islocal && doc.credit_days_based_on=='Fixed Days')",
"fieldname": "credit_days", "fieldname": "credit_days",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Credit Days", "label": "Credit Days",
@ -432,7 +440,7 @@
], ],
"icon": "icon-building", "icon": "icon-building",
"idx": 1, "idx": 1,
"modified": "2015-05-28 12:56:18.175509", "modified": "2015-07-09 14:20:56.619890",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@ -57,6 +57,15 @@
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "credit_days_based_on",
"fieldtype": "Select",
"label": "Credit Days Based On",
"options": "\nFixed Days\nLast Day of the Next Month",
"permlevel": 0,
"precision": ""
},
{
"depends_on": "eval:doc.credit_days_based_on=='Fixed Days'",
"fieldname": "credit_days", "fieldname": "credit_days",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Credit Days", "label": "Credit Days",
@ -128,7 +137,7 @@
"icon": "icon-sitemap", "icon": "icon-sitemap",
"idx": 1, "idx": 1,
"in_create": 0, "in_create": 0,
"modified": "2015-02-24 17:34:40.749511", "modified": "2015-07-09 12:43:18.846143",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Customer Group", "name": "Customer Group",