Merge branch 'develop' into patch-9

This commit is contained in:
Ernesto Ruiz 2022-11-09 15:52:35 -06:00 committed by GitHub
commit 499dfee2d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 443 additions and 47 deletions

View File

@ -1410,7 +1410,7 @@ class PurchaseInvoice(BuyingController):
self.repost_future_sle_and_gle()
self.update_project()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = (

View File

@ -0,0 +1,53 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Repost Payment Ledger', {
setup: function(frm) {
frm.set_query("voucher_type", () => {
return {
filters: {
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']]
}
};
});
frm.fields_dict['repost_vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
return {
filters: {
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']]
}
}
}
frm.fields_dict['repost_vouchers'].grid.get_field('voucher_no').get_query = function(doc) {
if (doc.company) {
return {
filters: {
company: doc.company,
docstatus: 1
}
}
}
}
},
refresh: function(frm) {
if (frm.doc.docstatus==1 && ['Queued', 'Failed'].find(x => x == frm.doc.repost_status)) {
frm.set_intro(__("Use 'Repost in background' button to trigger background job. Job can only be triggered when document is in Queued or Failed status."));
var btn_label = __("Repost in background")
frm.add_custom_button(btn_label, () => {
frappe.call({
method: 'erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.execute_repost_payment_ledger',
args: {
docname: frm.doc.name,
}
});
frappe.msgprint(__('Reposting in the background.'));
});
}
}
});

View File

@ -0,0 +1,159 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-10-19 21:59:33.553852",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"filters_section",
"company",
"posting_date",
"column_break_4",
"voucher_type",
"add_manually",
"status_section",
"repost_status",
"repost_error_log",
"selected_vouchers_section",
"repost_vouchers",
"amended_from"
],
"fields": [
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date",
"reqd": 1
},
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"label": "Voucher Type",
"options": "DocType"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Repost Payment Ledger",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "selected_vouchers_section",
"fieldtype": "Section Break",
"label": "Vouchers"
},
{
"fieldname": "filters_section",
"fieldtype": "Section Break",
"label": "Filters"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "repost_vouchers",
"fieldtype": "Table",
"label": "Selected Vouchers",
"options": "Repost Payment Ledger Items"
},
{
"fieldname": "repost_status",
"fieldtype": "Select",
"label": "Repost Status",
"options": "\nQueued\nFailed\nCompleted",
"read_only": 1
},
{
"fieldname": "status_section",
"fieldtype": "Section Break",
"label": "Status"
},
{
"default": "0",
"description": "Ignore Voucher Type filter and Select Vouchers Manually",
"fieldname": "add_manually",
"fieldtype": "Check",
"label": "Add Manually"
},
{
"depends_on": "eval:doc.repost_error_log",
"fieldname": "repost_error_log",
"fieldtype": "Long Text",
"label": "Repost Error Log"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-11-08 07:38:40.079038",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"permlevel": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,111 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import copy
import frappe
from frappe import _, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils.background_jobs import is_job_queued
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
if voucher_type and voucher_no and gle_map:
_delete_pl_entries(voucher_type, voucher_no)
create_payment_ledger_entry(gle_map, cancel=0)
@frappe.whitelist()
def start_payment_ledger_repost(docname=None):
"""
Repost Payment Ledger Entries for Vouchers through Background Job
"""
if docname:
repost_doc = frappe.get_doc("Repost Payment Ledger", docname)
if repost_doc.docstatus == 1 and repost_doc.repost_status in ["Queued", "Failed"]:
try:
for entry in repost_doc.repost_vouchers:
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
if doc.doctype in ["Payment Entry", "Journal Entry"]:
gle_map = doc.build_gl_map()
else:
gle_map = doc.get_gl_entries()
repost_ple_for_voucher(entry.voucher_type, entry.voucher_no, gle_map)
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", "")
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_status", "Completed")
except Exception as e:
frappe.db.rollback()
traceback = frappe.get_traceback()
if traceback:
message = "Traceback: <br>" + traceback
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_status", "Failed")
class RepostPaymentLedger(Document):
def __init__(self, *args, **kwargs):
super(RepostPaymentLedger, self).__init__(*args, **kwargs)
self.vouchers = []
def before_validate(self):
self.load_vouchers_based_on_filters()
self.set_status()
def load_vouchers_based_on_filters(self):
if not self.add_manually:
self.repost_vouchers.clear()
self.get_vouchers()
self.extend("repost_vouchers", copy.deepcopy(self.vouchers))
def get_vouchers(self):
self.vouchers.clear()
filter_on_voucher_types = [self.voucher_type] if self.voucher_type else VOUCHER_TYPES
for vtype in filter_on_voucher_types:
doc = qb.DocType(vtype)
doctype_name = ConstantColumn(vtype)
query = (
qb.from_(doc)
.select(doctype_name.as_("voucher_type"), doc.name.as_("voucher_no"))
.where(
(doc.docstatus == 1)
& (doc.company == self.company)
& (doc.posting_date.gte(self.posting_date))
)
)
entries = query.run(as_dict=True)
self.vouchers.extend(entries)
def set_status(self):
if self.docstatus == 0:
self.repost_status = "Queued"
def on_submit(self):
execute_repost_payment_ledger(self.name)
frappe.msgprint(_("Repost started in the background"))
@frappe.whitelist()
def execute_repost_payment_ledger(docname):
"""Repost Payment Ledger Entries by background job."""
job_name = "payment_ledger_repost_" + docname
if not is_job_queued(job_name):
frappe.enqueue(
method="erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.start_payment_ledger_repost",
docname=docname,
is_async=True,
job_name=job_name,
)

View File

@ -0,0 +1,12 @@
frappe.listview_settings["Repost Payment Ledger"] = {
add_fields: ["repost_status"],
get_indicator: function(doc) {
var colors = {
'Queued': 'orange',
'Completed': 'green',
'Failed': 'red',
};
let status = doc.repost_status;
return [__(status), colors[status], 'status,=,'+status];
},
};

View File

@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestRepostPaymentLedger(FrappeTestCase):
pass

View File

@ -0,0 +1,35 @@
{
"actions": [],
"creation": "2022-10-20 10:44:18.796489",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"voucher_type",
"voucher_no"
],
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"label": "Voucher Type",
"options": "DocType"
},
{
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"label": "Voucher No",
"options": "voucher_type"
}
],
"istable": 1,
"links": [],
"modified": "2022-10-28 14:47:11.838109",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger Items",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class RepostPaymentLedgerItems(Document):
pass

