Merge branch 'hotfix'

This commit is contained in:
Sahil Khan
2019-05-28 14:50:05 +05:30
46 changed files with 6607 additions and 6364 deletions

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '11.1.33' __version__ = '11.1.34'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@@ -6,8 +6,8 @@ frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"], "currency", "is_return", "release_date", "on_hold"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) { if(flt(doc.outstanding_amount) < 0 && doc.docstatus == 1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<,0"]
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { } else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) { if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"]; return [__("On Hold"), "darkgrey"];
@@ -18,9 +18,9 @@ frappe.listview_settings['Purchase Invoice'] = {
} else { } else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"]; return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"];
} }
} else if(flt(doc.outstanding_amount) < 0 && doc.docstatus == 1) { } else if(cint(doc.is_return)) {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<,0"] return [__("Return"), "darkgrey", "is_return,=,Yes"];
}else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) { } else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"]; return [__("Paid"), "green", "outstanding_amount,=,0"];
} }
} }

View File

@@ -54,8 +54,8 @@ class SalesInvoice(SellingController):
def set_indicator(self): def set_indicator(self):
"""Set indicator for portal""" """Set indicator for portal"""
if cint(self.is_return) == 1: if self.outstanding_amount < 0:
self.indicator_title = _("Return") self.indicator_title = _("Credit Note Issued")
self.indicator_color = "darkgrey" self.indicator_color = "darkgrey"
elif self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()): elif self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()):
self.indicator_color = "orange" self.indicator_color = "orange"
@@ -63,8 +63,8 @@ class SalesInvoice(SellingController):
elif self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()): elif self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()):
self.indicator_color = "red" self.indicator_color = "red"
self.indicator_title = _("Overdue") self.indicator_title = _("Overdue")
elif self.outstanding_amount < 0: elif cint(self.is_return) == 1:
self.indicator_title = _("Credit Note Issued") self.indicator_title = _("Return")
self.indicator_color = "darkgrey" self.indicator_color = "darkgrey"
else: else:
self.indicator_color = "green" self.indicator_color = "green"

View File

@@ -6,16 +6,16 @@ frappe.listview_settings['Sales Invoice'] = {
add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return"], "currency", "is_return"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) { if(flt(doc.outstanding_amount) < 0) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0) {
return [__("Paid"), "green", "outstanding_amount,=,0"]
} else if(flt(doc.outstanding_amount) < 0) {
return [__("Credit Note Issued"), "darkgrey", "outstanding_amount,<,0"] return [__("Credit Note Issued"), "darkgrey", "outstanding_amount,<,0"]
}else if (flt(doc.outstanding_amount) > 0 && doc.due_date >= frappe.datetime.get_today()) { } else if (flt(doc.outstanding_amount) > 0 && doc.due_date >= frappe.datetime.get_today()) {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>,Today"] return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>,Today"]
} else if (flt(doc.outstanding_amount) > 0 && doc.due_date < frappe.datetime.get_today()) { } else if (flt(doc.outstanding_amount) > 0 && doc.due_date < frappe.datetime.get_today()) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<=,Today"] return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<=,Today"]
} else if(cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0) {
return [__("Paid"), "green", "outstanding_amount,=,0"]
} }
}, },
right_column: "grand_total" right_column: "grand_total"

View File

