Merge branch 'develop' into fix-dn-mapping
This commit is contained in:
commit
4c8408a572
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
|
||||||
|
|
||||||
|
|||||||
@ -135,7 +135,7 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
||||||
rate = flt(row.outstanding_amount) / flt(row.qty)
|
rate = flt(row.outstanding_amount) / flt(row.qty)
|
||||||
|
|
||||||
return frappe._dict({
|
item_dict = frappe._dict({
|
||||||
"uom": default_uom,
|
"uom": default_uom,
|
||||||
"rate": rate or 0.0,
|
"rate": rate or 0.0,
|
||||||
"qty": row.qty,
|
"qty": row.qty,
|
||||||
@ -146,6 +146,13 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
"cost_center": cost_center
|
"cost_center": cost_center
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for dimension in get_accounting_dimensions():
|
||||||
|
item_dict.update({
|
||||||
|
dimension: row.get(dimension)
|
||||||
|
})
|
||||||
|
|
||||||
|
return item_dict
|
||||||
|
|
||||||
item = get_item_dict()
|
item = get_item_dict()
|
||||||
|
|
||||||
invoice = frappe._dict({
|
invoice = frappe._dict({
|
||||||
@ -166,7 +173,7 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
accounting_dimension = get_accounting_dimensions()
|
accounting_dimension = get_accounting_dimensions()
|
||||||
for dimension in accounting_dimension:
|
for dimension in accounting_dimension:
|
||||||
invoice.update({
|
invoice.update({
|
||||||
dimension: item.get(dimension)
|
dimension: self.get(dimension) or item.get(dimension)
|
||||||
})
|
})
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|||||||
@ -7,21 +7,26 @@ import frappe
|
|||||||
from frappe.cache_manager import clear_doctype_cache
|
from frappe.cache_manager import clear_doctype_cache
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
||||||
|
create_dimension,
|
||||||
|
disable_dimension,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
|
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
|
||||||
get_temporary_opening_account,
|
get_temporary_opening_account,
|
||||||
)
|
)
|
||||||
|
|
||||||
test_dependencies = ["Customer", "Supplier"]
|
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
|
||||||
|
|
||||||
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
make_company()
|
make_company()
|
||||||
|
create_dimension()
|
||||||
|
|
||||||
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
|
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None):
|
||||||
doc = frappe.get_single("Opening Invoice Creation Tool")
|
doc = frappe.get_single("Opening Invoice Creation Tool")
|
||||||
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
||||||
party_1=party_1, party_2=party_2, invoice_number=invoice_number)
|
party_1=party_1, party_2=party_2, invoice_number=invoice_number, department=department)
|
||||||
doc.update(args)
|
doc.update(args)
|
||||||
return doc.make_invoices()
|
return doc.make_invoices()
|
||||||
|
|
||||||
@ -106,6 +111,19 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
doc = frappe.get_doc('Sales Invoice', inv)
|
doc = frappe.get_doc('Sales Invoice', inv)
|
||||||
doc.cancel()
|
doc.cancel()
|
||||||
|
|
||||||
|
def test_opening_invoice_with_accounting_dimension(self):
|
||||||
|
invoices = self.make_invoices(invoice_type="Sales", company="_Test Opening Invoice Company", department='Sales - _TOIC')
|
||||||
|
|
||||||
|
expected_value = {
|
||||||
|
"keys": ["customer", "outstanding_amount", "status", "department"],
|
||||||
|
0: ["_Test Customer", 300, "Overdue", "Sales - _TOIC"],
|
||||||
|
1: ["_Test Customer 1", 250, "Overdue", "Sales - _TOIC"],
|
||||||
|
}
|
||||||
|
self.check_expected_values(invoices, expected_value, invoice_type="Sales")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
disable_dimension()
|
||||||
|
|
||||||
def get_opening_invoice_creation_dict(**args):
|
def get_opening_invoice_creation_dict(**args):
|
||||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||||
company = args.get("company", "_Test Company")
|
company = args.get("company", "_Test Company")
|
||||||
|
|||||||
@ -58,7 +58,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
|
|||||||
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
||||||
|
|
||||||
party = frappe.get_doc(party_type, party)
|
party = frappe.get_doc(party_type, party)
|
||||||
currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
|
currency = party.get("default_currency") or currency or get_company_currency(company)
|
||||||
|
|
||||||
party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
|
party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
|
||||||
set_contact_details(party_details, party, party_type)
|
set_contact_details(party_details, party, party_type)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,42 @@ from erpnext.stock.doctype.item.test_item import create_item
|
|||||||
class TestDeferredRevenueAndExpense(unittest.TestCase):
|
class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
clear_old_entries()
|
clear_accounts_and_items()
|
||||||
create_company()
|
create_company()
|
||||||
|
self.maxDiff = None
|
||||||
|
|
||||||
|
def clear_old_entries(self):
|
||||||
|
sinv = qb.DocType("Sales Invoice")
|
||||||
|
sinv_item = qb.DocType("Sales Invoice Item")
|
||||||
|
pinv = qb.DocType("Purchase Invoice")
|
||||||
|
pinv_item = qb.DocType("Purchase Invoice Item")
|
||||||
|
|
||||||
|
# delete existing invoices with deferred items
|
||||||
|
deferred_invoices = (
|
||||||
|
qb.from_(sinv)
|
||||||
|
.join(sinv_item)
|
||||||
|
.on(sinv.name == sinv_item.parent)
|
||||||
|
.select(sinv.name)
|
||||||
|
.where(sinv_item.enable_deferred_revenue == 1)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
if deferred_invoices:
|
||||||
|
qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
|
||||||
|
|
||||||
|
deferred_invoices = (
|
||||||
|
qb.from_(pinv)
|
||||||
|
.join(pinv_item)
|
||||||
|
.on(pinv.name == pinv_item.parent)
|
||||||
|
.select(pinv.name)
|
||||||
|
.where(pinv_item.enable_deferred_expense == 1)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
if deferred_invoices:
|
||||||
|
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
||||||
|
|
||||||
def test_deferred_revenue(self):
|
def test_deferred_revenue(self):
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
# created deferred expense accounts, if not found
|
# created deferred expense accounts, if not found
|
||||||
deferred_revenue_account = create_account(
|
deferred_revenue_account = create_account(
|
||||||
account_name="Deferred Revenue",
|
account_name="Deferred Revenue",
|
||||||
@ -108,6 +140,8 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
self.assertEqual(report.period_total, expected)
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
def test_deferred_expense(self):
|
def test_deferred_expense(self):
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
# created deferred expense accounts, if not found
|
# created deferred expense accounts, if not found
|
||||||
deferred_expense_account = create_account(
|
deferred_expense_account = create_account(
|
||||||
account_name="Deferred Expense",
|
account_name="Deferred Expense",
|
||||||
@ -198,6 +232,91 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(report.period_total, expected)
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
def test_zero_months(self):
|
||||||
|
self.clear_old_entries()
|
||||||
|
# created deferred expense accounts, if not found
|
||||||
|
deferred_revenue_account = create_account(
|
||||||
|
account_name="Deferred Revenue",
|
||||||
|
parent_account="Current Liabilities - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
|
||||||
|
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||||
|
acc_settings.book_deferred_entries_based_on = "Months"
|
||||||
|
acc_settings.save()
|
||||||
|
|
||||||
|
customer = frappe.new_doc("Customer")
|
||||||
|
customer.customer_name = "_Test Customer DR"
|
||||||
|
customer.type = "Individual"
|
||||||
|
customer.insert()
|
||||||
|
|
||||||
|
item = create_item(
|
||||||
|
"_Test Internet Subscription",
|
||||||
|
is_stock_item=0,
|
||||||
|
warehouse="All Warehouses - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
item.enable_deferred_revenue = 1
|
||||||
|
item.deferred_revenue_account = deferred_revenue_account
|
||||||
|
item.no_of_months = 0
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
item=item.name,
|
||||||
|
company="_Test Company DR",
|
||||||
|
customer="_Test Customer DR",
|
||||||
|
debit_to="Debtors - _CD",
|
||||||
|
posting_date="2021-05-01",
|
||||||
|
parent_cost_center="Main - _CD",
|
||||||
|
cost_center="Main - _CD",
|
||||||
|
do_not_submit=True,
|
||||||
|
rate=300,
|
||||||
|
price_list_rate=300,
|
||||||
|
)
|
||||||
|
si.items[0].enable_deferred_revenue = 1
|
||||||
|
si.items[0].deferred_revenue_account = deferred_revenue_account
|
||||||
|
si.items[0].income_account = "Sales - _CD"
|
||||||
|
si.save()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
pda = frappe.get_doc(
|
||||||
|
dict(
|
||||||
|
doctype="Process Deferred Accounting",
|
||||||
|
posting_date=nowdate(),
|
||||||
|
start_date="2021-05-01",
|
||||||
|
end_date="2021-08-01",
|
||||||
|
type="Income",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pda.insert()
|
||||||
|
pda.submit()
|
||||||
|
|
||||||
|
# execute report
|
||||||
|
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||||
|
self.filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
"filter_based_on": "Date Range",
|
||||||
|
"period_start_date": "2021-05-01",
|
||||||
|
"period_end_date": "2021-08-01",
|
||||||
|
"from_fiscal_year": fiscal_year.year,
|
||||||
|
"to_fiscal_year": fiscal_year.year,
|
||||||
|
"periodicity": "Monthly",
|
||||||
|
"type": "Revenue",
|
||||||
|
"with_upcoming_postings": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
|
||||||
|
report.run()
|
||||||
|
expected = [
|
||||||
|
{"key": "may_2021", "total": 300.0, "actual": 300.0},
|
||||||
|
{"key": "jun_2021", "total": 0, "actual": 0},
|
||||||
|
{"key": "jul_2021", "total": 0, "actual": 0},
|
||||||
|
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||||
|
]
|
||||||
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
def create_company():
|
def create_company():
|
||||||
company = frappe.db.exists("Company", "_Test Company DR")
|
company = frappe.db.exists("Company", "_Test Company DR")
|
||||||
@ -209,15 +328,11 @@ def create_company():
|
|||||||
company.insert()
|
company.insert()
|
||||||
|
|
||||||
|
|
||||||
def clear_old_entries():
|
def clear_accounts_and_items():
|
||||||
item = qb.DocType("Item")
|
item = qb.DocType("Item")
|
||||||
account = qb.DocType("Account")
|
account = qb.DocType("Account")
|
||||||
customer = qb.DocType("Customer")
|
customer = qb.DocType("Customer")
|
||||||
supplier = qb.DocType("Supplier")
|
supplier = qb.DocType("Supplier")
|
||||||
sinv = qb.DocType("Sales Invoice")
|
|
||||||
sinv_item = qb.DocType("Sales Invoice Item")
|
|
||||||
pinv = qb.DocType("Purchase Invoice")
|
|
||||||
pinv_item = qb.DocType("Purchase Invoice Item")
|
|
||||||
|
|
||||||
qb.from_(account).delete().where(
|
qb.from_(account).delete().where(
|
||||||
(account.account_name == "Deferred Revenue")
|
(account.account_name == "Deferred Revenue")
|
||||||
@ -228,26 +343,3 @@ def clear_old_entries():
|
|||||||
).run()
|
).run()
|
||||||
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
|
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
|
||||||
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
|
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
|
||||||
|
|
||||||
# delete existing invoices with deferred items
|
|
||||||
deferred_invoices = (
|
|
||||||
qb.from_(sinv)
|
|
||||||
.join(sinv_item)
|
|
||||||
.on(sinv.name == sinv_item.parent)
|
|
||||||
.select(sinv.name)
|
|
||||||
.where(sinv_item.enable_deferred_revenue == 1)
|
|
||||||
.run()
|
|
||||||
)
|
|
||||||
if deferred_invoices:
|
|
||||||
qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
|
|
||||||
|
|
||||||
deferred_invoices = (
|
|
||||||
qb.from_(pinv)
|
|
||||||
.join(pinv_item)
|
|
||||||
.on(pinv.name == pinv_item.parent)
|
|
||||||
.select(pinv.name)
|
|
||||||
.where(pinv_item.enable_deferred_expense == 1)
|
|
||||||
.run()
|
|
||||||
)
|
|
||||||
if deferred_invoices:
|
|
||||||
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
|
||||||
|
|||||||
@ -70,9 +70,18 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
|
|
||||||
# set contact and address details for supplier, if they are not mentioned
|
# set contact and address details for supplier, if they are not mentioned
|
||||||
if getattr(self, "supplier", None):
|
if getattr(self, "supplier", None):
|
||||||
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
|
self.update_if_missing(
|
||||||
doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'),
|
get_party_details(
|
||||||
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template')))
|
self.supplier,
|
||||||
|
party_type="Supplier",
|
||||||
|
doctype=self.doctype,
|
||||||
|
company=self.company,
|
||||||
|
party_address=self.get("supplier_address"),
|
||||||
|
shipping_address=self.get('shipping_address'),
|
||||||
|
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'),
|
||||||
|
ignore_permissions=self.flags.ignore_permissions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.set_missing_item_details(for_validate)
|
self.set_missing_item_details(for_validate)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
|||||||
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
|
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
|
||||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
|
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
|
||||||
from erpnext.hr.utils import (
|
from erpnext.hr.utils import (
|
||||||
|
get_holiday_dates_for_employee,
|
||||||
get_leave_period,
|
get_leave_period,
|
||||||
set_employee_name,
|
set_employee_name,
|
||||||
share_doc_with_approver,
|
share_doc_with_approver,
|
||||||
@ -159,33 +160,57 @@ class LeaveApplication(Document):
|
|||||||
.format(formatdate(future_allocation[0].from_date), future_allocation[0].name))
|
.format(formatdate(future_allocation[0].from_date), future_allocation[0].name))
|
||||||
|
|
||||||
def update_attendance(self):
|
def update_attendance(self):
|
||||||
if self.status == "Approved":
|
if self.status != "Approved":
|
||||||
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
|
return
|
||||||
date = dt.strftime("%Y-%m-%d")
|
|
||||||
status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
|
|
||||||
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
|
|
||||||
attendance_date = date, docstatus = ('!=', 2)))
|
|
||||||
|
|
||||||
|
holiday_dates = []
|
||||||
|
if not frappe.db.get_value("Leave Type", self.leave_type, "include_holiday"):
|
||||||
|
holiday_dates = get_holiday_dates_for_employee(self.employee, self.from_date, self.to_date)
|
||||||
|
|
||||||
|
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
|
||||||
|
date = dt.strftime("%Y-%m-%d")
|
||||||
|
attendance_name = frappe.db.exists("Attendance", dict(employee = self.employee,
|
||||||
|
attendance_date = date, docstatus = ('!=', 2)))
|
||||||
|
|
||||||
|
# don't mark attendance for holidays
|
||||||
|
# if leave type does not include holidays within leaves as leaves
|
||||||
|
if date in holiday_dates:
|
||||||
if attendance_name:
|
if attendance_name:
|
||||||
# update existing attendance, change absent to on leave
|
# cancel and delete existing attendance for holidays
|
||||||
doc = frappe.get_doc('Attendance', attendance_name)
|
attendance = frappe.get_doc("Attendance", attendance_name)
|
||||||
if doc.status != status:
|
attendance.flags.ignore_permissions = True
|
||||||
doc.db_set('status', status)
|
if attendance.docstatus == 1:
|
||||||
doc.db_set('leave_type', self.leave_type)
|
attendance.cancel()
|
||||||
doc.db_set('leave_application', self.name)
|
frappe.delete_doc("Attendance", attendance_name, force=1)
|
||||||
else:
|
continue
|
||||||
# make new attendance and submit it
|
|
||||||
doc = frappe.new_doc("Attendance")
|
self.create_or_update_attendance(attendance_name, date)
|
||||||
doc.employee = self.employee
|
|
||||||
doc.employee_name = self.employee_name
|
def create_or_update_attendance(self, attendance_name, date):
|
||||||
doc.attendance_date = date
|
status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
|
||||||
doc.company = self.company
|
|
||||||
doc.leave_type = self.leave_type
|
if attendance_name:
|
||||||
doc.leave_application = self.name
|
# update existing attendance, change absent to on leave
|
||||||
doc.status = status
|
doc = frappe.get_doc('Attendance', attendance_name)
|
||||||
doc.flags.ignore_validate = True
|
if doc.status != status:
|
||||||
doc.insert(ignore_permissions=True)
|
doc.db_set({
|
||||||
doc.submit()
|
'status': status,
|
||||||
|
'leave_type': self.leave_type,
|
||||||
|
'leave_application': self.name
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# make new attendance and submit it
|
||||||
|
doc = frappe.new_doc("Attendance")
|
||||||
|
doc.employee = self.employee
|
||||||
|
doc.employee_name = self.employee_name
|
||||||
|
doc.attendance_date = date
|
||||||
|
doc.company = self.company
|
||||||
|
doc.leave_type = self.leave_type
|
||||||
|
doc.leave_application = self.name
|
||||||
|
doc.status = status
|
||||||
|
doc.flags.ignore_validate = True
|
||||||
|
doc.insert(ignore_permissions=True)
|
||||||
|
doc.submit()
|
||||||
|
|
||||||
def cancel_attendance(self):
|
def cancel_attendance(self):
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
|
|||||||
@ -5,7 +5,16 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.permissions import clear_user_permissions_for_doctype
|
from frappe.permissions import clear_user_permissions_for_doctype
|
||||||
from frappe.utils import add_days, add_months, getdate, nowdate
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
add_months,
|
||||||
|
get_first_day,
|
||||||
|
get_last_day,
|
||||||
|
get_year_ending,
|
||||||
|
get_year_start,
|
||||||
|
getdate,
|
||||||
|
nowdate,
|
||||||
|
)
|
||||||
|
|
||||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
|
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
|
||||||
@ -19,6 +28,10 @@ from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
|
|||||||
create_assignment_for_multiple_employees,
|
create_assignment_for_multiple_employees,
|
||||||
)
|
)
|
||||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||||
|
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
|
||||||
|
make_holiday_list,
|
||||||
|
make_leave_application,
|
||||||
|
)
|
||||||
|
|
||||||
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
|
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
|
||||||
|
|
||||||
@ -61,13 +74,15 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]:
|
for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]:
|
||||||
frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec
|
frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec
|
||||||
|
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
set_leave_approver()
|
set_leave_approver()
|
||||||
frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
|
frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.set_user("Administrator")
|
frappe.db.rollback()
|
||||||
|
|
||||||
def _clear_roles(self):
|
def _clear_roles(self):
|
||||||
frappe.db.sql("""delete from `tabHas Role` where parent in
|
frappe.db.sql("""delete from `tabHas Role` where parent in
|
||||||
@ -106,6 +121,72 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
for d in ('2018-01-01', '2018-01-02', '2018-01-03'):
|
for d in ('2018-01-01', '2018-01-02', '2018-01-03'):
|
||||||
self.assertTrue(getdate(d) in dates)
|
self.assertTrue(getdate(d) in dates)
|
||||||
|
|
||||||
|
def test_attendance_for_include_holidays(self):
|
||||||
|
# Case 1: leave type with 'Include holidays within leaves as leaves' enabled
|
||||||
|
frappe.delete_doc_if_exists("Leave Type", "Test Include Holidays", force=1)
|
||||||
|
leave_type = frappe.get_doc(dict(
|
||||||
|
leave_type_name="Test Include Holidays",
|
||||||
|
doctype="Leave Type",
|
||||||
|
include_holiday=True
|
||||||
|
)).insert()
|
||||||
|
|
||||||
|
date = getdate()
|
||||||
|
make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
|
||||||
|
|
||||||
|
holiday_list = make_holiday_list()
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list)
|
||||||
|
first_sunday = get_first_sunday(holiday_list)
|
||||||
|
|
||||||
|
leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name)
|
||||||
|
leave_application.reload()
|
||||||
|
self.assertEqual(leave_application.total_leave_days, 4)
|
||||||
|
self.assertEqual(frappe.db.count('Attendance', {'leave_application': leave_application.name}), 4)
|
||||||
|
|
||||||
|
leave_application.cancel()
|
||||||
|
|
||||||
|
def test_attendance_update_for_exclude_holidays(self):
|
||||||
|
# Case 2: leave type with 'Include holidays within leaves as leaves' disabled
|
||||||
|
frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1)
|
||||||
|
leave_type = frappe.get_doc(dict(
|
||||||
|
leave_type_name="Test Do Not Include Holidays",
|
||||||
|
doctype="Leave Type",
|
||||||
|
include_holiday=False
|
||||||
|
)).insert()
|
||||||
|
|
||||||
|
date = getdate()
|
||||||
|
make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
|
||||||
|
|
||||||
|
holiday_list = make_holiday_list()
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list)
|
||||||
|
first_sunday = get_first_sunday(holiday_list)
|
||||||
|
|
||||||
|
# already marked attendance on a holiday should be deleted in this case
|
||||||
|
config = {
|
||||||
|
"doctype": "Attendance",
|
||||||
|
"employee": "_T-Employee-00001",
|
||||||
|
"status": "Present"
|
||||||
|
}
|
||||||
|
attendance_on_holiday = frappe.get_doc(config)
|
||||||
|
attendance_on_holiday.attendance_date = first_sunday
|
||||||
|
attendance_on_holiday.save()
|
||||||
|
|
||||||
|
# already marked attendance on a non-holiday should be updated
|
||||||
|
attendance = frappe.get_doc(config)
|
||||||
|
attendance.attendance_date = add_days(first_sunday, 3)
|
||||||
|
attendance.save()
|
||||||
|
|
||||||
|
leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name)
|
||||||
|
leave_application.reload()
|
||||||
|
# holiday should be excluded while marking attendance
|
||||||
|
self.assertEqual(leave_application.total_leave_days, 3)
|
||||||
|
self.assertEqual(frappe.db.count("Attendance", {"leave_application": leave_application.name}), 3)
|
||||||
|
|
||||||
|
# attendance on holiday deleted
|
||||||
|
self.assertFalse(frappe.db.exists("Attendance", attendance_on_holiday.name))
|
||||||
|
|
||||||
|
# attendance on non-holiday updated
|
||||||
|
self.assertEqual(frappe.db.get_value("Attendance", attendance.name, "status"), "On Leave")
|
||||||
|
|
||||||
def test_block_list(self):
|
def test_block_list(self):
|
||||||
self._clear_roles()
|
self._clear_roles()
|
||||||
|
|
||||||
@ -241,7 +322,13 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
leave_period = get_leave_period()
|
leave_period = get_leave_period()
|
||||||
today = nowdate()
|
today = nowdate()
|
||||||
holiday_list = 'Test Holiday List for Optional Holiday'
|
holiday_list = 'Test Holiday List for Optional Holiday'
|
||||||
optional_leave_date = add_days(today, 7)
|
employee = get_employee()
|
||||||
|
|
||||||
|
default_holiday_list = make_holiday_list()
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "default_holiday_list", default_holiday_list)
|
||||||
|
first_sunday = get_first_sunday(default_holiday_list)
|
||||||
|
|
||||||
|
optional_leave_date = add_days(first_sunday, 1)
|
||||||
|
|
||||||
if not frappe.db.exists('Holiday List', holiday_list):
|
if not frappe.db.exists('Holiday List', holiday_list):
|
||||||
frappe.get_doc(dict(
|
frappe.get_doc(dict(
|
||||||
@ -253,7 +340,6 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
dict(holiday_date = optional_leave_date, description = 'Test')
|
dict(holiday_date = optional_leave_date, description = 'Test')
|
||||||
]
|
]
|
||||||
)).insert()
|
)).insert()
|
||||||
employee = get_employee()
|
|
||||||
|
|
||||||
frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list)
|
frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list)
|
||||||
leave_type = 'Test Optional Type'
|
leave_type = 'Test Optional Type'
|
||||||
@ -266,7 +352,7 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
|
|
||||||
allocate_leaves(employee, leave_period, leave_type, 10)
|
allocate_leaves(employee, leave_period, leave_type, 10)
|
||||||
|
|
||||||
date = add_days(today, 6)
|
date = add_days(first_sunday, 2)
|
||||||
|
|
||||||
leave_application = frappe.get_doc(dict(
|
leave_application = frappe.get_doc(dict(
|
||||||
doctype = 'Leave Application',
|
doctype = 'Leave Application',
|
||||||
@ -637,13 +723,13 @@ def create_carry_forwarded_allocation(employee, leave_type):
|
|||||||
carry_forward=1)
|
carry_forward=1)
|
||||||
leave_allocation.submit()
|
leave_allocation.submit()
|
||||||
|
|
||||||
def make_allocation_record(employee=None, leave_type=None):
|
def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None):
|
||||||
allocation = frappe.get_doc({
|
allocation = frappe.get_doc({
|
||||||
"doctype": "Leave Allocation",
|
"doctype": "Leave Allocation",
|
||||||
"employee": employee or "_T-Employee-00001",
|
"employee": employee or "_T-Employee-00001",
|
||||||
"leave_type": leave_type or "_Test Leave Type",
|
"leave_type": leave_type or "_Test Leave Type",
|
||||||
"from_date": "2013-01-01",
|
"from_date": from_date or "2013-01-01",
|
||||||
"to_date": "2019-12-31",
|
"to_date": to_date or "2019-12-31",
|
||||||
"new_leaves_allocated": 30
|
"new_leaves_allocated": 30
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -692,3 +778,16 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
|
|||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
allocate_leave.submit()
|
allocate_leave.submit()
|
||||||
|
|
||||||
|
|
||||||
|
def get_first_sunday(holiday_list):
|
||||||
|
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 = %s
|
||||||
|
and holiday_date between %s and %s
|
||||||
|
order by holiday_date
|
||||||
|
""", (holiday_list, month_start_date, month_end_date))[0][0]
|
||||||
|
|
||||||
|
return first_sunday
|
||||||
@ -331,7 +331,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (has_template_rm) {
|
if (has_template_rm && has_template_rm.length) {
|
||||||
dialog.fields_dict.items.grid.refresh();
|
dialog.fields_dict.items.grid.refresh();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -467,7 +467,8 @@ var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) {
|
|||||||
"uom": d.uom,
|
"uom": d.uom,
|
||||||
"stock_uom": d.stock_uom,
|
"stock_uom": d.stock_uom,
|
||||||
"conversion_factor": d.conversion_factor,
|
"conversion_factor": d.conversion_factor,
|
||||||
"sourced_by_supplier": d.sourced_by_supplier
|
"sourced_by_supplier": d.sourced_by_supplier,
|
||||||
|
"do_not_explode": d.do_not_explode
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
d = locals[cdt][cdn];
|
d = locals[cdt][cdn];
|
||||||
@ -640,6 +641,13 @@ frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("BOM Item", {
|
||||||
|
do_not_explode: function(frm, cdt, cdn) {
|
||||||
|
get_bom_material_detail(frm.doc, cdt, cdn, false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) {
|
frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
d.stock_qty = d.qty * d.conversion_factor;
|
d.stock_qty = d.qty * d.conversion_factor;
|
||||||
|
|||||||
@ -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()
|
||||||
@ -203,6 +204,10 @@ class BOM(WebsiteGenerator):
|
|||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
self.validate_bom_currency(item)
|
self.validate_bom_currency(item)
|
||||||
|
|
||||||
|
item.bom_no = ''
|
||||||
|
if not item.do_not_explode:
|
||||||
|
item.bom_no = item.bom_no
|
||||||
|
|
||||||
ret = self.get_bom_material_detail({
|
ret = self.get_bom_material_detail({
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"item_code": item.item_code,
|
"item_code": item.item_code,
|
||||||
@ -214,8 +219,10 @@ class BOM(WebsiteGenerator):
|
|||||||
"uom": item.uom,
|
"uom": item.uom,
|
||||||
"stock_uom": item.stock_uom,
|
"stock_uom": item.stock_uom,
|
||||||
"conversion_factor": item.conversion_factor,
|
"conversion_factor": item.conversion_factor,
|
||||||
"sourced_by_supplier": item.sourced_by_supplier
|
"sourced_by_supplier": item.sourced_by_supplier,
|
||||||
|
"do_not_explode": item.do_not_explode
|
||||||
})
|
})
|
||||||
|
|
||||||
for r in ret:
|
for r in ret:
|
||||||
if not item.get(r):
|
if not item.get(r):
|
||||||
item.set(r, ret[r])
|
item.set(r, ret[r])
|
||||||
@ -267,6 +274,9 @@ class BOM(WebsiteGenerator):
|
|||||||
'sourced_by_supplier' : args.get('sourced_by_supplier', 0)
|
'sourced_by_supplier' : args.get('sourced_by_supplier', 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.get('do_not_explode'):
|
||||||
|
ret_item['bom_no'] = ''
|
||||||
|
|
||||||
return ret_item
|
return ret_item
|
||||||
|
|
||||||
def validate_bom_currency(self, item):
|
def validate_bom_currency(self, item):
|
||||||
@ -681,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()
|
||||||
|
|||||||
@ -385,6 +385,53 @@ class TestBOM(ERPNextTestCase):
|
|||||||
self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
|
self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
|
||||||
self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
|
self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
|
||||||
|
|
||||||
|
def test_exclude_exploded_items_from_bom(self):
|
||||||
|
bom_no = get_default_bom()
|
||||||
|
new_bom = frappe.copy_doc(frappe.get_doc('BOM', bom_no))
|
||||||
|
for row in new_bom.items:
|
||||||
|
if row.item_code == '_Test Item Home Desktop Manufactured':
|
||||||
|
self.assertTrue(row.bom_no)
|
||||||
|
row.do_not_explode = True
|
||||||
|
|
||||||
|
new_bom.docstatus = 0
|
||||||
|
new_bom.save()
|
||||||
|
new_bom.load_from_db()
|
||||||
|
|
||||||
|
for row in new_bom.items:
|
||||||
|
if row.item_code == '_Test Item Home Desktop Manufactured' and row.do_not_explode:
|
||||||
|
self.assertFalse(row.bom_no)
|
||||||
|
|
||||||
|
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})
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"item_name",
|
"item_name",
|
||||||
"operation",
|
"operation",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
|
"do_not_explode",
|
||||||
"bom_no",
|
"bom_no",
|
||||||
"source_warehouse",
|
"source_warehouse",
|
||||||
"allow_alternative_item",
|
"allow_alternative_item",
|
||||||
@ -73,6 +74,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.do_not_explode",
|
||||||
"fieldname": "bom_no",
|
"fieldname": "bom_no",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
@ -284,18 +286,25 @@
|
|||||||
"fieldname": "sourced_by_supplier",
|
"fieldname": "sourced_by_supplier",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Sourced by Supplier"
|
"label": "Sourced by Supplier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "do_not_explode",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Do Not Explode"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-08 14:19:37.563300",
|
"modified": "2022-01-24 16:57:57.020232",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Item",
|
"name": "BOM Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
||||||
@ -911,6 +911,54 @@ class TestWorkOrder(ERPNextTestCase):
|
|||||||
|
|
||||||
self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
|
self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
|
||||||
|
|
||||||
|
def test_partial_manufacture_entries(self):
|
||||||
|
cancel_stock_entry = []
|
||||||
|
|
||||||
|
frappe.db.set_value("Manufacturing Settings", None,
|
||||||
|
"backflush_raw_materials_based_on", "Material Transferred for Manufacture")
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
|
||||||
|
ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
|
||||||
|
target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
|
||||||
|
ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
|
||||||
|
target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
|
||||||
|
|
||||||
|
cancel_stock_entry.extend([ste1.name, ste2.name])
|
||||||
|
|
||||||
|
sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
|
||||||
|
for row in sm.get('items'):
|
||||||
|
if row.get('item_code') == '_Test Item':
|
||||||
|
row.qty = 110
|
||||||
|
|
||||||
|
sm.submit()
|
||||||
|
cancel_stock_entry.append(sm.name)
|
||||||
|
|
||||||
|
s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
|
||||||
|
for row in s.get('items'):
|
||||||
|
if row.get('item_code') == '_Test Item':
|
||||||
|
self.assertEqual(row.get('qty'), 100)
|
||||||
|
s.submit()
|
||||||
|
cancel_stock_entry.append(s.name)
|
||||||
|
|
||||||
|
s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
|
||||||
|
for row in s1.get('items'):
|
||||||
|
if row.get('item_code') == '_Test Item':
|
||||||
|
self.assertEqual(row.get('qty'), 5)
|
||||||
|
s1.submit()
|
||||||
|
cancel_stock_entry.append(s1.name)
|
||||||
|
|
||||||
|
s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
|
||||||
|
for row in s2.get('items'):
|
||||||
|
if row.get('item_code') == '_Test Item':
|
||||||
|
self.assertEqual(row.get('qty'), 5)
|
||||||
|
|
||||||
|
cancel_stock_entry.reverse()
|
||||||
|
for ste in cancel_stock_entry:
|
||||||
|
doc = frappe.get_doc("Stock Entry", ste)
|
||||||
|
doc.cancel()
|
||||||
|
|
||||||
|
frappe.db.set_value("Manufacturing Settings", None,
|
||||||
|
"backflush_raw_materials_based_on", "BOM")
|
||||||
|
|
||||||
def update_job_card(job_card, jc_qty=None):
|
def update_job_card(job_card, jc_qty=None):
|
||||||
employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name')
|
employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name')
|
||||||
|
|||||||
@ -333,12 +333,13 @@
|
|||||||
"options": "fa fa-wrench"
|
"options": "fa fa-wrench"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Work Order",
|
|
||||||
"depends_on": "operations",
|
"depends_on": "operations",
|
||||||
|
"fetch_from": "bom_no.transfer_material_against",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "transfer_material_against",
|
"fieldname": "transfer_material_against",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Transfer Material Against",
|
"label": "Transfer Material Against",
|
||||||
"options": "Work Order\nJob Card"
|
"options": "\nWork Order\nJob Card"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "operations",
|
"fieldname": "operations",
|
||||||
@ -574,7 +575,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-11-08 17:36:07.016300",
|
"modified": "2022-01-24 21:18:12.160114",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order",
|
"name": "Work Order",
|
||||||
@ -607,6 +608,7 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
|
"states": [],
|
||||||
"title_field": "production_item",
|
"title_field": "production_item",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
|
|||||||
@ -65,6 +65,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 +73,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()
|
||||||
@ -625,6 +627,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:
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
[pre_model_sync]
|
||||||
erpnext.patches.v12_0.update_is_cancelled_field
|
erpnext.patches.v12_0.update_is_cancelled_field
|
||||||
erpnext.patches.v11_0.rename_production_order_to_work_order
|
erpnext.patches.v11_0.rename_production_order_to_work_order
|
||||||
erpnext.patches.v11_0.refactor_naming_series
|
erpnext.patches.v11_0.refactor_naming_series
|
||||||
@ -226,7 +227,6 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
|||||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||||
erpnext.patches.v13_0.update_member_email_address
|
erpnext.patches.v13_0.update_member_email_address
|
||||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
|
||||||
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||||
erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
|
erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
|
||||||
erpnext.patches.v13_0.add_po_to_global_search
|
erpnext.patches.v13_0.add_po_to_global_search
|
||||||
@ -252,7 +252,7 @@ erpnext.patches.v12_0.add_gst_category_in_delivery_note
|
|||||||
erpnext.patches.v12_0.purchase_receipt_status
|
erpnext.patches.v12_0.purchase_receipt_status
|
||||||
erpnext.patches.v13_0.fix_non_unique_represents_company
|
erpnext.patches.v13_0.fix_non_unique_represents_company
|
||||||
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
||||||
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
|
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 #17-01-2022
|
||||||
erpnext.patches.v13_0.update_shipment_status
|
erpnext.patches.v13_0.update_shipment_status
|
||||||
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
|
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
|
||||||
erpnext.patches.v13_0.germany_make_custom_fields
|
erpnext.patches.v13_0.germany_make_custom_fields
|
||||||
@ -266,7 +266,6 @@ erpnext.patches.v13_0.set_training_event_attendance
|
|||||||
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
|
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
|
||||||
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
|
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
|
||||||
erpnext.patches.v13_0.update_response_by_variance
|
erpnext.patches.v13_0.update_response_by_variance
|
||||||
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
|
|
||||||
erpnext.patches.v13_0.update_job_card_details
|
erpnext.patches.v13_0.update_job_card_details
|
||||||
erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
||||||
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
||||||
@ -285,11 +284,9 @@ erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
|
|||||||
erpnext.patches.v13_0.einvoicing_deprecation_warning
|
erpnext.patches.v13_0.einvoicing_deprecation_warning
|
||||||
execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings")
|
execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings")
|
||||||
execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category")
|
execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category")
|
||||||
erpnext.patches.v14_0.delete_einvoicing_doctypes
|
|
||||||
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
|
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.set_operation_time_based_on_operating_cost
|
||||||
erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
|
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
|
erpnext.patches.v13_0.fix_invoice_statuses
|
||||||
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
|
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
|
||||||
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
|
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
|
||||||
@ -312,24 +309,29 @@ erpnext.patches.v13_0.item_naming_series_not_mandatory
|
|||||||
erpnext.patches.v14_0.delete_healthcare_doctypes
|
erpnext.patches.v14_0.delete_healthcare_doctypes
|
||||||
erpnext.patches.v13_0.update_category_in_ltds_certificate
|
erpnext.patches.v13_0.update_category_in_ltds_certificate
|
||||||
erpnext.patches.v13_0.create_pan_field_for_india #2
|
erpnext.patches.v13_0.create_pan_field_for_india #2
|
||||||
erpnext.patches.v14_0.delete_hub_doctypes
|
|
||||||
erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
|
erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
|
||||||
erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022
|
erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022
|
||||||
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
|
||||||
erpnext.patches.v14_0.migrate_crm_settings
|
erpnext.patches.v14_0.migrate_crm_settings
|
||||||
erpnext.patches.v13_0.rename_ksa_qr_field
|
erpnext.patches.v13_0.rename_ksa_qr_field
|
||||||
erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty
|
erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty
|
||||||
erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
|
erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
|
||||||
erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
|
|
||||||
erpnext.patches.v13_0.update_tax_category_for_rcm
|
erpnext.patches.v13_0.update_tax_category_for_rcm
|
||||||
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
||||||
erpnext.patches.v14_0.set_payroll_cost_centers
|
erpnext.patches.v14_0.set_payroll_cost_centers
|
||||||
erpnext.patches.v13_0.agriculture_deprecation_warning
|
erpnext.patches.v13_0.agriculture_deprecation_warning
|
||||||
erpnext.patches.v14_0.delete_agriculture_doctypes
|
|
||||||
erpnext.patches.v13_0.hospitality_deprecation_warning
|
erpnext.patches.v13_0.hospitality_deprecation_warning
|
||||||
erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
|
|
||||||
erpnext.patches.v13_0.update_exchange_rate_settings
|
erpnext.patches.v13_0.update_exchange_rate_settings
|
||||||
erpnext.patches.v14_0.rearrange_company_fields
|
|
||||||
erpnext.patches.v14_0.update_leave_notification_template
|
|
||||||
erpnext.patches.v13_0.update_asset_quantity_field
|
erpnext.patches.v13_0.update_asset_quantity_field
|
||||||
erpnext.patches.v13_0.delete_bank_reconciliation_detail
|
erpnext.patches.v13_0.delete_bank_reconciliation_detail
|
||||||
|
|
||||||
|
[post_model_sync]
|
||||||
|
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
||||||
|
erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
|
||||||
|
erpnext.patches.v14_0.delete_einvoicing_doctypes
|
||||||
|
erpnext.patches.v14_0.delete_shopify_doctypes
|
||||||
|
erpnext.patches.v14_0.delete_hub_doctypes
|
||||||
|
erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
|
||||||
|
erpnext.patches.v14_0.delete_agriculture_doctypes
|
||||||
|
erpnext.patches.v14_0.rearrange_company_fields
|
||||||
|
erpnext.patches.v14_0.update_leave_notification_template
|
||||||
|
erpnext.patches.v13_0.update_sane_transfer_against
|
||||||
|
|||||||
@ -2,14 +2,28 @@ import frappe
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
try:
|
#handle type casting for is_cancelled field
|
||||||
frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
|
module_doctypes = (
|
||||||
frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
|
('stock', 'Stock Ledger Entry'),
|
||||||
|
('stock', 'Serial No'),
|
||||||
|
('accounts', 'GL Entry')
|
||||||
|
)
|
||||||
|
|
||||||
frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 1 where is_cancelled = 'Yes'")
|
for module, doctype in module_doctypes:
|
||||||
frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 1 where is_cancelled = 'Yes'")
|
if (not frappe.db.has_column(doctype, "is_cancelled")
|
||||||
|
or frappe.db.get_column_type(doctype, "is_cancelled").lower() == "int(1)"
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
frappe.reload_doc("stock", "doctype", "stock_ledger_entry")
|
frappe.db.sql("""
|
||||||
frappe.reload_doc("stock", "doctype", "serial_no")
|
UPDATE `tab{doctype}`
|
||||||
except Exception:
|
SET is_cancelled = 0
|
||||||
pass
|
where is_cancelled in ('', NULL, 'No')"""
|
||||||
|
.format(doctype=doctype))
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE `tab{doctype}`
|
||||||
|
SET is_cancelled = 1
|
||||||
|
where is_cancelled = 'Yes'"""
|
||||||
|
.format(doctype=doctype))
|
||||||
|
|
||||||
|
frappe.reload_doc(module, "doctype", frappe.scrub(doctype))
|
||||||
|
|||||||
@ -12,6 +12,7 @@ def execute():
|
|||||||
|
|
||||||
for report in reports_to_delete:
|
for report in reports_to_delete:
|
||||||
if frappe.db.exists("Report", report):
|
if frappe.db.exists("Report", report):
|
||||||
|
delete_links_from_desktop_icons(report)
|
||||||
delete_auto_email_reports(report)
|
delete_auto_email_reports(report)
|
||||||
check_and_delete_linked_reports(report)
|
check_and_delete_linked_reports(report)
|
||||||
|
|
||||||
@ -22,3 +23,9 @@ def delete_auto_email_reports(report):
|
|||||||
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
|
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
|
||||||
for auto_email_report in auto_email_reports:
|
for auto_email_report in auto_email_reports:
|
||||||
frappe.delete_doc("Auto Email Report", auto_email_report[0])
|
frappe.delete_doc("Auto Email Report", auto_email_report[0])
|
||||||
|
|
||||||
|
def delete_links_from_desktop_icons(report):
|
||||||
|
""" Check for one or multiple Desktop Icons and delete """
|
||||||
|
desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
|
||||||
|
for desktop_icon in desktop_icons:
|
||||||
|
frappe.delete_doc("Desktop Icon", desktop_icon[0])
|
||||||
@ -10,8 +10,15 @@ from erpnext.setup.install import add_non_standard_user_types
|
|||||||
def execute():
|
def execute():
|
||||||
doctype_dict = {
|
doctype_dict = {
|
||||||
'projects': ['Timesheet'],
|
'projects': ['Timesheet'],
|
||||||
'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'],
|
'payroll': [
|
||||||
'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request']
|
'Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission',
|
||||||
|
'Employee Benefit Application', 'Employee Benefit Claim'
|
||||||
|
],
|
||||||
|
'hr': [
|
||||||
|
'Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request',
|
||||||
|
'Holiday List', 'Employee Advance', 'Training Program', 'Training Feedback',
|
||||||
|
'Shift Request', 'Employee Grievance', 'Employee Referral', 'Travel Request'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
for module, doctypes in doctype_dict.items():
|
for module, doctypes in doctype_dict.items():
|
||||||
|
|||||||
@ -5,6 +5,9 @@ from erpnext.regional.india.setup import make_custom_fields
|
|||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
if frappe.get_all('Company', filters = {'country': 'India'}):
|
if frappe.get_all('Company', filters = {'country': 'India'}):
|
||||||
|
frappe.reload_doc('accounts', 'doctype', 'POS Invoice')
|
||||||
|
frappe.reload_doc('accounts', 'doctype', 'POS Invoice Item')
|
||||||
|
|
||||||
make_custom_fields()
|
make_custom_fields()
|
||||||
|
|
||||||
if not frappe.db.exists('Party Type', 'Donor'):
|
if not frappe.db.exists('Party Type', 'Donor'):
|
||||||
|
|||||||
@ -37,4 +37,4 @@ def execute():
|
|||||||
jc.production_item = wo.production_item, jc.item_name = wo.item_name
|
jc.production_item = wo.production_item, jc.item_name = wo.item_name
|
||||||
WHERE
|
WHERE
|
||||||
jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
|
jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
|
||||||
""")
|
""")
|
||||||
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()
|
||||||
@ -5,9 +5,6 @@ from frappe import _
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc("email", "doctype", "email_template")
|
|
||||||
frappe.reload_doc("hr", "doctype", "hr_settings")
|
|
||||||
|
|
||||||
template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
|
template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
|
||||||
if not template:
|
if not template:
|
||||||
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
|
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import frappe
|
|
||||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc('setup', 'doctype', 'company')
|
|
||||||
|
|
||||||
custom_fields = {
|
custom_fields = {
|
||||||
'Company': [
|
'Company': [
|
||||||
dict(fieldname='hra_section', label='HRA Settings',
|
dict(fieldname='hra_section', label='HRA Settings',
|
||||||
@ -28,4 +25,4 @@ def execute():
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
create_custom_fields(custom_fields, update=True)
|
create_custom_fields(custom_fields, update=True)
|
||||||
|
|||||||
@ -994,6 +994,8 @@ def make_leave_application(employee, from_date, to_date, leave_type, company=Non
|
|||||||
))
|
))
|
||||||
leave_application.submit()
|
leave_application.submit()
|
||||||
|
|
||||||
|
return leave_application
|
||||||
|
|
||||||
def setup_test():
|
def setup_test():
|
||||||
make_earning_salary_component(setup=True, company_list=["_Test Company"])
|
make_earning_salary_component(setup=True, company_list=["_Test Company"])
|
||||||
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
|
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -142,7 +142,7 @@ class Customer(TransactionBase):
|
|||||||
self.update_lead_status()
|
self.update_lead_status()
|
||||||
|
|
||||||
if self.flags.is_new_doc:
|
if self.flags.is_new_doc:
|
||||||
self.create_lead_address_contact()
|
self.link_lead_address_and_contact()
|
||||||
|
|
||||||
self.update_customer_groups()
|
self.update_customer_groups()
|
||||||
|
|
||||||
@ -176,62 +176,24 @@ class Customer(TransactionBase):
|
|||||||
if self.lead_name:
|
if self.lead_name:
|
||||||
frappe.db.set_value("Lead", self.lead_name, "status", "Converted")
|
frappe.db.set_value("Lead", self.lead_name, "status", "Converted")
|
||||||
|
|
||||||
def create_lead_address_contact(self):
|
def link_lead_address_and_contact(self):
|
||||||
if self.lead_name:
|
if self.lead_name:
|
||||||
# assign lead address to customer (if already not set)
|
# assign lead address and contact to customer (if already not set)
|
||||||
address_names = frappe.get_all('Dynamic Link', filters={
|
linked_contacts_and_addresses = frappe.get_all(
|
||||||
"parenttype":"Address",
|
"Dynamic Link",
|
||||||
"link_doctype":"Lead",
|
filters=[
|
||||||
"link_name":self.lead_name
|
["parenttype", "in", ["Contact", "Address"]],
|
||||||
}, fields=["parent as name"])
|
["link_doctype", "=", "Lead"],
|
||||||
|
["link_name", "=", self.lead_name],
|
||||||
|
],
|
||||||
|
fields=["parent as name", "parenttype as doctype"],
|
||||||
|
)
|
||||||
|
|
||||||
for address_name in address_names:
|
for row in linked_contacts_and_addresses:
|
||||||
address = frappe.get_doc('Address', address_name.get('name'))
|
linked_doc = frappe.get_doc(row.doctype, row.name)
|
||||||
if not address.has_link('Customer', self.name):
|
if not linked_doc.has_link('Customer', self.name):
|
||||||
address.append('links', dict(link_doctype='Customer', link_name=self.name))
|
linked_doc.append('links', dict(link_doctype='Customer', link_name=self.name))
|
||||||
address.save(ignore_permissions=self.flags.ignore_permissions)
|
linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
|
||||||
|
|
||||||
lead = frappe.db.get_value("Lead", self.lead_name, ["company_name", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True)
|
|
||||||
|
|
||||||
if not lead.lead_name:
|
|
||||||
frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name))
|
|
||||||
|
|
||||||
contact_names = frappe.get_all('Dynamic Link', filters={
|
|
||||||
"parenttype":"Contact",
|
|
||||||
"link_doctype":"Lead",
|
|
||||||
"link_name":self.lead_name
|
|
||||||
}, fields=["parent as name"])
|
|
||||||
|
|
||||||
for contact_name in contact_names:
|
|
||||||
contact = frappe.get_doc('Contact', contact_name.get('name'))
|
|
||||||
if not contact.has_link('Customer', self.name):
|
|
||||||
contact.append('links', dict(link_doctype='Customer', link_name=self.name))
|
|
||||||
contact.save(ignore_permissions=self.flags.ignore_permissions)
|
|
||||||
|
|
||||||
if not contact_names:
|
|
||||||
lead.lead_name = lead.lead_name.lstrip().split(" ")
|
|
||||||
lead.first_name = lead.lead_name[0]
|
|
||||||
lead.last_name = " ".join(lead.lead_name[1:])
|
|
||||||
|
|
||||||
# create contact from lead
|
|
||||||
contact = frappe.new_doc('Contact')
|
|
||||||
contact.first_name = lead.first_name
|
|
||||||
contact.last_name = lead.last_name
|
|
||||||
contact.gender = lead.gender
|
|
||||||
contact.salutation = lead.salutation
|
|
||||||
contact.email_id = lead.email_id
|
|
||||||
contact.phone = lead.phone
|
|
||||||
contact.mobile_no = lead.mobile_no
|
|
||||||
contact.is_primary_contact = 1
|
|
||||||
contact.append('links', dict(link_doctype='Customer', link_name=self.name))
|
|
||||||
if lead.email_id:
|
|
||||||
contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1))
|
|
||||||
if lead.mobile_no:
|
|
||||||
contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1))
|
|
||||||
contact.flags.ignore_permissions = self.flags.ignore_permissions
|
|
||||||
contact.autoname()
|
|
||||||
if not frappe.db.exists("Contact", contact.name):
|
|
||||||
contact.insert()
|
|
||||||
|
|
||||||
def validate_name_with_customer_group(self):
|
def validate_name_with_customer_group(self):
|
||||||
if frappe.db.exists("Customer Group", self.name):
|
if frappe.db.exists("Customer Group", self.name):
|
||||||
|
|||||||
@ -85,7 +85,7 @@ def get_data(conditions, filters):
|
|||||||
and so.docstatus = 1
|
and so.docstatus = 1
|
||||||
{conditions}
|
{conditions}
|
||||||
GROUP BY soi.name
|
GROUP BY soi.name
|
||||||
ORDER BY so.transaction_date ASC
|
ORDER BY so.transaction_date ASC, soi.item_code ASC
|
||||||
""".format(conditions=conditions), filters, as_dict=1)
|
""".format(conditions=conditions), filters, as_dict=1)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -189,7 +189,7 @@ def add_non_standard_user_types():
|
|||||||
|
|
||||||
user_type_limit = {}
|
user_type_limit = {}
|
||||||
for user_type, data in user_types.items():
|
for user_type, data in user_types.items():
|
||||||
user_type_limit.setdefault(frappe.scrub(user_type), 10)
|
user_type_limit.setdefault(frappe.scrub(user_type), 20)
|
||||||
|
|
||||||
update_site_config('user_type_doctype_limit', user_type_limit)
|
update_site_config('user_type_doctype_limit', user_type_limit)
|
||||||
|
|
||||||
@ -204,15 +204,33 @@ def get_user_types_data():
|
|||||||
'apply_user_permission_on': 'Employee',
|
'apply_user_permission_on': 'Employee',
|
||||||
'user_id_field': 'user_id',
|
'user_id_field': 'user_id',
|
||||||
'doctypes': {
|
'doctypes': {
|
||||||
'Salary Slip': ['read'],
|
# masters
|
||||||
|
'Holiday List': ['read'],
|
||||||
'Employee': ['read', 'write'],
|
'Employee': ['read', 'write'],
|
||||||
|
# payroll
|
||||||
|
'Salary Slip': ['read'],
|
||||||
|
'Employee Benefit Application': ['read', 'write', 'create', 'delete'],
|
||||||
|
# expenses
|
||||||
'Expense Claim': ['read', 'write', 'create', 'delete'],
|
'Expense Claim': ['read', 'write', 'create', 'delete'],
|
||||||
|
'Employee Advance': ['read', 'write', 'create', 'delete'],
|
||||||
|
# leave and attendance
|
||||||
'Leave Application': ['read', 'write', 'create', 'delete'],
|
'Leave Application': ['read', 'write', 'create', 'delete'],
|
||||||
'Attendance Request': ['read', 'write', 'create', 'delete'],
|
'Attendance Request': ['read', 'write', 'create', 'delete'],
|
||||||
'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
|
'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
|
||||||
|
# tax
|
||||||
'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
|
'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
|
||||||
'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
|
'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
|
||||||
'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend']
|
# projects
|
||||||
|
'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
|
||||||
|
# trainings
|
||||||
|
'Training Program': ['read'],
|
||||||
|
'Training Feedback': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
|
||||||
|
# shifts
|
||||||
|
'Shift Request': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
|
||||||
|
# misc
|
||||||
|
'Employee Grievance': ['read', 'write', 'create', 'delete'],
|
||||||
|
'Employee Referral': ['read', 'write', 'create', 'delete'],
|
||||||
|
'Travel Request': ['read', 'write', 'create', 'delete']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -602,14 +602,6 @@ class Item(WebsiteGenerator):
|
|||||||
frappe.throw(_("Barcode {0} is not a valid {1} code").format(
|
frappe.throw(_("Barcode {0} is not a valid {1} code").format(
|
||||||
item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
|
item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
|
||||||
|
|
||||||
if item_barcode.barcode != item_barcode.name:
|
|
||||||
# if barcode is getting updated , the row name has to reset.
|
|
||||||
# Delete previous old row doc and re-enter row as if new to reset name in db.
|
|
||||||
item_barcode.set("__islocal", True)
|
|
||||||
item_barcode_entry_name = item_barcode.name
|
|
||||||
item_barcode.name = None
|
|
||||||
frappe.delete_doc("Item Barcode", item_barcode_entry_name)
|
|
||||||
|
|
||||||
def validate_warehouse_for_reorder(self):
|
def validate_warehouse_for_reorder(self):
|
||||||
'''Validate Reorder level table for duplicate and conditional mandatory'''
|
'''Validate Reorder level table for duplicate and conditional mandatory'''
|
||||||
warehouse = []
|
warehouse = []
|
||||||
|
|||||||
@ -1445,14 +1445,15 @@ class StockEntry(StockController):
|
|||||||
qty = req_qty_each * flt(self.fg_completed_qty)
|
qty = req_qty_each * flt(self.fg_completed_qty)
|
||||||
|
|
||||||
elif backflushed_materials.get(item.item_code):
|
elif backflushed_materials.get(item.item_code):
|
||||||
|
precision = frappe.get_precision("Stock Entry Detail", "qty")
|
||||||
for d in backflushed_materials.get(item.item_code):
|
for d in backflushed_materials.get(item.item_code):
|
||||||
if d.get(item.warehouse):
|
if d.get(item.warehouse) > 0:
|
||||||
if (qty > req_qty):
|
if (qty > req_qty):
|
||||||
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
|
qty = ((flt(qty, precision) - flt(d.get(item.warehouse), precision))
|
||||||
|
/ (flt(trans_qty, precision) - flt(produced_qty, precision))
|
||||||
|
) * flt(self.fg_completed_qty)
|
||||||
|
|
||||||
if consumed_qty and frappe.db.get_single_value("Manufacturing Settings",
|
d[item.warehouse] -= qty
|
||||||
"material_consumption"):
|
|
||||||
qty -= consumed_qty
|
|
||||||
|
|
||||||
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
|
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
|
||||||
qty = frappe.utils.ceil(qty)
|
qty = frappe.utils.ceil(qty)
|
||||||
|
|||||||
@ -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>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
|
from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
|
||||||
|
|
||||||
DEFAULT_FILTERS = {
|
DEFAULT_FILTERS = {
|
||||||
@ -10,8 +12,12 @@ DEFAULT_FILTERS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
batch = frappe.db.get_value("Batch", fieldname=["name"], as_dict=True, order_by="creation desc")
|
||||||
|
|
||||||
REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
|
REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
|
||||||
("Stock Ledger", {"_optional": True}),
|
("Stock Ledger", {"_optional": True}),
|
||||||
|
("Stock Ledger", {"batch_no": batch}),
|
||||||
|
("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}),
|
||||||
("Stock Balance", {"_optional": True}),
|
("Stock Balance", {"_optional": True}),
|
||||||
("Stock Projected Qty", {"_optional": True}),
|
("Stock Projected Qty", {"_optional": True}),
|
||||||
("Batch-Wise Balance History", {}),
|
("Batch-Wise Balance History", {}),
|
||||||
@ -40,6 +46,13 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
|
|||||||
("Item Variant Details", {"item": "_Test Variant Item",}),
|
("Item Variant Details", {"item": "_Test Variant Item",}),
|
||||||
("Total Stock Summary", {"group_by": "warehouse",}),
|
("Total Stock Summary", {"group_by": "warehouse",}),
|
||||||
("Batch Item Expiry Status", {}),
|
("Batch Item Expiry Status", {}),
|
||||||
|
("Incorrect Stock Value Report", {"company": "_Test Company with perpetual inventory"}),
|
||||||
|
("Incorrect Serial No Valuation", {}),
|
||||||
|
("Incorrect Balance Qty After Transaction", {}),
|
||||||
|
("Supplier-Wise Sales Analytics", {}),
|
||||||
|
("Item Prices", {"items": "Enabled Items only"}),
|
||||||
|
("Delayed Item Report", {"based_on": "Sales Invoice"}),
|
||||||
|
("Delayed Item Report", {"based_on": "Delivery Note"}),
|
||||||
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
||||||
("Stock Ledger Invariant Check",
|
("Stock Ledger Invariant Check",
|
||||||
{
|
{
|
||||||
|
|||||||
@ -92,6 +92,8 @@ def change_settings(doctype, settings_dict):
|
|||||||
for key, value in settings_dict.items():
|
for key, value in settings_dict.items():
|
||||||
setattr(settings, key, value)
|
setattr(settings, key, value)
|
||||||
settings.save()
|
settings.save()
|
||||||
|
# singles are cached by default, clear to avoid flake
|
||||||
|
frappe.db.value_cache[settings] = {}
|
||||||
yield # yield control to calling function
|
yield # yield control to calling function
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user