View File

@ -373,7 +373,7 @@ class SalesInvoice(SellingController):
if self.update_stock == 1:
self.repost_future_sle_and_gle()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.db_set("repost_required", 0)
if (
@ -2399,7 +2399,7 @@ def get_loyalty_programs(customer):
lp_details = get_loyalty_programs(customer)
if len(lp_details) == 1:
frappe.db.set(customer, "loyalty_program", lp_details[0])
customer.db_set("loyalty_program", lp_details[0])
return lp_details
else:
return lp_details

View File

@ -1146,10 +1146,10 @@ def repost_gle_for_stock_vouchers(
if not existing_gle or not compare_existing_and_expected_gle(
existing_gle, expected_gle, precision
):
_delete_gl_entries(voucher_type, voucher_no)
_delete_accounting_ledger_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
_delete_accounting_ledger_entries(voucher_type, voucher_no)
if not frappe.flags.in_test:
frappe.db.commit()
@ -1161,18 +1161,28 @@ def repost_gle_for_stock_vouchers(
)
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql(
"""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""",
(voucher_type, voucher_no),
)
def _delete_pl_entries(voucher_type, voucher_no):
ple = qb.DocType("Payment Ledger Entry")
qb.from_(ple).delete().where(
(ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)
).run()
def _delete_gl_entries(voucher_type, voucher_no):
gle = qb.DocType("GL Entry")
qb.from_(gle).delete().where(
(gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)
).run()
def _delete_accounting_ledger_entries(voucher_type, voucher_no):
"""
Remove entries from both General and Payment Ledger for specified Voucher
"""
_delete_gl_entries(voucher_type, voucher_no)
_delete_pl_entries(voucher_type, voucher_no)
def sort_stock_vouchers_by_posting_date(
stock_vouchers: List[Tuple[str, str]]
) -> List[Tuple[str, str]]:

View File

@ -361,7 +361,7 @@ class PurchaseOrder(BuyingController):
self.update_reserved_qty_for_subcontract()
self.check_on_hold_or_closed_status()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_prevdoc_status()

View File

@ -31,7 +31,7 @@ class RequestforQuotation(BuyingController):
if self.docstatus < 1:
# after amend and save, status still shows as cancelled, until submit
frappe.db.set(self, "status", "Draft")
self.db_set("status", "Draft")
def validate_duplicate_supplier(self):
supplier_list = [d.supplier for d in self.suppliers]
@ -73,14 +73,14 @@ class RequestforQuotation(BuyingController):
)
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
for supplier in self.suppliers:
supplier.email_sent = 0
supplier.quote_status = "Pending"
self.send_to_supplier()
def on_cancel(self):
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
@frappe.whitelist()
def get_supplier_email_preview(self, supplier):

View File

@ -145,7 +145,7 @@ class Supplier(TransactionBase):
def after_rename(self, olddn, newdn, merge=False):
if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name":
frappe.db.set(self, "supplier_name", newdn)
self.db_set("supplier_name", newdn)
@frappe.whitelist()

View File

@ -30,11 +30,11 @@ class SupplierQuotation(BuyingController):
self.validate_valid_till()
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
self.update_rfq_supplier_status(1)
def on_cancel(self):
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_rfq_supplier_status(0)
def on_trash(self):

View File

@ -60,7 +60,7 @@ class Opportunity(TransactionBase, CRMNote):
if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field):
try:
value = frappe.db.get_value(self.opportunity_from, self.party_name, field)
frappe.db.set(self, field, value)
self.db_set(field, value)
except Exception:
continue

