solved conflicts
This commit is contained in:
commit
9e9f22b957
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2013-05-21 16:16:39",
|
"creation": "2013-05-21 16:16:39",
|
||||||
@ -417,6 +418,7 @@
|
|||||||
"fieldname": "contact_email",
|
"fieldname": "contact_email",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
|
"options": "Email",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1287,7 +1289,8 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-09-17 22:31:42.666601",
|
"links": [],
|
||||||
|
"modified": "2019-12-24 12:51:58.613538",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -90,6 +90,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.validate_account_for_change_amount()
|
self.validate_account_for_change_amount()
|
||||||
self.validate_fixed_asset()
|
self.validate_fixed_asset()
|
||||||
self.set_income_account_for_fixed_assets()
|
self.set_income_account_for_fixed_assets()
|
||||||
|
self.validate_item_cost_centers()
|
||||||
validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference)
|
validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference)
|
||||||
|
|
||||||
if cint(self.is_pos):
|
if cint(self.is_pos):
|
||||||
@ -147,6 +148,12 @@ class SalesInvoice(SellingController):
|
|||||||
elif asset.status in ("Scrapped", "Cancelled", "Sold"):
|
elif asset.status in ("Scrapped", "Cancelled", "Sold"):
|
||||||
frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
|
frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
|
||||||
|
|
||||||
|
def validate_item_cost_centers(self):
|
||||||
|
for item in self.items:
|
||||||
|
cost_center_company = frappe.get_cached_value("Cost Center", item.cost_center, "company")
|
||||||
|
if cost_center_company != self.company:
|
||||||
|
frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)))
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
set_account_for_mode_of_payment(self)
|
set_account_for_mode_of_payment(self)
|
||||||
|
|
||||||
|
@ -18,6 +18,10 @@ def reconcile(bank_transaction, payment_doctype, payment_name):
|
|||||||
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
|
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
|
||||||
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
|
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
|
||||||
|
|
||||||
|
if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
|
||||||
|
frappe.throw(_("The unallocated amount of Payment Entry {0} \
|
||||||
|
is greater than the Bank Transaction's unallocated amount").format(payment_name))
|
||||||
|
|
||||||
if transaction.unallocated_amount == 0:
|
if transaction.unallocated_amount == 0:
|
||||||
frappe.throw(_("This bank transaction is already fully reconciled"))
|
frappe.throw(_("This bank transaction is already fully reconciled"))
|
||||||
|
|
||||||
|
@ -4,30 +4,30 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import formatdate, getdate, flt, add_days
|
from frappe.utils import formatdate, flt, add_days
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
filters.day_before_from_date = add_days(filters.from_date, -1)
|
filters.day_before_from_date = add_days(filters.from_date, -1)
|
||||||
columns, data = get_columns(filters), get_data(filters)
|
columns, data = get_columns(filters), get_data(filters)
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
asset_categories = get_asset_categories(filters)
|
asset_categories = get_asset_categories(filters)
|
||||||
assets = get_assets(filters)
|
assets = get_assets(filters)
|
||||||
asset_costs = get_asset_costs(assets, filters)
|
|
||||||
asset_depreciations = get_accumulated_depreciations(assets, filters)
|
|
||||||
|
|
||||||
for asset_category in asset_categories:
|
for asset_category in asset_categories:
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
row.asset_category = asset_category
|
# row.asset_category = asset_category
|
||||||
row.update(asset_costs.get(asset_category))
|
row.update(asset_category)
|
||||||
|
|
||||||
row.cost_as_on_to_date = (flt(row.cost_as_on_from_date) + flt(row.cost_of_new_purchase)
|
row.cost_as_on_to_date = (flt(row.cost_as_on_from_date) + flt(row.cost_of_new_purchase) -
|
||||||
- flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset))
|
flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset))
|
||||||
|
|
||||||
row.update(asset_depreciations.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))
|
||||||
|
|
||||||
@ -41,88 +41,103 @@ def get_data(filters):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_asset_categories(filters):
|
def get_asset_categories(filters):
|
||||||
return frappe.db.sql_list("""
|
return frappe.db.sql("""
|
||||||
select distinct asset_category from `tabAsset`
|
SELECT asset_category,
|
||||||
where docstatus=1 and company=%s and purchase_date <= %s
|
ifnull(sum(case when purchase_date < %(from_date)s then
|
||||||
""", (filters.company, filters.to_date))
|
case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_as_on_from_date,
|
||||||
|
ifnull(sum(case when purchase_date >= %(from_date)s then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_new_purchase,
|
||||||
|
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||||
|
and disposal_date >= %(from_date)s
|
||||||
|
and disposal_date <= %(to_date)s then
|
||||||
|
case when status = "Sold" then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_sold_asset,
|
||||||
|
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||||
|
and disposal_date >= %(from_date)s
|
||||||
|
and disposal_date <= %(to_date)s then
|
||||||
|
case when status = "Scrapped" then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_scrapped_asset
|
||||||
|
from `tabAsset`
|
||||||
|
where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s
|
||||||
|
group by asset_category
|
||||||
|
""", {"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 name, asset_category, purchase_date, gross_purchase_amount, disposal_date, status
|
SELECT results.asset_category,
|
||||||
from `tabAsset`
|
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
|
||||||
where docstatus=1 and company=%s and purchase_date <= %s""",
|
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
||||||
(filters.company, filters.to_date), as_dict=1)
|
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
||||||
|
from (SELECT a.asset_category,
|
||||||
|
ifnull(sum(a.opening_accumulated_depreciation +
|
||||||
|
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
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
||||||
|
and a.disposal_date <= %(to_date)s and ds.schedule_date <= a.disposal_date then
|
||||||
|
ds.depreciation_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
|
||||||
def get_asset_costs(assets, filters):
|
ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s
|
||||||
asset_costs = frappe._dict()
|
and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then
|
||||||
for d in assets:
|
ds.depreciation_amount
|
||||||
asset_costs.setdefault(d.asset_category, frappe._dict({
|
else
|
||||||
"cost_as_on_from_date": 0,
|
0
|
||||||
"cost_of_new_purchase": 0,
|
end), 0) as depreciation_amount_during_the_period
|
||||||
"cost_of_sold_asset": 0,
|
from `tabAsset` a, `tabDepreciation Schedule` ds
|
||||||
"cost_of_scrapped_asset": 0
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent
|
||||||
}))
|
group by a.asset_category
|
||||||
|
union
|
||||||
|
SELECT a.asset_category,
|
||||||
|
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
|
||||||
|
0
|
||||||
|
else
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
0 as depreciation_amount_during_the_period
|
||||||
|
from `tabAsset` a
|
||||||
|
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 results.asset_category
|
||||||
|
""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1)
|
||||||
|
|
||||||
costs = asset_costs[d.asset_category]
|
|
||||||
|
|
||||||
if getdate(d.purchase_date) < getdate(filters.from_date):
|
|
||||||
if not d.disposal_date or getdate(d.disposal_date) >= getdate(filters.from_date):
|
|
||||||
costs.cost_as_on_from_date += flt(d.gross_purchase_amount)
|
|
||||||
else:
|
|
||||||
costs.cost_of_new_purchase += flt(d.gross_purchase_amount)
|
|
||||||
|
|
||||||
if d.disposal_date and getdate(d.disposal_date) >= getdate(filters.from_date) \
|
|
||||||
and getdate(d.disposal_date) <= getdate(filters.to_date):
|
|
||||||
if d.status == "Sold":
|
|
||||||
costs.cost_of_sold_asset += flt(d.gross_purchase_amount)
|
|
||||||
elif d.status == "Scrapped":
|
|
||||||
costs.cost_of_scrapped_asset += flt(d.gross_purchase_amount)
|
|
||||||
|
|
||||||
return asset_costs
|
|
||||||
|
|
||||||
def get_accumulated_depreciations(assets, filters):
|
|
||||||
asset_depreciations = frappe._dict()
|
|
||||||
for d in assets:
|
|
||||||
asset = frappe.get_doc("Asset", d.name)
|
|
||||||
|
|
||||||
if d.asset_category in asset_depreciations:
|
|
||||||
asset_depreciations[d.asset_category]['accumulated_depreciation_as_on_from_date'] += asset.opening_accumulated_depreciation
|
|
||||||
else:
|
|
||||||
asset_depreciations.setdefault(d.asset_category, frappe._dict({
|
|
||||||
"accumulated_depreciation_as_on_from_date": asset.opening_accumulated_depreciation,
|
|
||||||
"depreciation_amount_during_the_period": 0,
|
|
||||||
"depreciation_eliminated_during_the_period": 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
depr = asset_depreciations[d.asset_category]
|
|
||||||
|
|
||||||
if not asset.schedules: # if no schedule,
|
|
||||||
if asset.disposal_date:
|
|
||||||
# and disposal is NOT within the period, then opening accumulated depreciation not included
|
|
||||||
if getdate(asset.disposal_date) < getdate(filters.from_date) or getdate(asset.disposal_date) > getdate(filters.to_date):
|
|
||||||
asset_depreciations[d.asset_category]['accumulated_depreciation_as_on_from_date'] = 0
|
|
||||||
|
|
||||||
# if no schedule, and disposal is within period, accumulated dep is the amount eliminated
|
|
||||||
if getdate(asset.disposal_date) >= getdate(filters.from_date) and getdate(asset.disposal_date) <= getdate(filters.to_date):
|
|
||||||
depr.depreciation_eliminated_during_the_period += asset.opening_accumulated_depreciation
|
|
||||||
|
|
||||||
for schedule in asset.get("schedules"):
|
|
||||||
if getdate(schedule.schedule_date) < getdate(filters.from_date):
|
|
||||||
if not asset.disposal_date or getdate(asset.disposal_date) >= getdate(filters.from_date):
|
|
||||||
depr.accumulated_depreciation_as_on_from_date += flt(schedule.depreciation_amount)
|
|
||||||
elif getdate(schedule.schedule_date) <= getdate(filters.to_date):
|
|
||||||
if not asset.disposal_date:
|
|
||||||
depr.depreciation_amount_during_the_period += flt(schedule.depreciation_amount)
|
|
||||||
else:
|
|
||||||
if getdate(schedule.schedule_date) <= getdate(asset.disposal_date):
|
|
||||||
depr.depreciation_amount_during_the_period += flt(schedule.depreciation_amount)
|
|
||||||
|
|
||||||
if asset.disposal_date and getdate(asset.disposal_date) >= getdate(filters.from_date) and getdate(asset.disposal_date) <= getdate(filters.to_date):
|
|
||||||
if getdate(schedule.schedule_date) <= getdate(asset.disposal_date):
|
|
||||||
depr.depreciation_eliminated_during_the_period += flt(schedule.depreciation_amount)
|
|
||||||
|
|
||||||
return asset_depreciations
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
return [
|
return [
|
||||||
|
@ -46,13 +46,24 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
fieldtype: "Select",
|
fieldtype: "Select",
|
||||||
options: ["Cost Center", "Project"],
|
options: ["Cost Center", "Project"],
|
||||||
default: "Cost Center",
|
default: "Cost Center",
|
||||||
reqd: 1
|
reqd: 1,
|
||||||
|
on_change: function() {
|
||||||
|
frappe.query_report.set_filter_value("budget_against_filter", []);
|
||||||
|
frappe.query_report.refresh();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "cost_center",
|
fieldname:"budget_against_filter",
|
||||||
label: __("Cost Center"),
|
label: __('Dimension Filter'),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
options: "Cost Center"
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let budget_against = frappe.query_report.get_filter_value('budget_against');
|
||||||
|
if (!budget_against) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(budget_against, txt);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname:"show_cumulative",
|
fieldname:"show_cumulative",
|
||||||
|
@ -12,22 +12,22 @@ from six import iteritems
|
|||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
if not filters: filters = {}
|
if not filters: filters = {}
|
||||||
validate_filters(filters)
|
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
if filters.get("cost_center"):
|
if filters.get("budget_against_filter"):
|
||||||
cost_centers = [filters.get("cost_center")]
|
dimensions = filters.get("budget_against_filter")
|
||||||
else:
|
else:
|
||||||
cost_centers = get_cost_centers(filters)
|
dimensions = get_cost_centers(filters)
|
||||||
|
|
||||||
period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"])
|
period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"])
|
||||||
cam_map = get_cost_center_account_month_map(filters)
|
cam_map = get_dimension_account_month_map(filters)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for cost_center in cost_centers:
|
for dimension in dimensions:
|
||||||
cost_center_items = cam_map.get(cost_center)
|
dimension_items = cam_map.get(dimension)
|
||||||
if cost_center_items:
|
if dimension_items:
|
||||||
for account, monthwise_data in iteritems(cost_center_items):
|
for account, monthwise_data in iteritems(dimension_items):
|
||||||
row = [cost_center, account]
|
row = [dimension, account]
|
||||||
totals = [0, 0, 0]
|
totals = [0, 0, 0]
|
||||||
for year in get_fiscal_years(filters):
|
for year in get_fiscal_years(filters):
|
||||||
last_total = 0
|
last_total = 0
|
||||||
@ -55,10 +55,6 @@ def execute(filters=None):
|
|||||||
|
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
def validate_filters(filters):
|
|
||||||
if filters.get("budget_against") != "Cost Center" and filters.get("cost_center"):
|
|
||||||
frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center"))
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"]
|
columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"]
|
||||||
|
|
||||||
@ -98,11 +94,12 @@ def get_cost_centers(filters):
|
|||||||
else:
|
else:
|
||||||
return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec
|
return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec
|
||||||
|
|
||||||
#Get cost center & target details
|
#Get dimension & target details
|
||||||
def get_cost_center_target_details(filters):
|
def get_dimension_target_details(filters):
|
||||||
cond = ""
|
cond = ""
|
||||||
if filters.get("cost_center"):
|
if filters.get("budget_against_filter"):
|
||||||
cond += " and b.cost_center=%s" % frappe.db.escape(filters.get("cost_center"))
|
cond += " and b.{budget_against} in (%s)".format(budget_against = \
|
||||||
|
frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter')))
|
||||||
|
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
|
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
|
||||||
@ -110,8 +107,8 @@ def get_cost_center_target_details(filters):
|
|||||||
where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s
|
where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s
|
||||||
and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year
|
and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year
|
||||||
""".format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond),
|
""".format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond),
|
||||||
(filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True)
|
tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')),
|
||||||
|
as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
#Get target distribution details of accounts of cost center
|
#Get target distribution details of accounts of cost center
|
||||||
@ -153,14 +150,14 @@ def get_actual_details(name, filters):
|
|||||||
|
|
||||||
return cc_actual_details
|
return cc_actual_details
|
||||||
|
|
||||||
def get_cost_center_account_month_map(filters):
|
def get_dimension_account_month_map(filters):
|
||||||
import datetime
|
import datetime
|
||||||
cost_center_target_details = get_cost_center_target_details(filters)
|
dimension_target_details = get_dimension_target_details(filters)
|
||||||
tdd = get_target_distribution_details(filters)
|
tdd = get_target_distribution_details(filters)
|
||||||
|
|
||||||
cam_map = {}
|
cam_map = {}
|
||||||
|
|
||||||
for ccd in cost_center_target_details:
|
for ccd in dimension_target_details:
|
||||||
actual_details = get_actual_details(ccd.budget_against, filters)
|
actual_details = get_actual_details(ccd.budget_against, filters)
|
||||||
|
|
||||||
for month_id in range(1, 13):
|
for month_id in range(1, 13):
|
||||||
|
@ -342,6 +342,7 @@
|
|||||||
"fieldname": "contact_email",
|
"fieldname": "contact_email",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
|
"options": "Email",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1054,7 +1055,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-18 13:13:22.852412",
|
"modified": "2019-12-24 12:44:13.137194",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -118,6 +118,73 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertEqual(po.get("items")[0].amount, 1400)
|
self.assertEqual(po.get("items")[0].amount, 1400)
|
||||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
|
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_new_item_in_update_child_qty_rate(self):
|
||||||
|
po = create_purchase_order(do_not_save=1)
|
||||||
|
po.items[0].qty = 4
|
||||||
|
po.save()
|
||||||
|
po.submit()
|
||||||
|
pr = make_pr_against_po(po.name, 2)
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
first_item_of_po = po.get("items")[0]
|
||||||
|
|
||||||
|
trans_item = json.dumps([
|
||||||
|
{
|
||||||
|
'item_code': first_item_of_po.item_code,
|
||||||
|
'rate': first_item_of_po.rate,
|
||||||
|
'qty': first_item_of_po.qty,
|
||||||
|
'docname': first_item_of_po.name
|
||||||
|
},
|
||||||
|
{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7}
|
||||||
|
])
|
||||||
|
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEquals(len(po.get('items')), 2)
|
||||||
|
self.assertEqual(po.status, 'To Receive and Bill')
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_item_in_update_child_qty_rate(self):
|
||||||
|
po = create_purchase_order(do_not_save=1)
|
||||||
|
po.items[0].qty = 4
|
||||||
|
po.save()
|
||||||
|
po.submit()
|
||||||
|
pr = make_pr_against_po(po.name, 2)
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
first_item_of_po = po.get("items")[0]
|
||||||
|
# add an item
|
||||||
|
trans_item = json.dumps([
|
||||||
|
{
|
||||||
|
'item_code': first_item_of_po.item_code,
|
||||||
|
'rate': first_item_of_po.rate,
|
||||||
|
'qty': first_item_of_po.qty,
|
||||||
|
'docname': first_item_of_po.name
|
||||||
|
},
|
||||||
|
{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7}])
|
||||||
|
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
# check if can remove received item
|
||||||
|
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}])
|
||||||
|
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name)
|
||||||
|
|
||||||
|
first_item_of_po = po.get("items")[0]
|
||||||
|
trans_item = json.dumps([
|
||||||
|
{
|
||||||
|
'item_code': first_item_of_po.item_code,
|
||||||
|
'rate': first_item_of_po.rate,
|
||||||
|
'qty': first_item_of_po.qty,
|
||||||
|
'docname': first_item_of_po.name
|
||||||
|
}
|
||||||
|
])
|
||||||
|
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEquals(len(po.get('items')), 1)
|
||||||
|
self.assertEqual(po.status, 'To Receive and Bill')
|
||||||
|
|
||||||
def test_update_qty(self):
|
def test_update_qty(self):
|
||||||
po = create_purchase_order()
|
po = create_purchase_order()
|
||||||
|
|
||||||
|
@ -1155,6 +1155,25 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
|
|||||||
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
||||||
return child_item
|
return child_item
|
||||||
|
|
||||||
|
def check_and_delete_children(parent, data):
|
||||||
|
deleted_children = []
|
||||||
|
updated_item_names = [d.get("docname") for d in data]
|
||||||
|
for item in parent.items:
|
||||||
|
if item.name not in updated_item_names:
|
||||||
|
deleted_children.append(item)
|
||||||
|
|
||||||
|
for d in deleted_children:
|
||||||
|
if parent.doctype == "Sales Order" and flt(d.delivered_qty):
|
||||||
|
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code))
|
||||||
|
|
||||||
|
if parent.doctype == "Purchase Order" and flt(d.received_qty):
|
||||||
|
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
|
||||||
|
|
||||||
|
if flt(d.billed_amt):
|
||||||
|
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))
|
||||||
|
|
||||||
|
d.cancel()
|
||||||
|
d.delete()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
||||||
@ -1163,6 +1182,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
|
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
|
||||||
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
|
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||||
|
|
||||||
|
check_and_delete_children(parent, data)
|
||||||
|
|
||||||
for d in data:
|
for d in data:
|
||||||
new_child_flag = False
|
new_child_flag = False
|
||||||
if not d.get("docname"):
|
if not d.get("docname"):
|
||||||
|
@ -311,6 +311,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
and sle.item_code = %(item_code)s
|
and sle.item_code = %(item_code)s
|
||||||
and sle.warehouse = %(warehouse)s
|
and sle.warehouse = %(warehouse)s
|
||||||
and (sle.batch_no like %(txt)s
|
and (sle.batch_no like %(txt)s
|
||||||
|
or batch.expiry_date like %(txt)s
|
||||||
or batch.manufacturing_date like %(txt)s)
|
or batch.manufacturing_date like %(txt)s)
|
||||||
and batch.docstatus < 2
|
and batch.docstatus < 2
|
||||||
{cond}
|
{cond}
|
||||||
@ -329,6 +330,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
where batch.disabled = 0
|
where batch.disabled = 0
|
||||||
and item = %(item_code)s
|
and item = %(item_code)s
|
||||||
and (name like %(txt)s
|
and (name like %(txt)s
|
||||||
|
or expiry_date like %(txt)s
|
||||||
or manufacturing_date like %(txt)s)
|
or manufacturing_date like %(txt)s)
|
||||||
and docstatus < 2
|
and docstatus < 2
|
||||||
{0}
|
{0}
|
||||||
|
@ -148,13 +148,6 @@ class SellingController(StockController):
|
|||||||
if sales_team and total != 100.0:
|
if sales_team and total != 100.0:
|
||||||
throw(_("Total allocated percentage for sales team should be 100"))
|
throw(_("Total allocated percentage for sales team should be 100"))
|
||||||
|
|
||||||
def validate_order_type(self):
|
|
||||||
valid_types = ["Sales", "Maintenance", "Shopping Cart"]
|
|
||||||
if not self.order_type:
|
|
||||||
self.order_type = "Sales"
|
|
||||||
elif self.order_type not in valid_types:
|
|
||||||
throw(_("Order Type must be one of {0}").format(comma_or(valid_types)))
|
|
||||||
|
|
||||||
def validate_max_discount(self):
|
def validate_max_discount(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.item_code:
|
if d.item_code:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext");
|
frappe.provide("erpnext");
|
||||||
@ -7,57 +7,54 @@ cur_frm.email_field = "email_id";
|
|||||||
erpnext.LeadController = frappe.ui.form.Controller.extend({
|
erpnext.LeadController = frappe.ui.form.Controller.extend({
|
||||||
setup: function () {
|
setup: function () {
|
||||||
this.frm.make_methods = {
|
this.frm.make_methods = {
|
||||||
|
'Customer': this.make_customer,
|
||||||
'Quotation': this.make_quotation,
|
'Quotation': this.make_quotation,
|
||||||
'Opportunity': this.create_opportunity
|
'Opportunity': this.make_opportunity
|
||||||
}
|
};
|
||||||
|
|
||||||
this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) {
|
|
||||||
return { query: "erpnext.controllers.queries.customer_query" }
|
|
||||||
}
|
|
||||||
|
|
||||||
this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
|
this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function () {
|
onload: function () {
|
||||||
if (cur_frm.fields_dict.lead_owner.df.options.match(/^User/)) {
|
this.frm.set_query("customer", function (doc, cdt, cdn) {
|
||||||
cur_frm.fields_dict.lead_owner.get_query = function (doc, cdt, cdn) {
|
return { query: "erpnext.controllers.queries.customer_query" }
|
||||||
return { query: "frappe.core.doctype.user.user.user_query" }
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur_frm.fields_dict.contact_by.df.options.match(/^User/)) {
|
this.frm.set_query("lead_owner", function (doc, cdt, cdn) {
|
||||||
cur_frm.fields_dict.contact_by.get_query = function (doc, cdt, cdn) {
|
|
||||||
return { query: "frappe.core.doctype.user.user.user_query" }
|
return { query: "frappe.core.doctype.user.user.user_query" }
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
this.frm.set_query("contact_by", function (doc, cdt, cdn) {
|
||||||
|
return { query: "frappe.core.doctype.user.user.user_query" }
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function () {
|
refresh: function () {
|
||||||
var doc = this.frm.doc;
|
let doc = this.frm.doc;
|
||||||
erpnext.toggle_naming_series();
|
erpnext.toggle_naming_series();
|
||||||
frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' }
|
frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' }
|
||||||
|
|
||||||
if(!doc.__islocal && doc.__onload && !doc.__onload.is_customer) {
|
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
|
||||||
this.frm.add_custom_button(__("Customer"), this.create_customer, __('Create'));
|
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
|
||||||
this.frm.add_custom_button(__("Opportunity"), this.create_opportunity, __('Create'));
|
this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create"));
|
||||||
this.frm.add_custom_button(__("Quotation"), this.make_quotation, __('Create'));
|
this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.frm.doc.__islocal) {
|
if (!this.frm.is_new()) {
|
||||||
frappe.contacts.render_address_and_contact(cur_frm);
|
frappe.contacts.render_address_and_contact(this.frm);
|
||||||
} else {
|
} else {
|
||||||
frappe.contacts.clear_address_and_contact(cur_frm);
|
frappe.contacts.clear_address_and_contact(this.frm);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
create_customer: function () {
|
make_customer: function () {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
method: "erpnext.crm.doctype.lead.lead.make_customer",
|
method: "erpnext.crm.doctype.lead.lead.make_customer",
|
||||||
frm: cur_frm
|
frm: cur_frm
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
create_opportunity: function () {
|
make_opportunity: function () {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
method: "erpnext.crm.doctype.lead.lead.make_opportunity",
|
method: "erpnext.crm.doctype.lead.lead.make_opportunity",
|
||||||
frm: cur_frm
|
frm: cur_frm
|
||||||
@ -77,7 +74,7 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
company_name: function () {
|
company_name: function () {
|
||||||
if (this.frm.doc.organization_lead == 1) {
|
if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) {
|
||||||
this.frm.set_value("lead_name", this.frm.doc.company_name);
|
this.frm.set_value("lead_name", this.frm.doc.company_name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -85,7 +82,7 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
|
|||||||
contact_date: function () {
|
contact_date: function () {
|
||||||
if (this.frm.doc.contact_date) {
|
if (this.frm.doc.contact_date) {
|
||||||
let d = moment(this.frm.doc.contact_date);
|
let d = moment(this.frm.doc.contact_date);
|
||||||
d.add(1, "hours");
|
d.add(1, "day");
|
||||||
this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat));
|
this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_events_in_timeline": 1,
|
"allow_events_in_timeline": 1,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
@ -16,6 +17,8 @@
|
|||||||
"col_break123",
|
"col_break123",
|
||||||
"lead_owner",
|
"lead_owner",
|
||||||
"status",
|
"status",
|
||||||
|
"salutation",
|
||||||
|
"designation",
|
||||||
"gender",
|
"gender",
|
||||||
"source",
|
"source",
|
||||||
"customer",
|
"customer",
|
||||||
@ -28,17 +31,22 @@
|
|||||||
"ends_on",
|
"ends_on",
|
||||||
"notes_section",
|
"notes_section",
|
||||||
"notes",
|
"notes",
|
||||||
"contact_info",
|
"address_info",
|
||||||
"address_desc",
|
|
||||||
"address_html",
|
"address_html",
|
||||||
|
"address_title",
|
||||||
|
"address_line1",
|
||||||
|
"address_line2",
|
||||||
|
"city",
|
||||||
|
"county",
|
||||||
"column_break2",
|
"column_break2",
|
||||||
"contact_html",
|
"contact_html",
|
||||||
|
"state",
|
||||||
|
"country",
|
||||||
|
"pincode",
|
||||||
|
"contact_section",
|
||||||
"phone",
|
"phone",
|
||||||
"salutation",
|
|
||||||
"mobile_no",
|
"mobile_no",
|
||||||
"fax",
|
"fax",
|
||||||
"website",
|
|
||||||
"territory",
|
|
||||||
"more_info",
|
"more_info",
|
||||||
"type",
|
"type",
|
||||||
"market_segment",
|
"market_segment",
|
||||||
@ -46,8 +54,11 @@
|
|||||||
"request_type",
|
"request_type",
|
||||||
"column_break3",
|
"column_break3",
|
||||||
"company",
|
"company",
|
||||||
|
"website",
|
||||||
|
"territory",
|
||||||
"unsubscribed",
|
"unsubscribed",
|
||||||
"blog_subscriber"
|
"blog_subscriber",
|
||||||
|
"title"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -73,7 +84,6 @@
|
|||||||
"set_only_once": 1
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.organization_lead",
|
|
||||||
"fieldname": "lead_name",
|
"fieldname": "lead_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
@ -130,7 +140,13 @@
|
|||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.organization_lead",
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "salutation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Salutation",
|
||||||
|
"options": "Salutation"
|
||||||
|
},
|
||||||
|
{
|
||||||
"fieldname": "gender",
|
"fieldname": "gender",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Gender",
|
"label": "Gender",
|
||||||
@ -216,40 +232,74 @@
|
|||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Notes"
|
"label": "Notes"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"collapsible": 1,
|
|
||||||
"fieldname": "contact_info",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Address & Contact",
|
|
||||||
"oldfieldtype": "Column Break",
|
|
||||||
"options": "fa fa-map-marker"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.__islocal",
|
|
||||||
"fieldname": "address_desc",
|
|
||||||
"fieldtype": "HTML",
|
|
||||||
"label": "Address Desc",
|
|
||||||
"print_hide": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "address_html",
|
"fieldname": "address_html",
|
||||||
"fieldtype": "HTML",
|
"fieldtype": "HTML",
|
||||||
"label": "Address HTML",
|
"label": "Address HTML",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "address_title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Address Title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "address_line1",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Address Line 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "address_line2",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Address Line 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "city",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "City/Town"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "county",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "County"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "state",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "State"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "country",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Country",
|
||||||
|
"options": "Country"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "pincode",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Postal Code",
|
||||||
|
"options": "Country"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break2",
|
"fieldname": "column_break2",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.organization_lead",
|
|
||||||
"fieldname": "contact_html",
|
"fieldname": "contact_html",
|
||||||
"fieldtype": "HTML",
|
"fieldtype": "HTML",
|
||||||
"label": "Contact HTML",
|
"label": "Contact HTML",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.organization_lead",
|
"depends_on": "eval: doc.__islocal",
|
||||||
"fieldname": "phone",
|
"fieldname": "phone",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Phone",
|
"label": "Phone",
|
||||||
@ -257,14 +307,7 @@
|
|||||||
"oldfieldtype": "Data"
|
"oldfieldtype": "Data"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.organization_lead",
|
"depends_on": "eval: doc.__islocal",
|
||||||
"fieldname": "salutation",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Salutation",
|
|
||||||
"options": "Salutation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:!doc.organization_lead",
|
|
||||||
"fieldname": "mobile_no",
|
"fieldname": "mobile_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Mobile No.",
|
"label": "Mobile No.",
|
||||||
@ -272,29 +315,13 @@
|
|||||||
"oldfieldtype": "Data"
|
"oldfieldtype": "Data"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.organization_lead",
|
"depends_on": "eval: doc.__islocal",
|
||||||
"fieldname": "fax",
|
"fieldname": "fax",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Fax",
|
"label": "Fax",
|
||||||
"oldfieldname": "fax",
|
"oldfieldname": "fax",
|
||||||
"oldfieldtype": "Data"
|
"oldfieldtype": "Data"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "website",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Website",
|
|
||||||
"oldfieldname": "website",
|
|
||||||
"oldfieldtype": "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "territory",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Territory",
|
|
||||||
"oldfieldname": "territory",
|
|
||||||
"oldfieldtype": "Link",
|
|
||||||
"options": "Territory",
|
|
||||||
"print_hide": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "more_info",
|
"fieldname": "more_info",
|
||||||
@ -350,6 +377,22 @@
|
|||||||
"options": "Company",
|
"options": "Company",
|
||||||
"remember_last_selected_value": 1
|
"remember_last_selected_value": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "website",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Website",
|
||||||
|
"oldfieldname": "website",
|
||||||
|
"oldfieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "territory",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Territory",
|
||||||
|
"oldfieldname": "territory",
|
||||||
|
"oldfieldtype": "Link",
|
||||||
|
"options": "Territory",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "unsubscribed",
|
"fieldname": "unsubscribed",
|
||||||
@ -361,12 +404,42 @@
|
|||||||
"fieldname": "blog_subscriber",
|
"fieldname": "blog_subscriber",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Blog Subscriber"
|
"label": "Blog Subscriber"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Title",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "designation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Designation",
|
||||||
|
"options": "Designation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"collapsible_depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "address_info",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Address & Contact",
|
||||||
|
"oldfieldtype": "Column Break",
|
||||||
|
"options": "fa fa-map-marker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"collapsible_depends_on": "eval: doc.__islocal",
|
||||||
|
"fieldname": "contact_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Contact"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 5,
|
"idx": 5,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"modified": "2019-09-19 12:49:02.536647",
|
"links": [],
|
||||||
|
"modified": "2019-12-24 16:00:44.239168",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Lead",
|
"name": "Lead",
|
||||||
@ -438,5 +511,5 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"title_field": "lead_name"
|
"title_field": "title"
|
||||||
}
|
}
|
@ -2,18 +2,19 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.utils import (cstr, validate_email_address, cint, comma_and, has_gravatar, now, getdate, nowdate)
|
|
||||||
from frappe.model.mapper import get_mapped_doc
|
|
||||||
|
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
import frappe
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
|
||||||
from erpnext.accounts.party import set_taxes
|
from erpnext.accounts.party import set_taxes
|
||||||
|
from erpnext.controllers.selling_controller import SellingController
|
||||||
|
from frappe import _
|
||||||
|
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||||
from frappe.email.inbox import link_communication_to_document
|
from frappe.email.inbox import link_communication_to_document
|
||||||
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
from frappe.utils import cint, comma_and, cstr, getdate, has_gravatar, nowdate, validate_email_address
|
||||||
|
|
||||||
sender_field = "email_id"
|
sender_field = "email_id"
|
||||||
|
|
||||||
|
|
||||||
class Lead(SellingController):
|
class Lead(SellingController):
|
||||||
def get_feed(self):
|
def get_feed(self):
|
||||||
return '{0}: {1}'.format(_(self.status), self.lead_name)
|
return '{0}: {1}'.format(_(self.status), self.lead_name)
|
||||||
@ -23,15 +24,23 @@ class Lead(SellingController):
|
|||||||
self.get("__onload").is_customer = customer
|
self.get("__onload").is_customer = customer
|
||||||
load_address_and_contact(self)
|
load_address_and_contact(self)
|
||||||
|
|
||||||
|
def before_insert(self):
|
||||||
|
self.address_doc = self.create_address()
|
||||||
|
self.contact_doc = self.create_contact()
|
||||||
|
|
||||||
|
def after_insert(self):
|
||||||
|
self.update_links()
|
||||||
|
# after the address and contact are created, flush the field values
|
||||||
|
# to avoid inconsistent reporting in case the documents are changed
|
||||||
|
self.flush_address_and_contact_fields()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.set_lead_name()
|
self.set_lead_name()
|
||||||
|
self.set_title()
|
||||||
self._prev = frappe._dict({
|
self._prev = frappe._dict({
|
||||||
"contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if \
|
"contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None,
|
||||||
(not cint(self.get("__islocal"))) else None,
|
"ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None,
|
||||||
"ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if \
|
"contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None,
|
||||||
(not cint(self.get("__islocal"))) else None,
|
|
||||||
"contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if \
|
|
||||||
(not cint(self.get("__islocal"))) else None,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
@ -39,7 +48,7 @@ class Lead(SellingController):
|
|||||||
|
|
||||||
if self.email_id:
|
if self.email_id:
|
||||||
if not self.flags.ignore_email_validation:
|
if not self.flags.ignore_email_validation:
|
||||||
validate_email_address(self.email_id, True)
|
validate_email_address(self.email_id, throw=True)
|
||||||
|
|
||||||
if self.email_id == self.lead_owner:
|
if self.email_id == self.lead_owner:
|
||||||
frappe.throw(_("Lead Owner cannot be same as the Lead"))
|
frappe.throw(_("Lead Owner cannot be same as the Lead"))
|
||||||
@ -53,8 +62,7 @@ class Lead(SellingController):
|
|||||||
if self.contact_date and getdate(self.contact_date) < getdate(nowdate()):
|
if self.contact_date and getdate(self.contact_date) < getdate(nowdate()):
|
||||||
frappe.throw(_("Next Contact Date cannot be in the past"))
|
frappe.throw(_("Next Contact Date cannot be in the past"))
|
||||||
|
|
||||||
if self.ends_on and self.contact_date and\
|
if self.ends_on and self.contact_date and (self.ends_on < self.contact_date):
|
||||||
(self.ends_on < self.contact_date):
|
|
||||||
frappe.throw(_("Ends On date cannot be before Next Contact Date."))
|
frappe.throw(_("Ends On date cannot be before Next Contact Date."))
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
@ -66,23 +74,21 @@ class Lead(SellingController):
|
|||||||
"starts_on": self.contact_date,
|
"starts_on": self.contact_date,
|
||||||
"ends_on": self.ends_on or "",
|
"ends_on": self.ends_on or "",
|
||||||
"subject": ('Contact ' + cstr(self.lead_name)),
|
"subject": ('Contact ' + cstr(self.lead_name)),
|
||||||
"description": ('Contact ' + cstr(self.lead_name)) + \
|
"description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
|
||||||
(self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
|
|
||||||
}, force)
|
}, force)
|
||||||
|
|
||||||
def check_email_id_is_unique(self):
|
def check_email_id_is_unique(self):
|
||||||
if self.email_id:
|
if self.email_id:
|
||||||
# validate email is unique
|
# validate email is unique
|
||||||
duplicate_leads = frappe.db.sql_list("""select name from tabLead
|
duplicate_leads = frappe.get_all("Lead", filters={"email_id": self.email_id, "name": ["!=", self.name]})
|
||||||
where email_id=%s and name!=%s""", (self.email_id, self.name))
|
duplicate_leads = [lead.name for lead in duplicate_leads]
|
||||||
|
|
||||||
if duplicate_leads:
|
if duplicate_leads:
|
||||||
frappe.throw(_("Email Address must be unique, already exists for {0}")
|
frappe.throw(_("Email Address must be unique, already exists for {0}")
|
||||||
.format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
|
.format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""",
|
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
|
||||||
self.name)
|
|
||||||
|
|
||||||
self.delete_events()
|
self.delete_events()
|
||||||
|
|
||||||
@ -115,10 +121,101 @@ class Lead(SellingController):
|
|||||||
|
|
||||||
self.lead_name = self.company_name
|
self.lead_name = self.company_name
|
||||||
|
|
||||||
|
def set_title(self):
|
||||||
|
if self.organization_lead:
|
||||||
|
self.title = self.company_name
|
||||||
|
else:
|
||||||
|
self.title = self.lead_name
|
||||||
|
|
||||||
|
def create_address(self):
|
||||||
|
address_fields = ["address_title", "address_line1", "address_line2",
|
||||||
|
"city", "county", "state", "country", "pincode"]
|
||||||
|
info_fields = ["email_id", "phone", "fax"]
|
||||||
|
|
||||||
|
# do not create an address if no fields are available,
|
||||||
|
# skipping country since the system auto-sets it from system defaults
|
||||||
|
if not any([self.get(field) for field in address_fields if field != "country"]):
|
||||||
|
return
|
||||||
|
|
||||||
|
address = frappe.new_doc("Address")
|
||||||
|
address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
|
||||||
|
address.update({info_field: self.get(info_field) for info_field in info_fields})
|
||||||
|
address.insert()
|
||||||
|
|
||||||
|
return address
|
||||||
|
|
||||||
|
def create_contact(self):
|
||||||
|
if not self.lead_name:
|
||||||
|
self.set_lead_name()
|
||||||
|
|
||||||
|
names = self.lead_name.split(" ")
|
||||||
|
if len(names) > 1:
|
||||||
|
first_name, last_name = names[0], " ".join(names[1:])
|
||||||
|
else:
|
||||||
|
first_name, last_name = self.lead_name, None
|
||||||
|
|
||||||
|
contact = frappe.new_doc("Contact")
|
||||||
|
contact.update({
|
||||||
|
"first_name": first_name,
|
||||||
|
"last_name": last_name,
|
||||||
|
"salutation": self.salutation,
|
||||||
|
"gender": self.gender,
|
||||||
|
"designation": self.designation,
|
||||||
|
})
|
||||||
|
|
||||||
|
if self.email_id:
|
||||||
|
contact.append("email_ids", {
|
||||||
|
"email_id": self.email_id,
|
||||||
|
"is_primary": 1
|
||||||
|
})
|
||||||
|
|
||||||
|
if self.phone:
|
||||||
|
contact.append("phone_nos", {
|
||||||
|
"phone": self.phone,
|
||||||
|
"is_primary": 1
|
||||||
|
})
|
||||||
|
|
||||||
|
if self.mobile_no:
|
||||||
|
contact.append("phone_nos", {
|
||||||
|
"phone": self.mobile_no
|
||||||
|
})
|
||||||
|
|
||||||
|
contact.insert()
|
||||||
|
|
||||||
|
return contact
|
||||||
|
|
||||||
|
def update_links(self):
|
||||||
|
# update address links
|
||||||
|
if self.address_doc:
|
||||||
|
self.address_doc.append("links", {
|
||||||
|
"link_doctype": "Lead",
|
||||||
|
"link_name": self.name,
|
||||||
|
"link_title": self.lead_name
|
||||||
|
})
|
||||||
|
self.address_doc.save()
|
||||||
|
|
||||||
|
# update contact links
|
||||||
|
if self.contact_doc:
|
||||||
|
self.contact_doc.append("links", {
|
||||||
|
"link_doctype": "Lead",
|
||||||
|
"link_name": self.name,
|
||||||
|
"link_title": self.lead_name
|
||||||
|
})
|
||||||
|
self.contact_doc.save()
|
||||||
|
|
||||||
|
def flush_address_and_contact_fields(self):
|
||||||
|
fields = ['address_line1', 'address_line2', 'address_title',
|
||||||
|
'city', 'county', 'country', 'fax', 'pincode', 'state']
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
self.set(field, None)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_customer(source_name, target_doc=None):
|
def make_customer(source_name, target_doc=None):
|
||||||
return _make_customer(source_name, target_doc)
|
return _make_customer(source_name, target_doc)
|
||||||
|
|
||||||
|
|
||||||
def _make_customer(source_name, target_doc=None, ignore_permissions=False):
|
def _make_customer(source_name, target_doc=None, ignore_permissions=False):
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
if source.company_name:
|
if source.company_name:
|
||||||
@ -143,6 +240,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_opportunity(source_name, target_doc=None):
|
def make_opportunity(source_name, target_doc=None):
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
@ -164,6 +262,7 @@ def make_opportunity(source_name, target_doc=None):
|
|||||||
|
|
||||||
return target_doc
|
return target_doc
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_quotation(source_name, target_doc=None):
|
def make_quotation(source_name, target_doc=None):
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
@ -205,7 +304,8 @@ def _set_missing_values(source, target):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_lead_details(lead, posting_date=None, company=None):
|
def get_lead_details(lead, posting_date=None, company=None):
|
||||||
if not lead: return {}
|
if not lead:
|
||||||
|
return {}
|
||||||
|
|
||||||
from erpnext.accounts.party import set_address_details
|
from erpnext.accounts.party import set_address_details
|
||||||
out = frappe._dict()
|
out = frappe._dict()
|
||||||
@ -231,6 +331,7 @@ def get_lead_details(lead, posting_date=None, company=None):
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_lead_from_communication(communication, ignore_communication_links=False):
|
def make_lead_from_communication(communication, ignore_communication_links=False):
|
||||||
""" raise a issue from email """
|
""" raise a issue from email """
|
||||||
|
@ -27,7 +27,7 @@ class Instructor(Document):
|
|||||||
self.validate_duplicate_employee()
|
self.validate_duplicate_employee()
|
||||||
|
|
||||||
def validate_duplicate_employee(self):
|
def validate_duplicate_employee(self):
|
||||||
if self.employee and frappe.db.get_value("Instructor", {'employee': self.employee}, 'name'):
|
if self.employee and frappe.db.get_value("Instructor", {'employee': self.employee, 'name': ['!=', self.name]}, 'name'):
|
||||||
frappe.throw(_("Employee ID is linked with another instructor"))
|
frappe.throw(_("Employee ID is linked with another instructor"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -164,6 +164,12 @@ class Employee(NestedSet):
|
|||||||
if self.personal_email:
|
if self.personal_email:
|
||||||
validate_email_address(self.personal_email, True)
|
validate_email_address(self.personal_email, True)
|
||||||
|
|
||||||
|
def set_preferred_email(self):
|
||||||
|
preferred_email_field = frappe.scrub(self.prefered_contact_email)
|
||||||
|
if preferred_email_field:
|
||||||
|
preferred_email = self.get(preferred_email_field)
|
||||||
|
self.prefered_email = preferred_email
|
||||||
|
|
||||||
def validate_status(self):
|
def validate_status(self):
|
||||||
if self.status == 'Left':
|
if self.status == 'Left':
|
||||||
reports_to = frappe.db.get_all('Employee',
|
reports_to = frappe.db.get_all('Employee',
|
||||||
|
@ -115,6 +115,16 @@ def get_valid_items(search_value=''):
|
|||||||
|
|
||||||
return valid_items
|
return valid_items
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_item(ref_doc, data):
|
||||||
|
data = json.loads(data)
|
||||||
|
|
||||||
|
data.update(dict(doctype='Hub Item', name=ref_doc))
|
||||||
|
try:
|
||||||
|
connection = get_hub_connection()
|
||||||
|
connection.update(data)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(message=e, title='Hub Sync Error')
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def publish_selected_items(items_to_publish):
|
def publish_selected_items(items_to_publish):
|
||||||
|
@ -649,5 +649,7 @@ erpnext.patches.v12_0.add_export_type_field_in_party_master
|
|||||||
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
|
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
|
||||||
erpnext.patches.v12_0.update_price_or_product_discount
|
erpnext.patches.v12_0.update_price_or_product_discount
|
||||||
erpnext.patches.v12_0.set_production_capacity_in_workstation
|
erpnext.patches.v12_0.set_production_capacity_in_workstation
|
||||||
|
erpnext.patches.v12_0.set_employee_preferred_emails
|
||||||
erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order
|
erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order
|
||||||
erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim
|
erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim
|
||||||
|
erpnext.patches.v12_0.set_lead_title_field
|
||||||
|
16
erpnext/patches/v12_0/set_employee_preferred_emails.py
Normal file
16
erpnext/patches/v12_0/set_employee_preferred_emails.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
employees = frappe.get_all("Employee",
|
||||||
|
filters={"prefered_email": ""},
|
||||||
|
fields=["name", "prefered_contact_email", "company_email", "personal_email", "user_id"])
|
||||||
|
|
||||||
|
for employee in employees:
|
||||||
|
preferred_email_field = frappe.scrub(employee.prefered_contact_email)
|
||||||
|
|
||||||
|
if not preferred_email_field:
|
||||||
|
continue
|
||||||
|
|
||||||
|
preferred_email = employee.get(preferred_email_field)
|
||||||
|
frappe.db.set_value("Employee", employee.name, "prefered_email", preferred_email, update_modified=False)
|
11
erpnext/patches/v12_0/set_lead_title_field.py
Normal file
11
erpnext/patches/v12_0/set_lead_title_field.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("crm", "doctype", "lead")
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE
|
||||||
|
`tabLead`
|
||||||
|
SET
|
||||||
|
title = IF(organization_lead = 1, company_name, lead_name)
|
||||||
|
""")
|
@ -1809,13 +1809,43 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
erpnext.show_serial_batch_selector = function (frm, d, callback, on_close, show_dialog) {
|
erpnext.show_serial_batch_selector = function (frm, d, callback, on_close, show_dialog) {
|
||||||
|
let warehouse, receiving_stock, existing_stock;
|
||||||
|
if (frm.doc.is_return) {
|
||||||
|
if (["Purchase Receipt", "Purchase Invoice"].includes(frm.doc.doctype)) {
|
||||||
|
existing_stock = true;
|
||||||
|
warehouse = d.warehouse;
|
||||||
|
} else if (["Delivery Note", "Sales Invoice"].includes(frm.doc.doctype)) {
|
||||||
|
receiving_stock = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (frm.doc.doctype == "Stock Entry") {
|
||||||
|
if (frm.doc.purpose == "Material Receipt") {
|
||||||
|
receiving_stock = true;
|
||||||
|
} else {
|
||||||
|
existing_stock = true;
|
||||||
|
warehouse = d.s_warehouse;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
existing_stock = true;
|
||||||
|
warehouse = d.warehouse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!warehouse) {
|
||||||
|
if (receiving_stock) {
|
||||||
|
warehouse = ["like", ""];
|
||||||
|
} else if (existing_stock) {
|
||||||
|
warehouse = ["!=", ""];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
||||||
new erpnext.SerialNoBatchSelector({
|
new erpnext.SerialNoBatchSelector({
|
||||||
frm: frm,
|
frm: frm,
|
||||||
item: d,
|
item: d,
|
||||||
warehouse_details: {
|
warehouse_details: {
|
||||||
type: "Warehouse",
|
type: "Warehouse",
|
||||||
name: d.warehouse
|
name: warehouse
|
||||||
},
|
},
|
||||||
callback: callback,
|
callback: callback,
|
||||||
on_close: on_close
|
on_close: on_close
|
||||||
|
41
erpnext/public/js/hub/components/edit_details_dialog.js
Normal file
41
erpnext/public/js/hub/components/edit_details_dialog.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
function edit_details_dialog(params) {
|
||||||
|
let dialog = new frappe.ui.Dialog({
|
||||||
|
title: __('Update Details'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Item Name',
|
||||||
|
fieldname: 'item_name',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
default: params.defaults.item_name,
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hub Category',
|
||||||
|
fieldname: 'hub_category',
|
||||||
|
fieldtype: 'Autocomplete',
|
||||||
|
default: params.defaults.hub_category,
|
||||||
|
options: [],
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Description',
|
||||||
|
fieldname: 'description',
|
||||||
|
fieldtype: 'Text',
|
||||||
|
default: params.defaults.description,
|
||||||
|
options: [],
|
||||||
|
reqd: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action_label: params.primary_action.label || __('Update Details'),
|
||||||
|
primary_action: params.primary_action.fn
|
||||||
|
});
|
||||||
|
|
||||||
|
hub.call('get_categories').then(categories => {
|
||||||
|
categories = categories.map(d => d.name);
|
||||||
|
dialog.fields_dict.hub_category.set_data(categories);
|
||||||
|
});
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { edit_details_dialog };
|
@ -1,10 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="marketplace-page" :data-page-name="page_name" v-if="init || item">
|
||||||
class="marketplace-page"
|
|
||||||
:data-page-name="page_name"
|
|
||||||
v-if="init || item"
|
|
||||||
>
|
|
||||||
|
|
||||||
<detail-view
|
<detail-view
|
||||||
:title="title"
|
:title="title"
|
||||||
:image="image"
|
:image="image"
|
||||||
@ -12,20 +7,15 @@
|
|||||||
:menu_items="menu_items"
|
:menu_items="menu_items"
|
||||||
:show_skeleton="init"
|
:show_skeleton="init"
|
||||||
>
|
>
|
||||||
<detail-header-item slot="detail-header-item"
|
<detail-header-item slot="detail-header-item" :value="item_subtitle"></detail-header-item>
|
||||||
:value="item_subtitle"
|
<detail-header-item slot="detail-header-item" :value="item_views_and_ratings"></detail-header-item>
|
||||||
></detail-header-item>
|
|
||||||
<detail-header-item slot="detail-header-item"
|
|
||||||
:value="item_views_and_ratings"
|
|
||||||
></detail-header-item>
|
|
||||||
|
|
||||||
<button v-if="primary_action" slot="detail-header-item"
|
<button
|
||||||
|
v-if="primary_action"
|
||||||
|
slot="detail-header-item"
|
||||||
class="btn btn-primary btn-sm margin-top"
|
class="btn btn-primary btn-sm margin-top"
|
||||||
@click="primary_action.action"
|
@click="primary_action.action"
|
||||||
>
|
>{{ primary_action.label }}</button>
|
||||||
{{ primary_action.label }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</detail-view>
|
</detail-view>
|
||||||
|
|
||||||
<review-area v-if="!init" :hub_item_name="hub_item_name"></review-area>
|
<review-area v-if="!init" :hub_item_name="hub_item_name"></review-area>
|
||||||
@ -35,6 +25,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import ReviewArea from '../components/ReviewArea.vue';
|
import ReviewArea from '../components/ReviewArea.vue';
|
||||||
import { get_rating_html } from '../components/reviews';
|
import { get_rating_html } from '../components/reviews';
|
||||||
|
import { edit_details_dialog } from '../components/edit_details_dialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'item-page',
|
name: 'item-page',
|
||||||
@ -51,8 +42,7 @@ export default {
|
|||||||
item: null,
|
item: null,
|
||||||
title: null,
|
title: null,
|
||||||
image: null,
|
image: null,
|
||||||
sections: [],
|
sections: []
|
||||||
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -92,7 +82,7 @@ export default {
|
|||||||
condition: hub.is_user_registered() && this.is_own_item,
|
condition: hub.is_user_registered() && this.is_own_item,
|
||||||
action: this.unpublish_item
|
action: this.unpublish_item
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
item_subtitle() {
|
item_subtitle() {
|
||||||
@ -105,10 +95,13 @@ export default {
|
|||||||
const rating = this.item.average_rating;
|
const rating = this.item.average_rating;
|
||||||
|
|
||||||
if (rating > 0) {
|
if (rating > 0) {
|
||||||
subtitle_items.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
|
subtitle_items.push(rating + `<i class='fa fa-fw fa-star-o'></i>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
subtitle_items.push({value:this.item.company,on_click:this.go_to_seller_profile_page});
|
subtitle_items.push({
|
||||||
|
value: this.item.company,
|
||||||
|
on_click: this.go_to_seller_profile_page
|
||||||
|
});
|
||||||
|
|
||||||
return subtitle_items;
|
return subtitle_items;
|
||||||
},
|
},
|
||||||
@ -123,7 +116,10 @@ export default {
|
|||||||
const views_message = __(`${this.item.view_count} Views`);
|
const views_message = __(`${this.item.view_count} Views`);
|
||||||
|
|
||||||
const rating_html = get_rating_html(this.item.average_rating);
|
const rating_html = get_rating_html(this.item.average_rating);
|
||||||
const rating_count = this.item.no_of_ratings > 0 ? `${this.item.no_of_ratings} reviews` : __('No reviews yet');
|
const rating_count =
|
||||||
|
this.item.no_of_ratings > 0
|
||||||
|
? `${this.item.no_of_ratings} reviews`
|
||||||
|
: __('No reviews yet');
|
||||||
|
|
||||||
stats = [views_message, rating_html, rating_count];
|
stats = [views_message, rating_html, rating_count];
|
||||||
}
|
}
|
||||||
@ -136,7 +132,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
label: __('Contact Seller'),
|
label: __('Contact Seller'),
|
||||||
action: this.contact_seller.bind(this)
|
action: this.contact_seller.bind(this)
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -156,7 +152,7 @@ export default {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hub.call('add_item_view', {
|
hub.call('add_item_view', {
|
||||||
hub_item_name: this.hub_item_name
|
hub_item_name: this.hub_item_name
|
||||||
})
|
});
|
||||||
// .then(() => {
|
// .then(() => {
|
||||||
// erpnext.hub.item_view_cache.push(this.hub_item_name);
|
// erpnext.hub.item_view_cache.push(this.hub_item_name);
|
||||||
// });
|
// });
|
||||||
@ -200,6 +196,7 @@ export default {
|
|||||||
make_dialogs() {
|
make_dialogs() {
|
||||||
this.make_contact_seller_dialog();
|
this.make_contact_seller_dialog();
|
||||||
this.make_report_item_dialog();
|
this.make_report_item_dialog();
|
||||||
|
this.make_editing_dialog();
|
||||||
},
|
},
|
||||||
|
|
||||||
add_to_saved_items() {
|
add_to_saved_items() {
|
||||||
@ -208,7 +205,9 @@ export default {
|
|||||||
hub_user: frappe.session.user
|
hub_user: frappe.session.user
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const saved_items_link = `<b><a href="#marketplace/saved-items">${__('Saved')}</a></b>`
|
const saved_items_link = `<b><a href="#marketplace/saved-items">${__(
|
||||||
|
'Saved'
|
||||||
|
)}</a></b>`;
|
||||||
frappe.show_alert(saved_items_link);
|
frappe.show_alert(saved_items_link);
|
||||||
erpnext.hub.trigger('action:item_save');
|
erpnext.hub.trigger('action:item_save');
|
||||||
})
|
})
|
||||||
@ -221,9 +220,11 @@ export default {
|
|||||||
hub.call('add_item_to_seller_featured_items', {
|
hub.call('add_item_to_seller_featured_items', {
|
||||||
hub_item_name: this.hub_item_name,
|
hub_item_name: this.hub_item_name,
|
||||||
hub_user: frappe.session.user
|
hub_user: frappe.session.user
|
||||||
},)
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const featured_items_link = `<b><a href="#marketplace/featured-items">${__('Added to Featured Items')}</a></b>`
|
const featured_items_link = `<b><a href="#marketplace/featured-items">${__(
|
||||||
|
'Added to Featured Items'
|
||||||
|
)}</a></b>`;
|
||||||
frappe.show_alert(featured_items_link);
|
frappe.show_alert(featured_items_link);
|
||||||
erpnext.hub.trigger('action:item_feature');
|
erpnext.hub.trigger('action:item_feature');
|
||||||
})
|
})
|
||||||
@ -258,7 +259,7 @@ export default {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.contact_seller_dialog.hide();
|
this.contact_seller_dialog.hide();
|
||||||
frappe.set_route('marketplace', 'buying', this.item.name);
|
frappe.set_route('marketplace', 'buying', this.item.name);
|
||||||
erpnext.hub.trigger('action:send_message')
|
erpnext.hub.trigger('action:send_message');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -275,7 +276,10 @@ export default {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
primary_action: ({ message }) => {
|
primary_action: ({ message }) => {
|
||||||
hub.call('add_reported_item', { hub_item_name: this.item.name, message })
|
hub.call('add_reported_item', {
|
||||||
|
hub_item_name: this.item.name,
|
||||||
|
message
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
d.hide();
|
d.hide();
|
||||||
frappe.show_alert(__('Item Reported'));
|
frappe.show_alert(__('Item Reported'));
|
||||||
@ -284,26 +288,62 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
make_editing_dialog() {
|
||||||
|
this.edit_dialog = edit_details_dialog({
|
||||||
|
primary_action: {
|
||||||
|
fn: values => {
|
||||||
|
this.update_details(values);
|
||||||
|
this.edit_dialog.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
item_name: this.item.item_name,
|
||||||
|
hub_category: this.item.hub_category,
|
||||||
|
description: this.item.description
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
update_details(values) {
|
||||||
|
frappe.call('erpnext.hub_node.api.update_item', {
|
||||||
|
ref_doc: this.item.name,
|
||||||
|
data: values
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
return this.get_item_details();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
frappe.show_alert(__(`${this.item.item_name} Updated`));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
contact_seller() {
|
contact_seller() {
|
||||||
this.contact_seller_dialog.show();
|
this.contact_seller_dialog.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
report_item() {
|
report_item() {
|
||||||
if (!hub.is_seller_registered()) {
|
if (!hub.is_seller_registered()) {
|
||||||
frappe.throw(__('Please login as a Marketplace User to report this item.'));
|
frappe.throw(
|
||||||
|
__('Please login as a Marketplace User to report this item.')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.report_item_dialog.show();
|
this.report_item_dialog.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
edit_details() {
|
edit_details() {
|
||||||
frappe.msgprint(__('This feature is under development...'));
|
if (!hub.is_seller_registered()) {
|
||||||
|
frappe.throw(
|
||||||
|
__('Please login as a Marketplace User to edit this item.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.edit_dialog.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
unpublish_item() {
|
unpublish_item() {
|
||||||
frappe.msgprint(__('This feature is under development...'));
|
frappe.msgprint(__('This feature is under development...'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
{% for(var i=0, l=addr_list.length; i<l; i++) { %}
|
{% for(var i=0, l=addr_list.length; i<l; i++) { %}
|
||||||
<div class="address-box">
|
<div class="address-box">
|
||||||
<p class="h6">
|
<p class="h6">
|
||||||
{%= i+1 %}. {%= addr_list[i].address_type!="Other" ? __(addr_list[i].address_type) : addr_list[i].address_title %}
|
{%= i+1 %}. {%= addr_list[i].address_title %}{% if(addr_list[i].address_type!="Other") { %}
|
||||||
|
<span class="text-muted">({%= __(addr_list[i].address_type) %})</span>{% } %}
|
||||||
{% if(addr_list[i].is_primary_address) { %}
|
{% if(addr_list[i].is_primary_address) { %}
|
||||||
<span class="text-muted">({%= __("Primary") %})</span>{% } %}
|
<span class="text-muted">({%= __("Primary") %})</span>{% } %}
|
||||||
{% if(addr_list[i].is_shipping_address) { %}
|
{% if(addr_list[i].is_shipping_address) { %}
|
||||||
<span class="text-muted">({%= __("Shipping") %})</span>{% } %}
|
<span class="text-muted">({%= __("Shipping") %})</span>{% } %}
|
||||||
|
|
||||||
<a href="#Form/Address/{%= encodeURIComponent(addr_list[i].name) %}"
|
<a href="#Form/Address/{%= encodeURIComponent(addr_list[i].name) %}" class="btn btn-default btn-xs pull-right"
|
||||||
class="btn btn-default btn-xs pull-right"
|
|
||||||
style="margin-top:-3px; margin-right: -5px;">
|
style="margin-top:-3px; margin-right: -5px;">
|
||||||
{%= __("Edit") %}</a>
|
{%= __("Edit") %}</a>
|
||||||
</p>
|
</p>
|
||||||
@ -20,4 +20,3 @@
|
|||||||
<p class="text-muted small">{%= __("No address added yet.") %}</p>
|
<p class="text-muted small">{%= __("No address added yet.") %}</p>
|
||||||
{% } %}
|
{% } %}
|
||||||
<p><button class="btn btn-xs btn-default btn-address">{{ __("New Address") }}</button></p>
|
<p><button class="btn btn-xs btn-default btn-address">{{ __("New Address") }}</button></p>
|
||||||
|
|
||||||
|
@ -458,7 +458,8 @@ erpnext.utils.update_child_items = function(opts) {
|
|||||||
fieldname:"item_code",
|
fieldname:"item_code",
|
||||||
options: 'Item',
|
options: 'Item',
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
read_only: 1,
|
read_only: 0,
|
||||||
|
disabled: 0,
|
||||||
label: __('Item Code')
|
label: __('Item Code')
|
||||||
}, {
|
}, {
|
||||||
fieldtype:'Float',
|
fieldtype:'Float',
|
||||||
|
@ -389,12 +389,14 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
|||||||
|
|
||||||
let serial_no_filters = {
|
let serial_no_filters = {
|
||||||
item_code: me.item_code,
|
item_code: me.item_code,
|
||||||
|
batch_no: this.doc.batch_no || null,
|
||||||
delivery_document_no: ""
|
delivery_document_no: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if (me.warehouse_details.name) {
|
if (me.warehouse_details.name) {
|
||||||
serial_no_filters['warehouse'] = me.warehouse_details.name;
|
serial_no_filters['warehouse'] = me.warehouse_details.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{fieldtype: 'Section Break', label: __('Serial Numbers')},
|
{fieldtype: 'Section Break', label: __('Serial Numbers')},
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,6 @@ class Quotation(SellingController):
|
|||||||
super(Quotation, self).validate()
|
super(Quotation, self).validate()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.update_opportunity()
|
self.update_opportunity()
|
||||||
self.validate_order_type()
|
|
||||||
self.validate_uom_is_integer("stock_uom", "qty")
|
self.validate_uom_is_integer("stock_uom", "qty")
|
||||||
self.validate_valid_till()
|
self.validate_valid_till()
|
||||||
self.set_customer_name()
|
self.set_customer_name()
|
||||||
@ -40,9 +39,6 @@ class Quotation(SellingController):
|
|||||||
def has_sales_order(self):
|
def has_sales_order(self):
|
||||||
return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})
|
return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})
|
||||||
|
|
||||||
def validate_order_type(self):
|
|
||||||
super(Quotation, self).validate_order_type()
|
|
||||||
|
|
||||||
def update_lead(self):
|
def update_lead(self):
|
||||||
if self.quotation_to == "Lead" and self.party_name:
|
if self.quotation_to == "Lead" and self.party_name:
|
||||||
frappe.get_doc("Lead", self.party_name).set_status(update=True)
|
frappe.get_doc("Lead", self.party_name).set_status(update=True)
|
||||||
|
@ -34,7 +34,6 @@ class SalesOrder(SellingController):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(SalesOrder, self).validate()
|
super(SalesOrder, self).validate()
|
||||||
self.validate_order_type()
|
|
||||||
self.validate_delivery_date()
|
self.validate_delivery_date()
|
||||||
self.validate_proj_cust()
|
self.validate_proj_cust()
|
||||||
self.validate_po()
|
self.validate_po()
|
||||||
@ -100,9 +99,6 @@ class SalesOrder(SellingController):
|
|||||||
frappe.msgprint(_("Quotation {0} not of type {1}")
|
frappe.msgprint(_("Quotation {0} not of type {1}")
|
||||||
.format(d.prevdoc_docname, self.order_type))
|
.format(d.prevdoc_docname, self.order_type))
|
||||||
|
|
||||||
def validate_order_type(self):
|
|
||||||
super(SalesOrder, self).validate_order_type()
|
|
||||||
|
|
||||||
def validate_delivery_date(self):
|
def validate_delivery_date(self):
|
||||||
if self.order_type == 'Sales' and not self.skip_delivery_note:
|
if self.order_type == 'Sales' and not self.skip_delivery_note:
|
||||||
delivery_date_list = [d.delivery_date for d in self.get("items") if d.delivery_date]
|
delivery_date_list = [d.delivery_date for d in self.get("items") if d.delivery_date]
|
||||||
|
@ -321,7 +321,12 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
create_dn_against_so(so.name, 4)
|
create_dn_against_so(so.name, 4)
|
||||||
make_sales_invoice(so.name)
|
make_sales_invoice(so.name)
|
||||||
|
|
||||||
trans_item = json.dumps([{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 7}])
|
first_item_of_so = so.get("items")[0]
|
||||||
|
trans_item = json.dumps([
|
||||||
|
{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
|
||||||
|
'qty' : first_item_of_so.qty, 'docname': first_item_of_so.name},
|
||||||
|
{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 7}
|
||||||
|
])
|
||||||
update_child_qty_rate('Sales Order', trans_item, so.name)
|
update_child_qty_rate('Sales Order', trans_item, so.name)
|
||||||
|
|
||||||
so.reload()
|
so.reload()
|
||||||
@ -331,6 +336,48 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
self.assertEqual(so.get("items")[-1].amount, 1400)
|
self.assertEqual(so.get("items")[-1].amount, 1400)
|
||||||
self.assertEqual(so.status, 'To Deliver and Bill')
|
self.assertEqual(so.status, 'To Deliver and Bill')
|
||||||
|
|
||||||
|
def test_remove_item_in_update_child_qty_rate(self):
|
||||||
|
so = make_sales_order(**{
|
||||||
|
"item_list": [{
|
||||||
|
"item_code": '_Test Item',
|
||||||
|
"qty": 5,
|
||||||
|
"rate":1000
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
create_dn_against_so(so.name, 2)
|
||||||
|
make_sales_invoice(so.name)
|
||||||
|
|
||||||
|
# add an item so as to try removing items
|
||||||
|
trans_item = json.dumps([
|
||||||
|
{"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name},
|
||||||
|
{"item_code": '_Test Item 2', "qty": 2, "rate":500}
|
||||||
|
])
|
||||||
|
update_child_qty_rate('Sales Order', trans_item, so.name)
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(len(so.get("items")), 2)
|
||||||
|
|
||||||
|
# check if delivered items can be removed
|
||||||
|
trans_item = json.dumps([{
|
||||||
|
"item_code": '_Test Item 2',
|
||||||
|
"qty": 2,
|
||||||
|
"rate":500,
|
||||||
|
"docname": so.get("items")[1].name
|
||||||
|
}])
|
||||||
|
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Sales Order', trans_item, so.name)
|
||||||
|
|
||||||
|
#remove last added item
|
||||||
|
trans_item = json.dumps([{
|
||||||
|
"item_code": '_Test Item',
|
||||||
|
"qty": 5,
|
||||||
|
"rate":1000,
|
||||||
|
"docname": so.get("items")[0].name
|
||||||
|
}])
|
||||||
|
update_child_qty_rate('Sales Order', trans_item, so.name)
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(len(so.get("items")), 1)
|
||||||
|
self.assertEqual(so.status, 'To Deliver and Bill')
|
||||||
|
|
||||||
|
|
||||||
def test_update_child_qty_rate(self):
|
def test_update_child_qty_rate(self):
|
||||||
so = make_sales_order(item_code= "_Test Item", qty=4)
|
so = make_sales_order(item_code= "_Test Item", qty=4)
|
||||||
|
@ -77,10 +77,12 @@
|
|||||||
"ordered_qty",
|
"ordered_qty",
|
||||||
"planned_qty",
|
"planned_qty",
|
||||||
"column_break_69",
|
"column_break_69",
|
||||||
"delivered_qty",
|
|
||||||
"work_order_qty",
|
"work_order_qty",
|
||||||
|
"delivered_qty",
|
||||||
"produced_qty",
|
"produced_qty",
|
||||||
"returned_qty",
|
"returned_qty",
|
||||||
|
"shopping_cart_section",
|
||||||
|
"additional_notes",
|
||||||
"section_break_63",
|
"section_break_63",
|
||||||
"page_break",
|
"page_break",
|
||||||
"item_tax_rate",
|
"item_tax_rate",
|
||||||
@ -746,15 +748,20 @@
|
|||||||
"label": "Image"
|
"label": "Image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"collapsible": 1,
|
||||||
"fieldname": "against_blanket_order",
|
"fieldname": "shopping_cart_section",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Section Break",
|
||||||
"label": "Against Blanket Order"
|
"label": "Shopping Cart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "additional_notes",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Additional Notes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-11-19 14:19:29.491945",
|
"modified": "2019-12-11 18:06:26.238169",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Item",
|
"name": "Sales Order Item",
|
||||||
|
@ -261,15 +261,14 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False):
|
|||||||
|
|
||||||
|
|
||||||
def get_batches(item_code, warehouse, qty=1, throw=False):
|
def get_batches(item_code, warehouse, qty=1, throw=False):
|
||||||
batches = frappe.db.sql(
|
|
||||||
'select batch_id, sum(actual_qty) as qty from `tabBatch` join `tabStock Ledger Entry` ignore index (item_code, warehouse) '
|
|
||||||
'on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )'
|
|
||||||
'where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s '
|
|
||||||
'and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL)'
|
|
||||||
'group by batch_id '
|
|
||||||
'order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC',
|
|
||||||
(item_code, warehouse),
|
|
||||||
as_dict=True
|
|
||||||
)
|
|
||||||
|
|
||||||
return batches
|
return frappe.db.sql("""
|
||||||
|
select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty
|
||||||
|
from `tabBatch`
|
||||||
|
join `tabStock Ledger Entry` ignore index (item_code, warehouse)
|
||||||
|
on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
|
||||||
|
where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
|
||||||
|
and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL)
|
||||||
|
group by batch_id
|
||||||
|
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
|
||||||
|
""", (item_code, warehouse), as_dict=True)
|
@ -79,6 +79,21 @@ frappe.ui.form.on('Delivery Trip', {
|
|||||||
}, () => {
|
}, () => {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
driver: function (frm) {
|
||||||
|
if (frm.doc.driver) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.get_driver_email",
|
||||||
|
args: {
|
||||||
|
driver: frm.doc.driver
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
frm.set_value("driver_email", data.message.email);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2017-10-16 16:45:48.293335",
|
"creation": "2017-10-16 16:45:48.293335",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -13,6 +14,7 @@
|
|||||||
"section_break_3",
|
"section_break_3",
|
||||||
"driver",
|
"driver",
|
||||||
"driver_name",
|
"driver_name",
|
||||||
|
"driver_email",
|
||||||
"driver_address",
|
"driver_address",
|
||||||
"total_distance",
|
"total_distance",
|
||||||
"uom",
|
"uom",
|
||||||
@ -167,10 +169,17 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Driver Address",
|
"label": "Driver Address",
|
||||||
"options": "Address"
|
"options": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "driver_email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Driver Email",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-09-27 15:43:01.975139",
|
"links": [],
|
||||||
|
"modified": "2019-12-06 17:06:59.681952",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Trip",
|
"name": "Delivery Trip",
|
||||||
|
@ -387,3 +387,9 @@ def get_attachments(delivery_stop):
|
|||||||
file_name="Delivery Note", print_format=dispatch_attachment)
|
file_name="Delivery Note", print_format=dispatch_attachment)
|
||||||
|
|
||||||
return [attachments]
|
return [attachments]
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_driver_email(driver):
|
||||||
|
employee = frappe.db.get_value("Driver", driver, "employee")
|
||||||
|
email = frappe.db.get_value("Employee", employee, "prefered_email")
|
||||||
|
return {"email": email}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2013-05-21 16:16:39",
|
"creation": "2013-05-21 16:16:39",
|
||||||
@ -305,6 +306,7 @@
|
|||||||
"fieldname": "contact_email",
|
"fieldname": "contact_email",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
|
"options": "Email",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1056,7 +1058,8 @@
|
|||||||
"icon": "fa fa-truck",
|
"icon": "fa fa-truck",
|
||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-09-27 14:24:49.044505",
|
"links": [],
|
||||||
|
"modified": "2019-12-24 12:52:17.216304",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
@ -610,19 +610,28 @@ def make_stock_entry(source_name,target_doc=None):
|
|||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
def get_item_account_wise_additional_cost(purchase_document):
|
def get_item_account_wise_additional_cost(purchase_document):
|
||||||
landed_cost_voucher = frappe.get_value("Landed Cost Purchase Receipt",
|
landed_cost_vouchers = frappe.get_all("Landed Cost Purchase Receipt", fields=["parent"],
|
||||||
{"receipt_document": purchase_document, "docstatus": 1}, "parent")
|
filters = {"receipt_document": purchase_document, "docstatus": 1})
|
||||||
|
|
||||||
if not landed_cost_voucher:
|
if not landed_cost_vouchers:
|
||||||
return
|
return
|
||||||
|
|
||||||
total_item_cost = 0
|
total_item_cost = 0
|
||||||
item_account_wise_cost = {}
|
item_account_wise_cost = {}
|
||||||
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", landed_cost_voucher)
|
item_cost_allocated = []
|
||||||
|
|
||||||
|
for lcv in landed_cost_vouchers:
|
||||||
|
landed_cost_voucher_doc = frappe.get_cached_doc("Landed Cost Voucher", lcv.parent)
|
||||||
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
|
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
|
||||||
|
|
||||||
for item in landed_cost_voucher_doc.items:
|
for item in landed_cost_voucher_doc.items:
|
||||||
|
if item.purchase_receipt_item not in item_cost_allocated:
|
||||||
total_item_cost += item.get(based_on_field)
|
total_item_cost += item.get(based_on_field)
|
||||||
|
item_cost_allocated.append(item.purchase_receipt_item)
|
||||||
|
|
||||||
|
for lcv in landed_cost_vouchers:
|
||||||
|
landed_cost_voucher_doc = frappe.get_cached_doc("Landed Cost Voucher", lcv.parent)
|
||||||
|
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
|
||||||
|
|
||||||
for item in landed_cost_voucher_doc.items:
|
for item in landed_cost_voucher_doc.items:
|
||||||
if item.receipt_document == purchase_document:
|
if item.receipt_document == purchase_document:
|
||||||
|
@ -808,24 +808,26 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
if self.bom_no:
|
if self.bom_no:
|
||||||
|
|
||||||
|
backflush_based_on = frappe.db.get_single_value("Manufacturing Settings",
|
||||||
|
"backflush_raw_materials_based_on")
|
||||||
|
|
||||||
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack",
|
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack",
|
||||||
"Send to Subcontractor", "Material Transfer for Manufacture", "Material Consumption for Manufacture"]:
|
"Send to Subcontractor", "Material Transfer for Manufacture", "Material Consumption for Manufacture"]:
|
||||||
|
|
||||||
if self.work_order and self.purpose == "Material Transfer for Manufacture":
|
if self.work_order and self.purpose == "Material Transfer for Manufacture":
|
||||||
item_dict = self.get_pending_raw_materials()
|
item_dict = self.get_pending_raw_materials(backflush_based_on)
|
||||||
if self.to_warehouse and self.pro_doc:
|
if self.to_warehouse and self.pro_doc:
|
||||||
for item in itervalues(item_dict):
|
for item in itervalues(item_dict):
|
||||||
item["to_warehouse"] = self.pro_doc.wip_warehouse
|
item["to_warehouse"] = self.pro_doc.wip_warehouse
|
||||||
self.add_to_stock_entry_detail(item_dict)
|
self.add_to_stock_entry_detail(item_dict)
|
||||||
|
|
||||||
elif (self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
|
elif (self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
|
||||||
and not self.pro_doc.skip_transfer and frappe.db.get_single_value("Manufacturing Settings",
|
and not self.pro_doc.skip_transfer and backflush_based_on == "Material Transferred for Manufacture"):
|
||||||
"backflush_raw_materials_based_on")== "Material Transferred for Manufacture"):
|
|
||||||
self.get_transfered_raw_materials()
|
self.get_transfered_raw_materials()
|
||||||
|
|
||||||
elif self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture") and \
|
elif (self.work_order and backflush_based_on== "BOM" and
|
||||||
frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")== "BOM" and \
|
(self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
|
||||||
frappe.db.get_single_value("Manufacturing Settings", "material_consumption")== 1:
|
and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")== 1):
|
||||||
self.get_unconsumed_raw_materials()
|
self.get_unconsumed_raw_materials()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -1034,10 +1036,6 @@ class StockEntry(StockController):
|
|||||||
filters={'parent': self.work_order, 'item_code': item_code},
|
filters={'parent': self.work_order, 'item_code': item_code},
|
||||||
fields=["required_qty", "consumed_qty"]
|
fields=["required_qty", "consumed_qty"]
|
||||||
)
|
)
|
||||||
if not req_items:
|
|
||||||
frappe.msgprint(_("Did not found transfered item {0} in Work Order {1}, the item not added in Stock Entry")
|
|
||||||
.format(item_code, self.work_order))
|
|
||||||
continue
|
|
||||||
|
|
||||||
req_qty = flt(req_items[0].required_qty)
|
req_qty = flt(req_items[0].required_qty)
|
||||||
req_qty_each = flt(req_qty / manufacturing_qty)
|
req_qty_each = flt(req_qty / manufacturing_qty)
|
||||||
@ -1085,18 +1083,20 @@ class StockEntry(StockController):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_pending_raw_materials(self):
|
def get_pending_raw_materials(self, backflush_based_on=None):
|
||||||
"""
|
"""
|
||||||
issue (item quantity) that is pending to issue or desire to transfer,
|
issue (item quantity) that is pending to issue or desire to transfer,
|
||||||
whichever is less
|
whichever is less
|
||||||
"""
|
"""
|
||||||
item_dict = self.get_pro_order_required_items()
|
item_dict = self.get_pro_order_required_items(backflush_based_on)
|
||||||
|
|
||||||
max_qty = flt(self.pro_doc.qty)
|
max_qty = flt(self.pro_doc.qty)
|
||||||
for item, item_details in iteritems(item_dict):
|
for item, item_details in iteritems(item_dict):
|
||||||
pending_to_issue = flt(item_details.required_qty) - flt(item_details.transferred_qty)
|
pending_to_issue = flt(item_details.required_qty) - flt(item_details.transferred_qty)
|
||||||
desire_to_transfer = flt(self.fg_completed_qty) * flt(item_details.required_qty) / max_qty
|
desire_to_transfer = flt(self.fg_completed_qty) * flt(item_details.required_qty) / max_qty
|
||||||
|
|
||||||
if desire_to_transfer <= pending_to_issue:
|
if (desire_to_transfer <= pending_to_issue or
|
||||||
|
(desire_to_transfer > 0 and backflush_based_on == "Material Transferred for Manufacture")):
|
||||||
item_dict[item]["qty"] = desire_to_transfer
|
item_dict[item]["qty"] = desire_to_transfer
|
||||||
elif pending_to_issue > 0:
|
elif pending_to_issue > 0:
|
||||||
item_dict[item]["qty"] = pending_to_issue
|
item_dict[item]["qty"] = pending_to_issue
|
||||||
@ -1114,7 +1114,7 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
return item_dict
|
return item_dict
|
||||||
|
|
||||||
def get_pro_order_required_items(self):
|
def get_pro_order_required_items(self, backflush_based_on=None):
|
||||||
item_dict = frappe._dict()
|
item_dict = frappe._dict()
|
||||||
pro_order = frappe.get_doc("Work Order", self.work_order)
|
pro_order = frappe.get_doc("Work Order", self.work_order)
|
||||||
if not frappe.db.get_value("Warehouse", pro_order.wip_warehouse, "is_group"):
|
if not frappe.db.get_value("Warehouse", pro_order.wip_warehouse, "is_group"):
|
||||||
@ -1123,7 +1123,8 @@ class StockEntry(StockController):
|
|||||||
wip_warehouse = None
|
wip_warehouse = None
|
||||||
|
|
||||||
for d in pro_order.get("required_items"):
|
for d in pro_order.get("required_items"):
|
||||||
if (flt(d.required_qty) > flt(d.transferred_qty) and
|
if ( ((flt(d.required_qty) > flt(d.transferred_qty)) or
|
||||||
|
(backflush_based_on == "Material Transferred for Manufacture")) and
|
||||||
(d.include_item_in_manufacturing or self.purpose != "Material Transfer for Manufacture")):
|
(d.include_item_in_manufacturing or self.purpose != "Material Transfer for Manufacture")):
|
||||||
item_row = d.as_dict()
|
item_row = d.as_dict()
|
||||||
if d.source_warehouse and not frappe.db.get_value("Warehouse", d.source_warehouse, "is_group"):
|
if d.source_warehouse and not frappe.db.get_value("Warehouse", d.source_warehouse, "is_group"):
|
||||||
|
@ -16,6 +16,29 @@ frappe.query_reports["Batch-Wise Balance History"] = {
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"width": "80",
|
"width": "80",
|
||||||
"default": frappe.datetime.get_today()
|
"default": frappe.datetime.get_today()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item",
|
||||||
|
"label": __("Item"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Item",
|
||||||
|
"width": "80"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatter": function (value, row, column, data, default_formatter) {
|
||||||
|
if (column.fieldname == "Batch" && data && !!data["Batch"]) {
|
||||||
|
value = data["Batch"];
|
||||||
|
column.link_onclick = "frappe.query_reports['Batch-Wise Balance History'].set_batch_route_to_stock_ledger(" + JSON.stringify(data) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
"set_batch_route_to_stock_ledger": function (data) {
|
||||||
|
frappe.route_options = {
|
||||||
|
"batch_no": data["Batch"]
|
||||||
|
};
|
||||||
|
|
||||||
|
frappe.set_route("query-report", "Stock Ledger");
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
@ -2,9 +2,11 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt, cint, getdate
|
from frappe.utils import cint, flt, getdate
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
if not filters: filters = {}
|
if not filters: filters = {}
|
||||||
@ -17,6 +19,7 @@ def execute(filters=None):
|
|||||||
|
|
||||||
data = []
|
data = []
|
||||||
for item in sorted(iwb_map):
|
for item in sorted(iwb_map):
|
||||||
|
if not filters.get("item") or filters.get("item") == item:
|
||||||
for wh in sorted(iwb_map[item]):
|
for wh in sorted(iwb_map[item]):
|
||||||
for batch in sorted(iwb_map[item][wh]):
|
for batch in sorted(iwb_map[item][wh]):
|
||||||
qty_dict = iwb_map[item][wh][batch]
|
qty_dict = iwb_map[item][wh][batch]
|
||||||
@ -29,6 +32,7 @@ def execute(filters=None):
|
|||||||
|
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
"""return columns based on filters"""
|
"""return columns based on filters"""
|
||||||
|
|
||||||
@ -37,9 +41,9 @@ def get_columns(filters):
|
|||||||
[_("In Qty") + ":Float:80"] + [_("Out Qty") + ":Float:80"] + [_("Balance Qty") + ":Float:90"] + \
|
[_("In Qty") + ":Float:80"] + [_("Out Qty") + ":Float:80"] + [_("Balance Qty") + ":Float:90"] + \
|
||||||
[_("UOM") + "::90"]
|
[_("UOM") + "::90"]
|
||||||
|
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
if not filters.get("from_date"):
|
if not filters.get("from_date"):
|
||||||
@ -52,6 +56,7 @@ def get_conditions(filters):
|
|||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
|
|
||||||
# get all details
|
# get all details
|
||||||
def get_stock_ledger_entries(filters):
|
def get_stock_ledger_entries(filters):
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters)
|
||||||
@ -63,6 +68,7 @@ def get_stock_ledger_entries(filters):
|
|||||||
order by item_code, warehouse""" %
|
order by item_code, warehouse""" %
|
||||||
conditions, as_dict=1)
|
conditions, as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def get_item_warehouse_batch_map(filters, float_precision):
|
def get_item_warehouse_batch_map(filters, float_precision):
|
||||||
sle = get_stock_ledger_entries(filters)
|
sle = get_stock_ledger_entries(filters)
|
||||||
iwb_map = {}
|
iwb_map = {}
|
||||||
@ -90,6 +96,7 @@ def get_item_warehouse_batch_map(filters, float_precision):
|
|||||||
|
|
||||||
return iwb_map
|
return iwb_map
|
||||||
|
|
||||||
|
|
||||||
def get_item_details(filters):
|
def get_item_details(filters):
|
||||||
item_map = {}
|
item_map = {}
|
||||||
for d in frappe.db.sql("select name, item_name, description, stock_uom from tabItem", as_dict=1):
|
for d in frappe.db.sql("select name, item_name, description, stock_uom from tabItem", as_dict=1):
|
||||||
|
@ -77,7 +77,15 @@ frappe.query_reports["Stock Ledger"] = {
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "UOM"
|
"options": "UOM"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"formatter": function (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
if (column.fieldname == "out_qty" && data.out_qty < 0) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// $(function() {
|
// $(function() {
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
|
||||||
from erpnext.stock.utils import update_included_uom_in_report
|
from erpnext.stock.utils import update_included_uom_in_report
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
include_uom = filters.get("include_uom")
|
include_uom = filters.get("include_uom")
|
||||||
@ -36,7 +38,22 @@ def execute(filters=None):
|
|||||||
|
|
||||||
sle.update({
|
sle.update({
|
||||||
"qty_after_transaction": actual_qty,
|
"qty_after_transaction": actual_qty,
|
||||||
"stock_value": stock_value
|
"stock_value": stock_value,
|
||||||
|
"in_qty": max(sle.actual_qty, 0),
|
||||||
|
"out_qty": min(sle.actual_qty, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
# get the name of the item that was produced using this item
|
||||||
|
if sle.voucher_type == "Stock Entry":
|
||||||
|
purpose, work_order, fg_completed_qty = frappe.db.get_value(sle.voucher_type, sle.voucher_no, ["purpose", "work_order", "fg_completed_qty"])
|
||||||
|
|
||||||
|
if purpose == "Manufacture" and work_order:
|
||||||
|
finished_product = frappe.db.get_value("Work Order", work_order, "item_name")
|
||||||
|
finished_qty = fg_completed_qty
|
||||||
|
|
||||||
|
sle.update({
|
||||||
|
"finished_product": finished_product,
|
||||||
|
"finished_qty": finished_qty,
|
||||||
})
|
})
|
||||||
|
|
||||||
data.append(sle)
|
data.append(sle)
|
||||||
@ -47,53 +64,74 @@ def execute(filters=None):
|
|||||||
update_included_uom_in_report(columns, data, include_uom, conversion_factors)
|
update_included_uom_in_report(columns, data, include_uom, conversion_factors)
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
def get_columns():
|
def get_columns():
|
||||||
columns = [
|
columns = [
|
||||||
{"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 95},
|
{"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 150},
|
||||||
{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 130},
|
{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
|
||||||
{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
|
{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
|
||||||
|
{"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90},
|
||||||
|
{"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
|
||||||
|
{"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
|
||||||
|
{"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"},
|
||||||
|
{"label": _("Finished Product"), "fieldname": "finished_product", "width": 100},
|
||||||
|
{"label": _("Finished Qty"), "fieldname": "finished_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
|
||||||
|
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 150},
|
||||||
|
{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 150},
|
||||||
{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
|
{"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
|
||||||
{"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
|
{"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
|
||||||
{"label": _("Description"), "fieldname": "description", "width": 200},
|
{"label": _("Description"), "fieldname": "description", "width": 200},
|
||||||
{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100},
|
{"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
|
||||||
{"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 100},
|
{"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
|
||||||
{"label": _("Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 50, "convertible": "qty"},
|
{"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"},
|
||||||
{"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"},
|
|
||||||
{"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110,
|
|
||||||
"options": "Company:company:default_currency", "convertible": "rate"},
|
|
||||||
{"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110,
|
|
||||||
"options": "Company:company:default_currency", "convertible": "rate"},
|
|
||||||
{"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110,
|
|
||||||
"options": "Company:company:default_currency"},
|
|
||||||
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
|
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
|
||||||
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
|
{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
|
||||||
{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
|
{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
|
||||||
{"label": _("Serial #"), "fieldname": "serial_no", "width": 100},
|
{"label": _("Serial #"), "fieldname": "serial_no", "fieldtype": "Link", "options": "Serial No", "width": 100},
|
||||||
{"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
|
{"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
|
||||||
{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110}
|
{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110}
|
||||||
]
|
]
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def get_stock_ledger_entries(filters, items):
|
def get_stock_ledger_entries(filters, items):
|
||||||
item_conditions_sql = ''
|
item_conditions_sql = ''
|
||||||
if items:
|
if items:
|
||||||
item_conditions_sql = 'and sle.item_code in ({})'\
|
item_conditions_sql = 'and sle.item_code in ({})'\
|
||||||
.format(', '.join([frappe.db.escape(i) for i in items]))
|
.format(', '.join([frappe.db.escape(i) for i in items]))
|
||||||
|
|
||||||
return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date,
|
sl_entries = frappe.db.sql("""
|
||||||
item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate,
|
SELECT
|
||||||
stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project, stock_value_difference
|
concat_ws(" ", posting_date, posting_time) AS date,
|
||||||
from `tabStock Ledger Entry` sle
|
item_code,
|
||||||
where company = %(company)s and
|
warehouse,
|
||||||
posting_date between %(from_date)s and %(to_date)s
|
actual_qty,
|
||||||
|
qty_after_transaction,
|
||||||
|
incoming_rate,
|
||||||
|
valuation_rate,
|
||||||
|
stock_value,
|
||||||
|
voucher_type,
|
||||||
|
voucher_no,
|
||||||
|
batch_no,
|
||||||
|
serial_no,
|
||||||
|
company,
|
||||||
|
project,
|
||||||
|
stock_value_difference
|
||||||
|
FROM
|
||||||
|
`tabStock Ledger Entry` sle
|
||||||
|
WHERE
|
||||||
|
company = %(company)s
|
||||||
|
AND posting_date BETWEEN %(from_date)s AND %(to_date)s
|
||||||
{sle_conditions}
|
{sle_conditions}
|
||||||
{item_conditions_sql}
|
{item_conditions_sql}
|
||||||
order by posting_date asc, posting_time asc, creation asc"""\
|
ORDER BY
|
||||||
.format(
|
posting_date asc, posting_time asc, creation asc
|
||||||
sle_conditions=get_sle_conditions(filters),
|
""".format(sle_conditions=get_sle_conditions(filters), item_conditions_sql=item_conditions_sql),
|
||||||
item_conditions_sql = item_conditions_sql
|
filters, as_dict=1)
|
||||||
), filters, as_dict=1)
|
|
||||||
|
return sl_entries
|
||||||
|
|
||||||
|
|
||||||
def get_items(filters):
|
def get_items(filters):
|
||||||
conditions = []
|
conditions = []
|
||||||
@ -111,6 +149,7 @@ def get_items(filters):
|
|||||||
.format(" and ".join(conditions)), filters)
|
.format(" and ".join(conditions)), filters)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
def get_item_details(items, sl_entries, include_uom):
|
def get_item_details(items, sl_entries, include_uom):
|
||||||
item_details = {}
|
item_details = {}
|
||||||
if not items:
|
if not items:
|
||||||
@ -140,6 +179,7 @@ def get_item_details(items, sl_entries, include_uom):
|
|||||||
|
|
||||||
return item_details
|
return item_details
|
||||||
|
|
||||||
|
|
||||||
def get_sle_conditions(filters):
|
def get_sle_conditions(filters):
|
||||||
conditions = []
|
conditions = []
|
||||||
if filters.get("warehouse"):
|
if filters.get("warehouse"):
|
||||||
@ -155,6 +195,7 @@ def get_sle_conditions(filters):
|
|||||||
|
|
||||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||||
|
|
||||||
|
|
||||||
def get_opening_balance(filters, columns):
|
def get_opening_balance(filters, columns):
|
||||||
if not (filters.item_code and filters.warehouse and filters.from_date):
|
if not (filters.item_code and filters.warehouse and filters.from_date):
|
||||||
return
|
return
|
||||||
@ -166,13 +207,17 @@ def get_opening_balance(filters, columns):
|
|||||||
"posting_date": filters.from_date,
|
"posting_date": filters.from_date,
|
||||||
"posting_time": "00:00:00"
|
"posting_time": "00:00:00"
|
||||||
})
|
})
|
||||||
row = {}
|
|
||||||
row["item_code"] = _("'Opening'")
|
row = {
|
||||||
for dummy, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
|
"item_code": _("'Opening'"),
|
||||||
row[v] = last_entry.get(v, 0)
|
"qty_after_transaction": last_entry.get("qty_after_transaction", 0),
|
||||||
|
"valuation_rate": last_entry.get("valuation_rate", 0),
|
||||||
|
"stock_value": last_entry.get("stock_value", 0)
|
||||||
|
}
|
||||||
|
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
def get_warehouse_condition(warehouse):
|
def get_warehouse_condition(warehouse):
|
||||||
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
|
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
|
||||||
if warehouse_details:
|
if warehouse_details:
|
||||||
@ -182,6 +227,7 @@ def get_warehouse_condition(warehouse):
|
|||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def get_item_group_condition(item_group):
|
def get_item_group_condition(item_group):
|
||||||
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
|
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
|
||||||
if item_group_details:
|
if item_group_details:
|
||||||
|
@ -64,16 +64,6 @@ frappe.ready(() => {
|
|||||||
fieldtype: 'Data',
|
fieldtype: 'Data',
|
||||||
reqd: 1
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: __('Address Type'),
|
|
||||||
fieldname: 'address_type',
|
|
||||||
fieldtype: 'Select',
|
|
||||||
options: [
|
|
||||||
'Billing',
|
|
||||||
'Shipping'
|
|
||||||
],
|
|
||||||
reqd: 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: __('Address Line 1'),
|
label: __('Address Line 1'),
|
||||||
fieldname: 'address_line1',
|
fieldname: 'address_line1',
|
||||||
@ -96,16 +86,37 @@ frappe.ready(() => {
|
|||||||
fieldname: 'state',
|
fieldname: 'state',
|
||||||
fieldtype: 'Data'
|
fieldtype: 'Data'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: __('Country'),
|
||||||
|
fieldname: 'country',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Country',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "column_break0",
|
||||||
|
fieldtype: "Column Break",
|
||||||
|
width: "50%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Address Type'),
|
||||||
|
fieldname: 'address_type',
|
||||||
|
fieldtype: 'Select',
|
||||||
|
options: [
|
||||||
|
'Billing',
|
||||||
|
'Shipping'
|
||||||
|
],
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: __('Pin Code'),
|
label: __('Pin Code'),
|
||||||
fieldname: 'pincode',
|
fieldname: 'pincode',
|
||||||
fieldtype: 'Data'
|
fieldtype: 'Data'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __('Country'),
|
fieldname: "phone",
|
||||||
fieldname: 'country',
|
fieldtype: "Data",
|
||||||
fieldtype: 'Link',
|
label: "Phone"
|
||||||
reqd: 1
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
primary_action_label: __('Save'),
|
primary_action_label: __('Save'),
|
||||||
|
@ -83,12 +83,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if cart_settings.enable_checkout %}
|
|
||||||
<div class="cart-addresses mt-5">
|
<div class="cart-addresses mt-5">
|
||||||
{% include "templates/includes/cart/cart_address.html" %}
|
{% include "templates/includes/cart/cart_address.html" %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
|
Loading…
Reference in New Issue
Block a user