@@ -314,13 +314,11 @@ class Subscription(Document):
self.save() self.save()
@property
def is_postpaid_to_invoice(self): def is_postpaid_to_invoice(self):
return getdate(nowdate()) > getdate(self.current_invoice_end) or \ return getdate(nowdate()) > getdate(self.current_invoice_end) or \
(getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \ (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \
not self.has_outstanding_invoice() not self.has_outstanding_invoice()
@property
def is_prepaid_to_invoice(self): def is_prepaid_to_invoice(self):
if not self.generate_invoice_at_period_start: if not self.generate_invoice_at_period_start:
return False return False
@@ -340,7 +338,7 @@ class Subscription(Document):
2. Change the `Subscription` status to 'Past Due Date' 2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled' 3. Change the `Subscription` status to 'Cancelled'
""" """
if self.is_postpaid_to_invoice or self.is_prepaid_to_invoice: if self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice():
self.generate_invoice() self.generate_invoice()
if self.current_invoice_is_past_due(): if self.current_invoice_is_past_due():
self.status = 'Past Due Date' self.status = 'Past Due Date'

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Payable"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Payable Summary"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Receivable"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -100,11 +100,14 @@ class ReceivablePayableReport(object):
self.filters["range2"] = "60" self.filters["range2"] = "60"
if not "range3" in self.filters: if not "range3" in self.filters:
self.filters["range3"] = "90" self.filters["range3"] = "90"
if not "range4" in self.filters:
self.filters["range4"] = "120"
for label in ("0-{range1}".format(range1=self.filters["range1"]), for label in ("0-{range1}".format(range1=self.filters["range1"]),
"{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]), "{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
"{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]), "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
"{range3}-{above}".format(range3=cint(self.filters["range3"])+ 1, above=_("Above"))): "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))):
columns.append({ columns.append({
"label": label, "label": label,
"fieldname":label, "fieldname":label,
@@ -328,18 +331,17 @@ class ReceivablePayableReport(object):
entry_date = gle.posting_date entry_date = gle.posting_date
row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2),
cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount) cint(self.filters.range3), cint(self.filters.range4), self.age_as_on, entry_date, outstanding_amount)
# issue 6371-Ageing buckets should not have amounts if due date is not reached # issue 6371-Ageing buckets should not have amounts if due date is not reached
if self.filters.ageing_based_on == "Due Date" \ if self.filters.ageing_based_on == "Due Date" \
and getdate(due_date) > getdate(self.filters.report_date): and getdate(due_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0 row[-1]=row[-2]=row[-3]=row[-4]=row[-5]=0
if self.filters.ageing_based_on == "Supplier Invoice Date" \ if self.filters.ageing_based_on == "Supplier Invoice Date" \
and getdate(bill_date) > getdate(self.filters.report_date): and getdate(bill_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0 row[-1]=row[-2]=row[-3]=row[-4]=row[-5]=0
if self.filters.get(scrub(args.get("party_type"))): if self.filters.get(scrub(args.get("party_type"))):
row.append(gle.account_currency) row.append(gle.account_currency)
@@ -585,13 +587,13 @@ class ReceivablePayableReport(object):
return payment_term_map return payment_term_map
def get_chart_data(self, columns, data): def get_chart_data(self, columns, data):
ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+4] ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+5]
rows = [] rows = []
for d in data: for d in data:
rows.append( rows.append(
{ {
'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+4] 'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+5]
} }
) )
@@ -610,21 +612,22 @@ def execute(filters=None):
} }
return ReceivablePayableReport(filters).run(args) return ReceivablePayableReport(filters).run(args)
def get_ageing_data(first_range, second_range, third_range, age_as_on, entry_date, outstanding_amount): def get_ageing_data(first_range, second_range, third_range,
# [0-30, 30-60, 60-90, 90-above] fourth_range, age_as_on, entry_date, outstanding_amount):
outstanding_range = [0.0, 0.0, 0.0, 0.0] # [0-30, 30-60, 60-90, 90-120, 120-above]
outstanding_range = [0.0, 0.0, 0.0, 0.0, 0.0]
if not (age_as_on and entry_date): if not (age_as_on and entry_date):
return [0] + outstanding_range return [0] + outstanding_range
age = (getdate(age_as_on) - getdate(entry_date)).days or 0 age = (getdate(age_as_on) - getdate(entry_date)).days or 0
index = None index = None
for i, days in enumerate([first_range, second_range, third_range]): for i, days in enumerate([first_range, second_range, third_range, fourth_range]):
if age <= days: if age <= days:
index = i index = i
break break
if index is None: index = 3 if index is None: index = 4
outstanding_range[index] = outstanding_amount outstanding_range[index] = outstanding_amount
return [age] + outstanding_range return [age] + outstanding_range

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Receivable Summary"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -82,8 +82,15 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"width": 160 "width": 160
}, },
{ {
"label": _(str(self.filters.range3) + _("-Above")), "label": _(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldname": scrub(str(self.filters.range3) + _("-Above")), "fieldname": scrub(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range4) + _("-Above")),
"fieldname": scrub(str(self.filters.range4) + _("-Above")),
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "currency", "options": "currency",
"width": 160 "width": 160
@@ -152,7 +159,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
row += [ row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt, party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,
party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range5
] ]
if args.get("party_type") == "Customer": if args.get("party_type") == "Customer":
@@ -178,6 +185,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"range2": 0, "range2": 0,
"range3": 0, "range3": 0,
"range4": 0, "range4": 0,
"range5": 0,
"sales_person": [] "sales_person": []
}) })
) )
@@ -209,7 +217,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
cols += ["bill_no", "bill_date"] cols += ["bill_no", "bill_date"]
cols += ["invoiced_amt", "paid_amt", "credit_amt", cols += ["invoiced_amt", "paid_amt", "credit_amt",
"outstanding_amt", "age", "range1", "range2", "range3", "range4", "currency", "pdc/lc_date", "pdc/lc_ref", "outstanding_amt", "age", "range1", "range2", "range3", "range4", "range5", "currency", "pdc/lc_date", "pdc/lc_ref",
"pdc/lc_amount"] "pdc/lc_amount"]
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":

View File

@@ -333,7 +333,7 @@ def reconcile_against_document(args):
doc = frappe.get_doc(d.voucher_type, d.voucher_no) doc = frappe.get_doc(d.voucher_type, d.voucher_no)
doc.make_gl_entries(cancel = 0, adv_adj =1) doc.make_gl_entries(cancel = 0, adv_adj =1)
if d.voucher_type == 'Payment Entry': if d.voucher_type in ('Payment Entry', 'Journal Entry'):
doc.update_expense_claim() doc.update_expense_claim()
def check_if_advance_entry_modified(args): def check_if_advance_entry_modified(args):
@@ -378,9 +378,9 @@ def check_if_advance_entry_modified(args):
def validate_allocated_amount(args): def validate_allocated_amount(args):
if args.get("allocated_amount") < 0: if args.get("allocated_amount") < 0:
throw(_("Allocated amount can not be negative")) throw(_("Allocated amount cannot be negative"))
elif args.get("allocated_amount") > args.get("unadjusted_amount"): elif args.get("allocated_amount") > args.get("unadjusted_amount"):
throw(_("Allocated amount can not greater than unadjusted amount")) throw(_("Allocated amount cannot be greater than unadjusted amount"))
def update_reference_in_journal_entry(d, jv_obj): def update_reference_in_journal_entry(d, jv_obj):
""" """

View File

@@ -237,6 +237,10 @@ class AccountsController(TransactionBase):
document_type = "{} Item".format(self.doctype) document_type = "{} Item".format(self.doctype)
parent_dict.update({"document_type": document_type}) parent_dict.update({"document_type": document_type})
# party_name field used for customer in quotation
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
parent_dict.update({"customer": parent_dict.get("party_name")})
for item in self.get("items"): for item in self.get("items"):
if item.get("item_code"): if item.get("item_code"):
args = parent_dict.copy() args = parent_dict.copy()
@@ -344,7 +348,7 @@ class AccountsController(TransactionBase):
'fiscal_year': fiscal_year, 'fiscal_year': fiscal_year,
'voucher_type': self.doctype, 'voucher_type': self.doctype,
'voucher_no': self.name, 'voucher_no': self.name,
'remarks': self.get("remarks"), 'remarks': self.get("remarks") or self.get("remark"),
'debit': 0, 'debit': 0,
'credit': 0, 'credit': 0,
'debit_in_account_currency': 0, 'debit_in_account_currency': 0,

