Merge branch 'develop' of https://github.com/frappe/erpnext into loan_refund_jv

This commit is contained in:
Deepesh Garg
2022-02-07 12:49:34 +05:30
584 changed files with 18355 additions and 22933 deletions

View File

@@ -0,0 +1,18 @@
from __future__ import unicode_literals
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company or not frappe.db.count('E Invoice User'):
return
frappe.reload_doc("regional", "doctype", "e_invoice_user")
for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']):
company_name = frappe.db.sql("""
select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
""", (creds.get('gstin')))
if company_name and len(company_name) > 0:
frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])

View File

@@ -0,0 +1,72 @@
from __future__ import unicode_literals
import json
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
# move hidden einvoice fields to a different section
custom_fields = {
'Sales Invoice': [
dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
print_hide=1, hidden=1),
dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
no_copy=1, print_hide=1),
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
no_copy=1, print_hide=1),
dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
no_copy=1, print_hide=1, read_only=1),
dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
no_copy=1, print_hide=1, read_only=1),
dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
no_copy=1, print_hide=1, read_only=1),
dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
]
}
create_custom_fields(custom_fields, update=True)
if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'):
frappe.db.sql('''
UPDATE `tabSales Invoice` SET einvoice_status = 'Pending'
WHERE
posting_date >= '2021-04-01'
AND ifnull(irn, '') = ''
AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '')
AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export')
''')
# set appropriate statuses
frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''')
frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
WHERE ifnull(irn_cancelled, 0) = 1''')
# set correct acknowledgement in e-invoices
einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice'])
if einvoices:
for inv in einvoices:
signed_einvoice = inv.get('signed_einvoice')
if signed_einvoice:
signed_einvoice = json.loads(signed_einvoice)
frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False)
frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False)

View File

@@ -0,0 +1,20 @@
from __future__ import unicode_literals
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
if frappe.db.exists('Report', 'E-Invoice Summary') and \
not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')):
frappe.get_doc(dict(
doctype='Custom Role',
report='E-Invoice Summary',
roles= [
dict(role='Accounts User'),
dict(role='Accounts Manager')
]
)).insert()

View File

@@ -0,0 +1,18 @@
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
custom_fields = {
'Sales Invoice': [
dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
]
}
create_custom_fields(custom_fields, update=True)

View File

@@ -0,0 +1,59 @@
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.regional.india.setup import add_permissions, add_print_formats
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
frappe.reload_doc("custom", "doctype", "custom_field")
frappe.reload_doc("regional", "doctype", "e_invoice_settings")
custom_fields = {
'Sales Invoice': [
dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
]
}
create_custom_fields(custom_fields, update=True)
add_permissions()
add_print_formats()
einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
t = {
'mode_of_transport': [{'default': None}],
'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}],
'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
'ewaybill': [
{'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'},
{'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
]
}
for field, conditions in t.items():
for c in conditions:
[(prop, value)] = c.items()
frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)

View File

@@ -0,0 +1,14 @@
from __future__ import unicode_literals
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'})
if irn_cancelled_field:
frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn')
frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0)

View File