View File

@ -119,7 +119,7 @@ class MaintenanceSchedule(TransactionBase):
event.add_participant(self.doctype, self.name)
event.insert(ignore_permissions=1)
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
def create_schedule_list(self, start_date, end_date, no_of_visit, sales_person):
schedule_list = []
@ -245,7 +245,7 @@ class MaintenanceSchedule(TransactionBase):
self.generate_schedule()
def on_update(self):
frappe.db.set(self, "status", "Draft")
self.db_set("status", "Draft")
def update_amc_date(self, serial_nos, amc_expiry_date=None):
for serial_no in serial_nos:
@ -344,7 +344,7 @@ class MaintenanceSchedule(TransactionBase):
if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no)
self.update_amc_date(serial_nos)
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
delete_events(self.doctype, self.name)
def on_trash(self):

View File

@ -125,12 +125,12 @@ class MaintenanceVisit(TransactionBase):
def on_submit(self):
self.update_customer_issue(1)
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
self.update_status_and_actual_date()
def on_cancel(self):
self.check_if_last_visit()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_status_and_actual_date(cancel=True)
def on_update(self):

View File

@ -206,8 +206,8 @@ class BOM(WebsiteGenerator):
self.manage_default_bom()
def on_cancel(self):
frappe.db.set(self, "is_active", 0)
frappe.db.set(self, "is_default", 0)
self.db_set("is_active", 0)
self.db_set("is_default", 0)
# check if used in any other bom
self.validate_bom_links()
@ -449,10 +449,10 @@ class BOM(WebsiteGenerator):
not frappe.db.exists(dict(doctype="BOM", docstatus=1, item=self.item, is_default=1))
and self.is_active
):
frappe.db.set(self, "is_default", 1)
self.db_set("is_default", 1)
frappe.db.set_value("Item", self.item, "default_bom", self.name)
else:
frappe.db.set(self, "is_default", 0)
self.db_set("is_default", 0)
item = frappe.get_doc("Item", self.item)
if item.default_bom == self.name:
frappe.db.set_value("Item", self.item, "default_bom", None)

View File