View File

@@ -45,9 +45,9 @@ status_map = {
"Sales Invoice": [ "Sales Invoice": [
["Draft", None], ["Draft", None],
["Submitted", "eval:self.docstatus==1"], ["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"], ["Return", "eval:self.is_return==1 and self.docstatus==1"],
["Paid", "eval:self.outstanding_amount<=0 and self.docstatus==1 and self.is_return==0"], ["Credit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1"],
["Credit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1 and self.is_return==0 and get_value('Sales 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"], ["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"], ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],
@@ -55,9 +55,9 @@ status_map = {
"Purchase Invoice": [ "Purchase Invoice": [
["Draft", None], ["Draft", None],
["Submitted", "eval:self.docstatus==1"], ["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"], ["Return", "eval:self.is_return==1 and self.docstatus==1"],
["Paid", "eval:self.outstanding_amount<=0 and self.docstatus==1 and self.is_return==0"], ["Debit Note Issued", "eval:self.outstanding_amount < 0 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"], ["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"], ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],

View File

@@ -80,7 +80,7 @@ class StockController(AccountsController):
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": flt(sle.stock_value_difference, 2), "debit": flt(sle.stock_value_difference, 2),
"is_opening": item_row.get("is_opening"), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"])) }, warehouse_account[sle.warehouse]["account_currency"]))
# to target warehouse / expense account # to target warehouse / expense account
@@ -91,7 +91,7 @@ class StockController(AccountsController):
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": flt(sle.stock_value_difference, 2), "credit": flt(sle.stock_value_difference, 2),
"project": item_row.get("project") or self.get("project"), "project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
})) }))
elif sle.warehouse not in warehouse_with_no_account: elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse) warehouse_with_no_account.append(sle.warehouse)

View File