@@ -2,14 +2,28 @@ import frappe
def execute():
try:
frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
#handle type casting for is_cancelled field
module_doctypes = (
('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'")
frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 1 where is_cancelled = 'Yes'")
for module, doctype in module_doctypes:
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.reload_doc("stock", "doctype", "serial_no")
except Exception:
pass
frappe.db.sql("""
UPDATE `tab{doctype}`
SET is_cancelled = 0
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))

View File

@@ -0,0 +1,63 @@
import frappe
from erpnext.stock.stock_balance import (
get_balance_qty_from_sle,
get_indented_qty,
get_ordered_qty,
get_planned_qty,
get_reserved_qty,
)
from erpnext.stock.utils import get_bin
def execute():
delete_broken_bins()
delete_and_patch_duplicate_bins()
def delete_broken_bins():
# delete useless bins
frappe.db.sql("delete from `tabBin` where item_code is null or warehouse is null")
def delete_and_patch_duplicate_bins():
duplicate_bins = frappe.db.sql("""
SELECT
item_code, warehouse, count(*) as bin_count
FROM
tabBin
GROUP BY
item_code, warehouse
HAVING
bin_count > 1
""", as_dict=1)
for duplicate_bin in duplicate_bins:
item_code = duplicate_bin.item_code
warehouse = duplicate_bin.warehouse
existing_bins = frappe.get_list("Bin",
filters={
"item_code": item_code,
"warehouse": warehouse
},
fields=["name"],
order_by="creation",)
# keep last one
existing_bins.pop()
for broken_bin in existing_bins:
frappe.delete_doc("Bin", broken_bin.name)
qty_dict = {
"reserved_qty": get_reserved_qty(item_code, warehouse),
"indented_qty": get_indented_qty(item_code, warehouse),
"ordered_qty": get_ordered_qty(item_code, warehouse),
"planned_qty": get_planned_qty(item_code, warehouse),
"actual_qty": get_balance_qty_from_sle(item_code, warehouse)
}
bin = get_bin(item_code, warehouse)
bin.update(qty_dict)
bin.update_reserved_qty_for_production()
bin.update_reserved_qty_for_sub_contracting()
bin.db_update()

View File

@@ -0,0 +1,57 @@
import json
from typing import List, Union
import frappe
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
def execute():
"""
Convert all Item links to Website Item link values in
exisitng 'Item Card Group' Web Page Block data.
"""
frappe.reload_doc("e_commerce", "web_template", "item_card_group")
blocks = frappe.db.get_all(
"Web Page Block",
filters={"web_template": "Item Card Group"},
fields=["parent", "web_template_values", "name"]
)
fields = generate_fields_to_edit()
for block in blocks:
web_template_value = json.loads(block.get('web_template_values'))
for field in fields:
item = web_template_value.get(field)
if not item:
continue
if frappe.db.exists("Website Item", {"item_code": item}):
website_item = frappe.db.get_value("Website Item", {"item_code": item})
else:
website_item = make_new_website_item(item)
if website_item:
web_template_value[field] = website_item
frappe.db.set_value("Web Page Block", block.name, "web_template_values", json.dumps(web_template_value))
def generate_fields_to_edit() -> List:
fields = []
for i in range(1, 13):
fields.append(f"card_{i}_item") # fields like 'card_1_item', etc.
return fields
def make_new_website_item(item: str) -> Union[str, None]:
try:
doc = frappe.get_doc("Item", item)
web_item = make_website_item(doc) # returns [website_item.name, item_name]
return web_item[0]
except Exception:
title = f"{item}: Error while converting to Website Item "
frappe.log_error(title + "for Item Card Group Template" + "\n\n" + frappe.get_traceback(), title=title)
return None

View File

@@ -0,0 +1,72 @@
import frappe
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
def execute():
frappe.reload_doc("e_commerce", "doctype", "website_item")
frappe.reload_doc("e_commerce", "doctype", "website_item_tabbed_section")
frappe.reload_doc("e_commerce", "doctype", "website_offer")
frappe.reload_doc("e_commerce", "doctype", "recommended_items")
frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings")
frappe.reload_doc("stock", "doctype", "item")
item_fields = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
"has_variants", "variant_of", "description", "weightage"]
web_fields_to_map = ["route", "slideshow", "website_image_alt",
"website_warehouse", "web_long_description", "website_content", "thumbnail"]
# get all valid columns (fields) from Item master DB schema
item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) # nosemgrep
item_table_fields = [d.get('Field') for d in item_table_fields]
# prepare fields to query from Item, check if the web field exists in Item master
web_query_fields = []
for web_field in web_fields_to_map:
if web_field in item_table_fields:
web_query_fields.append(web_field)
item_fields.append(web_field)
# check if the filter fields exist in Item master
or_filters = {}
for field in ["show_in_website", "show_variant_in_website"]:
if field in item_table_fields:
or_filters[field] = 1
if not web_query_fields or not or_filters:
# web fields to map are not present in Item master schema
# most likely a fresh installation that doesnt need this patch
return
items = frappe.db.get_all(
"Item",
fields=item_fields,
or_filters=or_filters
)
total_count = len(items)
for count, item in enumerate(items, start=1):
if frappe.db.exists("Website Item", {"item_code": item.item_code}):
continue
# make new website item from item (publish item)
website_item = make_website_item(item, save=False)
website_item.ranking = item.get("weightage")
for field in web_fields_to_map:
website_item.update({field: item.get(field)})
website_item.save()
# move Website Item Group & Website Specification table to Website Item
for doctype in ("Website Item Group", "Item Website Specification"):
frappe.db.set_value(
doctype,
{"parenttype": "Item", "parent": item.item_code}, # filters
{"parenttype": "Website Item", "parent": website_item.name} # value dict
)
if count % 20 == 0: # commit after every 20 items
frappe.db.commit()
frappe.utils.update_progress_bar('Creating Website Items', count, total_count)

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
def execute():
if frappe.db.exists('DocType', 'Bank Reconciliation Detail') and \
frappe.db.exists('DocType', 'Bank Clearance Detail'):
frappe.delete_doc("DocType", 'Bank Reconciliation Detail', force=1)

View File

@@ -12,6 +12,7 @@ def execute():
for report in reports_to_delete:
if frappe.db.exists("Report", report):
delete_links_from_desktop_icons(report)
delete_auto_email_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"])
for auto_email_report in auto_email_reports:
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])

View File

@@ -1,9 +0,0 @@
import click
def execute():
click.secho(
"Indian E-Invoicing integration is moved to a separate app and will be removed from ERPNext in version-14.\n"
"Please install the app to continue using the integration: https://github.com/frappe/erpnext_gst_compliance",
fg="yellow",
)

View File

@@ -0,0 +1,19 @@
import frappe
def execute():
frappe.reload_doc("setup", "doctype", "company")
company = frappe.qb.DocType("Company")
frappe.qb.update(
company
).set(
company.enable_provisional_accounting_for_non_stock_items, company.enable_perpetual_inventory_for_non_stock_items
).set(
company.default_provisional_account, company.service_received_but_not_billed
).where(
company.enable_perpetual_inventory_for_non_stock_items == 1
).where(
company.service_received_but_not_billed.isnotnull()
).run()

View File

@@ -0,0 +1,16 @@
import frappe
def execute():
if frappe.db.has_column("Item", "thumbnail"):
website_item = frappe.qb.DocType("Website Item").as_("wi")
item = frappe.qb.DocType("Item")
frappe.qb.update(website_item).inner_join(item).on(
website_item.item_code == item.item_code
).set(
website_item.thumbnail, item.thumbnail
).where(
website_item.website_image.notnull()
& website_item.thumbnail.isnull()
).run()

View File

@@ -0,0 +1,10 @@
import click
def execute():
click.secho(
"Hospitality domain is moved to a separate app and will be removed from ERPNext in version-14.\n"
"When upgrading to ERPNext version-14, please install the app to continue using the Hospitality domain: https://github.com/frappe/hospitality",
fg="yellow",
)

View File

@@ -0,0 +1,15 @@
import frappe
def execute():
homepage = frappe.get_doc("Homepage")
for row in homepage.products:
web_item = frappe.db.get_value("Website Item", {"item_code": row.item_code}, "name")
if not web_item:
continue
row.item_code = web_item
homepage.flags.ignore_mandatory = True
homepage.save()

View File

@@ -10,8 +10,15 @@ from erpnext.setup.install import add_non_standard_user_types
def execute():
doctype_dict = {
'projects': ['Timesheet'],
'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'],
'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request']
'payroll': [
'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():

View File

@@ -0,0 +1,62 @@
import frappe
from frappe.utils import cint
def execute():
frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings")
frappe.reload_doc("portal", "doctype", "website_filter_field")
frappe.reload_doc("portal", "doctype", "website_attribute")
products_settings_fields = [
"hide_variants", "products_per_page",
"enable_attribute_filters", "enable_field_filters"
]
shopping_cart_settings_fields = [
"enabled", "show_attachments", "show_price",
"show_stock_availability", "enable_variants", "show_contact_us_button",
"show_quantity_in_website", "show_apply_coupon_code_in_website",
"allow_items_not_in_stock", "company", "price_list", "default_customer_group",
"quotation_series", "enable_checkout", "payment_success_url",
"payment_gateway_account", "save_quotations_as_draft"
]
settings = frappe.get_doc("E Commerce Settings")
def map_into_e_commerce_settings(doctype, fields):
singles = frappe.qb.DocType("Singles")
query = (
frappe.qb.from_(singles)
.select(
singles["field"], singles.value
).where(
(singles.doctype == doctype)
& (singles["field"].isin(fields))
)
)
data = query.run(as_dict=True)
# {'enable_attribute_filters': '1', ...}
mapper = {row.field: row.value for row in data}
for key, value in mapper.items():
value = cint(value) if (value and value.isdigit()) else value
settings.update({key: value})
settings.save()
# shift data to E Commerce Settings
map_into_e_commerce_settings("Products Settings", products_settings_fields)
map_into_e_commerce_settings("Shopping Cart Settings", shopping_cart_settings_fields)
# move filters and attributes tables to E Commerce Settings from Products Settings
for doctype in ("Website Filter Field", "Website Attribute"):
frappe.db.set_value(
doctype,
{"parent": "Products Settings"},
{
"parenttype": "E Commerce Settings",
"parent": "E Commerce Settings"
},
update_modified=False
)

View File

@@ -3,6 +3,7 @@ from frappe import _
def execute():
frappe.reload_doctype('Selling Settings')
selling_settings = frappe.get_single("Selling Settings")
if selling_settings.customer_group in (_("All Customer Groups"), "All Customer Groups"):

View File

@@ -5,6 +5,9 @@ from erpnext.regional.india.setup import make_custom_fields
def execute():
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()
if not frappe.db.exists('Party Type', 'Donor'):

View File

@@ -0,0 +1,29 @@
import click
import frappe
def execute():
frappe.delete_doc("DocType", "Shopping Cart Settings", ignore_missing=True)
frappe.delete_doc("DocType", "Products Settings", ignore_missing=True)
frappe.delete_doc("DocType", "Supplier Item Group", ignore_missing=True)
if frappe.db.get_single_value("E Commerce Settings", "enabled"):
notify_users()
def notify_users():
click.secho(
"Shopping cart and Product settings are merged into E-commerce settings.\n"
"Checkout the documentation to learn more:"
"https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce",
fg="yellow",
)
note = frappe.new_doc("Note")
note.title = "New E-Commerce Module"
note.public = 1
note.notify_on_login = 1
note.content = """<div class="ql-editor read-mode"><p>You are seeing this message because Shopping Cart is enabled on your site. </p><p><br></p><p>Shopping Cart Settings and Products settings are now merged into "E Commerce Settings". </p><p><br></p><p>You can learn about new and improved E-Commerce features in the official documentation.</p><ol><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span><a href="https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce" rel="noopener noreferrer">https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce</a></li></ol><p><br></p></div>"""
note.insert(ignore_mandatory=True)

View File

@@ -9,13 +9,15 @@ def execute():
from `tabStock Ledger Entry`
where
is_cancelled = 0
and (serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s)
and ( serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s
or serial_no = %s )
""",
(
" %", # leading whitespace
"% ", # trailing whitespace
"%\n %", # leading whitespace on newline
"% \n%", # trailing whitespace on newline
"\n", # just new line
),
as_dict=True,
)

View File

@@ -37,4 +37,4 @@ def execute():
jc.production_item = wo.production_item, jc.item_name = wo.item_name
WHERE
jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
""")
""")

View File

@@ -0,0 +1,8 @@
import frappe
def execute():
if frappe.db.count('Asset'):
frappe.reload_doc("assets", "doctype", "Asset")
asset = frappe.qb.DocType('Asset')
frappe.qb.update(asset).set(asset.asset_quantity, 1).run()

View File

@@ -1,31 +0,0 @@
# Copyright (c) 2020, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
def execute():
for document in ["bom", "bom_item", "bom_explosion_item"]:
frappe.reload_doc('manufacturing', 'doctype', document)
frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1")
bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item`
where parent=bom.name and ifnull(bom_no, '')!='')""")
count = 0
while(count < len(bom_list)):
for parent_bom in get_parent_boms(bom_list[count]):
bom_doc = frappe.get_cached_doc("BOM", parent_bom)
bom_doc.set_bom_level(update=True)
bom_list.append(parent_bom)
count += 1
def get_parent_boms(bom_no):
return frappe.db.sql_list("""
select distinct bom_item.parent from `tabBOM Item` bom_item
where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
""", bom_no)

View File

@@ -0,0 +1,25 @@
import frappe
def execute():
frappe.reload_doctype('Maintenance Visit')
frappe.reload_doctype('Maintenance Visit Purpose')
# Updates the Maintenance Schedule link to fetch serial nos
from frappe.query_builder.functions import Coalesce
mvp = frappe.qb.DocType('Maintenance Visit Purpose')
mv = frappe.qb.DocType('Maintenance Visit')
frappe.qb.update(
mv
).join(
mvp
).on(mvp.parent == mv.name).set(
mv.maintenance_schedule,
Coalesce(mvp.prevdoc_docname, '')
).where(
(mv.maintenance_type == "Scheduled")
& (mvp.prevdoc_docname.notnull())
& (mv.docstatus < 2)
).run(as_dict=1)

View 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()

View File

@@ -0,0 +1,18 @@
import frappe
def execute():
doctype = "Stock Reconciliation Item"
if not frappe.db.has_column(doctype, "current_serial_no"):
# nothing to fix if column doesn't exist
return
sr_item = frappe.qb.DocType(doctype)
(frappe.qb
.update(sr_item)
.set(sr_item.current_serial_no, None)
.where(sr_item.current_qty == 0)
).run()

View File

@@ -5,9 +5,6 @@ from frappe import _
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"))
if not template:
base_path = frappe.get_app_path("erpnext", "hr", "doctype")

View File

@@ -1,10 +0,0 @@
import frappe
def execute():
frappe.delete_doc('DocType', 'E Invoice Settings', ignore_missing=True)
frappe.delete_doc('DocType', 'E Invoice User', ignore_missing=True)
frappe.delete_doc('Report', 'E-Invoice Summary', ignore_missing=True)
frappe.delete_doc('Print Format', 'GST E-Invoice', ignore_missing=True)
frappe.delete_doc('Custom Field', 'Sales Invoice-eway_bill_cancelled', ignore_missing=True)
frappe.delete_doc('Custom Field', 'Sales Invoice-irn_cancelled', ignore_missing=True)

View File

@@ -47,3 +47,18 @@ def execute():
frappe.delete_doc("DocType", doctype, ignore_missing=True)
frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True)
custom_fields = {
'Sales Invoice': ['patient', 'patient_name', 'ref_practitioner'],
'Sales Invoice Item': ['reference_dt', 'reference_dn'],
'Stock Entry': ['inpatient_medication_entry'],
'Stock Entry Detail': ['patient', 'inpatient_medication_entry_child'],
}
for doc, fields in custom_fields.items():
filters = {
'dt': doc,
'fieldname': ['in', fields]
}
records = frappe.get_all('Custom Field', filters=filters, pluck='name')
for record in records:
frappe.delete_doc('Custom Field', record, ignore_missing=True, force=True)

View File

@@ -0,0 +1,32 @@
import frappe
def execute():
modules = ['Hotels', 'Restaurant']
for module in modules:
frappe.delete_doc("Module Def", module, ignore_missing=True, force=True)
frappe.delete_doc("Workspace", module, ignore_missing=True, force=True)
reports = frappe.get_all("Report", {"module": module, "is_standard": "Yes"}, pluck='name')
for report in reports:
frappe.delete_doc("Report", report, ignore_missing=True, force=True)
dashboards = frappe.get_all("Dashboard", {"module": module, "is_standard": 1}, pluck='name')
for dashboard in dashboards:
frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
doctypes = frappe.get_all("DocType", {"module": module, "custom": 0}, pluck='name')
for doctype in doctypes:
frappe.delete_doc("DocType", doctype, ignore_missing=True)
custom_fields = [
{"dt": "Sales Invoice", "fieldname": "restaurant"},
{"dt": "Sales Invoice", "fieldname": "restaurant_table"},
{"dt": "Price List", "fieldname": "restaurant_menu"},
]
for field in custom_fields:
custom_field = frappe.db.get_value("Custom Field", field)
frappe.delete_doc("Custom Field", custom_field, ignore_missing=True)

View File

@@ -0,0 +1,48 @@
import frappe
from frappe.utils import today
def execute():
for dt in ("cost_center_allocation", "cost_center_allocation_percentage"):
frappe.reload_doc('accounts', 'doctype', dt)
cc_allocations = get_existing_cost_center_allocations()
if cc_allocations:
create_new_cost_center_allocation_records(cc_allocations)
frappe.delete_doc('DocType', 'Distributed Cost Center', ignore_missing=True)
def create_new_cost_center_allocation_records(cc_allocations):
for main_cc, allocations in cc_allocations.items():
cca = frappe.new_doc("Cost Center Allocation")
cca.main_cost_center = main_cc
cca.valid_from = today()
for child_cc, percentage in allocations.items():
cca.append("allocation_percentages", ({
"cost_center": child_cc,
"percentage": percentage
}))
cca.save()
cca.submit()
def get_existing_cost_center_allocations():
if not frappe.db.exists("DocType", "Distributed Cost Center"):
return
par = frappe.qb.DocType("Cost Center")
child = frappe.qb.DocType("Distributed Cost Center")
records = (
frappe.qb.from_(par)
.inner_join(child).on(par.name == child.parent)
.select(par.name, child.cost_center, child.percentage_allocation)
.where(par.enable_distributed_cost_center == 1)
).run(as_dict=True)
cc_allocations = frappe._dict()
for d in records:
cc_allocations.setdefault(d.name, frappe._dict())\
.setdefault(d.cost_center, d.percentage_allocation)
return cc_allocations

View File

@@ -9,8 +9,9 @@ def execute():
], as_dict=True)
frappe.reload_doc('crm', 'doctype', 'crm_settings')
frappe.db.set_value('CRM Settings', 'CRM Settings', {
'campaign_naming_by': settings.campaign_naming_by,
'close_opportunity_after_days': settings.close_opportunity_after_days,
'default_valid_till': settings.default_valid_till
})
if settings:
frappe.db.set_value('CRM Settings', 'CRM Settings', {
'campaign_naming_by': settings.campaign_naming_by,
'close_opportunity_after_days': settings.close_opportunity_after_days,
'default_valid_till': settings.default_valid_till
})

View File

@@ -0,0 +1,28 @@
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
custom_fields = {
'Company': [
dict(fieldname='hra_section', label='HRA Settings',
fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
dict(fieldname='basic_component', label='Basic Component',
fieldtype='Link', options='Salary Component', insert_after='hra_section'),
dict(fieldname='hra_component', label='HRA Component',
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
dict(fieldname='arrear_component', label='Arrear Component',
fieldtype='Link', options='Salary Component', insert_after='hra_column_break'),
dict(fieldname='non_profit_section', label='Non Profit Settings',
fieldtype='Section Break', insert_after='arrear_component', collapsible=1),
dict(fieldname='company_80g_number', label='80G Number',
fieldtype='Data', insert_after='non_profit_section'),
dict(fieldname='with_effect_from', label='80G With Effect From',
fieldtype='Date', insert_after='company_80g_number'),
dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
dict(fieldname='pan_details', label='PAN Number',
fieldtype='Data', insert_after='non_profit_column_break')
]
}
create_custom_fields(custom_fields, update=True)

View File

@@ -0,0 +1,24 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.regional.india.setup import add_permissions, add_print_formats
def execute():
# restores back the 2 custom fields that was deleted while removing e-invoicing from v14
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
custom_fields = {
'Sales Invoice': [
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
]
}
create_custom_fields(custom_fields, update=True)
add_permissions()
add_print_formats()

View File

@@ -0,0 +1,17 @@
import os
import frappe
from frappe import _
def execute():
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html"))
template = frappe.db.exists("Email Template", _("Leave Approval Notification"))
if template:
frappe.db.set_value("Email Template", template, "response", response)
template = frappe.db.exists("Email Template", _("Leave Status Notification"))
if template:
frappe.db.set_value("Email Template", template, "response", response)