Merge branch 'develop' into develop

This commit is contained in:
P-Froggy 2020-03-02 00:18:23 +01:00 committed by GitHub
commit 3bf04154b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 375 additions and 198 deletions

View File

@ -13,13 +13,15 @@ form_grid_templates = {
class BankReconciliation(Document): class BankReconciliation(Document):
def get_payment_entries(self): def get_payment_entries(self):
if not (self.bank_account and self.from_date and self.to_date): if not (self.from_date and self.to_date):
msgprint(_("Bank Account, From Date and To Date are Mandatory")) frappe.throw(_("From Date and To Date are Mandatory"))
return
if not self.account:
frappe.throw(_("Account is mandatory to get payment entries"))
condition = "" condition = ""
if not self.include_reconciled_entries: if not self.include_reconciled_entries:
condition = " and (clearance_date is null or clearance_date='0000-00-00')" condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
journal_entries = frappe.db.sql(""" journal_entries = frappe.db.sql("""
select select
@ -32,10 +34,14 @@ class BankReconciliation(Document):
where where
t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1 t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1
and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s
and ifnull(t1.is_opening, 'No') = 'No' %(condition)s and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC order by t1.posting_date ASC, t1.name DESC
""", {"condition":condition, "account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) """.format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
condition = ''
if self.bank_account:
condition += 'and bank_account = %(bank_account)s'
payment_entries = frappe.db.sql(""" payment_entries = frappe.db.sql("""
select select
@ -49,10 +55,10 @@ class BankReconciliation(Document):
where where
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date >= %(from)s and posting_date <= %(to)s and posting_date >= %(from)s and posting_date <= %(to)s
and bank_account = %(bank_account)s {condition}
order by order by
posting_date ASC, name DESC posting_date ASC, name DESC
""", {"account": self.account, "from":self.from_date, """.format(condition=condition), {"account": self.account, "from":self.from_date,
"to": self.to_date, "bank_account": self.bank_account}, as_dict=1) "to": self.to_date, "bank_account": self.bank_account}, as_dict=1)
pos_entries = [] pos_entries = []

View File

@ -18,7 +18,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Invoice", "label": "Invoice",
"options": "Sales Invoice", "options": "Sales Invoice",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"fetch_from": "sales_invoice.customer", "fetch_from": "sales_invoice.customer",
@ -60,7 +61,7 @@
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-09-26 11:05:36.016772", "modified": "2020-02-20 16:16:20.724620",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Discounted Invoice", "name": "Discounted Invoice",

View File

@ -7,4 +7,4 @@ from __future__ import unicode_literals
from frappe.model.document import Document from frappe.model.document import Document
class DiscountedInvoice(Document): class DiscountedInvoice(Document):
pass pass

View File

@ -232,11 +232,36 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
if bal < 0 and not on_cancel: if bal < 0 and not on_cancel:
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))) frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
# Update outstanding amt on against voucher
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal)
def update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal):
data = []
# Update outstanding amt on against voucher
if against_voucher_type == "Fees":
ref_doc = frappe.get_doc(against_voucher_type, against_voucher) ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
ref_doc.db_set('outstanding_amount', bal) ref_doc.db_set('outstanding_amount', bal)
ref_doc.set_status(update=True) ref_doc.set_status(update=True)
return
elif against_voucher_type == "Purchase Invoice":
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_status
data = frappe.db.get_value(against_voucher_type, against_voucher,
["name as purchase_invoice", "outstanding_amount",
"is_return", "due_date", "docstatus"])
elif against_voucher_type == "Sales Invoice":
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_status
data = frappe.db.get_value(against_voucher_type, against_voucher,
["name as sales_invoice", "outstanding_amount", "is_discounted",
"is_return", "due_date", "docstatus"])
precision = frappe.get_precision(against_voucher_type, "outstanding_amount")
data = list(data)
data.append(precision)
status = get_status(data)
frappe.db.set_value(against_voucher_type, against_voucher, {
'outstanding_amount': bal,
'status': status
})
def validate_frozen_account(account, adv_adj=None): def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.db.get_value("Account", account, "freeze_account") frozen_account = frappe.db.get_value("Account", account, "freeze_account")
@ -274,6 +299,9 @@ def update_against_account(voucher_type, voucher_no):
if d.against != new_against: if d.against != new_against:
frappe.db.set_value("GL Entry", d.name, "against", new_against) frappe.db.set_value("GL Entry", d.name, "against", new_against)
def on_doctype_update():
frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
def rename_gle_sle_docs(): def rename_gle_sle_docs():
for doctype in ["GL Entry", "Stock Ledger Entry"]: for doctype in ["GL Entry", "Stock Ledger Entry"]:

View File