@@ -150,8 +150,8 @@ def make_opportunity(source_name, target_doc=None):
"doctype": "Opportunity", "doctype": "Opportunity",
"field_map": { "field_map": {
"campaign_name": "campaign", "campaign_name": "campaign",
"doctype": "enquiry_from", "doctype": "opportunity_from",
"name": "lead", "name": "party_name",
"lead_name": "contact_display", "lead_name": "contact_display",
"company_name": "customer_name", "company_name": "customer_name",
"email_id": "contact_email", "email_id": "contact_email",

View File

@@ -35,14 +35,14 @@ def get_data(filters):
for lead in frappe.get_all('Lead', fields = ['name', 'lead_name', 'company_name'], filters=lead_filters): for lead in frappe.get_all('Lead', fields = ['name', 'lead_name', 'company_name'], filters=lead_filters):
data = frappe.db.sql(""" data = frappe.db.sql("""
select select
`tabCommunication`.reference_doctype, `tabCommunication`.reference_name, `tabCommunication`.reference_doctype, `tabCommunication`.reference_name,
`tabCommunication`.content, `tabCommunication`.communication_date `tabCommunication`.content, `tabCommunication`.communication_date
from from
( (
(select name, lead from `tabOpportunity` where lead = %(lead)s) (select name, party_name as lead from `tabOpportunity` where opportunity_from='Lead' and party_name = %(lead)s)
union union
(select name, lead from `tabQuotation` where lead = %(lead)s) (select name, party_name as lead from `tabQuotation` where quotation_to = 'Lead' and party_name = %(lead)s)
union union
(select name, lead from `tabIssue` where lead = %(lead)s and status!='Closed') (select name, lead from `tabIssue` where lead = %(lead)s and status!='Closed')
union union

View File

@@ -107,10 +107,18 @@ def get_series():
def setup_custom_fields(): def setup_custom_fields():
custom_fields = { custom_fields = {
"Customer": [dict(fieldname='shopify_customer_id', label='Shopify Customer Id', "Customer": [
fieldtype='Data', insert_after='series', read_only=1, print_hide=1)], dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
"Address": [dict(fieldname='shopify_address_id', label='Shopify Address Id', fieldtype='Data', insert_after='series', read_only=1, print_hide=1)
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)], ],
"Supplier": [
dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id',
fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1)
],
"Address": [
dict(fieldname='shopify_address_id', label='Shopify Address Id',
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)
],
"Item": [ "Item": [
dict(fieldname='shopify_variant_id', label='Shopify Variant Id', dict(fieldname='shopify_variant_id', label='Shopify Variant Id',
fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1), fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
@@ -119,16 +127,20 @@ def setup_custom_fields():
dict(fieldname='shopify_description', label='Shopify Description', dict(fieldname='shopify_description', label='Shopify Description',
fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1) fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1)
], ],
"Sales Order": [dict(fieldname='shopify_order_id', label='Shopify Order Id', "Sales Order": [
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)], dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
],
"Delivery Note":[ "Delivery Note":[
dict(fieldname='shopify_order_id', label='Shopify Order Id', dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1), fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id', dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1) fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
], ],
"Sales Invoice": [dict(fieldname='shopify_order_id', label='Shopify Order Id', "Sales Invoice": [
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)] dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
]
} }
create_custom_fields(custom_fields) create_custom_fields(custom_fields)

View File

@@ -219,7 +219,8 @@ frappe.ui.form.on("Expense Claim", {
frm.fields_dict["cost_center"].get_query = function() { frm.fields_dict["cost_center"].get_query = function() {
return { return {
filters: { filters: {
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
}; };
}; };
@@ -230,7 +231,9 @@ frappe.ui.form.on("Expense Claim", {
return { return {
filters: { filters: {
"report_type": "Balance Sheet", "report_type": "Balance Sheet",
"account_type": "Payable" "account_type": "Payable",
"company": frm.doc.company,
"is_group": 0
} }
}; };
}; };

View File

@@ -4,10 +4,17 @@ from frappe import _
def get_data(): def get_data():
return { return {
'fieldname': 'reference_name', 'fieldname': 'reference_name',
'internal_links': {
'Employee Advance': ['advances', 'employee_advance']
},
'transactions': [ 'transactions': [
{ {
'label': _('Payment'), 'label': _('Payment'),
'items': ['Payment Entry'] 'items': ['Payment Entry', 'Journal Entry']
},
{
'label': _('Reference'),
'items': ['Employee Advance']
}, },
] ]
} }

View File

@@ -79,6 +79,7 @@ def get_events(start, end, filters=None):
filters.append(['Holiday', 'holiday_date', '>', getdate(start)]) filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
if end: if end:
filters.append(['Holiday', 'holiday_date', '<', getdate(end)]) filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
return frappe.get_list('Holiday List', return frappe.get_list('Holiday List',
fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'], fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'],
filters = filters, filters = filters,

View File

@@ -3,8 +3,8 @@
frappe.views.calendar["Holiday List"] = { frappe.views.calendar["Holiday List"] = {
field_map: { field_map: {
"start": "from_date", "start": "holiday_date",
"end": "to_date", "end": "holiday_date",
"id": "name", "id": "name",
"title": "description", "title": "description",
"allDay": "allDay" "allDay": "allDay"

View File

@@ -119,7 +119,7 @@ class SalarySlip(TransactionBase):
if not self.salary_slip_based_on_timesheet: if not self.salary_slip_based_on_timesheet:
self.get_date_details() self.get_date_details()
self.validate_dates() self.validate_dates()
joining_date, relieving_date = frappe.db.get_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"])
self.get_leave_details(joining_date, relieving_date) self.get_leave_details(joining_date, relieving_date)
@@ -183,7 +183,7 @@ class SalarySlip(TransactionBase):
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None, for_preview=0): def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None, for_preview=0):
if not joining_date: if not joining_date:
joining_date, relieving_date = frappe.db.get_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"])
working_days = date_diff(self.end_date, self.start_date) + 1 working_days = date_diff(self.end_date, self.start_date) + 1
@@ -297,9 +297,6 @@ 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)
if self.net_pay < 0:
frappe.throw(_("Net Pay cannnot be negative"))
def calculate_component_amounts(self): def calculate_component_amounts(self):
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)
@@ -310,6 +307,7 @@ class SalarySlip(TransactionBase):
self.add_employee_benefits(payroll_period) self.add_employee_benefits(payroll_period)
self.add_additional_salary_components() self.add_additional_salary_components()
self.add_tax_components(payroll_period) self.add_tax_components(payroll_period)
self.set_component_amounts_based_on_payment_days()
def add_structure_components(self): def add_structure_components(self):
data = self.get_data_for_eval() data = self.get_data_for_eval()
@@ -401,14 +399,18 @@ class SalarySlip(TransactionBase):
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
struct_tax_components = [d.salary_component for d in self._salary_structure_doc.get("deductions") tax_components, other_deduction_components = [], []
if d.variable_based_on_taxable_salary == 1 and not d.formula and not d.amount] for d in self._salary_structure_doc.get("deductions"):
if d.variable_based_on_taxable_salary == 1 and not d.formula and not flt(d.amount):
tax_components.append(d.salary_component)
else:
other_deduction_components.append(d.salary_component)
if not struct_tax_components: if not tax_components:
struct_tax_components = [d.name for d in tax_components = [d.name for d in frappe.get_all("Salary Component", filters={"variable_based_on_taxable_salary": 1})
frappe.get_all("Salary Component", filters={"variable_based_on_taxable_salary": 1})] if d.name not in other_deduction_components]
for d in struct_tax_components: for d in tax_components:
tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period) tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period)
tax_row = self.get_salary_slip_row(d) tax_row = self.get_salary_slip_row(d)
self.update_component_row(tax_row, tax_amount, "deductions") self.update_component_row(tax_row, tax_amount, "deductions")
@@ -474,8 +476,7 @@ class SalarySlip(TransactionBase):
future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (math.ceil(remaining_sub_periods) - 1) future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (math.ceil(remaining_sub_periods) - 1)
# get taxable_earnings, addition_earnings for current actual payment days # get taxable_earnings, addition_earnings for current actual payment days
self.set_component_amounts_based_on_payment_days() current_taxable_earnings_for_payment_days = self.get_taxable_earnings(based_on_payment_days=1)
current_taxable_earnings_for_payment_days = self.get_taxable_earnings()
current_structured_taxable_earnings = current_taxable_earnings_for_payment_days.taxable_earnings current_structured_taxable_earnings = current_taxable_earnings_for_payment_days.taxable_earnings
current_additional_earnings = current_taxable_earnings_for_payment_days.additional_income current_additional_earnings = current_taxable_earnings_for_payment_days.additional_income
current_additional_earnings_with_full_tax = current_taxable_earnings_for_payment_days.additional_income_with_full_tax current_additional_earnings_with_full_tax = current_taxable_earnings_for_payment_days.additional_income_with_full_tax
@@ -498,7 +499,6 @@ class SalarySlip(TransactionBase):
# Structured tax amount # Structured tax amount
total_structured_tax_amount = self.calculate_tax_by_tax_slab(payroll_period, total_taxable_earnings_without_full_tax_addl_components) total_structured_tax_amount = self.calculate_tax_by_tax_slab(payroll_period, total_taxable_earnings_without_full_tax_addl_components)
current_structured_tax_amount = (total_structured_tax_amount - previous_total_paid_taxes) / remaining_sub_periods current_structured_tax_amount = (total_structured_tax_amount - previous_total_paid_taxes) / remaining_sub_periods
# Total taxable earnings with additional earnings with full tax # Total taxable earnings with additional earnings with full tax
@@ -557,25 +557,39 @@ class SalarySlip(TransactionBase):
return total_tax_paid return total_tax_paid
def get_taxable_earnings(self, only_flexi=0): def get_taxable_earnings(self, based_on_payment_days=0):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
if not relieving_date:
relieving_date = getdate(self.end_date)
if not joining_date:
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
taxable_earnings = 0 taxable_earnings = 0
additional_income = 0 additional_income = 0
additional_income_with_full_tax = 0 additional_income_with_full_tax = 0
flexi_benefits = 0 flexi_benefits = 0
for earning in self.earnings: for earning in self.earnings:
if based_on_payment_days:
amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date)
else:
amount, additional_amount = earning.amount, earning.additional_amount
if earning.is_tax_applicable: if earning.is_tax_applicable:
if flt(earning.additional_amount): if additional_amount:
taxable_earnings += (earning.amount - earning.additional_amount) taxable_earnings += (amount - additional_amount)
additional_income += earning.additional_amount additional_income += additional_amount
if earning.deduct_full_tax_on_selected_payroll_date: if earning.deduct_full_tax_on_selected_payroll_date:
additional_income_with_full_tax += earning.additional_amount additional_income_with_full_tax += additional_amount
continue continue
if earning.is_flexible_benefit: if earning.is_flexible_benefit:
flexi_benefits += earning.amount flexi_benefits += amount
else: else:
taxable_earnings += earning.amount taxable_earnings += amount
return frappe._dict({ return frappe._dict({
"taxable_earnings": taxable_earnings, "taxable_earnings": taxable_earnings,
@@ -584,6 +598,26 @@ class SalarySlip(TransactionBase):
"flexi_benefits": flexi_benefits "flexi_benefits": flexi_benefits
}) })
def get_amount_based_on_payment_days(self, row, joining_date, relieving_date):
amount, additional_amount = row.amount, row.additional_amount
if (self.salary_structure and
cint(row.depends_on_payment_days) and cint(self.total_working_days) and
(not self.salary_slip_based_on_timesheet or
getdate(self.start_date) < joining_date or
getdate(self.end_date) > relieving_date
)):
additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days)
/ cint(self.total_working_days)), row.precision("additional_amount"))
amount = flt((flt(row.default_amount) * flt(self.payment_days)
/ cint(self.total_working_days)), row.precision("amount")) + additional_amount
elif not self.payment_days and not self.salary_slip_based_on_timesheet and cint(row.depends_on_payment_days):
amount, additional_amount = 0, 0
elif not row.amount:
amount = row.default_amount + row.additional_amount
return amount, additional_amount
def calculate_unclaimed_taxable_benefits(self, payroll_period): def calculate_unclaimed_taxable_benefits(self, payroll_period):
# get total sum of benefits paid # get total sum of benefits paid
total_benefits_paid = flt(frappe.db.sql(""" total_benefits_paid = flt(frappe.db.sql("""
@@ -685,7 +719,7 @@ class SalarySlip(TransactionBase):
return total return total
def set_component_amounts_based_on_payment_days(self): def set_component_amounts_based_on_payment_days(self):
joining_date, relieving_date = frappe.db.get_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"])
if not relieving_date: if not relieving_date:
@@ -696,22 +730,7 @@ class SalarySlip(TransactionBase):
for component_type in ("earnings", "deductions"): for component_type in ("earnings", "deductions"):
for d in self.get(component_type): for d in self.get(component_type):
if (self.salary_structure and d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
cint(d.depends_on_payment_days) and cint(self.total_working_days) and
(not self.salary_slip_based_on_timesheet or
getdate(self.start_date) < joining_date or
getdate(self.end_date) > relieving_date
)):
d.amount = flt(
(flt(d.default_amount + d.additional_amount) * flt(self.payment_days)
/ cint(self.total_working_days))
, d.precision("amount"))
elif not self.payment_days and not self.salary_slip_based_on_timesheet and cint(d.depends_on_payment_days):
d.amount = 0
elif not d.amount:
d.amount = d.default_amount + d.additional_amount
def set_loan_repayment(self): def set_loan_repayment(self):
self.set('loans', []) self.set('loans', [])

View File

@@ -442,7 +442,8 @@ def make_deduction_salary_component(setup=False, test_tax=False):
"formula": 'base*.1', "formula": 'base*.1',
"type": "Deduction", "type": "Deduction",
"amount_based_on_formula": 1, "amount_based_on_formula": 1,
"depends_on_payment_days": 0 "depends_on_payment_days": 0,
"variable_based_on_taxable_salary": 1
} }
] ]
if not test_tax: if not test_tax:

View File

@@ -18,21 +18,22 @@ class JobCard(Document):
self.total_completed_qty = 0.0 self.total_completed_qty = 0.0
self.total_time_in_mins = 0.0 self.total_time_in_mins = 0.0
for d in self.get('time_logs'): if self.get('time_logs'):
if get_datetime(d.from_time) > get_datetime(d.to_time): for d in self.get('time_logs'):
frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx)) if get_datetime(d.from_time) > get_datetime(d.to_time):
frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx))
data = self.get_overlap_for(d) data = self.get_overlap_for(d)
if data: if data:
frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}") frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
.format(d.idx, self.name, data.name)) .format(d.idx, self.name, data.name))
if d.from_time and d.to_time: if d.from_time and d.to_time:
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60 d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
self.total_time_in_mins += d.time_in_mins self.total_time_in_mins += d.time_in_mins
if d.completed_qty: if d.completed_qty:
self.total_completed_qty += d.completed_qty self.total_completed_qty += d.completed_qty
def get_overlap_for(self, args): def get_overlap_for(self, args):
existing = frappe.db.sql("""select jc.name as name from existing = frappe.db.sql("""select jc.name as name from
@@ -112,8 +113,10 @@ class JobCard(Document):
for_quantity += doc.total_completed_qty for_quantity += doc.total_completed_qty
time_in_mins += doc.total_time_in_mins time_in_mins += doc.total_time_in_mins
for time_log in doc.time_logs: for time_log in doc.time_logs:
from_time_list.append(time_log.from_time) if time_log.from_time:
to_time_list.append(time_log.to_time) from_time_list.append(time_log.from_time)
if time_log.to_time:
to_time_list.append(time_log.to_time)
if for_quantity: if for_quantity:
wo = frappe.get_doc('Work Order', self.work_order) wo = frappe.get_doc('Work Order', self.work_order)

View File

@@ -599,4 +599,5 @@ erpnext.patches.v11_1.rename_depends_on_lwp
erpnext.patches.v11_1.set_missing_title_for_quotation erpnext.patches.v11_1.set_missing_title_for_quotation
execute:frappe.delete_doc("Report", "Inactive Items") execute:frappe.delete_doc("Report", "Inactive Items")
erpnext.patches.v11_1.delete_scheduling_tool erpnext.patches.v11_1.delete_scheduling_tool
erpnext.patches.v11_1.update_bank_transaction_status erpnext.patches.v11_1.update_bank_transaction_status
erpnext.patches.v11_1.renamed_delayed_item_report

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2015, 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 report in ["Delayed Order Item Summary", "Delayed Order Summary"]:
if frappe.db.exists("Report", report):
frappe.delete_doc("Report", report)

View File

@@ -80,8 +80,7 @@ def prepare_invoice(invoice, progressive_number):
invoice.stamp_duty = stamp_duty_charge_row.tax_amount invoice.stamp_duty = stamp_duty_charge_row.tax_amount
for item in invoice.e_invoice_items: for item in invoice.e_invoice_items:
if (item.tax_rate == 0.0 and item.tax_amount == 0.0 if item.tax_rate == 0.0 and item.tax_amount == 0.0 and tax_data.get("0.0"):
and item.charge_type != 'Actual' and tax_data.get("0.0")):
item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"] item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"]
customer_po_data = {} customer_po_data = {}

