mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 07:02:54 +00:00
Merge branch 'develop' into overseas_gst_tax
This commit is contained in:
1
.flake8
1
.flake8
@@ -28,6 +28,7 @@ ignore =
|
|||||||
B007,
|
B007,
|
||||||
B950,
|
B950,
|
||||||
W191,
|
W191,
|
||||||
|
E124, # closing bracket, irritating while writing QB code
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
exclude=.github/helper/semgrep_rules
|
exclude=.github/helper/semgrep_rules
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
from six.moves import reduce
|
|
||||||
|
|
||||||
from erpnext.controllers.status_updater import StatusUpdater
|
from erpnext.controllers.status_updater import StatusUpdater
|
||||||
|
|
||||||
|
|||||||
@@ -628,6 +628,26 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
for doc in [si, si1]:
|
for doc in [si, si1]:
|
||||||
doc.delete()
|
doc.delete()
|
||||||
|
|
||||||
|
def test_multiple_pricing_rules_with_min_qty(self):
|
||||||
|
make_pricing_rule(discount_percentage=20, selling=1, priority=1, min_qty=4,
|
||||||
|
apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 1")
|
||||||
|
make_pricing_rule(discount_percentage=10, selling=1, priority=2, min_qty=4,
|
||||||
|
apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 2")
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD")
|
||||||
|
item = si.items[0]
|
||||||
|
item.stock_qty = 1
|
||||||
|
si.save()
|
||||||
|
self.assertFalse(item.discount_percentage)
|
||||||
|
item.qty = 5
|
||||||
|
item.stock_qty = 5
|
||||||
|
si.save()
|
||||||
|
self.assertEqual(item.discount_percentage, 30)
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2")
|
||||||
|
|
||||||
test_dependencies = ["Campaign"]
|
test_dependencies = ["Campaign"]
|
||||||
|
|
||||||
def make_pricing_rule(**args):
|
def make_pricing_rule(**args):
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ def sorted_by_priority(pricing_rules, args, doc=None):
|
|||||||
for key in sorted(pricing_rule_dict):
|
for key in sorted(pricing_rule_dict):
|
||||||
pricing_rules_list.extend(pricing_rule_dict.get(key))
|
pricing_rules_list.extend(pricing_rule_dict.get(key))
|
||||||
|
|
||||||
return pricing_rules_list or pricing_rules
|
return pricing_rules_list
|
||||||
|
|
||||||
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
||||||
filtered_pricing_rules = []
|
filtered_pricing_rules = []
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ class ShippingRule(Document):
|
|||||||
if doc.currency != doc.company_currency:
|
if doc.currency != doc.company_currency:
|
||||||
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
|
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
|
||||||
|
|
||||||
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
|
if shipping_amount:
|
||||||
|
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
|
||||||
|
|
||||||
def get_shipping_amount_from_rules(self, value):
|
def get_shipping_amount_from_rules(self, value):
|
||||||
for condition in self.get("conditions"):
|
for condition in self.get("conditions"):
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt
|
||||||
from six import iteritems
|
|
||||||
|
|
||||||
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
||||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
||||||
@@ -40,7 +39,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
if self.filters.show_gl_balance:
|
if self.filters.show_gl_balance:
|
||||||
gl_balance_map = get_gl_balance(self.filters.report_date)
|
gl_balance_map = get_gl_balance(self.filters.report_date)
|
||||||
|
|
||||||
for party, party_dict in iteritems(self.party_total):
|
for party, party_dict in self.party_total.items():
|
||||||
if party_dict.outstanding == 0:
|
if party_dict.outstanding == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ frappe.ui.form.on('Asset', {
|
|||||||
frm.trigger("create_asset_repair");
|
frm.trigger("create_asset_repair");
|
||||||
}, __("Manage"));
|
}, __("Manage"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Split Asset"), function() {
|
||||||
|
frm.trigger("split_asset");
|
||||||
|
}, __("Manage"));
|
||||||
|
|
||||||
if (frm.doc.status != 'Fully Depreciated') {
|
if (frm.doc.status != 'Fully Depreciated') {
|
||||||
frm.add_custom_button(__("Adjust Asset Value"), function() {
|
frm.add_custom_button(__("Adjust Asset Value"), function() {
|
||||||
frm.trigger("create_asset_value_adjustment");
|
frm.trigger("create_asset_value_adjustment");
|
||||||
@@ -322,6 +326,43 @@ frappe.ui.form.on('Asset', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
split_asset: function(frm) {
|
||||||
|
const title = __('Split Asset');
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
fieldname: 'split_qty',
|
||||||
|
fieldtype: 'Int',
|
||||||
|
label: __('Split Qty'),
|
||||||
|
reqd: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let dialog = new frappe.ui.Dialog({
|
||||||
|
title: title,
|
||||||
|
fields: fields
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.set_primary_action(__('Split'), function() {
|
||||||
|
const dialog_data = dialog.get_values();
|
||||||
|
frappe.call({
|
||||||
|
args: {
|
||||||
|
"asset_name": frm.doc.name,
|
||||||
|
"split_qty": cint(dialog_data.split_qty)
|
||||||
|
},
|
||||||
|
method: "erpnext.assets.doctype.asset.asset.split_asset",
|
||||||
|
callback: function(r) {
|
||||||
|
let doclist = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
},
|
||||||
|
|
||||||
create_asset_value_adjustment: function(frm) {
|
create_asset_value_adjustment: function(frm) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2016-03-01 17:01:27.920130",
|
"creation": "2022-01-18 02:26:55.975005",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"asset_name",
|
"asset_name",
|
||||||
"asset_category",
|
"asset_category",
|
||||||
"location",
|
"location",
|
||||||
|
"split_from",
|
||||||
"custodian",
|
"custodian",
|
||||||
"department",
|
"department",
|
||||||
"disposal_date",
|
"disposal_date",
|
||||||
@@ -482,6 +483,13 @@
|
|||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Finance Books"
|
"label": "Finance Books"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "split_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Split From",
|
||||||
|
"options": "Asset",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "asset_quantity",
|
"fieldname": "asset_quantity",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
@@ -509,7 +517,7 @@
|
|||||||
"link_fieldname": "asset"
|
"link_fieldname": "asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-01-18 12:57:36.741192",
|
"modified": "2022-01-20 12:57:36.741192",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ class Asset(AccountsController):
|
|||||||
self.validate_item()
|
self.validate_item()
|
||||||
self.validate_cost_center()
|
self.validate_cost_center()
|
||||||
self.set_missing_values()
|
self.set_missing_values()
|
||||||
self.prepare_depreciation_data()
|
if not self.split_from:
|
||||||
|
self.prepare_depreciation_data()
|
||||||
self.validate_gross_and_purchase_amount()
|
self.validate_gross_and_purchase_amount()
|
||||||
if self.get("schedules"):
|
if self.get("schedules"):
|
||||||
self.validate_expected_value_after_useful_life()
|
self.validate_expected_value_after_useful_life()
|
||||||
@@ -202,143 +203,143 @@ class Asset(AccountsController):
|
|||||||
start = self.clear_depreciation_schedule()
|
start = self.clear_depreciation_schedule()
|
||||||
|
|
||||||
for finance_book in self.get('finance_books'):
|
for finance_book in self.get('finance_books'):
|
||||||
self.validate_asset_finance_books(finance_book)
|
self._make_depreciation_schedule(finance_book, start, date_of_sale)
|
||||||
|
|
||||||
# value_after_depreciation - current Asset value
|
def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
|
||||||
if self.docstatus == 1 and finance_book.value_after_depreciation:
|
self.validate_asset_finance_books(finance_book)
|
||||||
value_after_depreciation = flt(finance_book.value_after_depreciation)
|
|
||||||
else:
|
|
||||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
|
||||||
flt(self.opening_accumulated_depreciation))
|
|
||||||
|
|
||||||
finance_book.value_after_depreciation = value_after_depreciation
|
value_after_depreciation = self._get_value_after_depreciation(finance_book)
|
||||||
|
finance_book.value_after_depreciation = value_after_depreciation
|
||||||
|
|
||||||
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
|
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
|
||||||
cint(self.number_of_depreciations_booked)
|
cint(self.number_of_depreciations_booked)
|
||||||
|
|
||||||
has_pro_rata = self.check_is_pro_rata(finance_book)
|
has_pro_rata = self.check_is_pro_rata(finance_book)
|
||||||
|
if has_pro_rata:
|
||||||
|
number_of_pending_depreciations += 1
|
||||||
|
|
||||||
if has_pro_rata:
|
skip_row = False
|
||||||
number_of_pending_depreciations += 1
|
|
||||||
|
|
||||||
skip_row = False
|
for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
|
||||||
|
# If depreciation is already completed (for double declining balance)
|
||||||
|
if skip_row: continue
|
||||||
|
|
||||||
for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
|
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
|
||||||
# If depreciation is already completed (for double declining balance)
|
|
||||||
if skip_row: continue
|
|
||||||
|
|
||||||
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
|
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||||
|
schedule_date = add_months(finance_book.depreciation_start_date,
|
||||||
|
n * cint(finance_book.frequency_of_depreciation))
|
||||||
|
|
||||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
# schedule date will be a year later from start date
|
||||||
schedule_date = add_months(finance_book.depreciation_start_date,
|
# so monthly schedule date is calculated by removing 11 months from it
|
||||||
n * cint(finance_book.frequency_of_depreciation))
|
monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
|
||||||
|
|
||||||
# schedule date will be a year later from start date
|
# if asset is being sold
|
||||||
# so monthly schedule date is calculated by removing 11 months from it
|
if date_of_sale:
|
||||||
monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
|
from_date = self.get_from_date(finance_book.finance_book)
|
||||||
|
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
|
||||||
# if asset is being sold
|
from_date, date_of_sale)
|
||||||
if date_of_sale:
|
|
||||||
from_date = self.get_from_date(finance_book.finance_book)
|
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
|
|
||||||
from_date, date_of_sale)
|
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
|
||||||
self.append("schedules", {
|
|
||||||
"schedule_date": date_of_sale,
|
|
||||||
"depreciation_amount": depreciation_amount,
|
|
||||||
"depreciation_method": finance_book.depreciation_method,
|
|
||||||
"finance_book": finance_book.finance_book,
|
|
||||||
"finance_book_id": finance_book.idx
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
# For first row
|
|
||||||
if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
|
|
||||||
from_date = add_days(self.available_for_use_date, -1) # needed to calc depr amount for available_for_use_date too
|
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
|
|
||||||
from_date, finance_book.depreciation_start_date)
|
|
||||||
|
|
||||||
# For first depr schedule date will be the start date
|
|
||||||
# so monthly schedule date is calculated by removing month difference between use date and start date
|
|
||||||
monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
|
|
||||||
|
|
||||||
# For last row
|
|
||||||
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
|
||||||
if not self.flags.increase_in_asset_life:
|
|
||||||
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
|
|
||||||
self.to_date = add_months(self.available_for_use_date,
|
|
||||||
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
|
|
||||||
|
|
||||||
depreciation_amount_without_pro_rata = depreciation_amount
|
|
||||||
|
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
|
|
||||||
depreciation_amount, schedule_date, self.to_date)
|
|
||||||
|
|
||||||
depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
|
|
||||||
depreciation_amount, finance_book.finance_book)
|
|
||||||
|
|
||||||
monthly_schedule_date = add_months(schedule_date, 1)
|
|
||||||
schedule_date = add_days(schedule_date, days)
|
|
||||||
last_schedule_date = schedule_date
|
|
||||||
|
|
||||||
if not depreciation_amount: continue
|
|
||||||
value_after_depreciation -= flt(depreciation_amount,
|
|
||||||
self.precision("gross_purchase_amount"))
|
|
||||||
|
|
||||||
# Adjust depreciation amount in the last period based on the expected value after useful life
|
|
||||||
if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
|
|
||||||
and value_after_depreciation != finance_book.expected_value_after_useful_life)
|
|
||||||
or value_after_depreciation < finance_book.expected_value_after_useful_life):
|
|
||||||
depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
|
|
||||||
skip_row = True
|
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
if depreciation_amount > 0:
|
||||||
# With monthly depreciation, each depreciation is divided by months remaining until next date
|
self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method,
|
||||||
if self.allow_monthly_depreciation:
|
finance_book.finance_book, finance_book.idx)
|
||||||
# month range is 1 to 12
|
|
||||||
# In pro rata case, for first and last depreciation, month range would be different
|
|
||||||
month_range = months \
|
|
||||||
if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
|
|
||||||
else finance_book.frequency_of_depreciation
|
|
||||||
|
|
||||||
for r in range(month_range):
|
break
|
||||||
if (has_pro_rata and n == 0):
|
|
||||||
# For first entry of monthly depr
|
# For first row
|
||||||
if r == 0:
|
if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
|
||||||
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
|
from_date = add_days(self.available_for_use_date, -1) # needed to calc depr amount for available_for_use_date too
|
||||||
per_day_amt = depreciation_amount / days
|
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
|
||||||
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
|
from_date, finance_book.depreciation_start_date)
|
||||||
depreciation_amount -= depreciation_amount_for_current_month
|
|
||||||
date = monthly_schedule_date
|
# For first depr schedule date will be the start date
|
||||||
amount = depreciation_amount_for_current_month
|
# so monthly schedule date is calculated by removing month difference between use date and start date
|
||||||
else:
|
monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
|
||||||
date = add_months(monthly_schedule_date, r)
|
|
||||||
amount = depreciation_amount / (month_range - 1)
|
# For last row
|
||||||
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
|
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
||||||
# For last entry of monthly depr
|
if not self.flags.increase_in_asset_life:
|
||||||
date = last_schedule_date
|
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
|
||||||
amount = depreciation_amount / month_range
|
self.to_date = add_months(self.available_for_use_date,
|
||||||
|
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
|
||||||
|
|
||||||
|
depreciation_amount_without_pro_rata = depreciation_amount
|
||||||
|
|
||||||
|
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
|
||||||
|
depreciation_amount, schedule_date, self.to_date)
|
||||||
|
|
||||||
|
depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
|
||||||
|
depreciation_amount, finance_book.finance_book)
|
||||||
|
|
||||||
|
monthly_schedule_date = add_months(schedule_date, 1)
|
||||||
|
schedule_date = add_days(schedule_date, days)
|
||||||
|
last_schedule_date = schedule_date
|
||||||
|
|
||||||
|
if not depreciation_amount: continue
|
||||||
|
value_after_depreciation -= flt(depreciation_amount,
|
||||||
|
self.precision("gross_purchase_amount"))
|
||||||
|
|
||||||
|
# Adjust depreciation amount in the last period based on the expected value after useful life
|
||||||
|
if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
|
||||||
|
and value_after_depreciation != finance_book.expected_value_after_useful_life)
|
||||||
|
or value_after_depreciation < finance_book.expected_value_after_useful_life):
|
||||||
|
depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
|
||||||
|
skip_row = True
|
||||||
|
|
||||||
|
if depreciation_amount > 0:
|
||||||
|
# With monthly depreciation, each depreciation is divided by months remaining until next date
|
||||||
|
if self.allow_monthly_depreciation:
|
||||||
|
# month range is 1 to 12
|
||||||
|
# In pro rata case, for first and last depreciation, month range would be different
|
||||||
|
month_range = months \
|
||||||
|
if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
|
||||||
|
else finance_book.frequency_of_depreciation
|
||||||
|
|
||||||
|
for r in range(month_range):
|
||||||
|
if (has_pro_rata and n == 0):
|
||||||
|
# For first entry of monthly depr
|
||||||
|
if r == 0:
|
||||||
|
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
|
||||||
|
per_day_amt = depreciation_amount / days
|
||||||
|
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
|
||||||
|
depreciation_amount -= depreciation_amount_for_current_month
|
||||||
|
date = monthly_schedule_date
|
||||||
|
amount = depreciation_amount_for_current_month
|
||||||
else:
|
else:
|
||||||
date = add_months(monthly_schedule_date, r)
|
date = add_months(monthly_schedule_date, r)
|
||||||
amount = depreciation_amount / month_range
|
amount = depreciation_amount / (month_range - 1)
|
||||||
|
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
|
||||||
|
# For last entry of monthly depr
|
||||||
|
date = last_schedule_date
|
||||||
|
amount = depreciation_amount / month_range
|
||||||
|
else:
|
||||||
|
date = add_months(monthly_schedule_date, r)
|
||||||
|
amount = depreciation_amount / month_range
|
||||||
|
|
||||||
self.append("schedules", {
|
self._add_depreciation_row(date, amount, finance_book.depreciation_method,
|
||||||
"schedule_date": date,
|
finance_book.finance_book, finance_book.idx)
|
||||||
"depreciation_amount": amount,
|
else:
|
||||||
"depreciation_method": finance_book.depreciation_method,
|
self._add_depreciation_row(schedule_date, depreciation_amount, finance_book.depreciation_method,
|
||||||
"finance_book": finance_book.finance_book,
|
finance_book.finance_book, finance_book.idx)
|
||||||
"finance_book_id": finance_book.idx
|
|
||||||
})
|
def _add_depreciation_row(self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id):
|
||||||
else:
|
self.append("schedules", {
|
||||||
self.append("schedules", {
|
"schedule_date": schedule_date,
|
||||||
"schedule_date": schedule_date,
|
"depreciation_amount": depreciation_amount,
|
||||||
"depreciation_amount": depreciation_amount,
|
"depreciation_method": depreciation_method,
|
||||||
"depreciation_method": finance_book.depreciation_method,
|
"finance_book": finance_book,
|
||||||
"finance_book": finance_book.finance_book,
|
"finance_book_id": finance_book_id
|
||||||
"finance_book_id": finance_book.idx
|
})
|
||||||
})
|
|
||||||
|
def _get_value_after_depreciation(self, finance_book):
|
||||||
|
# value_after_depreciation - current Asset value
|
||||||
|
if self.docstatus == 1 and finance_book.value_after_depreciation:
|
||||||
|
value_after_depreciation = flt(finance_book.value_after_depreciation)
|
||||||
|
else:
|
||||||
|
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||||
|
flt(self.opening_accumulated_depreciation))
|
||||||
|
|
||||||
|
return value_after_depreciation
|
||||||
|
|
||||||
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
|
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
|
||||||
# JE: Journal Entry, FB: Finance Book
|
# JE: Journal Entry, FB: Finance Book
|
||||||
@@ -348,7 +349,6 @@ class Asset(AccountsController):
|
|||||||
depr_schedule = []
|
depr_schedule = []
|
||||||
|
|
||||||
for schedule in self.get('schedules'):
|
for schedule in self.get('schedules'):
|
||||||
|
|
||||||
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
|
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
|
||||||
if len(start) == (int(schedule.finance_book_id) - 2):
|
if len(start) == (int(schedule.finance_book_id) - 2):
|
||||||
start.append(num_of_depreciations_completed)
|
start.append(num_of_depreciations_completed)
|
||||||
@@ -924,3 +924,113 @@ def get_depreciation_amount(asset, depreciable_value, row):
|
|||||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
||||||
|
|
||||||
return depreciation_amount
|
return depreciation_amount
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def split_asset(asset_name, split_qty):
|
||||||
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
|
split_qty = cint(split_qty)
|
||||||
|
|
||||||
|
if split_qty >= asset.asset_quantity:
|
||||||
|
frappe.throw(_("Split qty cannot be grater than or equal to asset qty"))
|
||||||
|
|
||||||
|
remaining_qty = asset.asset_quantity - split_qty
|
||||||
|
|
||||||
|
new_asset = create_new_asset_after_split(asset, split_qty)
|
||||||
|
update_existing_asset(asset, remaining_qty)
|
||||||
|
|
||||||
|
return new_asset
|
||||||
|
|
||||||
|
def update_existing_asset(asset, remaining_qty):
|
||||||
|
remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity)
|
||||||
|
opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity)
|
||||||
|
|
||||||
|
frappe.db.set_value("Asset", asset.name, {
|
||||||
|
'opening_accumulated_depreciation': opening_accumulated_depreciation,
|
||||||
|
'gross_purchase_amount': remaining_gross_purchase_amount,
|
||||||
|
'asset_quantity': remaining_qty
|
||||||
|
})
|
||||||
|
|
||||||
|
for finance_book in asset.get('finance_books'):
|
||||||
|
value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.asset_quantity)
|
||||||
|
expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * remaining_qty)/asset.asset_quantity)
|
||||||
|
frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation)
|
||||||
|
frappe.db.set_value('Asset Finance Book', finance_book.name, 'expected_value_after_useful_life', expected_value_after_useful_life)
|
||||||
|
|
||||||
|
accumulated_depreciation = 0
|
||||||
|
|
||||||
|
for term in asset.get('schedules'):
|
||||||
|
depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.asset_quantity)
|
||||||
|
frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount)
|
||||||
|
accumulated_depreciation += depreciation_amount
|
||||||
|
frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', accumulated_depreciation)
|
||||||
|
|
||||||
|
def create_new_asset_after_split(asset, split_qty):
|
||||||
|
new_asset = frappe.copy_doc(asset)
|
||||||
|
new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity)
|
||||||
|
opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity)
|
||||||
|
|
||||||
|
new_asset.gross_purchase_amount = new_gross_purchase_amount
|
||||||
|
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
|
||||||
|
new_asset.asset_quantity = split_qty
|
||||||
|
new_asset.split_from = asset.name
|
||||||
|
accumulated_depreciation = 0
|
||||||
|
|
||||||
|
for finance_book in new_asset.get('finance_books'):
|
||||||
|
finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.asset_quantity)
|
||||||
|
finance_book.expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * split_qty)/asset.asset_quantity)
|
||||||
|
|
||||||
|
for term in new_asset.get('schedules'):
|
||||||
|
depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.asset_quantity)
|
||||||
|
term.depreciation_amount = depreciation_amount
|
||||||
|
accumulated_depreciation += depreciation_amount
|
||||||
|
term.accumulated_depreciation_amount = accumulated_depreciation
|
||||||
|
|
||||||
|
new_asset.submit()
|
||||||
|
new_asset.set_status()
|
||||||
|
|
||||||
|
for term in new_asset.get('schedules'):
|
||||||
|
# Update references in JV
|
||||||
|
if term.journal_entry:
|
||||||
|
add_reference_in_jv_on_split(term.journal_entry, new_asset.name, asset.name, term.depreciation_amount)
|
||||||
|
|
||||||
|
return new_asset
|
||||||
|
|
||||||
|
def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount):
|
||||||
|
journal_entry = frappe.get_doc('Journal Entry', entry_name)
|
||||||
|
entries_to_add = []
|
||||||
|
idx = len(journal_entry.get('accounts')) + 1
|
||||||
|
|
||||||
|
for account in journal_entry.get('accounts'):
|
||||||
|
if account.reference_name == old_asset_name:
|
||||||
|
entries_to_add.append(frappe.copy_doc(account).as_dict())
|
||||||
|
if account.credit:
|
||||||
|
account.credit = account.credit - depreciation_amount
|
||||||
|
account.credit_in_account_currency = account.credit_in_account_currency - \
|
||||||
|
account.exchange_rate * depreciation_amount
|
||||||
|
elif account.debit:
|
||||||
|
account.debit = account.debit - depreciation_amount
|
||||||
|
account.debit_in_account_currency = account.debit_in_account_currency - \
|
||||||
|
account.exchange_rate * depreciation_amount
|
||||||
|
|
||||||
|
for entry in entries_to_add:
|
||||||
|
entry.reference_name = new_asset_name
|
||||||
|
if entry.credit:
|
||||||
|
entry.credit = depreciation_amount
|
||||||
|
entry.credit_in_account_currency = entry.exchange_rate * depreciation_amount
|
||||||
|
elif entry.debit:
|
||||||
|
entry.debit = depreciation_amount
|
||||||
|
entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount
|
||||||
|
|
||||||
|
entry.idx = idx
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
journal_entry.append('accounts', entry)
|
||||||
|
|
||||||
|
journal_entry.flags.ignore_validate_update_after_submit = True
|
||||||
|
journal_entry.save()
|
||||||
|
|
||||||
|
# Repost GL Entries
|
||||||
|
journal_entry.docstatus = 2
|
||||||
|
journal_entry.make_gl_entries(1)
|
||||||
|
journal_entry.docstatus = 1
|
||||||
|
journal_entry.make_gl_entries()
|
||||||
@@ -7,7 +7,7 @@ import frappe
|
|||||||
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
|
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.assets.doctype.asset.asset import make_sales_invoice
|
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
|
||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
post_depreciation_entries,
|
post_depreciation_entries,
|
||||||
restore_asset,
|
restore_asset,
|
||||||
@@ -245,6 +245,57 @@ class TestAsset(AssetSetup):
|
|||||||
si.cancel()
|
si.cancel()
|
||||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||||
|
|
||||||
|
def test_asset_splitting(self):
|
||||||
|
asset = create_asset(
|
||||||
|
calculate_depreciation = 1,
|
||||||
|
asset_quantity=10,
|
||||||
|
available_for_use_date = '2020-01-01',
|
||||||
|
purchase_date = '2020-01-01',
|
||||||
|
expected_value_after_useful_life = 0,
|
||||||
|
total_number_of_depreciations = 6,
|
||||||
|
number_of_depreciations_booked = 1,
|
||||||
|
frequency_of_depreciation = 10,
|
||||||
|
depreciation_start_date = '2021-01-01',
|
||||||
|
opening_accumulated_depreciation=20000,
|
||||||
|
gross_purchase_amount=120000,
|
||||||
|
submit = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
post_depreciation_entries(date="2021-01-01")
|
||||||
|
|
||||||
|
self.assertEqual(asset.asset_quantity, 10)
|
||||||
|
self.assertEqual(asset.gross_purchase_amount, 120000)
|
||||||
|
self.assertEqual(asset.opening_accumulated_depreciation, 20000)
|
||||||
|
|
||||||
|
new_asset = split_asset(asset.name, 2)
|
||||||
|
asset.load_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(new_asset.asset_quantity, 2)
|
||||||
|
self.assertEqual(new_asset.gross_purchase_amount, 24000)
|
||||||
|
self.assertEqual(new_asset.opening_accumulated_depreciation, 4000)
|
||||||
|
self.assertEqual(new_asset.split_from, asset.name)
|
||||||
|
self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000)
|
||||||
|
self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000)
|
||||||
|
|
||||||
|
self.assertEqual(asset.asset_quantity, 8)
|
||||||
|
self.assertEqual(asset.gross_purchase_amount, 96000)
|
||||||
|
self.assertEqual(asset.opening_accumulated_depreciation, 16000)
|
||||||
|
self.assertEqual(asset.schedules[0].depreciation_amount, 16000)
|
||||||
|
self.assertEqual(asset.schedules[1].depreciation_amount, 16000)
|
||||||
|
|
||||||
|
journal_entry = asset.schedules[0].journal_entry
|
||||||
|
|
||||||
|
jv = frappe.get_doc('Journal Entry', journal_entry)
|
||||||
|
self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000)
|
||||||
|
self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000)
|
||||||
|
self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000)
|
||||||
|
self.assertEqual(jv.accounts[3].debit_in_account_currency, 4000)
|
||||||
|
|
||||||
|
self.assertEqual(jv.accounts[0].reference_name, asset.name)
|
||||||
|
self.assertEqual(jv.accounts[1].reference_name, asset.name)
|
||||||
|
self.assertEqual(jv.accounts[2].reference_name, new_asset.name)
|
||||||
|
self.assertEqual(jv.accounts[3].reference_name, new_asset.name)
|
||||||
|
|
||||||
def test_expense_head(self):
|
def test_expense_head(self):
|
||||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
qty=2, rate=200000.0, location="Test Location")
|
qty=2, rate=200000.0, location="Test Location")
|
||||||
@@ -1197,7 +1248,8 @@ def create_asset(**args):
|
|||||||
"available_for_use_date": args.available_for_use_date or "2020-06-06",
|
"available_for_use_date": args.available_for_use_date or "2020-06-06",
|
||||||
"location": args.location or "Test Location",
|
"location": args.location or "Test Location",
|
||||||
"asset_owner": args.asset_owner or "Company",
|
"asset_owner": args.asset_owner or "Company",
|
||||||
"is_existing_asset": args.is_existing_asset or 1
|
"is_existing_asset": args.is_existing_asset or 1,
|
||||||
|
"asset_quantity": args.get("asset_quantity") or 1
|
||||||
})
|
})
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
|
|||||||
@@ -132,13 +132,17 @@ class EmployeeBoardingController(Document):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
# delete task project
|
# delete task project
|
||||||
for task in frappe.get_all('Task', filters={'project': self.project}):
|
project = self.project
|
||||||
|
for task in frappe.get_all('Task', filters={'project': project}):
|
||||||
frappe.delete_doc('Task', task.name, force=1)
|
frappe.delete_doc('Task', task.name, force=1)
|
||||||
frappe.delete_doc('Project', self.project, force=1)
|
frappe.delete_doc('Project', project, force=1)
|
||||||
self.db_set('project', '')
|
self.db_set('project', '')
|
||||||
for activity in self.activities:
|
for activity in self.activities:
|
||||||
activity.db_set('task', '')
|
activity.db_set('task', '')
|
||||||
|
|
||||||
|
frappe.msgprint(_('Linked Project {} and Tasks deleted.').format(
|
||||||
|
project), alert=True, indicator='blue')
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_onboarding_details(parent, parenttype):
|
def get_onboarding_details(parent, parenttype):
|
||||||
|
|||||||
@@ -77,17 +77,17 @@ class StockController(AccountsController):
|
|||||||
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
|
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
|
||||||
|
|
||||||
def clean_serial_nos(self):
|
def clean_serial_nos(self):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string
|
||||||
|
|
||||||
for row in self.get("items"):
|
for row in self.get("items"):
|
||||||
if hasattr(row, "serial_no") and row.serial_no:
|
if hasattr(row, "serial_no") and row.serial_no:
|
||||||
# replace commas by linefeed
|
# remove extra whitespace and store one serial no on each line
|
||||||
row.serial_no = row.serial_no.replace(",", "\n")
|
row.serial_no = clean_serial_no_string(row.serial_no)
|
||||||
|
|
||||||
# strip preceeding and succeeding spaces for each SN
|
for row in self.get('packed_items') or []:
|
||||||
# (SN could have valid spaces in between e.g. SN - 123 - 2021)
|
if hasattr(row, "serial_no") and row.serial_no:
|
||||||
serial_no_list = row.serial_no.split("\n")
|
# remove extra whitespace and store one serial no on each line
|
||||||
serial_no_list = [sn.strip() for sn in serial_no_list]
|
row.serial_no = clean_serial_no_string(row.serial_no)
|
||||||
|
|
||||||
row.serial_no = "\n".join(serial_no_list)
|
|
||||||
|
|
||||||
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
|
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
|
||||||
default_cost_center=None):
|
default_cost_center=None):
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import requests
|
import requests
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import get_url_to_form
|
from frappe.utils import get_url_to_form
|
||||||
from frappe.utils.file_manager import get_file_path
|
from frappe.utils.file_manager import get_file_path
|
||||||
from six.moves.urllib.parse import urlencode
|
|
||||||
|
|
||||||
|
|
||||||
class LinkedInSettings(Document):
|
class LinkedInSettings(Document):
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
import csv
|
import csv
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
import dateutil
|
import dateutil
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from six import StringIO
|
|
||||||
|
|
||||||
import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
|
import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import gocardless_pro
|
import gocardless_pro
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.integrations.utils import create_payment_gateway, create_request_log
|
from frappe.integrations.utils import create_payment_gateway, create_request_log
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import call_hook_method, cint, flt, get_url
|
from frappe.utils import call_hook_method, cint, flt, get_url
|
||||||
from six.moves.urllib.parse import urlencode
|
|
||||||
|
|
||||||
|
|
||||||
class GoCardlessSettings(Document):
|
class GoCardlessSettings(Document):
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils.nestedset import get_root_of
|
from frappe.utils.nestedset import get_root_of
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
|
|
||||||
|
|
||||||
class WoocommerceSettings(Document):
|
class WoocommerceSettings(Document):
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
|
|
||||||
from erpnext import get_default_company
|
from erpnext import get_default_company
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ def get_data():
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('Lifecycle'),
|
'label': _('Lifecycle'),
|
||||||
'items': ['Employee Transfer', 'Employee Promotion', 'Employee Grievance']
|
'items': ['Employee Onboarding', 'Employee Transfer', 'Employee Promotion', 'Employee Grievance']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('Exit'),
|
'label': _('Exit'),
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:['Employee Onboarding', 'Employee Onboarding Template'].includes(doc.parenttype)",
|
||||||
"description": "Applicable in the case of Employee Onboarding",
|
"description": "Applicable in the case of Employee Onboarding",
|
||||||
"fieldname": "required_for_employee_creation",
|
"fieldname": "required_for_employee_creation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@@ -93,7 +94,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-07-30 15:55:22.470102",
|
"modified": "2022-01-29 14:05:00.543122",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Boarding Activity",
|
"name": "Employee Boarding Activity",
|
||||||
@@ -102,5 +103,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,6 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Employee Onboarding', {
|
frappe.ui.form.on('Employee Onboarding', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.add_fetch("employee_onboarding_template", "company", "company");
|
|
||||||
frm.add_fetch("employee_onboarding_template", "department", "department");
|
|
||||||
frm.add_fetch("employee_onboarding_template", "designation", "designation");
|
|
||||||
frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade");
|
|
||||||
|
|
||||||
|
|
||||||
frm.set_query("job_applicant", function () {
|
frm.set_query("job_applicant", function () {
|
||||||
return {
|
return {
|
||||||
filters:{
|
filters:{
|
||||||
@@ -71,5 +65,19 @@ frappe.ui.form.on('Employee Onboarding', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
job_applicant: function(frm) {
|
||||||
|
if (frm.doc.job_applicant) {
|
||||||
|
frappe.db.get_value('Employee', {'job_applicant': frm.doc.job_applicant}, 'name', (r) => {
|
||||||
|
if (r.name) {
|
||||||
|
frm.set_value('employee', r.name);
|
||||||
|
} else {
|
||||||
|
frm.set_value('employee', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
frm.set_value('employee', '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,6 +92,7 @@
|
|||||||
"options": "Employee Onboarding Template"
|
"options": "Employee Onboarding Template"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "employee_onboarding_template.company",
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
@@ -99,6 +100,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "employee_onboarding_template.department",
|
||||||
"fieldname": "department",
|
"fieldname": "department",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -106,6 +108,7 @@
|
|||||||
"options": "Department"
|
"options": "Department"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "employee_onboarding_template.designation",
|
||||||
"fieldname": "designation",
|
"fieldname": "designation",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -113,6 +116,7 @@
|
|||||||
"options": "Designation"
|
"options": "Designation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "employee_onboarding_template.employee_grade",
|
||||||
"fieldname": "employee_grade",
|
"fieldname": "employee_grade",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Employee Grade",
|
"label": "Employee Grade",
|
||||||
@@ -170,10 +174,11 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-07-30 14:55:04.560683",
|
"modified": "2022-01-29 12:33:57.120384",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Onboarding",
|
"name": "Employee Onboarding",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -194,6 +199,7 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "employee_name",
|
"title_field": "employee_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -14,10 +14,15 @@ class IncompleteTaskError(frappe.ValidationError): pass
|
|||||||
class EmployeeOnboarding(EmployeeBoardingController):
|
class EmployeeOnboarding(EmployeeBoardingController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(EmployeeOnboarding, self).validate()
|
super(EmployeeOnboarding, self).validate()
|
||||||
|
self.set_employee()
|
||||||
self.validate_duplicate_employee_onboarding()
|
self.validate_duplicate_employee_onboarding()
|
||||||
|
|
||||||
|
def set_employee(self):
|
||||||
|
if not self.employee:
|
||||||
|
self.employee = frappe.db.get_value('Employee', {'job_applicant': self.job_applicant}, 'name')
|
||||||
|
|
||||||
def validate_duplicate_employee_onboarding(self):
|
def validate_duplicate_employee_onboarding(self):
|
||||||
emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant})
|
emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)})
|
||||||
if emp_onboarding and emp_onboarding != self.name:
|
if emp_onboarding and emp_onboarding != self.name:
|
||||||
frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
|
frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ def make_employee(source_name, target_doc=None):
|
|||||||
"doctype": "Employee",
|
"doctype": "Employee",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"applicant_name": "employee_name",
|
"applicant_name": "employee_name",
|
||||||
|
"offer_date": "scheduled_confirmation_date"
|
||||||
}}
|
}}
|
||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
return doc
|
return doc
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ class BOM(WebsiteGenerator):
|
|||||||
self.set_bom_material_details()
|
self.set_bom_material_details()
|
||||||
self.set_bom_scrap_items_detail()
|
self.set_bom_scrap_items_detail()
|
||||||
self.validate_materials()
|
self.validate_materials()
|
||||||
|
self.validate_transfer_against()
|
||||||
self.set_routing_operations()
|
self.set_routing_operations()
|
||||||
self.validate_operations()
|
self.validate_operations()
|
||||||
self.calculate_cost()
|
self.calculate_cost()
|
||||||
@@ -690,6 +691,12 @@ class BOM(WebsiteGenerator):
|
|||||||
if act_pbom and act_pbom[0][0]:
|
if act_pbom and act_pbom[0][0]:
|
||||||
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
|
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
|
||||||
|
|
||||||
|
def validate_transfer_against(self):
|
||||||
|
if not self.with_operations:
|
||||||
|
self.transfer_material_against = "Work Order"
|
||||||
|
if not self.transfer_material_against and not self.is_new():
|
||||||
|
frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
|
||||||
|
|
||||||
def set_routing_operations(self):
|
def set_routing_operations(self):
|
||||||
if self.routing and self.with_operations and not self.operations:
|
if self.routing and self.with_operations and not self.operations:
|
||||||
self.get_routing()
|
self.get_routing()
|
||||||
|
|||||||
@@ -403,6 +403,36 @@ class TestBOM(ERPNextTestCase):
|
|||||||
|
|
||||||
new_bom.delete()
|
new_bom.delete()
|
||||||
|
|
||||||
|
def test_valid_transfer_defaults(self):
|
||||||
|
bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1})
|
||||||
|
bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False)
|
||||||
|
|
||||||
|
# test defaults
|
||||||
|
bom.docstatus = 0
|
||||||
|
bom.transfer_material_against = None
|
||||||
|
bom.insert()
|
||||||
|
self.assertEqual(bom.transfer_material_against, "Work Order")
|
||||||
|
|
||||||
|
bom.reload()
|
||||||
|
bom.transfer_material_against = None
|
||||||
|
with self.assertRaises(frappe.ValidationError):
|
||||||
|
bom.save()
|
||||||
|
bom.reload()
|
||||||
|
|
||||||
|
# test saner default
|
||||||
|
bom.transfer_material_against = "Job Card"
|
||||||
|
bom.with_operations = 0
|
||||||
|
bom.save()
|
||||||
|
self.assertEqual(bom.transfer_material_against, "Work Order")
|
||||||
|
|
||||||
|
# test no value on existing doc
|
||||||
|
bom.transfer_material_against = None
|
||||||
|
bom.with_operations = 0
|
||||||
|
bom.save()
|
||||||
|
self.assertEqual(bom.transfer_material_against, "Work Order")
|
||||||
|
bom.delete()
|
||||||
|
|
||||||
|
|
||||||
def get_default_bom(item_code="_Test FG Item 2"):
|
def get_default_bom(item_code="_Test FG Item 2"):
|
||||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from erpnext.stock.doctype.batch.batch import make_batch
|
|||||||
from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life
|
from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import (
|
from erpnext.stock.doctype.serial_no.serial_no import (
|
||||||
auto_make_serial_nos,
|
auto_make_serial_nos,
|
||||||
|
clean_serial_no_string,
|
||||||
get_auto_serial_nos,
|
get_auto_serial_nos,
|
||||||
get_serial_nos,
|
get_serial_nos,
|
||||||
)
|
)
|
||||||
@@ -65,6 +66,7 @@ class WorkOrder(Document):
|
|||||||
self.validate_warehouse_belongs_to_company()
|
self.validate_warehouse_belongs_to_company()
|
||||||
self.calculate_operating_cost()
|
self.calculate_operating_cost()
|
||||||
self.validate_qty()
|
self.validate_qty()
|
||||||
|
self.validate_transfer_against()
|
||||||
self.validate_operation_time()
|
self.validate_operation_time()
|
||||||
self.status = self.get_status()
|
self.status = self.get_status()
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
self.set_required_items(reset_only_qty = len(self.get("required_items")))
|
self.set_required_items(reset_only_qty = len(self.get("required_items")))
|
||||||
|
|
||||||
|
|
||||||
def validate_sales_order(self):
|
def validate_sales_order(self):
|
||||||
if self.sales_order:
|
if self.sales_order:
|
||||||
self.check_sales_order_on_hold_or_close()
|
self.check_sales_order_on_hold_or_close()
|
||||||
@@ -356,6 +359,7 @@ class WorkOrder(Document):
|
|||||||
frappe.delete_doc("Batch", row.name)
|
frappe.delete_doc("Batch", row.name)
|
||||||
|
|
||||||
def make_serial_nos(self, args):
|
def make_serial_nos(self, args):
|
||||||
|
self.serial_no = clean_serial_no_string(self.serial_no)
|
||||||
serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series")
|
serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series")
|
||||||
if serial_no_series:
|
if serial_no_series:
|
||||||
self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)
|
self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)
|
||||||
@@ -625,6 +629,16 @@ class WorkOrder(Document):
|
|||||||
if not self.qty > 0:
|
if not self.qty > 0:
|
||||||
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
|
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
|
||||||
|
|
||||||
|
def validate_transfer_against(self):
|
||||||
|
if not self.docstatus == 1:
|
||||||
|
# let user configure operations until they're ready to submit
|
||||||
|
return
|
||||||
|
if not self.operations:
|
||||||
|
self.transfer_material_against = "Work Order"
|
||||||
|
if not self.transfer_material_against:
|
||||||
|
frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
|
||||||
|
|
||||||
|
|
||||||
def validate_operation_time(self):
|
def validate_operation_time(self):
|
||||||
for d in self.operations:
|
for d in self.operations:
|
||||||
if not d.time_in_mins > 0:
|
if not d.time_in_mins > 0:
|
||||||
|
|||||||
@@ -334,3 +334,4 @@ erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
|
|||||||
erpnext.patches.v14_0.delete_agriculture_doctypes
|
erpnext.patches.v14_0.delete_agriculture_doctypes
|
||||||
erpnext.patches.v14_0.rearrange_company_fields
|
erpnext.patches.v14_0.rearrange_company_fields
|
||||||
erpnext.patches.v14_0.update_leave_notification_template
|
erpnext.patches.v14_0.update_leave_notification_template
|
||||||
|
erpnext.patches.v13_0.update_sane_transfer_against
|
||||||
|
|||||||
11
erpnext/patches/v13_0/update_sane_transfer_against.py
Normal file
11
erpnext/patches/v13_0/update_sane_transfer_against.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
bom = frappe.qb.DocType("BOM")
|
||||||
|
|
||||||
|
(frappe.qb
|
||||||
|
.update(bom)
|
||||||
|
.set(bom.transfer_material_against, "Work Order")
|
||||||
|
.where(bom.with_operations == 0)
|
||||||
|
).run()
|
||||||
@@ -235,13 +235,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "actual_start_date",
|
"fieldname": "actual_start_date",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Actual Start Date",
|
"label": "Actual Start Date (via Time Sheet)",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "actual_time",
|
"fieldname": "actual_time",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Actual Time (in Hours)",
|
"label": "Actual Time (in Hours via Time Sheet)",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "actual_end_date",
|
"fieldname": "actual_end_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Actual End Date",
|
"label": "Actual End Date (via Time Sheet)",
|
||||||
"oldfieldname": "act_completion_date",
|
"oldfieldname": "act_completion_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@@ -458,10 +458,11 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 4,
|
"max_attachments": 4,
|
||||||
"modified": "2021-04-28 16:36:11.654632",
|
"modified": "2022-01-29 13:58:27.712714",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Project",
|
"name": "Project",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -499,6 +500,7 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"timeline_field": "customer",
|
"timeline_field": "customer",
|
||||||
"title_field": "project_name",
|
"title_field": "project_name",
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
|
|||||||
@@ -249,7 +249,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "actual_time",
|
"fieldname": "actual_time",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Actual Time (in hours)",
|
"label": "Actual Time (in Hours via Time Sheet)",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -397,10 +397,11 @@
|
|||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 5,
|
"max_attachments": 5,
|
||||||
"modified": "2021-04-16 12:46:51.556741",
|
"modified": "2022-01-29 13:58:47.005241",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Task",
|
"name": "Task",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"nsm_parent_field": "parent_task",
|
"nsm_parent_field": "parent_task",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
@@ -421,6 +422,7 @@
|
|||||||
"show_preview_popup": 1,
|
"show_preview_popup": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"timeline_field": "project",
|
"timeline_field": "project",
|
||||||
"title_field": "subject",
|
"title_field": "subject",
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
|
|||||||
@@ -592,6 +592,6 @@ function check_can_calculate_pending_qty(me) {
|
|||||||
&& doc.fg_completed_qty
|
&& doc.fg_completed_qty
|
||||||
&& erpnext.stock.bom
|
&& erpnext.stock.bom
|
||||||
&& erpnext.stock.bom.name === doc.bom_no;
|
&& erpnext.stock.bom.name === doc.bom_no;
|
||||||
const itemChecks = !!item;
|
const itemChecks = !!item && !item.allow_alternative_item;
|
||||||
return docChecks && itemChecks;
|
return docChecks && itemChecks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import zipfile
|
import zipfile
|
||||||
from csv import QUOTE_NONNUMERIC
|
from csv import QUOTE_NONNUMERIC
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from six import BytesIO
|
|
||||||
|
|
||||||
from .datev_constants import DataCategory
|
from .datev_constants import DataCategory
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import zipfile
|
import zipfile
|
||||||
|
from io import BytesIO
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cstr, now_datetime, today
|
from frappe.utils import cstr, now_datetime, today
|
||||||
from six import BytesIO
|
|
||||||
|
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.regional.germany.utils.datev.datev_constants import (
|
from erpnext.regional.germany.utils.datev.datev_constants import (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@@ -10,7 +11,6 @@ from frappe.utils import cint, cstr, nowdate
|
|||||||
from frappe.utils.nestedset import NestedSet
|
from frappe.utils.nestedset import NestedSet
|
||||||
from frappe.website.utils import clear_cache
|
from frappe.website.utils import clear_cache
|
||||||
from frappe.website.website_generator import WebsiteGenerator
|
from frappe.website.website_generator import WebsiteGenerator
|
||||||
from six.moves.urllib.parse import quote
|
|
||||||
|
|
||||||
from erpnext.shopping_cart.filters import ProductFiltersBuilder
|
from erpnext.shopping_cart.filters import ProductFiltersBuilder
|
||||||
from erpnext.shopping_cart.product_info import set_product_info_for_website
|
from erpnext.shopping_cart.product_info import set_product_info_for_website
|
||||||
|
|||||||
@@ -465,6 +465,13 @@ def get_serial_nos(serial_no):
|
|||||||
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
|
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
|
||||||
if s.strip()]
|
if s.strip()]
|
||||||
|
|
||||||
|
def clean_serial_no_string(serial_no: str) -> str:
|
||||||
|
if not serial_no:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
serial_no_list = get_serial_nos(serial_no)
|
||||||
|
return "\n".join(serial_no_list)
|
||||||
|
|
||||||
def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
|
def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
|
||||||
for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]:
|
for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]:
|
||||||
if args.get(field):
|
if args.get(field):
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ frappe.query_reports["Stock Ledger"] = {
|
|||||||
],
|
],
|
||||||
"formatter": function (value, row, column, data, default_formatter) {
|
"formatter": function (value, row, column, data, default_formatter) {
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
if (column.fieldname == "out_qty" && data.out_qty < 0) {
|
if (column.fieldname == "out_qty" && data && data.out_qty < 0) {
|
||||||
value = "<span style='color:red'>" + value + "</span>";
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
}
|
}
|
||||||
else if (column.fieldname == "in_qty" && data.in_qty > 0) {
|
else if (column.fieldname == "in_qty" && data && data.in_qty > 0) {
|
||||||
value = "<span style='color:green'>" + value + "</span>";
|
value = "<span style='color:green'>" + value + "</span>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user