@ -125,6 +125,27 @@ class PurchaseInvoice(BuyingController):
else: else:
self.remarks = _("No Remarks") self.remarks = _("No Remarks")
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
if self.get('amended_from'):
self.status = 'Draft'
return
if not status:
precision = self.precision("outstanding_amount")
args = [
self.name,
self.outstanding_amount,
self.is_return,
self.due_date,
self.docstatus,
precision
]
status = get_status(args)
if update:
self.db_set('status', status, update_modified = update_modified)
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
if not self.credit_to: if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company) self.credit_to = get_party_account("Supplier", self.supplier, self.company)
@ -1007,6 +1028,34 @@ class PurchaseInvoice(BuyingController):
# calculate totals again after applying TDS # calculate totals again after applying TDS
self.calculate_taxes_and_totals() self.calculate_taxes_and_totals()
def get_status(*args):
purchase_invoice, outstanding_amount, is_return, due_date, docstatus, precision = args[0]
outstanding_amount = flt(outstanding_amount, precision)
due_date = getdate(due_date)
now_date = getdate()
if docstatus == 2:
status = "Cancelled"
elif docstatus == 1:
if outstanding_amount > 0 and due_date < now_date:
status = "Overdue"
elif outstanding_amount > 0 and due_date >= now_date:
status = "Unpaid"
#Check if outstanding amount is 0 due to debit note issued against invoice
elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': purchase_invoice, 'docstatus': 1}):
status = "Debit Note Issued"
elif is_return == 1:
status = "Return"
elif outstanding_amount <=0:
status = "Paid"
else:
status = "Submitted"
else:
status = "Draft"
return status
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)

View File

@ -1217,62 +1217,83 @@ class SalesInvoice(SellingController):
self.set_missing_values(for_validate = True) self.set_missing_values(for_validate = True)
def get_discounting_status(self):
status = None
if self.is_discounted:
invoice_discounting_list = frappe.db.sql("""
select status
from `tabInvoice Discounting` id, `tabDiscounted Invoice` d
where
id.name = d.parent
and d.sales_invoice=%s
and id.docstatus=1
and status in ('Disbursed', 'Settled')
""", self.name)
for d in invoice_discounting_list:
status = d[0]
if status == "Disbursed":
break
return status
def set_status(self, update=False, status=None, update_modified=True): def set_status(self, update=False, status=None, update_modified=True):
if self.is_new(): if self.is_new():
if self.get('amended_from'): if self.get('amended_from'):
self.status = 'Draft' self.status = 'Draft'
return return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
discountng_status = self.get_discounting_status()
if not status: if not status:
if self.docstatus == 2: precision = self.precision("outstanding_amount")
status = "Cancelled" args = [
elif self.docstatus == 1: self.name,
if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': self.outstanding_amount,
self.status = "Overdue and Discounted" self.is_discounted,
elif outstanding_amount > 0 and due_date < nowdate: self.is_return,
self.status = "Overdue" self.due_date,
elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed': self.docstatus,
self.status = "Unpaid and Discounted" precision,
elif outstanding_amount > 0 and due_date >= nowdate: ]
self.status = "Unpaid" status = get_status(args)
#Check if outstanding amount is 0 due to credit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Credit Note Issued"
elif self.is_return == 1:
self.status = "Return"
elif outstanding_amount<=0:
self.status = "Paid"
else:
self.status = "Submitted"
else:
self.status = "Draft"
if update: if update:
self.db_set('status', self.status, update_modified = update_modified) self.db_set('status', status, update_modified = update_modified)
def get_discounting_status(sales_invoice):
status = None
invoice_discounting_list = frappe.db.sql("""
select status
from `tabInvoice Discounting` id, `tabDiscounted Invoice` d
where
id.name = d.parent
and d.sales_invoice=%s
and id.docstatus=1
and status in ('Disbursed', 'Settled')
""", sales_invoice)
for d in invoice_discounting_list:
status = d[0]
if status == "Disbursed":
break
return status
def get_status(*args):
sales_invoice, outstanding_amount, is_discounted, is_return, due_date, docstatus, precision = args[0]
discounting_status = None
if is_discounted:
discounting_status = get_discounting_status(sales_invoice)
outstanding_amount = flt(outstanding_amount, precision)
due_date = getdate(due_date)
now_date = getdate()
if docstatus == 2:
status = "Cancelled"
elif docstatus == 1:
if outstanding_amount > 0 and due_date < now_date and is_discounted and discounting_status=='Disbursed':
status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < now_date:
status = "Overdue"
elif outstanding_amount > 0 and due_date >= now_date and is_discounted and discounting_status=='Disbursed':
status = "Unpaid and Discounted"
elif outstanding_amount > 0 and due_date >= now_date:
status = "Unpaid"
#Check if outstanding amount is 0 due to credit note issued against invoice
elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': sales_invoice, 'docstatus': 1}):
status = "Credit Note Issued"
elif is_return == 1:
status = "Return"
elif outstanding_amount <=0:
status = "Paid"
else:
status = "Submitted"
else:
status = "Draft"
return status
def validate_inter_company_party(doctype, party, company, inter_company_reference): def validate_inter_company_party(doctype, party, company, inter_company_reference):
if not party: if not party:
@ -1444,7 +1465,7 @@ def get_inter_company_details(doc, doctype):
"party": party, "party": party,
"company": company "company": company
} }
def get_internal_party(parties, link_doctype, doc): def get_internal_party(parties, link_doctype, doc):
if len(parties) == 1: if len(parties) == 1:
party = parties[0].name party = parties[0].name

View File

@ -140,8 +140,11 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle = frappe.get_doc(args) gle = frappe.get_doc(args)
gle.flags.ignore_permissions = 1 gle.flags.ignore_permissions = 1
gle.flags.from_repost = from_repost gle.flags.from_repost = from_repost
gle.insert() gle.validate()
gle.flags.ignore_permissions = True
gle.db_insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
gle.flags.ignore_validate = True
gle.submit() gle.submit()
def validate_account_for_perpetual_inventory(gl_map): def validate_account_for_perpetual_inventory(gl_map):

View File