View File

@@ -80,7 +80,10 @@ def get_gl_entries(filters):
jnl.cheque_no as JnlRef, jnl.posting_date as JnlPostDate, jnl.title as JnlTitle, jnl.cheque_no as JnlRef, jnl.posting_date as JnlPostDate, jnl.title as JnlTitle,
pay.name as PayName, pay.posting_date as PayPostDate, pay.title as PayTitle, pay.name as PayName, pay.posting_date as PayPostDate, pay.title as PayTitle,
cus.customer_name, cus.name as cusName, cus.customer_name, cus.name as cusName,
sup.supplier_name, sup.name as supName sup.supplier_name, sup.name as supName,
emp.employee_name, emp.name as empName,
stu.title as student_name, stu.name as stuName,
member_name, mem.name as memName
from `tabGL Entry` gl from `tabGL Entry` gl
left join `tabSales Invoice` inv on gl.voucher_no = inv.name left join `tabSales Invoice` inv on gl.voucher_no = inv.name
@@ -89,6 +92,9 @@ def get_gl_entries(filters):
left join `tabPayment Entry` pay on gl.voucher_no = pay.name left join `tabPayment Entry` pay on gl.voucher_no = pay.name
left join `tabCustomer` cus on gl.party = cus.name left join `tabCustomer` cus on gl.party = cus.name
left join `tabSupplier` sup on gl.party = sup.name left join `tabSupplier` sup on gl.party = sup.name
left join `tabEmployee` emp on gl.party = emp.name
left join `tabStudent` stu on gl.party = stu.name
left join `tabMember` mem on gl.party = mem.name
where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s
{group_by_condition} {group_by_condition}
order by GlPostDate, voucher_no"""\ order by GlPostDate, voucher_no"""\
@@ -128,6 +134,18 @@ def get_result_as_list(data, filters):
CompAuxNum = d.get("supName") CompAuxNum = d.get("supName")
CompAuxLib = d.get("supplier_name") CompAuxLib = d.get("supplier_name")
elif d.get("party_type") == "Employee":
CompAuxNum = d.get("empName")
CompAuxLib = d.get("employee_name")
elif d.get("party_type") == "Student":
CompAuxNum = d.get("stuName")
CompAuxLib = d.get("student_name")
elif d.get("party_type") == "Member":
CompAuxNum = d.get("memName")
CompAuxLib = d.get("member_name")
else: else:
CompAuxNum = "" CompAuxNum = ""
CompAuxLib = "" CompAuxLib = ""

