mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-22 06:29:20 +00:00
Merge branch 'nabinhait-billing_status' into develop
This commit is contained in:
@@ -11,6 +11,7 @@ import frappe.defaults
|
|||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
@@ -246,6 +247,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.update_against_document_in_jv()
|
self.update_against_document_in_jv()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||||
|
self.update_billing_status_in_pr()
|
||||||
|
|
||||||
self.update_project()
|
self.update_project()
|
||||||
|
|
||||||
@@ -407,6 +409,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||||
|
self.update_billing_status_in_pr()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
self.update_project()
|
self.update_project()
|
||||||
|
|
||||||
@@ -431,6 +435,21 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if pi:
|
if pi:
|
||||||
frappe.throw("Supplier Invoice No exists in Purchase Invoice {0}".format(pi))
|
frappe.throw("Supplier Invoice No exists in Purchase Invoice {0}".format(pi))
|
||||||
|
|
||||||
|
def update_billing_status_in_pr(self, update_modified=True):
|
||||||
|
updated_pr = []
|
||||||
|
for d in self.get("items"):
|
||||||
|
if d.pr_detail:
|
||||||
|
billed_amt = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
|
||||||
|
where pr_detail=%s and docstatus=1""", d.pr_detail)
|
||||||
|
billed_amt = billed_amt and billed_amt[0][0] or 0
|
||||||
|
frappe.db.set_value("Purchase Receipt Item", d.pr_detail, "billed_amt", billed_amt, update_modified=update_modified)
|
||||||
|
updated_pr.append(d.purchase_receipt)
|
||||||
|
elif d.po_detail:
|
||||||
|
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
|
||||||
|
|
||||||
|
for pr in set(updated_pr):
|
||||||
|
frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||||
from erpnext.controllers.queries import get_match_cond
|
from erpnext.controllers.queries import get_match_cond
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from frappe.model.mapper import get_mapped_doc
|
|||||||
|
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
from erpnext.controllers.selling_controller import SellingController
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
@@ -98,6 +99,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
self.update_billing_status_in_dn()
|
||||||
|
|
||||||
# this sequence because outstanding may get -ve
|
# this sequence because outstanding may get -ve
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
@@ -111,6 +113,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
self.update_time_log_batch(self.name)
|
self.update_time_log_batch(self.name)
|
||||||
|
|
||||||
|
|
||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
self.update_time_log_batch(None)
|
self.update_time_log_batch(None)
|
||||||
|
|
||||||
@@ -129,6 +132,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
self.update_billing_status_in_dn()
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||||
@@ -630,6 +634,21 @@ class SalesInvoice(SellingController):
|
|||||||
}, write_off_account_currency)
|
}, write_off_account_currency)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_billing_status_in_dn(self, update_modified=True):
|
||||||
|
updated_delivery_notes = []
|
||||||
|
for d in self.get("items"):
|
||||||
|
if d.dn_detail:
|
||||||
|
billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
|
||||||
|
where dn_detail=%s and docstatus=1""", d.dn_detail)
|
||||||
|
billed_amt = billed_amt and billed_amt[0][0] or 0
|
||||||
|
frappe.db.set_value("Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified)
|
||||||
|
updated_delivery_notes.append(d.delivery_note)
|
||||||
|
elif d.so_detail:
|
||||||
|
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
|
||||||
|
|
||||||
|
for dn in set(updated_delivery_notes):
|
||||||
|
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
|
||||||
|
|
||||||
def get_list_context(context=None):
|
def get_list_context(context=None):
|
||||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||||
list_context = get_list_context(context)
|
list_context = get_list_context(context)
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
- Billing Status in Delivery Note (DN) and Purchase Receipt (PR)
|
||||||
|
- If Invoice made directly against DN/PR, calculate based on actual billed amount
|
||||||
|
- If Invoice made via Sales Order/Purchase Order, allocate billed amount in DN/PR based on FIFO
|
||||||
|
- Billing status updated in existing DN/PR
|
||||||
@@ -52,13 +52,15 @@ status_map = {
|
|||||||
],
|
],
|
||||||
"Delivery Note": [
|
"Delivery Note": [
|
||||||
["Draft", None],
|
["Draft", None],
|
||||||
["Submitted", "eval:self.docstatus==1"],
|
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
|
||||||
|
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
|
||||||
["Cancelled", "eval:self.docstatus==2"],
|
["Cancelled", "eval:self.docstatus==2"],
|
||||||
["Closed", "eval:self.status=='Closed'"],
|
["Closed", "eval:self.status=='Closed'"],
|
||||||
],
|
],
|
||||||
"Purchase Receipt": [
|
"Purchase Receipt": [
|
||||||
["Draft", None],
|
["Draft", None],
|
||||||
["Submitted", "eval:self.docstatus==1"],
|
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
|
||||||
|
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
|
||||||
["Cancelled", "eval:self.docstatus==2"],
|
["Cancelled", "eval:self.docstatus==2"],
|
||||||
["Closed", "eval:self.status=='Closed'"],
|
["Closed", "eval:self.status=='Closed'"],
|
||||||
]
|
]
|
||||||
@@ -153,7 +155,6 @@ class StatusUpdater(Document):
|
|||||||
# check if overflow is within tolerance
|
# check if overflow is within tolerance
|
||||||
tolerance, self.tolerance, self.global_tolerance = get_tolerance_for(item['item_code'],
|
tolerance, self.tolerance, self.global_tolerance = get_tolerance_for(item['item_code'],
|
||||||
self.tolerance, self.global_tolerance)
|
self.tolerance, self.global_tolerance)
|
||||||
|
|
||||||
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
||||||
item[args['target_ref_field']]) * 100
|
item[args['target_ref_field']]) * 100
|
||||||
|
|
||||||
@@ -166,10 +167,10 @@ class StatusUpdater(Document):
|
|||||||
throw(_("{0} must be reduced by {1} or you should increase overflow tolerance")
|
throw(_("{0} must be reduced by {1} or you should increase overflow tolerance")
|
||||||
.format(_(item["target_ref_field"].title()), item["reduce_by"]))
|
.format(_(item["target_ref_field"].title()), item["reduce_by"]))
|
||||||
|
|
||||||
def update_qty(self, change_modified=True):
|
def update_qty(self, update_modified=True):
|
||||||
"""Updates qty or amount at row level
|
"""Updates qty or amount at row level
|
||||||
|
|
||||||
:param change_modified: If true, updates `modified` and `modified_by` for target parent doc
|
:param update_modified: If true, updates `modified` and `modified_by` for target parent doc
|
||||||
"""
|
"""
|
||||||
for args in self.status_updater:
|
for args in self.status_updater:
|
||||||
# condition to include current record (if submit or no if cancel)
|
# condition to include current record (if submit or no if cancel)
|
||||||
@@ -178,22 +179,19 @@ class StatusUpdater(Document):
|
|||||||
else:
|
else:
|
||||||
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
|
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
|
||||||
|
|
||||||
args['set_modified'] = ''
|
self._update_children(args, update_modified)
|
||||||
if change_modified:
|
|
||||||
args['set_modified'] = ', modified = now(), modified_by = "{0}"'\
|
|
||||||
.format(frappe.db.escape(frappe.session.user))
|
|
||||||
|
|
||||||
self._update_children(args)
|
|
||||||
|
|
||||||
if "percent_join_field" in args:
|
if "percent_join_field" in args:
|
||||||
self._update_percent_field(args)
|
self._update_percent_field_in_targets(args, update_modified)
|
||||||
|
|
||||||
def _update_children(self, args):
|
def _update_children(self, args, update_modified):
|
||||||
"""Update quantities or amount in child table"""
|
"""Update quantities or amount in child table"""
|
||||||
for d in self.get_all_children():
|
for d in self.get_all_children():
|
||||||
if d.doctype != args['source_dt']:
|
if d.doctype != args['source_dt']:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
self._update_modified(args, update_modified)
|
||||||
|
|
||||||
# updates qty in the child table
|
# updates qty in the child table
|
||||||
args['detail_id'] = d.get(args['join_field'])
|
args['detail_id'] = d.get(args['join_field'])
|
||||||
|
|
||||||
@@ -212,44 +210,58 @@ class StatusUpdater(Document):
|
|||||||
if not args.get("extra_cond"): args["extra_cond"] = ""
|
if not args.get("extra_cond"): args["extra_cond"] = ""
|
||||||
|
|
||||||
frappe.db.sql("""update `tab%(target_dt)s`
|
frappe.db.sql("""update `tab%(target_dt)s`
|
||||||
set %(target_field)s = (select ifnull(sum(%(source_field)s), 0)
|
set %(target_field)s = (
|
||||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
(select ifnull(sum(%(source_field)s), 0)
|
||||||
and (docstatus=1 %(cond)s) %(extra_cond)s) %(second_source_condition)s
|
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||||
|
and (docstatus=1 %(cond)s) %(extra_cond)s)
|
||||||
|
%(second_source_condition)s
|
||||||
|
)
|
||||||
|
%(update_modified)s
|
||||||
where name='%(detail_id)s'""" % args)
|
where name='%(detail_id)s'""" % args)
|
||||||
|
|
||||||
def _update_percent_field(self, args):
|
def _update_percent_field_in_targets(self, args, update_modified=True):
|
||||||
"""Update percent field in parent transaction"""
|
"""Update percent field in parent transaction"""
|
||||||
unique_transactions = set([d.get(args['percent_join_field']) for d in self.get_all_children(args['source_dt'])])
|
distinct_transactions = set([d.get(args['percent_join_field'])
|
||||||
|
for d in self.get_all_children(args['source_dt'])])
|
||||||
|
|
||||||
for name in unique_transactions:
|
for name in distinct_transactions:
|
||||||
if not name:
|
if name:
|
||||||
continue
|
args['name'] = name
|
||||||
|
self._update_percent_field(args, update_modified)
|
||||||
|
|
||||||
args['name'] = name
|
def _update_percent_field(self, args, update_modified=True):
|
||||||
|
"""Update percent field in parent transaction"""
|
||||||
|
|
||||||
# update percent complete in the parent table
|
self._update_modified(args, update_modified)
|
||||||
if args.get('target_parent_field'):
|
|
||||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
|
||||||
set %(target_parent_field)s = round(
|
|
||||||
ifnull((select
|
|
||||||
ifnull(sum(if(%(target_ref_field)s > %(target_field)s, %(target_field)s, %(target_ref_field)s)), 0)
|
|
||||||
/ sum(%(target_ref_field)s) * 100
|
|
||||||
from `tab%(target_dt)s` where parent="%(name)s"), 0), 2)
|
|
||||||
%(set_modified)s
|
|
||||||
where name='%(name)s'""" % args)
|
|
||||||
|
|
||||||
# update field
|
if args.get('target_parent_field'):
|
||||||
if args.get('status_field'):
|
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
set %(target_parent_field)s = round(
|
||||||
set %(status_field)s = if(%(target_parent_field)s<0.001,
|
ifnull((select
|
||||||
'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
|
ifnull(sum(if(%(target_ref_field)s > %(target_field)s, %(target_field)s, %(target_ref_field)s)), 0)
|
||||||
'Fully %(keyword)s', 'Partly %(keyword)s'))
|
/ sum(%(target_ref_field)s) * 100
|
||||||
where name='%(name)s'""" % args)
|
from `tab%(target_dt)s` where parent="%(name)s"), 0), 2)
|
||||||
|
%(update_modified)s
|
||||||
|
where name='%(name)s'""" % args)
|
||||||
|
|
||||||
if args.get("set_modified"):
|
# update field
|
||||||
target = frappe.get_doc(args["target_parent_dt"], name)
|
if args.get('status_field'):
|
||||||
target.set_status(update=True)
|
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||||
target.notify_update()
|
set %(status_field)s = if(%(target_parent_field)s<0.001,
|
||||||
|
'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
|
||||||
|
'Fully %(keyword)s', 'Partly %(keyword)s'))
|
||||||
|
where name='%(name)s'""" % args)
|
||||||
|
|
||||||
|
if update_modified:
|
||||||
|
target = frappe.get_doc(args["target_parent_dt"], args["name"])
|
||||||
|
target.set_status(update=True)
|
||||||
|
target.notify_update()
|
||||||
|
|
||||||
|
def _update_modified(self, args, update_modified):
|
||||||
|
args['update_modified'] = ''
|
||||||
|
if update_modified:
|
||||||
|
args['update_modified'] = ', modified = now(), modified_by = "{0}"'\
|
||||||
|
.format(frappe.db.escape(frappe.session.user))
|
||||||
|
|
||||||
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
|
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
|
||||||
ref_fieldname = ref_dt.lower().replace(" ", "_")
|
ref_fieldname = ref_dt.lower().replace(" ", "_")
|
||||||
|
|||||||
@@ -313,6 +313,16 @@ class StockController(AccountsController):
|
|||||||
for w in warehouses:
|
for w in warehouses:
|
||||||
validate_warehouse_company(w, self.company)
|
validate_warehouse_company(w, self.company)
|
||||||
|
|
||||||
|
def update_billing_percentage(self, update_modified=True):
|
||||||
|
self._update_percent_field({
|
||||||
|
"target_dt": self.doctype + " Item",
|
||||||
|
"target_parent_dt": self.doctype,
|
||||||
|
"target_parent_field": "per_billed",
|
||||||
|
"target_ref_field": "amount",
|
||||||
|
"target_field": "billed_amt",
|
||||||
|
"name": self.name,
|
||||||
|
}, update_modified)
|
||||||
|
|
||||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||||
warehouse_account=None):
|
warehouse_account=None):
|
||||||
def _delete_gl_entries(voucher_type, voucher_no):
|
def _delete_gl_entries(voucher_type, voucher_no):
|
||||||
|
|||||||
@@ -240,3 +240,4 @@ erpnext.patches.v6_10.fix_billed_amount_in_drop_ship_po
|
|||||||
erpnext.patches.v6_10.fix_delivery_status_of_drop_ship_item #2015-12-08
|
erpnext.patches.v6_10.fix_delivery_status_of_drop_ship_item #2015-12-08
|
||||||
erpnext.patches.v5_8.tax_rule #2015-12-08
|
erpnext.patches.v5_8.tax_rule #2015-12-08
|
||||||
erpnext.patches.v6_12.set_overdue_tasks
|
erpnext.patches.v6_12.set_overdue_tasks
|
||||||
|
erpnext.patches.v6_16.update_billing_status_in_dn_and_pr
|
||||||
@@ -13,6 +13,6 @@ def execute():
|
|||||||
where purchase_order=%s and docstatus=1""", po[0])
|
where purchase_order=%s and docstatus=1""", po[0])
|
||||||
if invoices:
|
if invoices:
|
||||||
for inv in invoices:
|
for inv in invoices:
|
||||||
frappe.get_doc("Purchase Invoice", inv[0]).update_qty(change_modified=False)
|
frappe.get_doc("Purchase Invoice", inv[0]).update_qty(update_modified=False)
|
||||||
else:
|
else:
|
||||||
frappe.db.sql("""update `tabPurchase Order` set per_billed=0 where name=%s""", po[0])
|
frappe.db.sql("""update `tabPurchase Order` set per_billed=0 where name=%s""", po[0])
|
||||||
@@ -14,4 +14,4 @@ def execute():
|
|||||||
{"patch_date": not_null_patch_date}):
|
{"patch_date": not_null_patch_date}):
|
||||||
|
|
||||||
doc = frappe.get_doc(doctype, name)
|
doc = frappe.get_doc(doctype, name)
|
||||||
doc.update_qty(change_modified=False)
|
doc.update_qty(update_modified=False)
|
||||||
|
|||||||
1
erpnext/patches/v6_16/__init__.py
Normal file
1
erpnext/patches/v6_16/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
33
erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py
Normal file
33
erpnext/patches/v6_16/update_billing_status_in_dn_and_pr.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for dt in ("Delivery Note", "Purchase Receipt"):
|
||||||
|
frappe.reload_doctype(dt)
|
||||||
|
frappe.reload_doctype(dt + " Item")
|
||||||
|
|
||||||
|
# Update billed_amt in DN and PR which are not against any order
|
||||||
|
for d in frappe.db.sql("""select name from `tabDelivery Note Item`
|
||||||
|
where (so_detail is null or so_detail = '') and docstatus=1""", as_dict=1):
|
||||||
|
billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
|
||||||
|
where dn_detail=%s and docstatus=1""", d.name)
|
||||||
|
billed_amt = billed_amt and billed_amt[0][0] or 0
|
||||||
|
frappe.db.set_value("Delivery Note Item", d.name, "billed_amt", billed_amt, update_modified=False)
|
||||||
|
|
||||||
|
# Update billed_amt in DN and PR which are not against any order
|
||||||
|
for d in frappe.db.sql("""select name from `tabPurchase Receipt Item`
|
||||||
|
where (prevdoc_detail_docname is null or prevdoc_detail_docname = '') and docstatus=1""", as_dict=1):
|
||||||
|
billed_amt = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
|
||||||
|
where pr_detail=%s and docstatus=1""", d.name)
|
||||||
|
billed_amt = billed_amt and billed_amt[0][0] or 0
|
||||||
|
frappe.db.set_value("Purchase Receipt Item", d.name, "billed_amt", billed_amt, update_modified=False)
|
||||||
|
|
||||||
|
# Update billed amt which are against order or invoice
|
||||||
|
# Update billing status for all
|
||||||
|
for d in frappe.db.sql("select name from `tab{0}` where docstatus=1".format(dt), as_dict=1):
|
||||||
|
doc = frappe.get_doc(dt, d.name)
|
||||||
|
doc.update_billing_status(update_modified=False)
|
||||||
|
doc.set_status(update=True, update_modified=False)
|
||||||
@@ -52,16 +52,8 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(doc.__onload && !doc.__onload.billing_complete && doc.docstatus==1
|
if(doc.docstatus==1 && !doc.is_return && doc.status!="Closed" && flt(doc.per_billed, 2) < 100) {
|
||||||
&& !doc.is_return && doc.status!="Closed") {
|
cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary");
|
||||||
// show Make Invoice button only if Delivery Note is not created from Sales Invoice
|
|
||||||
var from_sales_invoice = false;
|
|
||||||
from_sales_invoice = cur_frm.doc.items.some(function(item) {
|
|
||||||
return item.against_sales_invoice ? true : false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!from_sales_invoice)
|
|
||||||
cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(doc.docstatus==1 && doc.status === "Closed" && this.frm.has_perm("submit")) {
|
if(doc.docstatus==1 && doc.status === "Closed" && this.frm.has_perm("submit")) {
|
||||||
@@ -285,6 +277,3 @@ if (sys_defaults.auto_accounting_for_stock) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2154,6 +2154,30 @@
|
|||||||
"unique": 0,
|
"unique": 0,
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "per_billed",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "% Amount Billed",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
@@ -2317,7 +2341,7 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "\nDraft\nSubmitted\nCancelled\nClosed",
|
"options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
@@ -2672,7 +2696,7 @@
|
|||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"menu_index": 0,
|
"menu_index": 0,
|
||||||
"modified": "2015-12-17 16:19:27.004718",
|
"modified": "2015-12-25 16:20:39.014291",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note",
|
"name": "Delivery Note",
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ class DeliveryNote(SellingController):
|
|||||||
'target_ref_field': 'qty',
|
'target_ref_field': 'qty',
|
||||||
'source_field': 'qty',
|
'source_field': 'qty',
|
||||||
'percent_join_field': 'against_sales_invoice',
|
'percent_join_field': 'against_sales_invoice',
|
||||||
'overflow_type': 'delivery'
|
'overflow_type': 'delivery',
|
||||||
|
'no_tolerance': 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'source_dt': 'Delivery Note Item',
|
'source_dt': 'Delivery Note Item',
|
||||||
@@ -56,21 +57,11 @@ class DeliveryNote(SellingController):
|
|||||||
'join_field': 'so_detail',
|
'join_field': 'so_detail',
|
||||||
'target_field': 'returned_qty',
|
'target_field': 'returned_qty',
|
||||||
'target_parent_dt': 'Sales Order',
|
'target_parent_dt': 'Sales Order',
|
||||||
# 'target_parent_field': 'per_delivered',
|
|
||||||
# 'target_ref_field': 'qty',
|
|
||||||
'source_field': '-1 * qty',
|
'source_field': '-1 * qty',
|
||||||
# 'percent_join_field': 'against_sales_order',
|
|
||||||
# 'overflow_type': 'delivery',
|
|
||||||
'extra_cond': """ and exists (select name from `tabDelivery Note` where name=`tabDelivery Note Item`.parent and is_return=1)"""
|
'extra_cond': """ and exists (select name from `tabDelivery Note` where name=`tabDelivery Note Item`.parent and is_return=1)"""
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def onload(self):
|
def onload(self):
|
||||||
billed_qty = frappe.db.sql("""select sum(qty) from `tabSales Invoice Item`
|
|
||||||
where docstatus=1 and delivery_note=%s""", self.name)
|
|
||||||
if billed_qty:
|
|
||||||
total_qty = sum((item.qty for item in self.get("items")))
|
|
||||||
self.set_onload("billing_complete", (billed_qty[0][0] == total_qty))
|
|
||||||
|
|
||||||
self.set_onload("has_return_entry", len(frappe.db.exists({"doctype": "Delivery Note",
|
self.set_onload("has_return_entry", len(frappe.db.exists({"doctype": "Delivery Note",
|
||||||
"is_return": 1, "return_against": self.name, "docstatus": 1})))
|
"is_return": 1, "return_against": self.name, "docstatus": 1})))
|
||||||
|
|
||||||
@@ -199,6 +190,7 @@ class DeliveryNote(SellingController):
|
|||||||
|
|
||||||
# update delivered qty in sales order
|
# update delivered qty in sales order
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
self.update_billing_status()
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.check_credit_limit()
|
self.check_credit_limit()
|
||||||
@@ -206,18 +198,15 @@ class DeliveryNote(SellingController):
|
|||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
frappe.db.set(self, 'status', 'Submitted')
|
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_stop_or_close_sales_order("against_sales_order")
|
self.check_stop_or_close_sales_order("against_sales_order")
|
||||||
self.check_next_docstatus()
|
self.check_next_docstatus()
|
||||||
|
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
self.update_billing_status()
|
||||||
|
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
|
||||||
self.cancel_packing_slips()
|
self.cancel_packing_slips()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
@@ -280,6 +269,63 @@ class DeliveryNote(SellingController):
|
|||||||
self.notify_update()
|
self.notify_update()
|
||||||
clear_doctype_notifications(self)
|
clear_doctype_notifications(self)
|
||||||
|
|
||||||
|
def update_billing_status(self, update_modified=True):
|
||||||
|
updated_delivery_notes = [self.name]
|
||||||
|
for d in self.get("items"):
|
||||||
|
if d.si_detail and not d.so_detail:
|
||||||
|
d.db_set('billed_amt', d.amount, update_modified=update_modified)
|
||||||
|
elif d.so_detail:
|
||||||
|
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
|
||||||
|
|
||||||
|
for dn in set(updated_delivery_notes):
|
||||||
|
dn_doc = self if (dn == self.name) else frappe.get_doc("Delivery Note", dn)
|
||||||
|
dn_doc.update_billing_percentage(update_modified=update_modified)
|
||||||
|
|
||||||
|
self.load_from_db()
|
||||||
|
|
||||||
|
def update_billed_amount_based_on_so(so_detail, update_modified=True):
|
||||||
|
# Billed against Sales Order directly
|
||||||
|
billed_against_so = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
|
||||||
|
where so_detail=%s and (dn_detail is null or dn_detail = '') and docstatus=1""", so_detail)
|
||||||
|
billed_against_so = billed_against_so and billed_against_so[0][0] or 0
|
||||||
|
|
||||||
|
# Get all Delivery Note Item rows against the Sales Order Item row
|
||||||
|
dn_details = frappe.db.sql("""select dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent
|
||||||
|
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
|
||||||
|
where dn.name=dn_item.parent and dn_item.so_detail=%s
|
||||||
|
and dn.docstatus=1 and dn.is_return = 0
|
||||||
|
order by dn.posting_date asc, dn.posting_time asc, dn.name asc""", so_detail, as_dict=1)
|
||||||
|
|
||||||
|
updated_dn = []
|
||||||
|
for dnd in dn_details:
|
||||||
|
billed_amt_agianst_dn = 0
|
||||||
|
|
||||||
|
# If delivered against Sales Invoice
|
||||||
|
if dnd.si_detail:
|
||||||
|
billed_amt_agianst_dn = flt(dnd.amount)
|
||||||
|
billed_against_so -= billed_amt_agianst_dn
|
||||||
|
else:
|
||||||
|
# Get billed amount directly against Delivery Note
|
||||||
|
billed_amt_agianst_dn = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
|
||||||
|
where dn_detail=%s and docstatus=1""", dnd.name)
|
||||||
|
billed_amt_agianst_dn = billed_amt_agianst_dn and billed_amt_agianst_dn[0][0] or 0
|
||||||
|
|
||||||
|
# Distribute billed amount directly against SO between DNs based on FIFO
|
||||||
|
if billed_against_so and billed_amt_agianst_dn < dnd.amount:
|
||||||
|
pending_to_bill = flt(dnd.amount) - billed_amt_agianst_dn
|
||||||
|
if pending_to_bill <= billed_against_so:
|
||||||
|
billed_amt_agianst_dn += pending_to_bill
|
||||||
|
billed_against_so -= pending_to_bill
|
||||||
|
else:
|
||||||
|
billed_amt_agianst_dn += billed_against_so
|
||||||
|
billed_against_so = 0
|
||||||
|
|
||||||
|
frappe.db.set_value("Delivery Note Item", dnd.name, "billed_amt", billed_amt_agianst_dn, update_modified=update_modified)
|
||||||
|
|
||||||
|
updated_dn.append(dnd.parent)
|
||||||
|
|
||||||
|
return updated_dn
|
||||||
|
|
||||||
def get_list_context(context=None):
|
def get_list_context(context=None):
|
||||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||||
list_context = get_list_context(context)
|
list_context = get_list_context(context)
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
frappe.listview_settings['Delivery Note'] = {
|
frappe.listview_settings['Delivery Note'] = {
|
||||||
add_fields: ["customer", "customer_name", "base_grand_total", "per_installed",
|
add_fields: ["customer", "customer_name", "base_grand_total", "per_installed", "per_billed",
|
||||||
"transporter_name", "grand_total", "is_return", "status"],
|
"transporter_name", "grand_total", "is_return", "status"],
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if(cint(doc.is_return)==1) {
|
if(cint(doc.is_return)==1) {
|
||||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||||
} else if(doc.status==="Closed") {
|
} else if(doc.status==="Closed") {
|
||||||
return [__("Closed"), "green", "status,=,Closed"];
|
return [__("Closed"), "green", "status,=,Closed"];
|
||||||
|
} else if (flt(doc.per_billed, 2) < 100) {
|
||||||
|
return [__("To Bill"), "orange", "per_billed,<,100"];
|
||||||
|
} else if (flt(doc.per_billed, 2) == 100) {
|
||||||
|
return [__("Completed"), "green", "per_billed,=,100"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry \
|
|||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoWarehouseError
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoWarehouseError
|
||||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \
|
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \
|
||||||
import create_stock_reconciliation, set_valuation_method
|
import create_stock_reconciliation, set_valuation_method
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so
|
||||||
|
|
||||||
class TestDeliveryNote(unittest.TestCase):
|
class TestDeliveryNote(unittest.TestCase):
|
||||||
def test_over_billing_against_dn(self):
|
def test_over_billing_against_dn(self):
|
||||||
@@ -406,6 +407,112 @@ class TestDeliveryNote(unittest.TestCase):
|
|||||||
update_delivery_note_status(dn.name, "Closed")
|
update_delivery_note_status(dn.name, "Closed")
|
||||||
self.assertEquals(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
|
self.assertEquals(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
|
||||||
|
|
||||||
|
def test_dn_billing_status_case1(self):
|
||||||
|
# SO -> DN -> SI
|
||||||
|
so = make_sales_order()
|
||||||
|
dn = create_dn_against_so(so.name, delivered_qty=2)
|
||||||
|
|
||||||
|
self.assertEqual(dn.status, "To Bill")
|
||||||
|
self.assertEqual(dn.per_billed, 0)
|
||||||
|
|
||||||
|
si = make_sales_invoice(dn.name)
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
dn.load_from_db()
|
||||||
|
self.assertEqual(dn.get("items")[0].billed_amt, 200)
|
||||||
|
self.assertEqual(dn.per_billed, 100)
|
||||||
|
self.assertEqual(dn.status, "Completed")
|
||||||
|
|
||||||
|
def test_dn_billing_status_case2(self):
|
||||||
|
# SO -> SI and SO -> DN1, DN2
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
|
||||||
|
|
||||||
|
so = make_sales_order()
|
||||||
|
|
||||||
|
si = make_sales_invoice(so.name)
|
||||||
|
si.get("items")[0].qty = 5
|
||||||
|
si.insert()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||||
|
|
||||||
|
dn1 = make_delivery_note(so.name)
|
||||||
|
dn1.posting_time = "10:00"
|
||||||
|
dn1.get("items")[0].qty = 2
|
||||||
|
dn1.submit()
|
||||||
|
|
||||||
|
self.assertEqual(dn1.get("items")[0].billed_amt, 200)
|
||||||
|
self.assertEqual(dn1.per_billed, 100)
|
||||||
|
self.assertEqual(dn1.status, "Completed")
|
||||||
|
|
||||||
|
dn2 = make_delivery_note(so.name)
|
||||||
|
dn2.posting_time = "08:00"
|
||||||
|
dn2.get("items")[0].qty = 4
|
||||||
|
dn2.submit()
|
||||||
|
|
||||||
|
dn1.load_from_db()
|
||||||
|
self.assertEqual(dn1.get("items")[0].billed_amt, 100)
|
||||||
|
self.assertEqual(dn1.per_billed, 50)
|
||||||
|
self.assertEqual(dn1.status, "To Bill")
|
||||||
|
|
||||||
|
self.assertEqual(dn2.get("items")[0].billed_amt, 400)
|
||||||
|
self.assertEqual(dn2.per_billed, 100)
|
||||||
|
self.assertEqual(dn2.status, "Completed")
|
||||||
|
|
||||||
|
def test_dn_billing_status_case3(self):
|
||||||
|
# SO -> DN1 -> SI and SO -> SI and SO -> DN2
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order \
|
||||||
|
import make_delivery_note, make_sales_invoice as make_sales_invoice_from_so
|
||||||
|
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||||
|
|
||||||
|
so = make_sales_order()
|
||||||
|
|
||||||
|
dn1 = make_delivery_note(so.name)
|
||||||
|
dn1.posting_time = "10:00"
|
||||||
|
dn1.get("items")[0].qty = 2
|
||||||
|
dn1.submit()
|
||||||
|
|
||||||
|
si1 = make_sales_invoice(dn1.name)
|
||||||
|
si1.submit()
|
||||||
|
|
||||||
|
dn1.load_from_db()
|
||||||
|
self.assertEqual(dn1.per_billed, 100)
|
||||||
|
|
||||||
|
si2 = make_sales_invoice_from_so(so.name)
|
||||||
|
si2.get("items")[0].qty = 4
|
||||||
|
si2.submit()
|
||||||
|
|
||||||
|
dn2 = make_delivery_note(so.name)
|
||||||
|
dn2.posting_time = "08:00"
|
||||||
|
dn2.get("items")[0].qty = 5
|
||||||
|
dn2.submit()
|
||||||
|
|
||||||
|
dn1.load_from_db()
|
||||||
|
self.assertEqual(dn1.get("items")[0].billed_amt, 200)
|
||||||
|
self.assertEqual(dn1.per_billed, 100)
|
||||||
|
self.assertEqual(dn1.status, "Completed")
|
||||||
|
|
||||||
|
self.assertEqual(dn2.get("items")[0].billed_amt, 400)
|
||||||
|
self.assertEqual(dn2.per_billed, 80)
|
||||||
|
self.assertEqual(dn2.status, "To Bill")
|
||||||
|
|
||||||
|
def test_dn_billing_status_case4(self):
|
||||||
|
# SO -> SI -> DN
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
|
||||||
|
|
||||||
|
so = make_sales_order()
|
||||||
|
|
||||||
|
si = make_sales_invoice(so.name)
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
dn = make_delivery_note(si.name)
|
||||||
|
dn.submit()
|
||||||
|
|
||||||
|
self.assertEqual(dn.get("items")[0].billed_amt, 1000)
|
||||||
|
self.assertEqual(dn.per_billed, 100)
|
||||||
|
self.assertEqual(dn.status, "Completed")
|
||||||
|
|
||||||
def create_delivery_note(**args):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
dn = frappe.new_doc("Delivery Note")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -1226,6 +1226,31 @@
|
|||||||
"unique": 0,
|
"unique": 0,
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "billed_amt",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Billed Amt",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "currency",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
@@ -1261,7 +1286,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2016-01-06 02:22:36.503314",
|
"modified": "2016-01-06 02:22:36.503315",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note Item",
|
"name": "Delivery Note Item",
|
||||||
|
|||||||
@@ -1812,7 +1812,7 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "\nDraft\nSubmitted\nCancelled\nClosed",
|
"options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
@@ -1930,6 +1930,30 @@
|
|||||||
"unique": 0,
|
"unique": 0,
|
||||||
"width": "50%"
|
"width": "50%"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "per_billed",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "% Amount Billed",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
@@ -2278,7 +2302,7 @@
|
|||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"menu_index": 0,
|
"menu_index": 0,
|
||||||
"modified": "2015-12-17 16:18:48.969643",
|
"modified": "2015-12-30 18:15:06.678001",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
|||||||
@@ -243,6 +243,8 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
|
|
||||||
|
self.update_billing_status()
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
purchase_controller.update_last_purchase_rate(self, 1)
|
purchase_controller.update_last_purchase_rate(self, 1)
|
||||||
|
|
||||||
@@ -281,6 +283,8 @@ class PurchaseReceipt(BuyingController):
|
|||||||
# Must be called after updating received qty in PO
|
# Must be called after updating received qty in PO
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
|
|
||||||
|
self.update_billing_status()
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
pc_obj.update_last_purchase_rate(self, 0)
|
pc_obj.update_last_purchase_rate(self, 0)
|
||||||
|
|
||||||
@@ -436,6 +440,54 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.notify_update()
|
self.notify_update()
|
||||||
clear_doctype_notifications(self)
|
clear_doctype_notifications(self)
|
||||||
|
|
||||||
|
def update_billing_status(self, update_modified=True):
|
||||||
|
updated_pr = [self.name]
|
||||||
|
for d in self.get("items"):
|
||||||
|
if d.prevdoc_detail_docname:
|
||||||
|
updated_pr += update_billed_amount_based_on_po(d.prevdoc_detail_docname, update_modified)
|
||||||
|
|
||||||
|
for pr in set(updated_pr):
|
||||||
|
pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
|
||||||
|
pr_doc.update_billing_percentage(update_modified=update_modified)
|
||||||
|
|
||||||
|
self.load_from_db()
|
||||||
|
|
||||||
|
def update_billed_amount_based_on_po(po_detail, update_modified=True):
|
||||||
|
# Billed against Sales Order directly
|
||||||
|
billed_against_po = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
|
||||||
|
where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""", po_detail)
|
||||||
|
billed_against_po = billed_against_po and billed_against_po[0][0] or 0
|
||||||
|
|
||||||
|
# Get all Delivery Note Item rows against the Sales Order Item row
|
||||||
|
pr_details = frappe.db.sql("""select pr_item.name, pr_item.amount, pr_item.parent
|
||||||
|
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
|
||||||
|
where pr.name=pr_item.parent and pr_item.prevdoc_detail_docname=%s
|
||||||
|
and pr.docstatus=1 and pr.is_return = 0
|
||||||
|
order by pr.posting_date asc, pr.posting_time asc, pr.name asc""", po_detail, as_dict=1)
|
||||||
|
|
||||||
|
updated_pr = []
|
||||||
|
for pr_item in pr_details:
|
||||||
|
# Get billed amount directly against Purchase Receipt
|
||||||
|
billed_amt_agianst_pr = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
|
||||||
|
where pr_detail=%s and docstatus=1""", pr_item.name)
|
||||||
|
billed_amt_agianst_pr = billed_amt_agianst_pr and billed_amt_agianst_pr[0][0] or 0
|
||||||
|
|
||||||
|
# Distribute billed amount directly against PO between PRs based on FIFO
|
||||||
|
if billed_against_po and billed_amt_agianst_pr < pr_item.amount:
|
||||||
|
pending_to_bill = flt(pr_item.amount) - billed_amt_agianst_pr
|
||||||
|
if pending_to_bill <= billed_against_po:
|
||||||
|
billed_amt_agianst_pr += pending_to_bill
|
||||||
|
billed_against_po -= pending_to_bill
|
||||||
|
else:
|
||||||
|
billed_amt_agianst_pr += billed_against_po
|
||||||
|
billed_against_po = 0
|
||||||
|
|
||||||
|
frappe.db.set_value("Purchase Receipt Item", pr_item.name, "billed_amt", billed_amt_agianst_pr, update_modified=update_modified)
|
||||||
|
|
||||||
|
updated_pr.append(pr_item.parent)
|
||||||
|
|
||||||
|
return updated_pr
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_purchase_invoice(source_name, target_doc=None):
|
def make_purchase_invoice(source_name, target_doc=None):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
frappe.listview_settings['Purchase Receipt'] = {
|
frappe.listview_settings['Purchase Receipt'] = {
|
||||||
add_fields: ["supplier", "supplier_name", "base_grand_total", "is_subcontracted",
|
add_fields: ["supplier", "supplier_name", "base_grand_total", "is_subcontracted",
|
||||||
"transporter_name", "is_return", "status"],
|
"transporter_name", "is_return", "status", "per_billed"],
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if(cint(doc.is_return)==1) {
|
if(cint(doc.is_return)==1) {
|
||||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||||
} else if(doc.status==="Closed") {
|
} else if(doc.status==="Closed") {
|
||||||
return [__("Closed"), "green", "status,=,Closed"];
|
return [__("Closed"), "green", "status,=,Closed"];
|
||||||
|
} else if (flt(doc.per_billed, 2) < 100) {
|
||||||
|
return [__("To Bill"), "orange", "per_billed,<,100"];
|
||||||
|
} else if (flt(doc.per_billed, 2) == 100) {
|
||||||
|
return [__("Completed"), "green", "per_billed,=,100"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe.utils import cint, flt, cstr
|
from frappe.utils import cint, flt, cstr
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||||
|
|
||||||
class TestPurchaseReceipt(unittest.TestCase):
|
class TestPurchaseReceipt(unittest.TestCase):
|
||||||
def test_make_purchase_invoice(self):
|
def test_make_purchase_invoice(self):
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
|
||||||
|
|
||||||
pr = make_purchase_receipt(do_not_save=True)
|
pr = make_purchase_receipt(do_not_save=True)
|
||||||
self.assertRaises(frappe.ValidationError, make_purchase_invoice, pr.name)
|
self.assertRaises(frappe.ValidationError, make_purchase_invoice, pr.name)
|
||||||
pr.submit()
|
pr.submit()
|
||||||
@@ -185,6 +184,45 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
update_purchase_receipt_status(pr.name, "Closed")
|
update_purchase_receipt_status(pr.name, "Closed")
|
||||||
self.assertEquals(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")
|
self.assertEquals(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")
|
||||||
|
|
||||||
|
def test_pr_billing_status(self):
|
||||||
|
# PO -> PR1 -> PI and PO -> PI and PO -> PR2
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order \
|
||||||
|
import make_purchase_receipt, make_purchase_invoice as make_purchase_invoice_from_po
|
||||||
|
|
||||||
|
po = create_purchase_order()
|
||||||
|
|
||||||
|
pr1 = make_purchase_receipt(po.name)
|
||||||
|
pr1.posting_time = "10:00"
|
||||||
|
pr1.get("items")[0].received_qty = 2
|
||||||
|
pr1.get("items")[0].qty = 2
|
||||||
|
pr1.submit()
|
||||||
|
|
||||||
|
pi1 = make_purchase_invoice(pr1.name)
|
||||||
|
pi1.submit()
|
||||||
|
|
||||||
|
pr1.load_from_db()
|
||||||
|
self.assertEqual(pr1.per_billed, 100)
|
||||||
|
|
||||||
|
pi2 = make_purchase_invoice_from_po(po.name)
|
||||||
|
pi2.get("items")[0].qty = 4
|
||||||
|
pi2.submit()
|
||||||
|
|
||||||
|
pr2 = make_purchase_receipt(po.name)
|
||||||
|
pr2.posting_time = "08:00"
|
||||||
|
pr2.get("items")[0].received_qty = 5
|
||||||
|
pr2.get("items")[0].qty = 5
|
||||||
|
pr2.submit()
|
||||||
|
|
||||||
|
pr1.load_from_db()
|
||||||
|
self.assertEqual(pr1.get("items")[0].billed_amt, 1000)
|
||||||
|
self.assertEqual(pr1.per_billed, 100)
|
||||||
|
self.assertEqual(pr1.status, "Completed")
|
||||||
|
|
||||||
|
self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
|
||||||
|
self.assertEqual(pr2.per_billed, 80)
|
||||||
|
self.assertEqual(pr2.status, "To Bill")
|
||||||
|
|
||||||
def get_gl_entries(voucher_type, voucher_no):
|
def get_gl_entries(voucher_type, voucher_no):
|
||||||
return frappe.db.sql("""select account, debit, credit
|
return frappe.db.sql("""select account, debit, credit
|
||||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||||
|
|||||||
@@ -1311,6 +1311,30 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"fieldname": "billed_amt",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Billed Amt",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 1,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
@@ -1532,7 +1556,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2016-01-06 02:22:04.312515",
|
"modified": "2016-01-06 02:22:04.312514",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
|||||||
Reference in New Issue
Block a user