@ -29,7 +29,7 @@ def get_data(filters):
row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", ""))) row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", "")))
row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) +
flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated_during_the_period))
row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) -
flt(row.accumulated_depreciation_as_on_from_date)) flt(row.accumulated_depreciation_as_on_from_date))
@ -86,7 +86,6 @@ def get_asset_categories(filters):
group by asset_category group by asset_category
""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1)
def get_assets(filters): def get_assets(filters):
return frappe.db.sql(""" return frappe.db.sql("""
SELECT results.asset_category, SELECT results.asset_category,
@ -94,9 +93,7 @@ def get_assets(filters):
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category, from (SELECT a.asset_category,
ifnull(sum(a.opening_accumulated_depreciation + ifnull(sum(case when ds.schedule_date < %(from_date)s then
case when ds.schedule_date < %(from_date)s and
(ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
ds.depreciation_amount ds.depreciation_amount
else else
0 0
@ -107,7 +104,6 @@ def get_assets(filters):
else else
0 0
end), 0) as depreciation_eliminated_during_the_period, end), 0) as depreciation_eliminated_during_the_period,
ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s
and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then
ds.depreciation_amount ds.depreciation_amount
@ -120,7 +116,8 @@ def get_assets(filters):
union union
SELECT a.asset_category, SELECT a.asset_category,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s)
then
0 0
else else
a.opening_accumulated_depreciation a.opening_accumulated_depreciation
@ -133,7 +130,6 @@ def get_assets(filters):
0 as depreciation_amount_during_the_period 0 as depreciation_amount_during_the_period
from `tabAsset` a from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s
and not exists(select * from `tabDepreciation Schedule` ds where a.name = ds.parent)
group by a.asset_category) as results group by a.asset_category) as results
group by results.asset_category group by results.asset_category
""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1)

View File