View File

@@ -149,7 +149,8 @@ class Gstr1Report(object):
if self.filters.get("type_of_business") == "B2B": if self.filters.get("type_of_business") == "B2B":
conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1 conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1
and customer in ('{0}')""".format("', '".join([frappe.db.escape(c.name) for c in customers])) and customer in ('{0}') and (customer_gstin IS NOT NULL OR customer_gstin NOT IN ('', 'NA'))""".\
format("', '".join([frappe.db.escape(c.name) for c in customers]))
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, add_additional_cost from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, add_additional_cost
from erpnext.stock.utils import get_bin from erpnext.stock.utils import get_bin
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
import json import json
@@ -59,6 +60,7 @@ class StockEntry(StockController):
self.validate_batch() self.validate_batch()
self.validate_inspection() self.validate_inspection()
self.validate_fg_completed_qty() self.validate_fg_completed_qty()
self.validate_difference_account()
self.set_job_card_data() self.set_job_card_data()
if not self.from_bom: if not self.from_bom:
@@ -215,7 +217,18 @@ class StockEntry(StockController):
production_item = frappe.get_value('Work Order', self.work_order, 'production_item') production_item = frappe.get_value('Work Order', self.work_order, 'production_item')
for item in self.items: for item in self.items:
if item.item_code == production_item and item.qty != self.fg_completed_qty: if item.item_code == production_item and item.qty != self.fg_completed_qty:
frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different").format(item.qty, self.fg_completed_qty)) frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different")
.format(item.qty, self.fg_completed_qty))
def validate_difference_account(self):
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
return
for d in self.get("items"):
if not d.expense_account:
frappe.throw(_("Please enter Difference Account"))
elif self.is_opening == "Yes" and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"), OpeningEntryAccountError)
def validate_warehouse(self): def validate_warehouse(self):
"""perform various (sometimes conditional) validations on warehouse""" """perform various (sometimes conditional) validations on warehouse"""

