mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-13 10:11:20 +00:00
Merge branch 'v12-pre-release' into version-12
This commit is contained in:
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '12.25.0'
|
__version__ = '12.26.0'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@@ -36,5 +36,20 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject",
|
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject",
|
||||||
"default": "Invoice"
|
"default": "Invoice"
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
"tree": true,
|
||||||
|
"name_field": "parent",
|
||||||
|
"parent_field": "parent_invoice",
|
||||||
|
"initial_depth": 3,
|
||||||
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (data && data.indent == 0.0) {
|
||||||
|
value = $(`<span>${value}</span>`);
|
||||||
|
var $value = $(value).css("font-weight", "bold");
|
||||||
|
value = $value.wrap("<p></p>").parent().html();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,34 @@ def execute(filters=None):
|
|||||||
|
|
||||||
columns = get_columns(group_wise_columns, filters)
|
columns = get_columns(group_wise_columns, filters)
|
||||||
|
|
||||||
|
if filters.group_by == 'Invoice':
|
||||||
|
get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data):
|
||||||
|
column_names = get_column_names()
|
||||||
|
|
||||||
|
# to display item as Item Code: Item Name
|
||||||
|
columns[0] = 'Sales Invoice:Link/Item:300'
|
||||||
|
# removing Item Code and Item Name columns
|
||||||
|
del columns[4:6]
|
||||||
|
|
||||||
|
for src in gross_profit_data.si_list:
|
||||||
|
row = frappe._dict()
|
||||||
|
row.indent = src.indent
|
||||||
|
row.parent_invoice = src.parent_invoice
|
||||||
|
row.currency = filters.currency
|
||||||
|
|
||||||
|
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||||
|
row[column_names[col]] = src.get(col)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
|
||||||
for idx, src in enumerate(gross_profit_data.grouped_data):
|
for idx, src in enumerate(gross_profit_data.grouped_data):
|
||||||
row = []
|
row = []
|
||||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||||
@@ -51,8 +79,6 @@ def execute(filters=None):
|
|||||||
row[0] = frappe.bold("Total")
|
row[0] = frappe.bold("Total")
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return columns, data
|
|
||||||
|
|
||||||
def get_columns(group_wise_columns, filters):
|
def get_columns(group_wise_columns, filters):
|
||||||
columns = []
|
columns = []
|
||||||
column_map = frappe._dict({
|
column_map = frappe._dict({
|
||||||
@@ -93,12 +119,38 @@ def get_columns(group_wise_columns, filters):
|
|||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
def get_column_names():
|
||||||
|
return frappe._dict({
|
||||||
|
'parent': 'sales_invoice',
|
||||||
|
'customer': 'customer',
|
||||||
|
'customer_group': 'customer_group',
|
||||||
|
'posting_date': 'posting_date',
|
||||||
|
'item_code': 'item_code',
|
||||||
|
'item_name': 'item_name',
|
||||||
|
'item_group': 'item_group',
|
||||||
|
'brand': 'brand',
|
||||||
|
'description': 'description',
|
||||||
|
'warehouse': 'warehouse',
|
||||||
|
'qty': 'qty',
|
||||||
|
'base_rate': 'avg._selling_rate',
|
||||||
|
'buying_rate': 'valuation_rate',
|
||||||
|
'base_amount': 'selling_amount',
|
||||||
|
'buying_amount': 'buying_amount',
|
||||||
|
'gross_profit': 'gross_profit',
|
||||||
|
'gross_profit_percent': 'gross_profit_%',
|
||||||
|
'project': 'project'
|
||||||
|
})
|
||||||
|
|
||||||
class GrossProfitGenerator(object):
|
class GrossProfitGenerator(object):
|
||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
self.data = []
|
self.data = []
|
||||||
self.average_buying_rate = {}
|
self.average_buying_rate = {}
|
||||||
self.filters = frappe._dict(filters)
|
self.filters = frappe._dict(filters)
|
||||||
self.load_invoice_items()
|
self.load_invoice_items()
|
||||||
|
|
||||||
|
if filters.group_by == 'Invoice':
|
||||||
|
self.group_items_by_invoice()
|
||||||
|
|
||||||
self.load_stock_ledger_entries()
|
self.load_stock_ledger_entries()
|
||||||
self.load_product_bundle()
|
self.load_product_bundle()
|
||||||
self.load_non_stock_items()
|
self.load_non_stock_items()
|
||||||
@@ -112,7 +164,12 @@ class GrossProfitGenerator(object):
|
|||||||
self.currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
|
self.currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
|
||||||
self.float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
self.float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||||
|
|
||||||
for row in self.si_list:
|
grouped_by_invoice = True if self.filters.get("group_by") == "Invoice" else False
|
||||||
|
|
||||||
|
if grouped_by_invoice:
|
||||||
|
buying_amount = 0
|
||||||
|
|
||||||
|
for row in reversed(self.si_list):
|
||||||
if self.skip_row(row, self.product_bundles):
|
if self.skip_row(row, self.product_bundles):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -134,12 +191,20 @@ class GrossProfitGenerator(object):
|
|||||||
row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
|
row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
|
||||||
self.currency_precision)
|
self.currency_precision)
|
||||||
|
|
||||||
|
if grouped_by_invoice:
|
||||||
|
if row.indent == 1.0:
|
||||||
|
buying_amount += row.buying_amount
|
||||||
|
elif row.indent == 0.0:
|
||||||
|
row.buying_amount = buying_amount
|
||||||
|
buying_amount = 0
|
||||||
|
|
||||||
# get buying rate
|
# get buying rate
|
||||||
if row.qty:
|
if row.qty:
|
||||||
row.buying_rate = flt(row.buying_amount / row.qty, self.float_precision)
|
row.buying_rate = flt(row.buying_amount / row.qty, self.float_precision)
|
||||||
row.base_rate = flt(row.base_amount / row.qty, self.float_precision)
|
row.base_rate = flt(row.base_amount / row.qty, self.float_precision)
|
||||||
else:
|
else:
|
||||||
row.buying_rate, row.base_rate = 0.0, 0.0
|
if self.is_not_invoice_row(row):
|
||||||
|
row.buying_rate, row.base_rate = 0.0, 0.0
|
||||||
|
|
||||||
# calculate gross profit
|
# calculate gross profit
|
||||||
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
|
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
|
||||||
@@ -185,14 +250,17 @@ class GrossProfitGenerator(object):
|
|||||||
for returned_item_row in returned_item_rows:
|
for returned_item_row in returned_item_rows:
|
||||||
row.qty += returned_item_row.qty
|
row.qty += returned_item_row.qty
|
||||||
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
||||||
row.buying_amount = flt(row.qty * row.buying_rate, self.currency_precision)
|
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
||||||
if row.qty or row.base_amount:
|
if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row):
|
||||||
row = self.set_average_rate(row)
|
row = self.set_average_rate(row)
|
||||||
self.grouped_data.append(row)
|
self.grouped_data.append(row)
|
||||||
self.add_to_totals(row)
|
self.add_to_totals(row)
|
||||||
self.set_average_gross_profit(self.totals)
|
self.set_average_gross_profit(self.totals)
|
||||||
self.grouped_data.append(self.totals)
|
self.grouped_data.append(self.totals)
|
||||||
|
|
||||||
|
def is_not_invoice_row(self, row):
|
||||||
|
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
|
||||||
|
|
||||||
def set_average_rate(self, new_row):
|
def set_average_rate(self, new_row):
|
||||||
self.set_average_gross_profit(new_row)
|
self.set_average_gross_profit(new_row)
|
||||||
new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
||||||
@@ -356,6 +424,109 @@ class GrossProfitGenerator(object):
|
|||||||
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
||||||
sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1)
|
sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1)
|
||||||
|
|
||||||
|
def group_items_by_invoice(self):
|
||||||
|
"""
|
||||||
|
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parents = []
|
||||||
|
|
||||||
|
for row in self.si_list:
|
||||||
|
if row.parent not in parents:
|
||||||
|
parents.append(row.parent)
|
||||||
|
|
||||||
|
parents_index = 0
|
||||||
|
for index, row in enumerate(self.si_list):
|
||||||
|
if parents_index < len(parents) and row.parent == parents[parents_index]:
|
||||||
|
invoice = self.get_invoice_row(row)
|
||||||
|
self.si_list.insert(index, invoice)
|
||||||
|
parents_index += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# skipping the bundle items rows
|
||||||
|
if not row.indent:
|
||||||
|
row.indent = 1.0
|
||||||
|
row.parent_invoice = row.parent
|
||||||
|
row.parent = row.item_code
|
||||||
|
|
||||||
|
if frappe.db.exists('Product Bundle', row.item_code):
|
||||||
|
self.add_bundle_items(row, index)
|
||||||
|
|
||||||
|
def get_invoice_row(self, row):
|
||||||
|
return frappe._dict({
|
||||||
|
'parent_invoice': "",
|
||||||
|
'indent': 0.0,
|
||||||
|
'parent': row.parent,
|
||||||
|
'posting_date': row.posting_date,
|
||||||
|
'posting_time': row.posting_time,
|
||||||
|
'project': row.project,
|
||||||
|
'update_stock': row.update_stock,
|
||||||
|
'customer': row.customer,
|
||||||
|
'customer_group': row.customer_group,
|
||||||
|
'item_code': None,
|
||||||
|
'item_name': None,
|
||||||
|
'description': None,
|
||||||
|
'warehouse': None,
|
||||||
|
'item_group': None,
|
||||||
|
'brand': None,
|
||||||
|
'dn_detail': None,
|
||||||
|
'delivery_note': None,
|
||||||
|
'qty': None,
|
||||||
|
'item_row': None,
|
||||||
|
'is_return': row.is_return,
|
||||||
|
'cost_center': row.cost_center,
|
||||||
|
'base_net_amount': frappe.db.get_value('Sales Invoice', row.parent, 'base_net_total')
|
||||||
|
})
|
||||||
|
|
||||||
|
def add_bundle_items(self, product_bundle, index):
|
||||||
|
bundle_items = self.get_bundle_items(product_bundle)
|
||||||
|
|
||||||
|
for i, item in enumerate(bundle_items):
|
||||||
|
bundle_item = self.get_bundle_item_row(product_bundle, item)
|
||||||
|
self.si_list.insert((index+i+1), bundle_item)
|
||||||
|
|
||||||
|
def get_bundle_items(self, product_bundle):
|
||||||
|
return frappe.get_all(
|
||||||
|
'Product Bundle Item',
|
||||||
|
filters = {
|
||||||
|
'parent': product_bundle.item_code
|
||||||
|
},
|
||||||
|
fields = ['item_code', 'qty']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_bundle_item_row(self, product_bundle, item):
|
||||||
|
item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code)
|
||||||
|
|
||||||
|
return frappe._dict({
|
||||||
|
'parent_invoice': product_bundle.item_code,
|
||||||
|
'indent': product_bundle.indent + 1,
|
||||||
|
'parent': item.item_code,
|
||||||
|
'posting_date': product_bundle.posting_date,
|
||||||
|
'posting_time': product_bundle.posting_time,
|
||||||
|
'project': product_bundle.project,
|
||||||
|
'customer': product_bundle.customer,
|
||||||
|
'customer_group': product_bundle.customer_group,
|
||||||
|
'item_code': item.item_code,
|
||||||
|
'item_name': item_name,
|
||||||
|
'description': description,
|
||||||
|
'warehouse': product_bundle.warehouse,
|
||||||
|
'item_group': item_group,
|
||||||
|
'brand': brand,
|
||||||
|
'dn_detail': product_bundle.dn_detail,
|
||||||
|
'delivery_note': product_bundle.delivery_note,
|
||||||
|
'qty': (flt(product_bundle.qty) * flt(item.qty)),
|
||||||
|
'item_row': None,
|
||||||
|
'is_return': product_bundle.is_return,
|
||||||
|
'cost_center': product_bundle.cost_center
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_bundle_item_details(self, item_code):
|
||||||
|
return frappe.db.get_value(
|
||||||
|
'Item',
|
||||||
|
item_code,
|
||||||
|
['item_name', 'description', 'item_group', 'brand']
|
||||||
|
)
|
||||||
|
|
||||||
def load_stock_ledger_entries(self):
|
def load_stock_ledger_entries(self):
|
||||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||||
|
|||||||
@@ -122,11 +122,6 @@ class Asset(AccountsController):
|
|||||||
if self.is_existing_asset:
|
if self.is_existing_asset:
|
||||||
return
|
return
|
||||||
|
|
||||||
docname = self.purchase_receipt or self.purchase_invoice
|
|
||||||
if docname:
|
|
||||||
doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
|
|
||||||
date = frappe.db.get_value(doctype, docname, 'posting_date')
|
|
||||||
|
|
||||||
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
|
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
|
||||||
frappe.throw(_("Available-for-use Date should be after purchase date"))
|
frappe.throw(_("Available-for-use Date should be after purchase date"))
|
||||||
|
|
||||||
@@ -404,9 +399,10 @@ class Asset(AccountsController):
|
|||||||
if accumulated_depreciation_after_full_schedule:
|
if accumulated_depreciation_after_full_schedule:
|
||||||
accumulated_depreciation_after_full_schedule = max(accumulated_depreciation_after_full_schedule)
|
accumulated_depreciation_after_full_schedule = max(accumulated_depreciation_after_full_schedule)
|
||||||
|
|
||||||
asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) -
|
asset_value_after_full_schedule = flt(
|
||||||
flt(accumulated_depreciation_after_full_schedule),
|
flt(self.gross_purchase_amount) -
|
||||||
self.precision('gross_purchase_amount'))
|
flt(self.opening_accumulated_depreciation) -
|
||||||
|
flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount'))
|
||||||
|
|
||||||
if (row.expected_value_after_useful_life and
|
if (row.expected_value_after_useful_life and
|
||||||
row.expected_value_after_useful_life < asset_value_after_full_schedule):
|
row.expected_value_after_useful_life < asset_value_after_full_schedule):
|
||||||
|
|||||||
8
erpnext/change_log/v12/v12_26_0.md
Normal file
8
erpnext/change_log/v12/v12_26_0.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
## ERPNext Version 12.26.0 Release Notes
|
||||||
|
|
||||||
|
### Fixes & Enhancements
|
||||||
|
- Make Gross Profit Report more readable ([#27124](https://github.com/frappe/erpnext/pull/27124))
|
||||||
|
- Set item uom as stock_uom if it isn't set ([#27623](https://github.com/frappe/erpnext/pull/27623))
|
||||||
|
- Adding empty row on new maintenance visit ([#27626](https://github.com/frappe/erpnext/pull/27626))
|
||||||
|
- Employee Leave Balance report should only consider ledgers of transaction type Leave Allocation ([#28017](https://github.com/frappe/erpnext/pull/28017))
|
||||||
|
- Validate if item exists on uploading items in stock reco ([#27538](https://github.com/frappe/erpnext/pull/27538))
|
||||||
@@ -83,10 +83,8 @@ def add_bank_accounts(response, bank, company):
|
|||||||
if not acc_subtype:
|
if not acc_subtype:
|
||||||
add_account_subtype(account["subtype"])
|
add_account_subtype(account["subtype"])
|
||||||
|
|
||||||
existing_bank_account = frappe.db.exists("Bank Account", {
|
bank_account_name = "{} - {}".format(account["name"], bank["bank_name"])
|
||||||
'account_name': account["name"],
|
existing_bank_account = frappe.db.exists("Bank Account", bank_account_name)
|
||||||
'bank': bank["bank_name"]
|
|
||||||
})
|
|
||||||
|
|
||||||
if not existing_bank_account:
|
if not existing_bank_account:
|
||||||
try:
|
try:
|
||||||
@@ -198,6 +196,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
|||||||
|
|
||||||
plaid = PlaidConnector(access_token)
|
plaid = PlaidConnector(access_token)
|
||||||
|
|
||||||
|
transactions = []
|
||||||
try:
|
try:
|
||||||
transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
|
transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
|
||||||
except ItemError as e:
|
except ItemError as e:
|
||||||
@@ -206,7 +205,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
|||||||
msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
|
msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
|
||||||
frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
|
frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
|
||||||
|
|
||||||
return transactions or []
|
return transactions
|
||||||
|
|
||||||
|
|
||||||
def new_bank_transaction(transaction):
|
def new_bank_transaction(transaction):
|
||||||
|
|||||||
@@ -18,5 +18,8 @@ frappe.ui.form.on('Shopify Log', {
|
|||||||
})
|
})
|
||||||
}).addClass('btn-primary');
|
}).addClass('btn-primary');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
|
||||||
|
frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
|
|||||||
frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
|
frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
|
||||||
|
frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
$.extend(erpnext_integrations.shopify_settings, {
|
$.extend(erpnext_integrations.shopify_settings, {
|
||||||
|
|||||||
@@ -6,16 +6,10 @@ frappe.ui.form.on('Salary Component', {
|
|||||||
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
|
|
||||||
var root_type = "Liability";
|
|
||||||
if (frm.doc.type == "Deduction") {
|
|
||||||
root_type = "Expense";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"is_group": 0,
|
"is_group": 0,
|
||||||
"company": d.company,
|
"company": d.company
|
||||||
"root_type": root_type
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -124,11 +124,12 @@ def get_allocated_and_expired_leaves(records, from_date, to_date):
|
|||||||
def get_leave_ledger_entries(from_date, to_date, employee, leave_type):
|
def get_leave_ledger_entries(from_date, to_date, employee, leave_type):
|
||||||
records= frappe.db.sql("""
|
records= frappe.db.sql("""
|
||||||
SELECT
|
SELECT
|
||||||
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type
|
employee, leave_type, from_date, to_date, leaves, transaction_name,
|
||||||
is_carry_forward, is_expired
|
transaction_type, is_carry_forward, is_expired
|
||||||
FROM `tabLeave Ledger Entry`
|
FROM `tabLeave Ledger Entry`
|
||||||
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
|
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
|
||||||
AND docstatus=1
|
AND docstatus=1
|
||||||
|
AND transaction_type = 'Leave Allocation'
|
||||||
AND (from_date between %(from_date)s AND %(to_date)s
|
AND (from_date between %(from_date)s AND %(to_date)s
|
||||||
OR to_date between %(from_date)s AND %(to_date)s
|
OR to_date between %(from_date)s AND %(to_date)s
|
||||||
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
|
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ frappe.ui.form.on('Maintenance Visit', {
|
|||||||
if (frm.doc.__islocal) {
|
if (frm.doc.__islocal) {
|
||||||
frm.set_value({mntc_date: frappe.datetime.get_today()});
|
frm.set_value({mntc_date: frappe.datetime.get_today()});
|
||||||
}
|
}
|
||||||
|
if (frm.doc.purposes.length && frm.doc.purposes[0].item_name == undefined) {
|
||||||
|
frm.clear_table("purposes");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
customer: function(frm) {
|
customer: function(frm) {
|
||||||
erpnext.utils.get_party_details(frm);
|
erpnext.utils.get_party_details(frm);
|
||||||
|
|||||||
@@ -686,4 +686,4 @@ erpnext.patches.v12_0.purchase_receipt_status
|
|||||||
erpnext.patches.v12_0.add_company_link_to_einvoice_settings
|
erpnext.patches.v12_0.add_company_link_to_einvoice_settings
|
||||||
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
||||||
erpnext.patches.v12_0.create_taxable_value_field_in_purchase_invoice
|
erpnext.patches.v12_0.create_taxable_value_field_in_purchase_invoice
|
||||||
erpnext.patches.v12_0.show_einvoice_irn_cancelled_field
|
erpnext.patches.v12_0.show_einvoice_irn_cancelled_field
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import frappe
|
|||||||
from frappe.model.utils.rename_field import rename_field
|
from frappe.model.utils.rename_field import rename_field
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
|
# updating column value to handle field change from Data to Currency
|
||||||
|
frappe.db.sql("update `tabBOM` set base_scrap_material_cost = '0' where trim(coalesce(base_scrap_material_cost, ''))= ''")
|
||||||
|
|
||||||
for doctype in ['BOM Explosion Item', 'BOM Item', 'Work Order Item', 'Item']:
|
for doctype in ['BOM Explosion Item', 'BOM Item', 'Work Order Item', 'Item']:
|
||||||
if frappe.db.has_column(doctype, 'allow_transfer_for_manufacture'):
|
if frappe.db.has_column(doctype, 'allow_transfer_for_manufacture'):
|
||||||
if doctype != 'Item':
|
if doctype != 'Item':
|
||||||
@@ -26,4 +29,4 @@ def execute():
|
|||||||
else:
|
else:
|
||||||
frappe.db.sql(""" UPDATE `tab%s`
|
frappe.db.sql(""" UPDATE `tab%s`
|
||||||
SET transfer_material_against = 'Work Order'
|
SET transfer_material_against = 'Work Order'
|
||||||
WHERE docstatus < 2""" % (doctype))
|
WHERE docstatus < 2""" % (doctype))
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ class Project(Document):
|
|||||||
if self.sales_order:
|
if self.sales_order:
|
||||||
frappe.db.set_value("Sales Order", self.sales_order, "project", self.name)
|
frappe.db.set_value("Sales Order", self.sales_order, "project", self.name)
|
||||||
|
|
||||||
|
def on_trash(self):
|
||||||
|
for so in frappe.get_all("Sales Order", {"project": self.name}, ["name"]):
|
||||||
|
frappe.db.set_value("Sales Order", so.get('name'), "project", "")
|
||||||
|
|
||||||
def update_percent_complete(self):
|
def update_percent_complete(self):
|
||||||
if self.percent_complete_method == "Manual":
|
if self.percent_complete_method == "Manual":
|
||||||
if self.status == "Completed":
|
if self.status == "Completed":
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ test_records = frappe.get_test_records('Project')
|
|||||||
test_ignore = ["Sales Order"]
|
test_ignore = ["Sales Order"]
|
||||||
|
|
||||||
from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template
|
from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template
|
||||||
from erpnext.projects.doctype.project.project import set_project_status
|
from erpnext.selling.doctype.sales_order.sales_order import make_project as make_project_from_so
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
@@ -32,6 +33,21 @@ class TestProject(unittest.TestCase):
|
|||||||
self.assertEqual(task4.subject, 'Task 4')
|
self.assertEqual(task4.subject, 'Task 4')
|
||||||
self.assertEqual(getdate(task4.exp_end_date), getdate('2019-01-06'))
|
self.assertEqual(getdate(task4.exp_end_date), getdate('2019-01-06'))
|
||||||
|
|
||||||
|
def test_project_linking_with_sales_order(self):
|
||||||
|
so = make_sales_order()
|
||||||
|
project = make_project_from_so(so.name)
|
||||||
|
|
||||||
|
project.save()
|
||||||
|
self.assertEqual(project.sales_order, so.name)
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.project, project.name)
|
||||||
|
|
||||||
|
project.delete()
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertFalse(so.project)
|
||||||
|
|
||||||
def get_project(name):
|
def get_project(name):
|
||||||
template = get_project_template()
|
template = get_project_template()
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
"fieldname": "qty",
|
"fieldname": "qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Qty"
|
"label": "Qty",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "picked_qty",
|
"fieldname": "picked_qty",
|
||||||
@@ -180,7 +181,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-13 19:08:21.995986",
|
"modified": "2021-09-28 12:02:16.923056",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Pick List Item",
|
"name": "Pick List Item",
|
||||||
@@ -190,4 +191,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,6 +282,12 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
frm.trigger("setup_quality_inspection");
|
frm.trigger("setup_quality_inspection");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
before_save: function(frm) {
|
||||||
|
frm.doc.items.forEach((item) => {
|
||||||
|
item.uom = item.uom || item.stock_uom;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
purpose: function(frm) {
|
purpose: function(frm) {
|
||||||
frm.trigger('validate_purpose_consumption');
|
frm.trigger('validate_purpose_consumption');
|
||||||
frm.fields_dict.items.grid.refresh();
|
frm.fields_dict.items.grid.refresh();
|
||||||
|
|||||||
@@ -537,6 +537,11 @@ def get_stock_balance_for(item_code, warehouse,
|
|||||||
item_dict = frappe.db.get_value("Item", item_code,
|
item_dict = frappe.db.get_value("Item", item_code,
|
||||||
["has_serial_no", "has_batch_no"], as_dict=1)
|
["has_serial_no", "has_batch_no"], as_dict=1)
|
||||||
|
|
||||||
|
if not item_dict:
|
||||||
|
# In cases of data upload to Items table
|
||||||
|
msg = _("Item {} does not exist.").format(item_code)
|
||||||
|
frappe.throw(msg, title=_("Missing"))
|
||||||
|
|
||||||
serial_nos = ""
|
serial_nos = ""
|
||||||
with_serial_no = True if item_dict.get("has_serial_no") else False
|
with_serial_no = True if item_dict.get("has_serial_no") else False
|
||||||
data = get_stock_balance(item_code, warehouse, posting_date, posting_time,
|
data = get_stock_balance(item_code, warehouse, posting_date, posting_time,
|
||||||
|
|||||||
Reference in New Issue
Block a user