Merge branch 'develop' into home-onboarding
diff --git a/codecov.yml b/codecov.yml
index 67bd445..1fa602a 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -8,6 +8,16 @@
target: auto
threshold: 0.5%
+ patch:
+ default:
+ target: 85%
+ threshold: 0%
+ base: auto
+ branches:
+ - develop
+ if_ci_failed: ignore
+ only_pulls: true
+
comment:
layout: "diff, files"
require_changes: true
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index f4fd1bf..df957d2 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -297,8 +297,15 @@
item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
- item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
+ asset_category_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
company = self.company)
+ if not asset_category_account:
+ form_link = get_link_to_form('Asset Category', asset_category)
+ throw(
+ _("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
+ title=_("Missing Account")
+ )
+ item.expense_account = asset_category_account
elif item.is_fixed_asset and item.pr_detail:
item.expense_account = asset_received_but_not_billed
elif not item.expense_account and for_validate:
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index c2b1bbc..153f5c5 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -110,7 +110,7 @@
if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Adjust Asset Value"), function() {
- frm.trigger("create_asset_adjustment");
+ frm.trigger("create_asset_value_adjustment");
}, __("Manage"));
}
@@ -322,14 +322,14 @@
});
},
- create_asset_adjustment: function(frm) {
+ create_asset_value_adjustment: function(frm) {
frappe.call({
args: {
"asset": frm.doc.name,
"asset_category": frm.doc.asset_category,
"company": frm.doc.company
},
- method: "erpnext.assets.doctype.asset.asset.create_asset_adjustment",
+ method: "erpnext.assets.doctype.asset.asset.create_asset_value_adjustment",
freeze: 1,
callback: function(r) {
var doclist = frappe.model.sync(r.message);
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 333906a..a18b03a 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -185,83 +185,84 @@
if not self.available_for_use_date:
return
- for d in self.get('finance_books'):
- self.validate_asset_finance_books(d)
+ start = self.clear_depreciation_schedule()
- start = self.clear_depreciation_schedule()
+ for finance_book in self.get('finance_books'):
+ self.validate_asset_finance_books(finance_book)
# value_after_depreciation - current Asset value
- if self.docstatus == 1 and d.value_after_depreciation:
- value_after_depreciation = flt(d.value_after_depreciation)
+ 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))
- d.value_after_depreciation = value_after_depreciation
+ finance_book.value_after_depreciation = value_after_depreciation
- number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
+ number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked)
- has_pro_rata = self.check_is_pro_rata(d)
+ has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata:
number_of_pending_depreciations += 1
skip_row = False
- for n in range(start, number_of_pending_depreciations):
+
+ 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
- depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d)
+ 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(d.depreciation_start_date,
- n * cint(d.frequency_of_depreciation))
+ schedule_date = add_months(finance_book.depreciation_start_date,
+ n * cint(finance_book.frequency_of_depreciation))
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
- monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1)
+ monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_sale:
- from_date = self.get_from_date(d.finance_book)
- depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
+ 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": d.depreciation_method,
- "finance_book": d.finance_book,
- "finance_book_id": d.idx
+ "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:
- depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
- self.available_for_use_date, d.depreciation_start_date)
+ depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
+ self.available_for_use_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(d.depreciation_start_date, - months + 1)
+ 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(d.frequency_of_depreciation))
+ (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(d,
+ 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, d.finance_book)
+ depreciation_amount, finance_book.finance_book)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
@@ -272,10 +273,10 @@
self.precision("gross_purchase_amount"))
# Adjust depreciation amount in the last period based on the expected value after useful life
- if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
- and value_after_depreciation != d.expected_value_after_useful_life)
- or value_after_depreciation < d.expected_value_after_useful_life):
- depreciation_amount += (value_after_depreciation - d.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:
@@ -285,7 +286,7 @@
# 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 d.frequency_of_depreciation
+ else finance_book.frequency_of_depreciation
for r in range(month_range):
if (has_pro_rata and n == 0):
@@ -311,27 +312,52 @@
self.append("schedules", {
"schedule_date": date,
"depreciation_amount": amount,
- "depreciation_method": d.depreciation_method,
- "finance_book": d.finance_book,
- "finance_book_id": d.idx
+ "depreciation_method": finance_book.depreciation_method,
+ "finance_book": finance_book.finance_book,
+ "finance_book_id": finance_book.idx
})
else:
self.append("schedules", {
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
- "depreciation_method": d.depreciation_method,
- "finance_book": d.finance_book,
- "finance_book_id": d.idx
+ "depreciation_method": finance_book.depreciation_method,
+ "finance_book": finance_book.finance_book,
+ "finance_book_id": finance_book.idx
})
- # used when depreciation schedule needs to be modified due to increase in asset life
+ # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
+ # JE: Journal Entry, FB: Finance Book
def clear_depreciation_schedule(self):
- start = 0
- for n in range(len(self.schedules)):
- if not self.schedules[n].journal_entry:
- del self.schedules[n:]
- start = n
- break
+ start = []
+ num_of_depreciations_completed = 0
+ depr_schedule = []
+
+ for schedule in self.get('schedules'):
+
+ # 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):
+ start.append(num_of_depreciations_completed)
+ num_of_depreciations_completed = 0
+
+ # to ensure that start will only be updated once for each FB
+ if len(start) == (int(schedule.finance_book_id) - 1):
+ if schedule.journal_entry:
+ num_of_depreciations_completed += 1
+ depr_schedule.append(schedule)
+ else:
+ start.append(num_of_depreciations_completed)
+ num_of_depreciations_completed = 0
+
+ # to update start when all the schedule rows corresponding to the last FB are linked with JEs
+ if len(start) == (len(self.finance_books) - 1):
+ start.append(num_of_depreciations_completed)
+
+ # when the Depreciation Schedule is being created for the first time
+ if start == []:
+ start = [0] * len(self.finance_books)
+ else:
+ self.schedules = depr_schedule
+
return start
def get_from_date(self, finance_book):
@@ -469,7 +495,6 @@
asset_value_after_full_schedule = flt(
flt(self.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
@@ -731,14 +756,14 @@
return asset_repair
@frappe.whitelist()
-def create_asset_adjustment(asset, asset_category, company):
- asset_maintenance = frappe.get_doc("Asset Value Adjustment")
- asset_maintenance.update({
+def create_asset_value_adjustment(asset, asset_category, company):
+ asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
+ asset_value_adjustment.update({
"asset": asset,
"company": company,
"asset_category": asset_category
})
- return asset_maintenance
+ return asset_value_adjustment
@frappe.whitelist()
def transfer_asset(args):
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index ce2cb01..44c4ce5 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -955,6 +955,82 @@
self.assertEqual(len(asset.schedules), 1)
+ def test_clear_depreciation_schedule_for_multiple_finance_books(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ available_for_use_date = "2019-12-31",
+ do_not_save = 1
+ )
+
+ asset.calculate_depreciation = 1
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 1,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-01-31"
+ })
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 1,
+ "total_number_of_depreciations": 6,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-01-31"
+ })
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31"
+ })
+ asset.submit()
+
+ post_depreciation_entries(date="2020-04-01")
+ asset.load_from_db()
+
+ asset.clear_depreciation_schedule()
+
+ self.assertEqual(len(asset.schedules), 6)
+
+ for schedule in asset.schedules:
+ if schedule.idx <= 3:
+ self.assertEqual(schedule.finance_book_id, "1")
+ else:
+ self.assertEqual(schedule.finance_book_id, "2")
+
+ def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self):
+ asset = create_asset(
+ item_code = "Macbook Pro",
+ available_for_use_date = "2019-12-31",
+ do_not_save = 1
+ )
+
+ asset.calculate_depreciation = 1
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 3,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31"
+ })
+ asset.append("finance_books", {
+ "depreciation_method": "Straight Line",
+ "frequency_of_depreciation": 12,
+ "total_number_of_depreciations": 6,
+ "expected_value_after_useful_life": 10000,
+ "depreciation_start_date": "2020-12-31"
+ })
+ asset.save()
+
+ self.assertEqual(len(asset.schedules), 9)
+
+ for schedule in asset.schedules:
+ if schedule.idx <= 3:
+ self.assertEqual(schedule.finance_book_id, 1)
+ else:
+ self.assertEqual(schedule.finance_book_id, 2)
+
def test_depreciation_entry_cancellation(self):
asset = create_asset(
item_code = "Macbook Pro",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index bde00cb..e7049fd 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -124,6 +124,14 @@
dialog.show()
},
+ schedule_date(frm) {
+ if(frm.doc.schedule_date){
+ frm.doc.items.forEach((item) => {
+ item.schedule_date = frm.doc.schedule_date;
+ })
+ }
+ refresh_field("items");
+ },
preview: (frm) => {
let dialog = new frappe.ui.Dialog({
title: __('Preview Email'),
@@ -184,7 +192,13 @@
dialog.show();
}
})
-
+frappe.ui.form.on("Request for Quotation Item", {
+ items_add(frm, cdt, cdn) {
+ if (frm.doc.schedule_date) {
+ frappe.model.set_value(cdt, cdn, 'schedule_date', frm.doc.schedule_date);
+ }
+ }
+});
frappe.ui.form.on("Request for Quotation Supplier",{
supplier: function(frm, cdt, cdn) {
var d = locals[cdt][cdn]
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index 4ce4100..4993df9 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -12,6 +12,7 @@
"vendor",
"column_break1",
"transaction_date",
+ "schedule_date",
"status",
"amended_from",
"suppliers_section",
@@ -246,16 +247,22 @@
"fieldname": "sec_break_email_2",
"fieldtype": "Section Break",
"hide_border": 1
+ },
+ {
+ "fieldname": "schedule_date",
+ "fieldtype": "Date",
+ "label": "Required Date"
}
],
"icon": "fa fa-shopping-cart",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-05 22:04:29.017134",
+ "modified": "2021-11-24 17:47:49.909000",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/hr/doctype/exit_interview/test_exit_interview.py b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
index a0bf9b3..8e076ed 100644
--- a/erpnext/hr/doctype/exit_interview/test_exit_interview.py
+++ b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
@@ -19,7 +19,7 @@
frappe.db.sql('delete from `tabExit Interview`')
def test_duplicate_interview(self):
- employee = make_employee('employeeexit1@example.com')
+ employee = make_employee('employeeexitint1@example.com')
frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
interview = create_exit_interview(employee)
@@ -27,7 +27,7 @@
self.assertRaises(frappe.DuplicateEntryError, doc.save)
def test_relieving_date_validation(self):
- employee = make_employee('employeeexit2@example.com')
+ employee = make_employee('employeeexitint2@example.com')
# unset relieving date
frappe.db.set_value('Employee', employee, 'relieving_date', None)
diff --git a/erpnext/hr/report/employee_exits/employee_exits.py b/erpnext/hr/report/employee_exits/employee_exits.py
index 8e0b07d..bde5a89 100644
--- a/erpnext/hr/report/employee_exits/employee_exits.py
+++ b/erpnext/hr/report/employee_exits/employee_exits.py
@@ -108,12 +108,11 @@
interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'),
interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement'))
.distinct()
- .orderby(employee.relieving_date, order=Order.asc)
.where(
((employee.relieving_date.isnotnull()) | (employee.relieving_date != ''))
& ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
& ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
- )
+ ).orderby(employee.relieving_date, order=Order.asc)
)
query = get_conditions(filters, query, employee, interview, fnf)
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index d5db3fc..eff2344 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -1,17 +1,15 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-
-import unittest
-
import frappe
from frappe.utils import add_months, today
from erpnext import get_company_currency
+from erpnext.tests.utils import ERPNextTestCase
from .blanket_order import make_order
-class TestBlanketOrder(unittest.TestCase):
+class TestBlanketOrder(ERPNextTestCase):
def setUp(self):
frappe.flags.args = frappe._dict()
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 4c03230..178d92c 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-import unittest
from collections import deque
from functools import partial
@@ -18,10 +17,11 @@
create_stock_reconciliation,
)
from erpnext.tests.test_subcontracting import set_backflush_based_on
+from erpnext.tests.utils import ERPNextTestCase
test_records = frappe.get_test_records('BOM')
-class TestBOM(unittest.TestCase):
+class TestBOM(ERPNextTestCase):
def setUp(self):
if not frappe.get_value('Item', '_Test Item'):
make_test_records('Item')
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index 526c243..12576cb 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -1,19 +1,16 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-
-
-import unittest
-
import frappe
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.tests.utils import ERPNextTestCase
test_records = frappe.get_test_records('BOM')
-class TestBOMUpdateTool(unittest.TestCase):
+class TestBOMUpdateTool(ERPNextTestCase):
def test_replace_bom(self):
current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 453ad50..dac7b36 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -75,6 +75,15 @@
&& (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons");
}
+
+ if (frm.doc.work_order) {
+ frappe.db.get_value('Work Order', frm.doc.work_order,
+ 'transfer_material_against').then((r) => {
+ if (r.message.transfer_material_against == 'Work Order') {
+ frm.set_df_property('items', 'hidden', 1);
+ }
+ });
+ }
},
setup_corrective_job_card: function(frm) {
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index 9b4fc8b..bb5004b 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -1,6 +1,5 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
import frappe
from frappe.utils import random_string
@@ -12,9 +11,10 @@
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.tests.utils import ERPNextTestCase
-class TestJobCard(unittest.TestCase):
+class TestJobCard(ERPNextTestCase):
def setUp(self):
make_bom_for_jc_tests()
@@ -329,4 +329,4 @@
bom.rm_cost_as_per = "Valuation Rate"
bom.items[0].uom = "_Test UOM 1"
bom.items[0].conversion_factor = 5
- bom.insert()
\ No newline at end of file
+ bom.insert()
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index a2980a7..2febc1e 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1,8 +1,5 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-
-import unittest
-
import frappe
from frappe.utils import add_to_date, flt, now_datetime, nowdate
@@ -17,9 +14,10 @@
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
+from erpnext.tests.utils import ERPNextTestCase
-class TestProductionPlan(unittest.TestCase):
+class TestProductionPlan(ERPNextTestCase):
def setUp(self):
for item in ['Test Production Item 1', 'Subassembly Item 1',
'Raw Material Item 1', 'Raw Material Item 2']:
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 68d9dec..e90b0a7 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -1,17 +1,15 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-
-import unittest
-
import frappe
from frappe.test_runner import make_test_records
from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.tests.utils import ERPNextTestCase
-class TestRouting(unittest.TestCase):
+class TestRouting(ERPNextTestCase):
@classmethod
def setUpClass(cls):
cls.item_code = "Test Routing Item - A"
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 1e74b6d..165e0ac 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1,6 +1,5 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-import unittest
import frappe
from frappe.utils import add_months, cint, flt, now, today
@@ -95,7 +94,7 @@
def test_reserved_qty_for_partial_completion(self):
item = "_Test Item"
- warehouse = create_warehouse("Test Warehouse for reserved_qty - _TC")
+ warehouse = "_Test Warehouse - _TC"
bin1_at_start = get_bin(item, warehouse)
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index 5ed5153..c298c0a 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -1,8 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
# See license.txt
-
-import unittest
-
import frappe
from frappe.test_runner import make_test_records
@@ -13,12 +10,13 @@
WorkstationHolidayError,
check_if_within_operating_hours,
)
+from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Warehouse"]
test_records = frappe.get_test_records('Workstation')
make_test_records('Workstation')
-class TestWorkstation(unittest.TestCase):
+class TestWorkstation(ERPNextTestCase):
def test_validate_timings(self):
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0687b42..65ad79e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -165,6 +165,7 @@
erpnext.patches.v12_0.set_default_payroll_based_on
erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse
erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign
+erpnext.patches.v13_0.validate_options_for_data_field
erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.rename_pos_closing_doctype
@@ -287,7 +288,6 @@
erpnext.patches.v14_0.delete_einvoicing_doctypes
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
-erpnext.patches.v13_0.validate_options_for_data_field
erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
erpnext.patches.v14_0.delete_shopify_doctypes
erpnext.patches.v13_0.fix_invoice_statuses
diff --git a/erpnext/patches/v13_0/rename_ksa_qr_field.py b/erpnext/patches/v13_0/rename_ksa_qr_field.py
index 0bb86e0..f4f9b17 100644
--- a/erpnext/patches/v13_0/rename_ksa_qr_field.py
+++ b/erpnext/patches/v13_0/rename_ksa_qr_field.py
@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.model.utils.rename_field import rename_field
@@ -12,5 +13,20 @@
if frappe.db.exists('DocType', 'Sales Invoice'):
frappe.reload_doc('accounts', 'doctype', 'sales_invoice', force=True)
+
+ # rename_field method assumes that the field already exists or the doc is synced
+ if not frappe.db.has_column('Sales Invoice', 'ksa_einv_qr'):
+ create_custom_fields({
+ 'Sales Invoice': [
+ dict(
+ fieldname='ksa_einv_qr',
+ label='KSA E-Invoicing QR',
+ fieldtype='Attach Image',
+ read_only=1, no_copy=1, hidden=1
+ )
+ ]
+ })
+
if frappe.db.has_column('Sales Invoice', 'qr_code'):
rename_field('Sales Invoice', 'qr_code', 'ksa_einv_qr')
+ frappe.delete_doc_if_exists("Custom Field", "Sales Invoice-qr_code")
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 05af09e..b035292 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -940,10 +940,12 @@
def get_amount_based_on_payment_days(self, row, joining_date, relieving_date):
amount, additional_amount = row.amount, row.additional_amount
+ timesheet_component = frappe.db.get_value("Salary Structure", self.salary_structure, "salary_component")
+
if (self.salary_structure and
cint(row.depends_on_payment_days) and cint(self.total_working_days)
and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary
- and (not self.salary_slip_based_on_timesheet or
+ and (row.salary_component != timesheet_component or
getdate(self.start_date) < joining_date or
(relieving_date and getdate(self.end_date) > relieving_date)
)):
@@ -952,7 +954,7 @@
amount = flt((flt(row.default_amount) * flt(self.payment_days)
/ cint(self.total_working_days)), row.precision("amount")) + additional_amount
- elif not self.payment_days and not self.salary_slip_based_on_timesheet and cint(row.depends_on_payment_days):
+ elif not self.payment_days and row.salary_component != timesheet_component and cint(row.depends_on_payment_days):
amount, additional_amount = 0, 0
elif not row.amount:
amount = flt(row.default_amount) + flt(row.additional_amount)
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index e4618c3..3052a2b 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -134,6 +134,57 @@
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
+ def test_payment_days_in_salary_slip_based_on_timesheet(self):
+ from erpnext.hr.doctype.attendance.attendance import mark_attendance
+ from erpnext.projects.doctype.timesheet.test_timesheet import (
+ make_salary_structure_for_timesheet,
+ make_timesheet,
+ )
+ from erpnext.projects.doctype.timesheet.timesheet import (
+ make_salary_slip as make_salary_slip_for_timesheet,
+ )
+
+ # Payroll based on attendance
+ frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
+
+ emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company")
+ frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
+
+ # mark attendance
+ month_start_date = get_first_day(nowdate())
+ month_end_date = get_last_day(nowdate())
+
+ first_sunday = frappe.db.sql("""
+ select holiday_date from `tabHoliday`
+ where parent = 'Salary Slip Test Holiday List'
+ and holiday_date between %s and %s
+ order by holiday_date
+ """, (month_start_date, month_end_date))[0][0]
+
+ mark_attendance(emp, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent
+
+ # salary structure based on timesheet
+ make_salary_structure_for_timesheet(emp)
+ timesheet = make_timesheet(emp, simulate=True, is_billable=1)
+ salary_slip = make_salary_slip_for_timesheet(timesheet.name)
+ salary_slip.start_date = month_start_date
+ salary_slip.end_date = month_end_date
+ salary_slip.save()
+ salary_slip.submit()
+
+ no_of_days = self.get_no_of_days()
+ days_in_month = no_of_days[0]
+ no_of_holidays = no_of_days[1]
+
+ self.assertEqual(salary_slip.payment_days, days_in_month - no_of_holidays - 1)
+
+ # gross pay calculation based on attendance (payment days)
+ gross_pay = 78100 - ((78000 / (days_in_month - no_of_holidays)) * flt(salary_slip.leave_without_pay + salary_slip.absent_days))
+
+ self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2))
+
+ frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
+
def test_component_amount_dependent_on_another_payment_days_based_component(self):
from erpnext.hr.doctype.attendance.attendance import mark_attendance
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index d59cc01..148d8ba 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -34,10 +34,6 @@
for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]:
frappe.db.sql("delete from `tab%s`" % dt)
- if not frappe.db.exists("Salary Component", "Timesheet Component"):
- frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert()
-
-
def test_timesheet_billing_amount(self):
emp = make_employee("test_employee_6@salary.com")
@@ -160,6 +156,9 @@
salary_structure_name = "Timesheet Salary Structure Test"
frequency = "Monthly"
+ if not frappe.db.exists("Salary Component", "Timesheet Component"):
+ frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert()
+
salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True)
salary_structure.salary_component = "Timesheet Component"
salary_structure.salary_slip_based_on_timesheet = 1
diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
index e03ad37..1c1335e 100644
--- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
+++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
@@ -114,9 +114,11 @@
items = frappe.db.sql("""
select
- `tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate,
- `tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty,
- `tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_amount,
+ `tabSales Invoice Item`.gst_hsn_code,
+ `tabSales Invoice Item`.stock_uom,
+ sum(`tabSales Invoice Item`.stock_qty) as stock_qty,
+ sum(`tabSales Invoice Item`.base_net_amount) as base_net_amount,
+ sum(`tabSales Invoice Item`.base_price_list_rate) as base_price_list_rate,
`tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code,
`tabGST HSN Code`.description
from `tabSales Invoice`, `tabSales Invoice Item`, `tabGST HSN Code`
@@ -124,6 +126,8 @@
and `tabSales Invoice`.docstatus = 1
and `tabSales Invoice Item`.gst_hsn_code is not NULL
and `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name %s %s
+ group by
+ `tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code
""" % (conditions, match_conditions), filters, as_dict=1)
diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/test_hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/test_hsn_wise_summary_of_outward_supplies.py
new file mode 100644
index 0000000..86dc458
--- /dev/null
+++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/test_hsn_wise_summary_of_outward_supplies.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+
+from unittest import TestCase
+
+import frappe
+
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import (
+ make_company as setup_company,
+)
+from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import (
+ make_customers as setup_customers,
+)
+from erpnext.regional.doctype.gstr_3b_report.test_gstr_3b_report import (
+ set_account_heads as setup_gst_settings,
+)
+from erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies import (
+ execute as run_report,
+)
+from erpnext.stock.doctype.item.test_item import make_item
+
+
+class TestHSNWiseSummaryReport(TestCase):
+ @classmethod
+ def setUpClass(cls):
+ setup_company()
+ setup_customers()
+ setup_gst_settings()
+ make_item("Golf Car", properties={ "gst_hsn_code": "999900" })
+
+ @classmethod
+ def tearDownClass(cls):
+ frappe.db.rollback()
+
+ def test_hsn_summary_for_invoice_with_duplicate_items(self):
+ si = create_sales_invoice(
+ company="_Test Company GST",
+ customer = "_Test GST Customer",
+ currency = "INR",
+ warehouse = "Finished Goods - _GST",
+ debit_to = "Debtors - _GST",
+ income_account = "Sales - _GST",
+ expense_account = "Cost of Goods Sold - _GST",
+ cost_center = "Main - _GST",
+ do_not_save=1
+ )
+
+ si.items = []
+ si.append("items", {
+ "item_code": "Golf Car",
+ "gst_hsn_code": "999900",
+ "qty": "1",
+ "rate": "120",
+ "cost_center": "Main - _GST"
+ })
+ si.append("items", {
+ "item_code": "Golf Car",
+ "gst_hsn_code": "999900",
+ "qty": "1",
+ "rate": "140",
+ "cost_center": "Main - _GST"
+ })
+ si.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "Output Tax IGST - _GST",
+ "cost_center": "Main - _GST",
+ "description": "IGST @ 18.0",
+ "rate": 18
+ })
+ si.posting_date = "2020-11-17"
+ si.submit()
+ si.reload()
+
+ [columns, data] = run_report(filters=frappe._dict({
+ "company": "_Test Company GST",
+ "gst_hsn_code": "999900",
+ "company_gstin": si.company_gstin,
+ "from_date": si.posting_date,
+ "to_date": si.posting_date
+ }))
+
+ filtered_rows = list(filter(lambda row: row['gst_hsn_code'] == "999900", data))
+ self.assertTrue(filtered_rows)
+
+ hsn_row = filtered_rows[0]
+ self.assertEquals(hsn_row['stock_qty'], 2.0)
+ self.assertEquals(hsn_row['total_amount'], 306.8)
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 7d6b74d..5301fd0 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -2,8 +2,6 @@
# License: GNU General Public License v3. See license.txt
-import unittest
-
import frappe
from frappe.test_runner import make_test_records
from frappe.utils import flt
@@ -11,7 +9,7 @@
from erpnext.accounts.party import get_due_date
from erpnext.exceptions import PartyDisabled, PartyFrozen
from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding
-from erpnext.tests.utils import create_test_contact_and_address
+from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address
test_ignore = ["Price List"]
test_dependencies = ['Payment Term', 'Payment Terms Template']
@@ -19,7 +17,7 @@
-class TestCustomer(unittest.TestCase):
+class TestCustomer(ERPNextTestCase):
def setUp(self):
if not frappe.get_value('Item', '_Test Item'):
make_test_records('Item')
diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
index 874a364..b951044 100644
--- a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
+++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
@@ -6,6 +6,7 @@
import frappe
from erpnext.controllers.queries import item_query
+from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ['Item', 'Customer', 'Supplier']
@@ -17,7 +18,7 @@
psi.based_on_value = args.get('based_on_value')
psi.insert()
-class TestPartySpecificItem(unittest.TestCase):
+class TestPartySpecificItem(ERPNextTestCase):
def setUp(self):
self.customer = frappe.get_last_doc("Customer")
self.supplier = frappe.get_last_doc("Supplier")
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index aa83726..4357201 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -1,15 +1,15 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-import unittest
-
import frappe
from frappe.utils import add_days, add_months, flt, getdate, nowdate
+from erpnext.tests.utils import ERPNextTestCase
+
test_dependencies = ["Product Bundle"]
-class TestQuotation(unittest.TestCase):
+class TestQuotation(ERPNextTestCase):
def test_make_quotation_without_terms(self):
quotation = make_quotation(do_not_save=1)
self.assertFalse(quotation.get('payment_schedule'))
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 2a0752e..42bc0b7 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
import json
-import unittest
import frappe
import frappe.permissions
@@ -28,12 +27,14 @@
)
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.tests.utils import ERPNextTestCase
-class TestSalesOrder(unittest.TestCase):
+class TestSalesOrder(ERPNextTestCase):
@classmethod
def setUpClass(cls):
+ super().setUpClass()
cls.unlink_setting = int(frappe.db.get_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order"))
@@ -42,6 +43,7 @@
# reset config to previous state
frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting)
+ super().tearDownClass()
def tearDown(self):
frappe.set_user("Administrator")
diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py
index 9c30afc..d62915f 100644
--- a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py
+++ b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py
@@ -2,8 +2,6 @@
# For license information, please see license.txt
-import unittest
-
from frappe.utils import add_months, nowdate
from erpnext.selling.doctype.sales_order.sales_order import make_material_request
@@ -11,9 +9,10 @@
from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request import (
execute,
)
+from erpnext.tests.utils import ERPNextTestCase
-class TestPendingSOItemsForPurchaseRequest(unittest.TestCase):
+class TestPendingSOItemsForPurchaseRequest(ERPNextTestCase):
def test_result_for_partial_material_request(self):
so = make_sales_order()
mr=make_material_request(so.name)
diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py
index 8ffc5d6..f56cce2 100644
--- a/erpnext/selling/report/sales_analytics/test_analytics.py
+++ b/erpnext/selling/report/sales_analytics/test_analytics.py
@@ -2,15 +2,14 @@
# For license information, please see license.txt
-import unittest
-
import frappe
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.selling.report.sales_analytics.sales_analytics import execute
+from erpnext.tests.utils import ERPNextTestCase
-class TestAnalytics(unittest.TestCase):
+class TestAnalytics(ERPNextTestCase):
def test_sales_analytics(self):
frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'")
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index a33134b..37b5411 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -43,9 +43,9 @@
frappe.qb
.from_(wo)
.from_(wo_item)
- .select(Case()
- .when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty))
- .else_(Sum(wo_item.required_qty - wo_item.consumed_qty))
+ .select(Sum(Case()
+ .when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
+ .else_(wo_item.required_qty - wo_item.consumed_qty))
)
.where(
(wo_item.item_code == self.item_code)
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 4f4e691..29abd45 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -361,8 +361,7 @@
"fieldname": "valuation_method",
"fieldtype": "Select",
"label": "Valuation Method",
- "options": "\nFIFO\nMoving Average",
- "set_only_once": 1
+ "options": "\nFIFO\nMoving Average"
},
{
"depends_on": "is_stock_item",
@@ -1035,7 +1034,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-12-03 08:32:03.869294",
+ "modified": "2021-12-14 04:13:16.857534",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json
index 29c4193..4270839 100644
--- a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json
+++ b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.json
@@ -1,451 +1,140 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-04-08 13:10:16",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-04-08 13:10:16",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "column_break_2",
+ "item_name",
+ "batch_no",
+ "desc_section",
+ "description",
+ "quantity_section",
+ "qty",
+ "net_weight",
+ "column_break_10",
+ "stock_uom",
+ "weight_uom",
+ "page_break",
+ "dn_detail"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Item Code",
+ "options": "Item",
+ "print_width": "100px",
+ "reqd": 1,
"width": "100px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "options": "item_code.item_name",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "200px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fetch_from": "item_code.item_name",
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Item Name",
+ "print_width": "200px",
+ "read_only": 1,
"width": "200px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "batch_no",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Batch No",
- "length": 0,
- "no_copy": 0,
- "options": "Batch",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "batch_no",
+ "fieldtype": "Link",
+ "label": "Batch No",
+ "options": "Batch"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fieldname": "desc_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "fieldname": "desc_section",
+ "fieldtype": "Section Break",
+ "label": "Description"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "quantity_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Quantity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "quantity_section",
+ "fieldtype": "Section Break",
+ "label": "Quantity"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Quantity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Quantity",
+ "print_width": "100px",
+ "reqd": 1,
"width": "100px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "net_weight",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Net Weight",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "net_weight",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Net Weight",
+ "print_width": "100px",
"width": "100px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_10",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "stock_uom",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "UOM",
- "length": 0,
- "no_copy": 0,
- "options": "UOM",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "options": "UOM",
+ "print_width": "100px",
+ "read_only": 1,
"width": "100px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "weight_uom",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Weight UOM",
- "length": 0,
- "no_copy": 0,
- "options": "UOM",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "weight_uom",
+ "fieldtype": "Link",
+ "label": "Weight UOM",
+ "options": "UOM",
+ "print_width": "100px",
"width": "100px"
- },
+ },
{
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "page_break",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Page Break",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "page_break",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Page Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "dn_detail",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "DN Detail",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "dn_detail",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "in_list_view": 1,
+ "label": "DN Detail"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-06-01 07:21:58.220980",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Packing Slip Item",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-12-14 01:22:00.715935",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Packing Slip Item",
+ "naming_rule": "Random",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index 314f160..3f49065 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -48,6 +48,7 @@
conditions = [get_item_group_condition(filters.get("item_group"))]
if filters.get("brand"):
conditions.append("item.brand=%(brand)s")
+ conditions.append("is_stock_item = 1")
return frappe.db.sql("""select name, item_name, description, brand, item_group,
safety_stock, lead_time_days from `tabItem` item where {}"""
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 50f31fd..c94700b 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -420,13 +420,13 @@
if is_open_status(prev_status) and is_hold_status(doc.status):
# Issue is on hold -> Set on_hold_since
doc.on_hold_since = now_time
+ reset_expected_response_and_resolution(doc)
# Replied to Open
if is_hold_status(prev_status) and is_open_status(doc.status):
# Issue was on hold -> Calculate Total Hold Time
calculate_hold_hours()
# Issue is open -> reset resolution_date
- reset_expected_response_and_resolution(doc)
reset_resolution_metrics(doc)
# Open to Closed
@@ -440,7 +440,6 @@
# Issue was closed -> Calculate Total Hold Time from resolution_date
calculate_hold_hours()
# Issue is open -> reset resolution_date
- reset_expected_response_and_resolution(doc)
reset_resolution_metrics(doc)
# Closed to Replied
@@ -449,6 +448,7 @@
calculate_hold_hours()
# Issue is on hold -> Set on_hold_since
doc.on_hold_since = now_time
+ reset_expected_response_and_resolution(doc)
# Replied to Closed
if is_hold_status(prev_status) and is_fulfilled_status(doc.status):
@@ -640,28 +640,35 @@
if not parent.meta.has_field('service_level_agreement'):
return
- for_resolution = frappe.db.get_value('Service Level Agreement', parent.service_level_agreement, 'apply_sla_for_resolution')
-
if (
doc.sent_or_received == "Received" # a reply is received
and parent.get('status') == 'Open' # issue status is set as open from communication.py
- and parent._doc_before_save
+ and parent.get_doc_before_save()
and parent.get('status') != parent._doc_before_save.get('status') # status changed
):
# undo the status change in db
# since prev status is fetched from db
- frappe.db.set_value(parent.doctype, parent.name, 'status', parent._doc_before_save.get('status'))
+ frappe.db.set_value(
+ parent.doctype, parent.name,
+ 'status', parent._doc_before_save.get('status'),
+ update_modified=False
+ )
elif (
doc.sent_or_received == "Sent" # a reply is sent
and parent.get('first_responded_on') # first_responded_on is set from communication.py
- and parent._doc_before_save
+ and parent.get_doc_before_save()
and not parent._doc_before_save.get('first_responded_on') # first_responded_on was not set
):
# reset first_responded_on since it will be handled/set later on
parent.first_responded_on = None
parent.flags.on_first_reply = True
+ else:
+ return
+
+ for_resolution = frappe.db.get_value('Service Level Agreement', parent.service_level_agreement, 'apply_sla_for_resolution')
+
handle_status_change(parent, for_resolution)
update_response_and_resolution_metrics(parent, for_resolution)
update_agreement_status(parent, for_resolution)
@@ -670,12 +677,10 @@
def reset_expected_response_and_resolution(doc):
- update_values = {}
if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
- update_values['response_by'] = None
+ doc.response_by = None
if doc.meta.has_field("resolution_by") and not doc.get('resolution_date'):
- update_values['resolution_by'] = None
- doc.db_set(update_values)
+ doc.resolution_by = None
def set_response_by(doc, start_date_time, priority):