View File

@@ -89,10 +89,11 @@ def make_stock_entry(**args):
s.purchase_receipt_no = args.purchase_receipt_no s.purchase_receipt_no = args.purchase_receipt_no
s.delivery_note_no = args.delivery_note_no s.delivery_note_no = args.delivery_note_no
s.sales_invoice_no = args.sales_invoice_no s.sales_invoice_no = args.sales_invoice_no
s.is_opening = args.is_opening or "No"
if not args.cost_center: if not args.cost_center:
args.cost_center = frappe.get_value('Company', s.company, 'cost_center') args.cost_center = frappe.get_value('Company', s.company, 'cost_center')
if not args.expense_account: if not args.expense_account and s.is_opening == "No":
args.expense_account = frappe.get_value('Company', s.company, 'stock_adjustment_account') args.expense_account = frappe.get_value('Company', s.company, 'stock_adjustment_account')
# We can find out the serial number using the batch source document # We can find out the serial number using the batch source document

View File

@@ -6,8 +6,7 @@ import frappe, unittest
import frappe.defaults import frappe.defaults
from frappe.utils import flt, nowdate, nowtime from frappe.utils import flt, nowdate, nowtime
from erpnext.stock.doctype.serial_no.serial_no import * from erpnext.stock.doctype.serial_no.serial_no import *
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ from erpnext import set_perpetual_inventory
import set_perpetual_inventory
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
from frappe.permissions import add_user_permission, remove_user_permission from frappe.permissions import add_user_permission, remove_user_permission
@@ -16,6 +15,7 @@ from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
from six import iteritems from six import iteritems
@@ -720,6 +720,22 @@ class TestStockEntry(unittest.TestCase):
for d in stock_entry.get('items'): for d in stock_entry.get('items'):
self.assertEqual(item_quantity.get(d.item_code), d.qty) self.assertEqual(item_quantity.get(d.item_code), d.qty)
def test_gle_for_opening_stock_entry(self):
set_perpetual_inventory(1)
mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
qty=50, basic_rate=100, expense_account="Stock Adjustment - _TC", is_opening="Yes", do_not_save=True)
self.assertRaises(OpeningEntryAccountError, mr.save)
mr.items[0].expense_account = "Temporary Opening - _TC"
mr.save()
mr.submit()
is_opening = frappe.db.get_value("GL Entry",
filters={"voucher_type": "Stock Entry", "voucher_no": mr.name}, fieldname="is_opening")
self.assertEqual(is_opening, "Yes")
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
se = frappe.copy_doc(test_records[0]) se = frappe.copy_doc(test_records[0])
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series" se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"

View File

@@ -238,8 +238,8 @@ class StockReconciliation(StockController):
return return
if not self.expense_account: if not self.expense_account:
msgprint(_("Please enter Expense Account"), raise_exception=1) frappe.throw(_("Please enter Expense Account"))
elif not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""): elif self.purpose == "Opening Stock" or not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""):
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss": if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError) frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)
@@ -276,7 +276,8 @@ def get_items(warehouse, posting_date, posting_time, company):
items = frappe.db.sql(""" items = frappe.db.sql("""
select i.name, i.item_name, bin.warehouse select i.name, i.item_name, bin.warehouse
from tabBin bin, tabItem i from tabBin bin, tabItem i
where i.name=bin.item_code and i.disabled=0 where i.name=bin.item_code and i.disabled=0 and i.is_stock_item = 1
and i.has_variants = 0 and i.has_serial_no = 0 and i.has_batch_no = 0
and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse) and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse)
""", (lft, rgt)) """, (lft, rgt))

View File

@@ -140,7 +140,7 @@ class Warehouse(NestedSet):
@frappe.whitelist() @frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False): def get_children(doctype, parent=None, company=None, is_root=False):
from erpnext.stock.utils import get_stock_value_on from erpnext.stock.utils import get_stock_value_from_bin
if is_root: if is_root:
parent = "" parent = ""
@@ -156,7 +156,7 @@ def get_children(doctype, parent=None, company=None, is_root=False):
# return warehouses # return warehouses
for wh in warehouses: for wh in warehouses:
wh["balance"] = get_stock_value_on(warehouse=wh.value, posting_date=nowdate()) wh["balance"] = get_stock_value_from_bin(warehouse=wh.value)
if company: if company:
wh["company_currency"] = frappe.db.get_value('Company', company, 'default_currency') wh["company_currency"] = frappe.db.get_value('Company', company, 'default_currency')
return warehouses return warehouses

View File

@@ -588,7 +588,7 @@ def get_party_item_code(args, item_doc, out):
if args.transaction_type=="selling" and args.customer: if args.transaction_type=="selling" and args.customer:
out.customer_item_code = None out.customer_item_code = None
if args.quotation_to != 'Customer': if args.quotation_to and args.quotation_to != 'Customer':
return return
customer_item_code = item_doc.get("customer_items", {"customer_name": args.customer}) customer_item_code = item_doc.get("customer_items", {"customer_name": args.customer})

View File