@ -306,10 +306,6 @@ def get_conditions(filters):
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
conditions = get_conditions(filters) conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Purchase Invoice")
if match_conditions:
match_conditions = " and {0} ".format(match_conditions)
if additional_query_columns: if additional_query_columns:
additional_query_columns = ', ' + ', '.join(additional_query_columns) additional_query_columns = ', ' + ', '.join(additional_query_columns)
@ -327,8 +323,8 @@ def get_items(filters, additional_query_columns):
`tabPurchase Invoice`.supplier_name, `tabPurchase Invoice`.mode_of_payment {0} `tabPurchase Invoice`.supplier_name, `tabPurchase Invoice`.mode_of_payment {0}
from `tabPurchase Invoice`, `tabPurchase Invoice Item` from `tabPurchase Invoice`, `tabPurchase Invoice Item`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabPurchase Invoice`.docstatus = 1 %s %s `tabPurchase Invoice`.docstatus = 1 %s
""".format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1) """.format(additional_query_columns) % (conditions), filters, as_dict=1)
def get_aii_accounts(): def get_aii_accounts():
return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany")) return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany"))

View File

@ -119,7 +119,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns) add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
data.append(total_row_map.get('total_row')) data.append(total_row_map.get('total_row'))
skip_total_row = 1 skip_total_row = 1
return columns, data, None, None, None, skip_total_row return columns, data, None, None, None, skip_total_row
def get_columns(additional_table_columns, filters): def get_columns(additional_table_columns, filters):
@ -370,10 +370,6 @@ def get_group_by_conditions(filters, doctype):
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
conditions = get_conditions(filters) conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Sales Invoice")
if match_conditions:
match_conditions = " and {0} ".format(match_conditions)
if additional_query_columns: if additional_query_columns:
additional_query_columns = ', ' + ', '.join(additional_query_columns) additional_query_columns = ', ' + ', '.join(additional_query_columns)
@ -394,8 +390,8 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item` from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Invoice`.docstatus = 1 {1} {2} and `tabSales Invoice`.docstatus = 1 {1}
""".format(additional_query_columns or '', conditions, match_conditions), filters, as_dict=1) #nosec """.format(additional_query_columns or '', conditions), filters, as_dict=1) #nosec
def get_delivery_notes_against_sales_order(item_list): def get_delivery_notes_against_sales_order(item_list):
so_dn_map = frappe._dict() so_dn_map = frappe._dict()

View File

@ -12,7 +12,6 @@ from erpnext.stock.doctype.item.item import validate_end_of_life
def update_last_purchase_rate(doc, is_submit): def update_last_purchase_rate(doc, is_submit):
"""updates last_purchase_rate in item table for each item""" """updates last_purchase_rate in item table for each item"""
import frappe.utils import frappe.utils
this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date')) this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date'))
@ -23,7 +22,7 @@ def update_last_purchase_rate(doc, is_submit):
# compare last purchase date and this transaction's date # compare last purchase date and this transaction's date
last_purchase_rate = None last_purchase_rate = None
if last_purchase_details and \ if last_purchase_details and \
(last_purchase_details.purchase_date > this_purchase_date): (doc.get('docstatus') == 2 or last_purchase_details.purchase_date > this_purchase_date):
last_purchase_rate = last_purchase_details['base_net_rate'] last_purchase_rate = last_purchase_details['base_net_rate']
elif is_submit == 1: elif is_submit == 1:
# even if this transaction is the latest one, it should be submitted # even if this transaction is the latest one, it should be submitted

View File

@ -7,6 +7,13 @@ def get_data():
"label": _("Purchasing"), "label": _("Purchasing"),
"icon": "fa fa-star", "icon": "fa fa-star",
"items": [ "items": [
{
"type": "doctype",
"name": "Material Request",
"onboard": 1,
"dependencies": ["Item"],
"description": _("Request for purchase."),
},
{ {
"type": "doctype", "type": "doctype",
"name": "Purchase Order", "name": "Purchase Order",
@ -20,13 +27,6 @@ def get_data():
"onboard": 1, "onboard": 1,
"dependencies": ["Item", "Supplier"] "dependencies": ["Item", "Supplier"]
}, },
{
"type": "doctype",
"name": "Material Request",
"onboard": 1,
"dependencies": ["Item"],
"description": _("Request for purchase."),
},
{ {
"type": "doctype", "type": "doctype",
"name": "Request for Quotation", "name": "Request for Quotation",
@ -63,6 +63,11 @@ def get_data():
"name": "Price List", "name": "Price List",
"description": _("Price List master.") "description": _("Price List master.")
}, },
{
"type": "doctype",
"name": "Pricing Rule",
"description": _("Rules for applying pricing and discount.")
},
{ {
"type": "doctype", "type": "doctype",
"name": "Product Bundle", "name": "Product Bundle",
@ -80,11 +85,6 @@ def get_data():
"type": "doctype", "type": "doctype",
"name": "Promotional Scheme", "name": "Promotional Scheme",
"description": _("Rules for applying different promotional schemes.") "description": _("Rules for applying different promotional schemes.")
},
{
"type": "doctype",
"name": "Pricing Rule",
"description": _("Rules for applying pricing and discount.")
} }
] ]
}, },
@ -149,13 +149,6 @@ def get_data():
"reference_doctype": "Purchase Order", "reference_doctype": "Purchase Order",
"onboard": 1 "onboard": 1
}, },
{
"type": "report",
"is_query_report": True,
"name": "Supplier-Wise Sales Analytics",
"reference_doctype": "Stock Ledger Entry",
"onboard": 1
},
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,
@ -177,6 +170,16 @@ def get_data():
"reference_doctype": "Material Request", "reference_doctype": "Material Request",
"onboard": 1, "onboard": 1,
}, },
{
"type": "report",
"is_query_report": True,
"name": "Address And Contacts",
"label": _("Supplier Addresses And Contacts"),
"reference_doctype": "Address",
"route_options": {
"party_type": "Supplier"
}
}
] ]
}, },
{ {
@ -226,18 +229,15 @@ def get_data():
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,
"name": "Material Requests for which Supplier Quotations are not created", "name": "Supplier-Wise Sales Analytics",
"reference_doctype": "Material Request" "reference_doctype": "Stock Ledger Entry",
"onboard": 1
}, },
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,
"name": "Address And Contacts", "name": "Material Requests for which Supplier Quotations are not created",
"label": _("Supplier Addresses And Contacts"), "reference_doctype": "Material Request"
"reference_doctype": "Address",
"route_options": {
"party_type": "Supplier"
}
} }
] ]
}, },

View File

@ -1200,6 +1200,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code")) child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code"))
else: else:
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")):
continue
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
frappe.throw(_("Cannot set quantity less than delivered quantity")) frappe.throw(_("Cannot set quantity less than delivered quantity"))

View File

@ -44,17 +44,6 @@ status_map = {
["Closed", "eval:self.status=='Closed'"], ["Closed", "eval:self.status=='Closed'"],
["On Hold", "eval:self.status=='On Hold'"], ["On Hold", "eval:self.status=='On Hold'"],
], ],
"Purchase Invoice": [
["Draft", None],
["Submitted", "eval:self.docstatus==1"],
["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
["Return", "eval:self.is_return==1 and self.docstatus==1"],
["Debit Note Issued",
"eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
["Cancelled", "eval:self.docstatus==2"],
],
"Purchase Order": [ "Purchase Order": [
["Draft", None], ["Draft", None],
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"], ["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],

View File

@ -429,7 +429,7 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc""".format(condition=condition), order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True): tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no]) future_stock_vouchers.append([d.voucher_type, d.voucher_no])

View File

@ -11,5 +11,12 @@ frappe.ui.form.on('Student Admission', {
academic_year: function(frm) { academic_year: function(frm) {
frm.trigger("program"); frm.trigger("program");
},
admission_end_date: function(frm) {
if(frm.doc.admission_end_date && frm.doc.admission_end_date <= frm.doc.admission_start_date){
frm.set_value("admission_end_date", "");
frappe.throw(__("Admission End Date should be greater than Admission Start Date."));
}
} }
}); });

View File

@ -39,19 +39,21 @@ class AdditionalSalary(Document):
return amount_per_day * no_of_days return amount_per_day * no_of_days
@frappe.whitelist() @frappe.whitelist()
def get_additional_salary_component(employee, start_date, end_date): def get_additional_salary_component(employee, start_date, end_date, component_type):
additional_components = frappe.db.sql(""" additional_components = frappe.db.sql("""
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
from `tabAdditional Salary` from `tabAdditional Salary`
where employee=%(employee)s where employee=%(employee)s
and docstatus = 1 and docstatus = 1
and payroll_date between %(from_date)s and %(to_date)s and payroll_date between %(from_date)s and %(to_date)s
and type = %(component_type)s
group by salary_component, overwrite_salary_structure_amount group by salary_component, overwrite_salary_structure_amount
order by salary_component, overwrite_salary_structure_amount order by salary_component, overwrite_salary_structure_amount
""", { """, {
'employee': employee, 'employee': employee,
'from_date': start_date, 'from_date': start_date,
'to_date': end_date 'to_date': end_date,
'component_type': "Earning" if component_type == "earnings" else "Deduction"
}, as_dict=1) }, as_dict=1)
additional_components_list = [] additional_components_list = []

View File

@ -64,6 +64,9 @@ class LeaveEncashment(Document):
allocation = self.get_leave_allocation() allocation = self.get_leave_allocation()
if not allocation:
frappe.throw(_("No Leaves Allocated to Employee: {0} for Leave Type: {1}").format(self.employee, self.leave_type))
self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\ self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\
- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date)
@ -116,4 +119,4 @@ def create_leave_encashment(leave_allocation):
leave_type=allocation.leave_type, leave_type=allocation.leave_type,
encashment_date=allocation.to_date encashment_date=allocation.to_date
)) ))
leave_encashment.insert(ignore_permissions=True) leave_encashment.insert(ignore_permissions=True)

View File

@ -299,9 +299,11 @@ class SalarySlip(TransactionBase):
def calculate_net_pay(self): def calculate_net_pay(self):
if self.salary_structure: if self.salary_structure:
self.calculate_component_amounts() self.calculate_component_amounts("earnings")
self.gross_pay = self.get_component_totals("earnings") self.gross_pay = self.get_component_totals("earnings")
if self.salary_structure:
self.calculate_component_amounts("deductions")
self.total_deduction = self.get_component_totals("deductions") self.total_deduction = self.get_component_totals("deductions")
self.set_loan_repayment() self.set_loan_repayment()
@ -309,25 +311,27 @@ class SalarySlip(TransactionBase):
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
self.rounded_total = rounded(self.net_pay) self.rounded_total = rounded(self.net_pay)
def calculate_component_amounts(self): def calculate_component_amounts(self, component_type):
if not getattr(self, '_salary_structure_doc', None): if not getattr(self, '_salary_structure_doc', None):
self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure) self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure)
payroll_period = get_payroll_period(self.start_date, self.end_date, self.company) payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
self.add_structure_components() self.add_structure_components(component_type)
self.add_employee_benefits(payroll_period) self.add_additional_salary_components(component_type)
self.add_additional_salary_components() if component_type == "earnings":
self.add_tax_components(payroll_period) self.add_employee_benefits(payroll_period)
self.set_component_amounts_based_on_payment_days() else:
self.add_tax_components(payroll_period)
def add_structure_components(self): self.set_component_amounts_based_on_payment_days(component_type)
def add_structure_components(self, component_type):
data = self.get_data_for_eval() data = self.get_data_for_eval()
for key in ('earnings', 'deductions'): for struct_row in self._salary_structure_doc.get(component_type):
for struct_row in self._salary_structure_doc.get(key): amount = self.eval_condition_and_formula(struct_row, data)
amount = self.eval_condition_and_formula(struct_row, data) if amount and struct_row.statistical_component == 0:
if amount and struct_row.statistical_component == 0: self.update_component_row(struct_row, amount, component_type)
self.update_component_row(struct_row, amount, key)
def get_data_for_eval(self): def get_data_for_eval(self):
'''Returns data for evaluating formula''' '''Returns data for evaluating formula'''
@ -400,14 +404,15 @@ class SalarySlip(TransactionBase):
amount = last_benefit.amount amount = last_benefit.amount
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
def add_additional_salary_components(self): def add_additional_salary_components(self, component_type):
additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date) additional_components = get_additional_salary_component(self.employee,
self.start_date, self.end_date, component_type)
if additional_components: if additional_components:
for additional_component in additional_components: for additional_component in additional_components:
amount = additional_component.amount amount = additional_component.amount
overwrite = additional_component.overwrite overwrite = additional_component.overwrite
key = "earnings" if additional_component.type == "Earning" else "deductions" self.update_component_row(frappe._dict(additional_component.struct_row), amount,
self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite) component_type, overwrite=overwrite)
def add_tax_components(self, payroll_period): def add_tax_components(self, payroll_period):
# Calculate variable_based_on_taxable_salary after all components updated in salary slip # Calculate variable_based_on_taxable_salary after all components updated in salary slip
@ -736,7 +741,7 @@ class SalarySlip(TransactionBase):
total += d.amount total += d.amount
return total return total
def set_component_amounts_based_on_payment_days(self): def set_component_amounts_based_on_payment_days(self, component_type):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"]) ["date_of_joining", "relieving_date"])
@ -746,9 +751,8 @@ class SalarySlip(TransactionBase):
if not joining_date: if not joining_date:
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
for component_type in ("earnings", "deductions"): for d in self.get(component_type):
for d in self.get(component_type): d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
def set_loan_repayment(self): def set_loan_repayment(self):
self.set('loans', []) self.set('loans', [])

View File

@ -25,7 +25,6 @@ class TestSalaryStructure(unittest.TestCase):
make_employee("test_employee@salary.com") make_employee("test_employee@salary.com")
make_employee("test_employee_2@salary.com") make_employee("test_employee_2@salary.com")
def make_holiday_list(self): def make_holiday_list(self):
if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
holiday_list = frappe.get_doc({ holiday_list = frappe.get_doc({
@ -38,6 +37,29 @@ class TestSalaryStructure(unittest.TestCase):
holiday_list.get_weekly_off_dates() holiday_list.get_weekly_off_dates()
holiday_list.save() holiday_list.save()
def test_salary_structure_deduction_based_on_gross_pay(self):
emp = make_employee("test_employee_3@salary.com")
sal_struct = make_salary_structure("Salary Structure 2", "Monthly", dont_submit = True)
sal_struct.earnings = [sal_struct.earnings[0]]
sal_struct.earnings[0].amount_based_on_formula = 1
sal_struct.earnings[0].formula = "base"
sal_struct.deductions = [sal_struct.deductions[0]]
sal_struct.deductions[0].amount_based_on_formula = 1
sal_struct.deductions[0].condition = "gross_pay > 100"
sal_struct.deductions[0].formula = "gross_pay * 0.2"
sal_struct.submit()
assignment = create_salary_structure_assignment(emp, "Salary Structure 2")
ss = make_salary_slip(sal_struct.name, employee = emp)
self.assertEqual(assignment.base * 0.2, ss.deductions[0].amount)
def test_amount_totals(self): def test_amount_totals(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"}) sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"})

View File

@ -75,7 +75,7 @@ class ShiftType(Document):
for date in dates: for date in dates:
shift_details = get_employee_shift(employee, date, True) shift_details = get_employee_shift(employee, date, True)
if shift_details and shift_details.shift_type.name == self.name: if shift_details and shift_details.shift_type.name == self.name:
mark_attendance(employee, date, self.name, 'Absent') mark_attendance(employee, date, 'Absent', self.name)
def get_assigned_employee(self, from_date=None, consider_default_shift=False): def get_assigned_employee(self, from_date=None, consider_default_shift=False):
filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'}

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "ACC-LOAP-.YYYY.-.#####", "autoname": "ACC-LOAP-.YYYY.-.#####",
"creation": "2019-08-29 17:46:49.201740", "creation": "2019-08-29 17:46:49.201740",
"doctype": "DocType", "doctype": "DocType",
@ -122,7 +123,6 @@
}, },
{ {
"depends_on": "eval: doc.is_term_loan == 1", "depends_on": "eval: doc.is_term_loan == 1",
"fetch_from": "loan_type.repayment_method",
"fetch_if_empty": 1, "fetch_if_empty": 1,
"fieldname": "repayment_method", "fieldname": "repayment_method",
"fieldtype": "Select", "fieldtype": "Select",
@ -213,7 +213,8 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-10-24 10:32:03.740558", "links": [],
"modified": "2020-03-01 10:21:44.413353",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Application", "name": "Loan Application",

View File

@ -229,13 +229,14 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-02-24 07:35:47.168123", "modified": "2020-02-26 06:18:54.934538",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Repayment", "name": "Loan Repayment",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -245,9 +246,11 @@
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -257,6 +260,7 @@
"report": 1, "report": 1,
"role": "Loan Manager", "role": "Loan Manager",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],

View File

@ -55,7 +55,10 @@ def check_for_ltv_shortfall(process_loan_security_shortfall=None):
"valid_upto": (">=", update_time) "valid_upto": (">=", update_time)
}, as_list=1)) }, as_list=1))
loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
fields=["name", "loan_to_value_ratio"], as_list=1))
loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty, lp.loan_security_type
FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1 FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1
and l.is_secured_loan and l.status = 'Disbursed' and p.status in ('Pledged', 'Partially Unpledged')""", as_dict=1) and l.is_secured_loan and l.status = 'Disbursed' and p.status in ('Pledged', 'Partially Unpledged')""", as_dict=1)
@ -68,11 +71,12 @@ def check_for_ltv_shortfall(process_loan_security_shortfall=None):
}) })
current_loan_security_amount = loan_security_price_map.get(loan.loan_security, 0) * loan.qty current_loan_security_amount = loan_security_price_map.get(loan.loan_security, 0) * loan.qty
ltv_ratio = ltv_ratio_map.get(loan.loan_security_type)
loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100) loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100)
for loan, value in iteritems(loan_security_map): for loan, value in iteritems(loan_security_map):
if value["security_value"] < value["loan_amount"]: if (value["security_value"]/value["loan_amount"]) < ltv_ratio:
create_loan_security_shortfall(loan, value, process_loan_security_shortfall) create_loan_security_shortfall(loan, value, process_loan_security_shortfall)
def create_loan_security_shortfall(loan, value, process_loan_security_shortfall): def create_loan_security_shortfall(loan, value, process_loan_security_shortfall):

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "field:loan_security_type", "autoname": "field:loan_security_type",
"creation": "2019-08-29 18:46:07.322056", "creation": "2019-08-29 18:46:07.322056",
"doctype": "DocType", "doctype": "DocType",
@ -8,7 +9,9 @@
"loan_security_type", "loan_security_type",
"unit_of_measure", "unit_of_measure",
"haircut", "haircut",
"disabled" "disabled",
"column_break_5",
"loan_to_value_ratio"
], ],
"fields": [ "fields": [
{ {
@ -33,9 +36,19 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Unit Of Measure", "label": "Unit Of Measure",
"options": "UOM" "options": "UOM"
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "loan_to_value_ratio",
"fieldtype": "Percent",
"label": "Loan To Value Ratio"
} }
], ],
"modified": "2019-10-10 03:05:37.912866", "links": [],
"modified": "2020-02-28 12:43:20.364447",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security Type", "name": "Loan Security Type",

View File

@ -108,8 +108,8 @@ def delete_lead_addresses(company_name):
def delete_communications(doctype, company_name, company_fieldname): def delete_communications(doctype, company_name, company_fieldname):
reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
reference_doc_names = [r.name for r in reference_docs] reference_doc_names = [r.name for r in reference_docs]
communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]})
communication_names = [c.name for c in communications] communication_names = [c.name for c in communications]
frappe.delete_doc("Communication", communication_names) frappe.delete_doc("Communication", communication_names, ignore_permissions=True)

View File

@ -981,6 +981,7 @@ def _msgprint(msg, verbose):
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
"""returns last purchase details in stock uom""" """returns last purchase details in stock uom"""
# get last purchase order item details # get last purchase order item details
last_purchase_order = frappe.db.sql("""\ last_purchase_order = frappe.db.sql("""\
select po.name, po.transaction_date, po.conversion_rate, select po.name, po.transaction_date, po.conversion_rate,
po_item.conversion_factor, po_item.base_price_list_rate, po_item.conversion_factor, po_item.base_price_list_rate,
@ -991,6 +992,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
order by po.transaction_date desc, po.name desc order by po.transaction_date desc, po.name desc
limit 1""", (item_code, cstr(doc_name)), as_dict=1) limit 1""", (item_code, cstr(doc_name)), as_dict=1)
# get last purchase receipt item details # get last purchase receipt item details
last_purchase_receipt = frappe.db.sql("""\ last_purchase_receipt = frappe.db.sql("""\
select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
@ -1002,19 +1004,20 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
order by pr.posting_date desc, pr.posting_time desc, pr.name desc order by pr.posting_date desc, pr.posting_time desc, pr.name desc
limit 1""", (item_code, cstr(doc_name)), as_dict=1) limit 1""", (item_code, cstr(doc_name)), as_dict=1)
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
or "1900-01-01") or "1900-01-01")
purchase_receipt_date = getdate(last_purchase_receipt and purchase_receipt_date = getdate(last_purchase_receipt and
last_purchase_receipt[0].posting_date or "1900-01-01") last_purchase_receipt[0].posting_date or "1900-01-01")
if (purchase_order_date > purchase_receipt_date) or \ if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt):
(last_purchase_order and not last_purchase_receipt):
# use purchase order # use purchase order
last_purchase = last_purchase_order[0] last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date purchase_date = purchase_order_date
elif (purchase_receipt_date > purchase_order_date) or \ elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order):
(last_purchase_receipt and not last_purchase_order):
# use purchase receipt # use purchase receipt
last_purchase = last_purchase_receipt[0] last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date purchase_date = purchase_receipt_date
@ -1026,10 +1029,11 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
out = frappe._dict({ out = frappe._dict({
"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
"base_rate": flt(last_purchase.base_rate) / conversion_factor, "base_rate": flt(last_purchase.base_rate) / conversion_factor,
"base_net_rate": flt(last_purchase.net_rate) / conversion_factor, "base_net_rate": flt(last_purchase.base_net_rate) / conversion_factor,
"discount_percentage": flt(last_purchase.discount_percentage), "discount_percentage": flt(last_purchase.discount_percentage),
"purchase_date": purchase_date "purchase_date": purchase_date
}) })
conversion_rate = flt(conversion_rate) or 1.0 conversion_rate = flt(conversion_rate) or 1.0
out.update({ out.update({

View File

@ -205,6 +205,7 @@ def process_serial_no(sle):
def validate_serial_no(sle, item_det): def validate_serial_no(sle, item_det):
serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else [] serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else []
validate_material_transfer_entry(sle)
if item_det.has_serial_no==0: if item_det.has_serial_no==0:
if serial_nos: if serial_nos:
@ -224,7 +225,9 @@ def validate_serial_no(sle, item_det):
for serial_no in serial_nos: for serial_no in serial_nos:
if frappe.db.exists("Serial No", serial_no): if frappe.db.exists("Serial No", serial_no):
sr = frappe.get_doc("Serial No", serial_no) sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
"delivery_document_no", "delivery_document_type", "warehouse",
"purchase_document_no", "company"], as_dict=1)
if sr.item_code!=sle.item_code: if sr.item_code!=sle.item_code:
if not allow_serial_nos_with_different_item(serial_no, sle): if not allow_serial_nos_with_different_item(serial_no, sle):
@ -305,6 +308,19 @@ def validate_serial_no(sle, item_det):
frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
def validate_material_transfer_entry(sle_doc):
sle_doc.update({
"skip_update_serial_no": False,
"skip_serial_no_validaiton": False
})
if (sle_doc.voucher_type == "Stock Entry" and sle_doc.is_cancelled == "No" and
frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"):
if sle_doc.actual_qty < 0:
sle_doc.skip_update_serial_no = True
else:
sle_doc.skip_serial_no_validaiton = True
def validate_so_serial_no(sr, sales_order,): def validate_so_serial_no(sr, sales_order,):
if not sr.sales_order or sr.sales_order!= sales_order: if not sr.sales_order or sr.sales_order!= sales_order:
frappe.throw(_("""Sales Order {0} has reservation for item {1}, you can frappe.throw(_("""Sales Order {0} has reservation for item {1}, you can
@ -312,7 +328,8 @@ def validate_so_serial_no(sr, sales_order,):
be delivered""").format(sales_order, sr.item_code, sr.name)) be delivered""").format(sales_order, sr.item_code, sr.name))
def has_duplicate_serial_no(sn, sle): def has_duplicate_serial_no(sn, sle):
if sn.warehouse and sle.voucher_type != 'Stock Reconciliation': if (sn.warehouse and not sle.skip_serial_no_validaiton
and sle.voucher_type != 'Stock Reconciliation'):
return True return True
if sn.company != sle.company: if sn.company != sle.company:
@ -337,7 +354,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle):
""" """
allow_serial_nos = False allow_serial_nos = False
if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0: if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0:
stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no)
if stock_entry.purpose in ("Repack", "Manufacture"): if stock_entry.purpose in ("Repack", "Manufacture"):
for d in stock_entry.get("items"): for d in stock_entry.get("items"):
if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse): if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse):
@ -348,6 +365,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle):
return allow_serial_nos return allow_serial_nos
def update_serial_nos(sle, item_det): def update_serial_nos(sle, item_det):
if sle.skip_update_serial_no: return
if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \
and item_det.has_serial_no == 1 and item_det.serial_no_series: and item_det.has_serial_no == 1 and item_det.serial_no_series:
serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty)
@ -369,22 +387,16 @@ def auto_make_serial_nos(args):
voucher_type = args.get('voucher_type') voucher_type = args.get('voucher_type')
item_code = args.get('item_code') item_code = args.get('item_code')
for serial_no in serial_nos: for serial_no in serial_nos:
is_new = False
if frappe.db.exists("Serial No", serial_no): if frappe.db.exists("Serial No", serial_no):
sr = frappe.get_doc("Serial No", serial_no) sr = frappe.get_cached_doc("Serial No", serial_no)
sr.via_stock_ledger = True
sr.item_code = item_code
sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None
sr.batch_no = args.get('batch_no')
sr.location = args.get('location')
sr.company = args.get('company')
sr.supplier = args.get('supplier')
if sr.sales_order and voucher_type == "Stock Entry" \
and not args.get('actual_qty', 0) > 0:
sr.sales_order = None
sr.update_serial_no_reference()
sr.save(ignore_permissions=True)
elif args.get('actual_qty', 0) > 0: elif args.get('actual_qty', 0) > 0:
created_numbers.append(make_serial_no(serial_no, args)) sr = frappe.new_doc("Serial No")
is_new = True
sr = update_args_for_serial_no(sr, serial_no, args, is_new=is_new)
if is_new:
created_numbers.append(sr.name)
form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers)) form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
@ -419,20 +431,34 @@ def get_serial_nos(serial_no):
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
if s.strip()] if s.strip()]
def make_serial_no(serial_no, args): def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
sr = frappe.new_doc("Serial No") serial_no_doc.update({
sr.serial_no = serial_no "item_code": args.get("item_code"),
sr.item_code = args.get('item_code') "company": args.get("company"),
sr.company = args.get('company') "batch_no": args.get("batch_no"),
sr.batch_no = args.get('batch_no') "via_stock_ledger": args.get("via_stock_ledger") or True,
sr.via_stock_ledger = args.get('via_stock_ledger') or True "supplier": args.get("supplier"),
sr.warehouse = args.get('warehouse') "location": args.get("location"),
"warehouse": (args.get("warehouse")
if args.get("actual_qty", 0) > 0 else None)
})
sr.validate_item() if is_new:
sr.update_serial_no_reference(serial_no) serial_no_doc.serial_no = serial_no
sr.db_insert()
return sr.name if (serial_no_doc.sales_order and args.get("voucher_type") == "Stock Entry"
and not args.get("actual_qty", 0) > 0):
serial_no_doc.sales_order = None
serial_no_doc.validate_item()
serial_no_doc.update_serial_no_reference(serial_no)
if is_new:
serial_no_doc.db_insert()
else:
serial_no_doc.db_update()
return serial_no_doc
def update_serial_nos_after_submit(controller, parentfield): def update_serial_nos_after_submit(controller, parentfield):
stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse

View File

@ -240,6 +240,7 @@
"options": "Company", "options": "Company",
"print_width": "150px", "print_width": "150px",
"read_only": 1, "read_only": 1,
"search_index": 1,
"width": "150px" "width": "150px"
}, },
{ {
@ -274,7 +275,7 @@
"icon": "fa fa-list", "icon": "fa fa-list",
"idx": 1, "idx": 1,
"in_create": 1, "in_create": 1,
"modified": "2019-11-27 12:17:31.522675", "modified": "2020-02-25 22:53:33.504681",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Ledger Entry", "name": "Stock Ledger Entry",

View File

@ -136,7 +136,7 @@ def get_bin(item_code, warehouse):
bin_obj.flags.ignore_permissions = 1 bin_obj.flags.ignore_permissions = 1
bin_obj.insert() bin_obj.insert()
else: else:
bin_obj = frappe.get_doc('Bin', bin) bin_obj = frappe.get_cached_doc('Bin', bin)
bin_obj.flags.ignore_permissions = True bin_obj.flags.ignore_permissions = True
return bin_obj return bin_obj

View File

@ -366,7 +366,7 @@
"icon": "fa fa-ticket", "icon": "fa fa-ticket",
"idx": 7, "idx": 7,
"links": [], "links": [],
"modified": "2020-02-18 21:26:35.636013", "modified": "2020-02-26 02:19:49.477928",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Support", "module": "Support",
"name": "Issue", "name": "Issue",
@ -387,9 +387,9 @@
"quick_entry": 1, "quick_entry": 1,
"search_fields": "status,customer,subject,raised_by", "search_fields": "status,customer,subject,raised_by",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "DESC",
"timeline_field": "customer", "timeline_field": "customer",
"title_field": "subject", "title_field": "subject",
"track_changes": 1, "track_changes": 1,
"track_seen": 1 "track_seen": 1
} }