@ -373,7 +373,7 @@ class WorkOrder(Document):
def on_cancel(self):
self.validate_cancel()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
if self.production_plan and frappe.db.exists(
"Production Plan Item Reference", {"parent": self.production_plan}

View File

@ -294,7 +294,7 @@ class Customer(TransactionBase):
def after_rename(self, olddn, newdn, merge=False):
if frappe.defaults.get_global_default("cust_master_name") == "Customer Name":
frappe.db.set(self, "customer_name", newdn)
self.db_set("customer_name", newdn)
def set_loyalty_program(self):
if self.loyalty_program:

View File

@ -87,13 +87,13 @@ class InstallationNote(TransactionBase):
frappe.throw(_("Please pull items from Delivery Note"))
def on_update(self):
frappe.db.set(self, "status", "Draft")
self.db_set("status", "Draft")
def on_submit(self):
self.validate_serial_no()
self.update_prevdoc_status()
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
def on_cancel(self):
self.update_prevdoc_status()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")

View File

@ -119,10 +119,10 @@ class Quotation(SellingController):
if not (self.is_fully_ordered() or self.is_partially_ordered()):
get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
frappe.db.set(self, "status", "Lost")
self.db_set("status", "Lost")
if detailed_reason:
frappe.db.set(self, "order_lost_reason", detailed_reason)
self.db_set("order_lost_reason", detailed_reason)
for reason in lost_reasons_list:
if reason.get("lost_reason") in lost_reasons_lst:

View File

@ -246,7 +246,7 @@ class SalesOrder(SellingController):
self.update_project()
self.update_prevdoc_status("cancel")
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_blanket_order()

View File

@ -207,15 +207,14 @@ class Company(NestedSet):
frappe.local.flags.ignore_root_company_validation = True
create_charts(self.name, self.chart_of_accounts, self.existing_company)
frappe.db.set(
self,
self.db_set(
"default_receivable_account",
frappe.db.get_value(
"Account", {"company": self.name, "account_type": "Receivable", "is_group": 0}
),
)
frappe.db.set(
self,
self.db_set(
"default_payable_account",
frappe.db.get_value(
"Account", {"company": self.name, "account_type": "Payable", "is_group": 0}
@ -491,12 +490,12 @@ class Company(NestedSet):
cc_doc.flags.ignore_mandatory = True
cc_doc.insert()
frappe.db.set(self, "cost_center", _("Main") + " - " + self.abbr)
frappe.db.set(self, "round_off_cost_center", _("Main") + " - " + self.abbr)
frappe.db.set(self, "depreciation_cost_center", _("Main") + " - " + self.abbr)
self.db_set("cost_center", _("Main") + " - " + self.abbr)
self.db_set("round_off_cost_center", _("Main") + " - " + self.abbr)
self.db_set("depreciation_cost_center", _("Main") + " - " + self.abbr)
def after_rename(self, olddn, newdn, merge=False):
frappe.db.set(self, "company_name", newdn)
self.db_set("company_name", newdn)
frappe.db.sql(
"""update `tabDefaultValue` set defvalue=%s

View File

@ -120,7 +120,6 @@ class MaterialRequest(BuyingController):
self.title = _("{0} Request for {1}").format(self.material_request_type, items)[:100]
def on_submit(self):
# frappe.db.set(self, 'status', 'Submitted')
self.update_requested_qty()
self.update_requested_qty_in_production_plan()
if self.material_request_type == "Purchase":

View File

@ -216,7 +216,7 @@ class TestMaterialRequest(FrappeTestCase):
po.load_from_db()
mr.update_status("Stopped")
self.assertRaises(frappe.InvalidStatusError, po.submit)
frappe.db.set(po, "docstatus", 1)
po.db_set("docstatus", 1)
self.assertRaises(frappe.InvalidStatusError, po.cancel)
# resubmit and check for per complete

View File

@ -35,7 +35,7 @@ class WarrantyClaim(TransactionBase):
lst1 = ",".join(x[0] for x in lst)
frappe.throw(_("Cancel Material Visit {0} before cancelling this Warranty Claim").format(lst1))
else:
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
def on_update(self):
pass