@@ -2,7 +2,7 @@
// For license information, please see license.txt // For license information, please see license.txt
/* eslint-disable */ /* eslint-disable */
frappe.query_reports["Delayed Order Summary"] = { frappe.query_reports["Delayed Item Report"] = {
"filters": [ "filters": [
{ {
fieldname: "company", fieldname: "company",
@@ -55,7 +55,7 @@ frappe.query_reports["Delayed Order Summary"] = {
label: __("Based On"), label: __("Based On"),
fieldtype: "Select", fieldtype: "Select",
options: ["Delivery Note", "Sales Invoice"], options: ["Delivery Note", "Sales Invoice"],
default: "Sales Invoice", default: "Delivery Note",
reqd: 1 reqd: 1
}, },
] ]

View File

@@ -1,6 +1,6 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"creation": "2019-05-12 12:38:44.907187", "creation": "2019-05-27 19:11:50.605376",
"disable_prepared_report": 0, "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
@@ -8,14 +8,14 @@
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"letter_head": "Gadgets International", "letter_head": "Gadgets International",
"modified": "2019-05-12 12:39:16.378341", "modified": "2019-05-27 19:11:50.605376",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delayed Order Summary", "name": "Delayed Item Report",
"owner": "Administrator", "owner": "Administrator",
"prepared_report": 0, "prepared_report": 0,
"ref_doctype": "Delivery Note", "ref_doctype": "Delivery Note",
"report_name": "Delayed Order Summary", "report_name": "Delayed Item Report",
"report_type": "Script Report", "report_type": "Script Report",
"roles": [ "roles": [
{ {

View File

@@ -7,11 +7,11 @@ from frappe import _
from frappe.utils import date_diff from frappe.utils import date_diff
def execute(filters=None, consolidated = False): def execute(filters=None, consolidated = False):
data, columns = DelayedOrderItemSummary(filters).run() data, columns = DelayedItemReport(filters).run()
return data, columns return data, columns
class DelayedOrderItemSummary(object): class DelayedItemReport(object):
def __init__(self, filters=None): def __init__(self, filters=None):
self.filters = frappe._dict(filters or {}) self.filters = frappe._dict(filters or {})
@@ -83,7 +83,7 @@ class DelayedOrderItemSummary(object):
key = row.sales_order if consolidated else (row.sales_order, row.so_detail) key = row.sales_order if consolidated else (row.sales_order, row.so_detail)
row.update({ row.update({
'delivery_date': so_data.get(key), 'delivery_date': so_data.get(key),
'delay_days': date_diff(row.posting_date, so_data.get(key)) 'delayed_days': date_diff(row.posting_date, so_data.get(key))
}) })
return self.transactions return self.transactions
@@ -148,8 +148,8 @@ class DelayedOrderItemSummary(object):
"width": 100 "width": 100
}, },
{ {
"label": _("Delay Days"), "label": _("Delayed Days"),
"fieldname": "delay_days", "fieldname": "delayed_days",
"fieldtype": "Int", "fieldtype": "Int",
"width": 100 "width": 100
}, },

View File

@@ -2,7 +2,7 @@
// For license information, please see license.txt // For license information, please see license.txt
/* eslint-disable */ /* eslint-disable */
frappe.query_reports["Delayed Order Item Summary"] = { frappe.query_reports["Delayed Order Report"] = {
"filters": [ "filters": [
{ {
fieldname: "company", fieldname: "company",
@@ -55,7 +55,7 @@ frappe.query_reports["Delayed Order Item Summary"] = {
label: __("Based On"), label: __("Based On"),
fieldtype: "Select", fieldtype: "Select",
options: ["Delivery Note", "Sales Invoice"], options: ["Delivery Note", "Sales Invoice"],
default: "Sales Invoice", default: "Delivery Note",
reqd: 1 reqd: 1
}, },
] ]

View File

@@ -1,6 +1,6 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"creation": "2019-05-12 13:05:39.162734", "creation": "2019-05-27 19:12:24.719610",
"disable_prepared_report": 0, "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
@@ -8,14 +8,14 @@
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"letter_head": "Gadgets International", "letter_head": "Gadgets International",
"modified": "2019-05-12 13:05:39.162734", "modified": "2019-05-27 19:12:24.719610",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delayed Order Item Summary", "name": "Delayed Order Report",
"owner": "Administrator", "owner": "Administrator",
"prepared_report": 0, "prepared_report": 0,
"ref_doctype": "Delivery Note", "ref_doctype": "Delivery Note",
"report_name": "Delayed Order Item Summary", "report_name": "Delayed Order Report",
"report_type": "Script Report", "report_type": "Script Report",
"roles": [ "roles": [
{ {

View File

@@ -3,21 +3,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from frappe import _ from frappe import _
from erpnext.stock.report.delayed_order_item_summary.delayed_order_item_summary import DelayedOrderItemSummary from erpnext.stock.report.delayed_item_report.delayed_item_report import DelayedItemReport
def execute(filters=None): def execute(filters=None):
columns, data = [], [] columns, data = [], []
columns, data = DelayedOrderSummary(filters).run() columns, data = DelayedOrderReport(filters).run()
return columns, data return columns, data
class DelayedOrderSummary(DelayedOrderItemSummary): class DelayedOrderReport(DelayedItemReport):
def run(self): def run(self):
return self.get_columns(), self.get_data(consolidated=True) or [] return self.get_columns(), self.get_data(consolidated=True) or []
def get_data(self, consolidated=False): def get_data(self, consolidated=False):
data = super(DelayedOrderSummary, self).get_data(consolidated) or [] data = super(DelayedOrderReport, self).get_data(consolidated) or []
so_list = [] so_list = []
result = [] result = []
@@ -63,8 +63,8 @@ class DelayedOrderSummary(DelayedOrderItemSummary):
"width": 100 "width": 100
}, },
{ {
"label": _("Delay Days"), "label": _("Delayed Days"),
"fieldname": "delay_days", "fieldname": "delayed_days",
"fieldtype": "Int", "fieldtype": "Int",
"width": 100 "width": 100
}, },