mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-05 05:09:11 +00:00
Merge pull request #33404 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -485,6 +485,10 @@ def set_default_accounts(company):
|
|||||||
"default_payable_account": frappe.db.get_value(
|
"default_payable_account": frappe.db.get_value(
|
||||||
"Account", {"company": company.name, "account_type": "Payable", "is_group": 0}
|
"Account", {"company": company.name, "account_type": "Payable", "is_group": 0}
|
||||||
),
|
),
|
||||||
|
"default_provisional_account": frappe.db.get_value(
|
||||||
|
"Account",
|
||||||
|
{"company": company.name, "account_type": "Service Received But Not Billed", "is_group": 0},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -305,6 +305,7 @@
|
|||||||
"fieldname": "source_exchange_rate",
|
"fieldname": "source_exchange_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Exchange Rate",
|
"label": "Exchange Rate",
|
||||||
|
"precision": "9",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -334,6 +335,7 @@
|
|||||||
"fieldname": "target_exchange_rate",
|
"fieldname": "target_exchange_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Exchange Rate",
|
"label": "Exchange Rate",
|
||||||
|
"precision": "9",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -731,7 +733,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-02-23 20:08:39.559814",
|
"modified": "2022-12-08 16:25:43.824051",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -684,35 +684,34 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_payment_against_negative_invoice(self):
|
def validate_payment_against_negative_invoice(self):
|
||||||
if (self.payment_type == "Pay" and self.party_type == "Customer") or (
|
if (self.payment_type != "Pay" or self.party_type != "Customer") and (
|
||||||
self.payment_type == "Receive" and self.party_type == "Supplier"
|
self.payment_type != "Receive" or self.party_type != "Supplier"
|
||||||
):
|
):
|
||||||
|
return
|
||||||
|
|
||||||
total_negative_outstanding = sum(
|
total_negative_outstanding = sum(
|
||||||
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
||||||
|
)
|
||||||
|
|
||||||
|
paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
|
||||||
|
additional_charges = sum(flt(d.amount) for d in self.deductions)
|
||||||
|
|
||||||
|
if not total_negative_outstanding:
|
||||||
|
if self.party_type == "Customer":
|
||||||
|
msg = _("Cannot pay to Customer without any negative outstanding invoice")
|
||||||
|
else:
|
||||||
|
msg = _("Cannot receive from Supplier without any negative outstanding invoice")
|
||||||
|
|
||||||
|
frappe.throw(msg, InvalidPaymentEntry)
|
||||||
|
|
||||||
|
elif paid_amount - additional_charges > total_negative_outstanding:
|
||||||
|
frappe.throw(
|
||||||
|
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
|
||||||
|
total_negative_outstanding
|
||||||
|
),
|
||||||
|
InvalidPaymentEntry,
|
||||||
)
|
)
|
||||||
|
|
||||||
paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
|
|
||||||
additional_charges = sum([flt(d.amount) for d in self.deductions])
|
|
||||||
|
|
||||||
if not total_negative_outstanding:
|
|
||||||
frappe.throw(
|
|
||||||
_("Cannot {0} {1} {2} without any negative outstanding invoice").format(
|
|
||||||
_(self.payment_type),
|
|
||||||
(_("to") if self.party_type == "Customer" else _("from")),
|
|
||||||
self.party_type,
|
|
||||||
),
|
|
||||||
InvalidPaymentEntry,
|
|
||||||
)
|
|
||||||
|
|
||||||
elif paid_amount - additional_charges > total_negative_outstanding:
|
|
||||||
frappe.throw(
|
|
||||||
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
|
|
||||||
total_negative_outstanding
|
|
||||||
),
|
|
||||||
InvalidPaymentEntry,
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
if frappe.flags.in_import and self.title:
|
if frappe.flags.in_import and self.title:
|
||||||
# do not set title dynamically if title exists during data import.
|
# do not set title dynamically if title exists during data import.
|
||||||
@@ -1188,6 +1187,7 @@ def get_outstanding_reference_documents(args):
|
|||||||
|
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
common_filter = []
|
common_filter = []
|
||||||
|
accounting_dimensions_filter = []
|
||||||
posting_and_due_date = []
|
posting_and_due_date = []
|
||||||
|
|
||||||
# confirm that Supplier is not blocked
|
# confirm that Supplier is not blocked
|
||||||
@@ -1217,7 +1217,7 @@ def get_outstanding_reference_documents(args):
|
|||||||
# Add cost center condition
|
# Add cost center condition
|
||||||
if args.get("cost_center"):
|
if args.get("cost_center"):
|
||||||
condition += " and cost_center='%s'" % args.get("cost_center")
|
condition += " and cost_center='%s'" % args.get("cost_center")
|
||||||
common_filter.append(ple.cost_center == args.get("cost_center"))
|
accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center"))
|
||||||
|
|
||||||
date_fields_dict = {
|
date_fields_dict = {
|
||||||
"posting_date": ["from_posting_date", "to_posting_date"],
|
"posting_date": ["from_posting_date", "to_posting_date"],
|
||||||
@@ -1243,6 +1243,7 @@ def get_outstanding_reference_documents(args):
|
|||||||
posting_date=posting_and_due_date,
|
posting_date=posting_and_due_date,
|
||||||
min_outstanding=args.get("outstanding_amt_greater_than"),
|
min_outstanding=args.get("outstanding_amt_greater_than"),
|
||||||
max_outstanding=args.get("outstanding_amt_less_than"),
|
max_outstanding=args.get("outstanding_amt_less_than"),
|
||||||
|
accounting_dimensions=accounting_dimensions_filter,
|
||||||
)
|
)
|
||||||
|
|
||||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
||||||
@@ -1639,7 +1640,7 @@ def get_payment_entry(
|
|||||||
):
|
):
|
||||||
reference_doc = None
|
reference_doc = None
|
||||||
doc = frappe.get_doc(dt, dn)
|
doc = frappe.get_doc(dt, dn)
|
||||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
|
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= 99.99:
|
||||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||||
|
|
||||||
if not party_type:
|
if not party_type:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class PaymentReconciliation(Document):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(PaymentReconciliation, self).__init__(*args, **kwargs)
|
super(PaymentReconciliation, self).__init__(*args, **kwargs)
|
||||||
self.common_filter_conditions = []
|
self.common_filter_conditions = []
|
||||||
|
self.accounting_dimension_filter_conditions = []
|
||||||
self.ple_posting_date_filter = []
|
self.ple_posting_date_filter = []
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -193,6 +194,7 @@ class PaymentReconciliation(Document):
|
|||||||
posting_date=self.ple_posting_date_filter,
|
posting_date=self.ple_posting_date_filter,
|
||||||
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
|
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
|
||||||
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
|
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
|
||||||
|
accounting_dimensions=self.accounting_dimension_filter_conditions,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.invoice_limit:
|
if self.invoice_limit:
|
||||||
@@ -381,7 +383,7 @@ class PaymentReconciliation(Document):
|
|||||||
self.common_filter_conditions.append(ple.company == self.company)
|
self.common_filter_conditions.append(ple.company == self.company)
|
||||||
|
|
||||||
if self.get("cost_center") and (get_invoices or get_return_invoices):
|
if self.get("cost_center") and (get_invoices or get_return_invoices):
|
||||||
self.common_filter_conditions.append(ple.cost_center == self.cost_center)
|
self.accounting_dimension_filter_conditions.append(ple.cost_center == self.cost_center)
|
||||||
|
|
||||||
if get_invoices:
|
if get_invoices:
|
||||||
if self.from_invoice_date:
|
if self.from_invoice_date:
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from frappe import qb
|
|||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, nowdate
|
from frappe.utils import add_days, nowdate
|
||||||
|
|
||||||
|
from erpnext import get_default_cost_center
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||||
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.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
@@ -20,6 +22,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.create_item()
|
self.create_item()
|
||||||
self.create_customer()
|
self.create_customer()
|
||||||
self.create_account()
|
self.create_account()
|
||||||
|
self.create_cost_center()
|
||||||
self.clear_old_entries()
|
self.clear_old_entries()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@@ -216,6 +219,22 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
return je
|
return je
|
||||||
|
|
||||||
|
def create_cost_center(self):
|
||||||
|
# Setup cost center
|
||||||
|
cc_name = "Sub"
|
||||||
|
|
||||||
|
self.main_cc = frappe.get_doc("Cost Center", get_default_cost_center(self.company))
|
||||||
|
|
||||||
|
cc_exists = frappe.db.get_list("Cost Center", filters={"cost_center_name": cc_name})
|
||||||
|
if cc_exists:
|
||||||
|
self.sub_cc = frappe.get_doc("Cost Center", cc_exists[0].name)
|
||||||
|
else:
|
||||||
|
sub_cc = frappe.new_doc("Cost Center")
|
||||||
|
sub_cc.cost_center_name = "Sub"
|
||||||
|
sub_cc.parent_cost_center = self.main_cc.parent_cost_center
|
||||||
|
sub_cc.company = self.main_cc.company
|
||||||
|
self.sub_cc = sub_cc.save()
|
||||||
|
|
||||||
def test_filter_min_max(self):
|
def test_filter_min_max(self):
|
||||||
# check filter condition minimum and maximum amount
|
# check filter condition minimum and maximum amount
|
||||||
self.create_sales_invoice(qty=1, rate=300)
|
self.create_sales_invoice(qty=1, rate=300)
|
||||||
@@ -578,3 +597,24 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.assertEqual(len(pr.payments), 1)
|
self.assertEqual(len(pr.payments), 1)
|
||||||
self.assertEqual(pr.payments[0].amount, amount)
|
self.assertEqual(pr.payments[0].amount, amount)
|
||||||
self.assertEqual(pr.payments[0].currency, "EUR")
|
self.assertEqual(pr.payments[0].currency, "EUR")
|
||||||
|
|
||||||
|
def test_differing_cost_center_on_invoice_and_payment(self):
|
||||||
|
"""
|
||||||
|
Cost Center filter should not affect outstanding amount calculation
|
||||||
|
"""
|
||||||
|
|
||||||
|
si = self.create_sales_invoice(qty=1, rate=100, do_not_submit=True)
|
||||||
|
si.cost_center = self.main_cc.name
|
||||||
|
si.submit()
|
||||||
|
pr = get_payment_entry(si.doctype, si.name)
|
||||||
|
pr.cost_center = self.sub_cc.name
|
||||||
|
pr = pr.save().submit()
|
||||||
|
|
||||||
|
pr = self.create_payment_reconciliation()
|
||||||
|
pr.cost_center = self.main_cc.name
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
# check PR tool output
|
||||||
|
self.assertEqual(len(pr.get("invoices")), 0)
|
||||||
|
self.assertEqual(len(pr.get("payments")), 0)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!frm.doc.payment_gateway_account && frm.doc.status == "Initiated") {
|
if((!frm.doc.payment_gateway_account || frm.doc.payment_request_type == "Outward") && frm.doc.status == "Initiated") {
|
||||||
frm.add_custom_button(__('Create Payment Entry'), function(){
|
frm.add_custom_button(__('Create Payment Entry'), function(){
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.accounts.doctype.payment_request.payment_request.make_payment_entry",
|
method: "erpnext.accounts.doctype.payment_request.payment_request.make_payment_entry",
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
payment_entry.update(
|
payment_entry.update(
|
||||||
{
|
{
|
||||||
|
"mode_of_payment": self.mode_of_payment,
|
||||||
"reference_no": self.name,
|
"reference_no": self.name,
|
||||||
"reference_date": nowdate(),
|
"reference_date": nowdate(),
|
||||||
"remarks": "Payment Entry against {0} {1} via Payment Request {2}".format(
|
"remarks": "Payment Entry against {0} {1} via Payment Request {2}".format(
|
||||||
@@ -403,25 +404,22 @@ def make_payment_request(**args):
|
|||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
existing_payment_request = None
|
draft_payment_request = frappe.db.get_value(
|
||||||
if args.order_type == "Shopping Cart":
|
"Payment Request",
|
||||||
existing_payment_request = frappe.db.get_value(
|
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
|
||||||
"Payment Request",
|
)
|
||||||
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ("!=", 2)},
|
|
||||||
)
|
|
||||||
|
|
||||||
if existing_payment_request:
|
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
|
||||||
|
|
||||||
|
if existing_payment_request_amount:
|
||||||
|
grand_total -= existing_payment_request_amount
|
||||||
|
|
||||||
|
if draft_payment_request:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False
|
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
|
||||||
)
|
)
|
||||||
pr = frappe.get_doc("Payment Request", existing_payment_request)
|
pr = frappe.get_doc("Payment Request", draft_payment_request)
|
||||||
else:
|
else:
|
||||||
if args.order_type != "Shopping Cart":
|
|
||||||
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
|
|
||||||
|
|
||||||
if existing_payment_request_amount:
|
|
||||||
grand_total -= existing_payment_request_amount
|
|
||||||
|
|
||||||
pr = frappe.new_doc("Payment Request")
|
pr = frappe.new_doc("Payment Request")
|
||||||
pr.update(
|
pr.update(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,12 +64,13 @@
|
|||||||
"tax_withholding_net_total",
|
"tax_withholding_net_total",
|
||||||
"base_tax_withholding_net_total",
|
"base_tax_withholding_net_total",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_58",
|
"column_break_58",
|
||||||
"tax_category",
|
|
||||||
"column_break_49",
|
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
|
"column_break_49",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"section_break_51",
|
"section_break_51",
|
||||||
"taxes",
|
"taxes",
|
||||||
"totals",
|
"totals",
|
||||||
@@ -1541,13 +1542,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-27 16:28:45.559785",
|
"modified": "2022-12-14 18:37:38.142688",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -61,12 +61,13 @@
|
|||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_38",
|
"column_break_38",
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
"incoterm",
|
|
||||||
"column_break_55",
|
"column_break_55",
|
||||||
"tax_category",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"section_break_40",
|
"section_break_40",
|
||||||
"taxes",
|
"taxes",
|
||||||
"section_break_43",
|
"section_break_43",
|
||||||
@@ -2105,6 +2106,12 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@@ -2117,7 +2124,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-12-05 16:18:14.532114",
|
"modified": "2022-12-12 18:34:33.409895",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -121,12 +121,24 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
|||||||
else:
|
else:
|
||||||
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
|
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
|
||||||
|
|
||||||
|
cost_center = get_cost_center(inv)
|
||||||
|
tax_row.update({"cost_center": cost_center})
|
||||||
|
|
||||||
if inv.doctype == "Purchase Invoice":
|
if inv.doctype == "Purchase Invoice":
|
||||||
return tax_row, tax_deducted_on_advances, voucher_wise_amount
|
return tax_row, tax_deducted_on_advances, voucher_wise_amount
|
||||||
else:
|
else:
|
||||||
return tax_row
|
return tax_row
|
||||||
|
|
||||||
|
|
||||||
|
def get_cost_center(inv):
|
||||||
|
cost_center = frappe.get_cached_value("Company", inv.company, "cost_center")
|
||||||
|
|
||||||
|
if len(inv.get("taxes", [])) > 0:
|
||||||
|
cost_center = inv.get("taxes")[0].cost_center
|
||||||
|
|
||||||
|
return cost_center
|
||||||
|
|
||||||
|
|
||||||
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
|
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
|
||||||
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
|
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from frappe.utils import cint, cstr
|
|||||||
|
|
||||||
from erpnext.accounts.report.financial_statements import (
|
from erpnext.accounts.report.financial_statements import (
|
||||||
get_columns,
|
get_columns,
|
||||||
|
get_cost_centers_with_children,
|
||||||
get_data,
|
get_data,
|
||||||
get_filtered_list_for_consolidated_report,
|
get_filtered_list_for_consolidated_report,
|
||||||
get_period_list,
|
get_period_list,
|
||||||
@@ -160,10 +161,11 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
|
|||||||
total = 0
|
total = 0
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
start_date = get_start_date(period, accumulated_values, company)
|
start_date = get_start_date(period, accumulated_values, company)
|
||||||
|
filters.start_date = start_date
|
||||||
|
filters.end_date = period["to_date"]
|
||||||
|
filters.account_type = account_type
|
||||||
|
|
||||||
amount = get_account_type_based_gl_data(
|
amount = get_account_type_based_gl_data(company, filters)
|
||||||
company, start_date, period["to_date"], account_type, filters
|
|
||||||
)
|
|
||||||
|
|
||||||
if amount and account_type == "Depreciation":
|
if amount and account_type == "Depreciation":
|
||||||
amount *= -1
|
amount *= -1
|
||||||
@@ -175,7 +177,7 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None):
|
def get_account_type_based_gl_data(company, filters=None):
|
||||||
cond = ""
|
cond = ""
|
||||||
filters = frappe._dict(filters or {})
|
filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
@@ -191,17 +193,21 @@ def get_account_type_based_gl_data(company, start_date, end_date, account_type,
|
|||||||
frappe.db.escape(cstr(filters.finance_book))
|
frappe.db.escape(cstr(filters.finance_book))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if filters.get("cost_center"):
|
||||||
|
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
|
||||||
|
cond += " and cost_center in %(cost_center)s"
|
||||||
|
|
||||||
gl_sum = frappe.db.sql_list(
|
gl_sum = frappe.db.sql_list(
|
||||||
"""
|
"""
|
||||||
select sum(credit) - sum(debit)
|
select sum(credit) - sum(debit)
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where company=%s and posting_date >= %s and posting_date <= %s
|
where company=%(company)s and posting_date >= %(start_date)s and posting_date <= %(end_date)s
|
||||||
and voucher_type != 'Period Closing Voucher'
|
and voucher_type != 'Period Closing Voucher'
|
||||||
and account in ( SELECT name FROM tabAccount WHERE account_type = %s) {cond}
|
and account in ( SELECT name FROM tabAccount WHERE account_type = %(account_type)s) {cond}
|
||||||
""".format(
|
""".format(
|
||||||
cond=cond
|
cond=cond
|
||||||
),
|
),
|
||||||
(company, start_date, end_date, account_type),
|
filters,
|
||||||
)
|
)
|
||||||
|
|
||||||
return gl_sum[0] if gl_sum and gl_sum[0] else 0
|
return gl_sum[0] if gl_sum and gl_sum[0] else 0
|
||||||
|
|||||||
@@ -268,10 +268,12 @@ def get_cash_flow_data(fiscal_year, companies, filters):
|
|||||||
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
|
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
|
||||||
data = {}
|
data = {}
|
||||||
total = 0
|
total = 0
|
||||||
|
filters.account_type = account_type
|
||||||
|
filters.start_date = fiscal_year.year_start_date
|
||||||
|
filters.end_date = fiscal_year.year_end_date
|
||||||
|
|
||||||
for company in companies:
|
for company in companies:
|
||||||
amount = get_account_type_based_gl_data(
|
amount = get_account_type_based_gl_data(company, filters)
|
||||||
company, fiscal_year.year_start_date, fiscal_year.year_end_date, account_type, filters
|
|
||||||
)
|
|
||||||
|
|
||||||
if amount and account_type == "Depreciation":
|
if amount and account_type == "Depreciation":
|
||||||
amount *= -1
|
amount *= -1
|
||||||
|
|||||||
@@ -836,6 +836,7 @@ def get_outstanding_invoices(
|
|||||||
posting_date=None,
|
posting_date=None,
|
||||||
min_outstanding=None,
|
min_outstanding=None,
|
||||||
max_outstanding=None,
|
max_outstanding=None,
|
||||||
|
accounting_dimensions=None,
|
||||||
):
|
):
|
||||||
|
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
@@ -866,6 +867,7 @@ def get_outstanding_invoices(
|
|||||||
min_outstanding=min_outstanding,
|
min_outstanding=min_outstanding,
|
||||||
max_outstanding=max_outstanding,
|
max_outstanding=max_outstanding,
|
||||||
get_invoices=True,
|
get_invoices=True,
|
||||||
|
accounting_dimensions=accounting_dimensions or [],
|
||||||
)
|
)
|
||||||
|
|
||||||
for d in invoice_list:
|
for d in invoice_list:
|
||||||
@@ -1615,6 +1617,7 @@ class QueryPaymentLedger(object):
|
|||||||
.where(ple.delinked == 0)
|
.where(ple.delinked == 0)
|
||||||
.where(Criterion.all(filter_on_voucher_no))
|
.where(Criterion.all(filter_on_voucher_no))
|
||||||
.where(Criterion.all(self.common_filter))
|
.where(Criterion.all(self.common_filter))
|
||||||
|
.where(Criterion.all(self.dimensions_filter))
|
||||||
.where(Criterion.all(self.voucher_posting_date))
|
.where(Criterion.all(self.voucher_posting_date))
|
||||||
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
|
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
|
||||||
)
|
)
|
||||||
@@ -1702,6 +1705,7 @@ class QueryPaymentLedger(object):
|
|||||||
max_outstanding=None,
|
max_outstanding=None,
|
||||||
get_payments=False,
|
get_payments=False,
|
||||||
get_invoices=False,
|
get_invoices=False,
|
||||||
|
accounting_dimensions=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE
|
Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE
|
||||||
@@ -1717,6 +1721,7 @@ class QueryPaymentLedger(object):
|
|||||||
self.reset()
|
self.reset()
|
||||||
self.vouchers = vouchers
|
self.vouchers = vouchers
|
||||||
self.common_filter = common_filter or []
|
self.common_filter = common_filter or []
|
||||||
|
self.dimensions_filter = accounting_dimensions or []
|
||||||
self.voucher_posting_date = posting_date or []
|
self.voucher_posting_date = posting_date or []
|
||||||
self.min_outstanding = min_outstanding
|
self.min_outstanding = min_outstanding
|
||||||
self.max_outstanding = max_outstanding
|
self.max_outstanding = max_outstanding
|
||||||
|
|||||||
@@ -235,11 +235,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
cur_frm.add_custom_button(__('Purchase Invoice'),
|
cur_frm.add_custom_button(__('Purchase Invoice'),
|
||||||
this.make_purchase_invoice, __('Create'));
|
this.make_purchase_invoice, __('Create'));
|
||||||
|
|
||||||
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
|
if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
|
||||||
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
|
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(flt(doc.per_billed)==0) {
|
if(flt(doc.per_billed) < 100) {
|
||||||
this.frm.add_custom_button(__('Payment Request'),
|
this.frm.add_custom_button(__('Payment Request'),
|
||||||
function() { me.make_payment_request() }, __('Create'));
|
function() { me.make_payment_request() }, __('Create'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,12 +62,13 @@
|
|||||||
"set_reserve_warehouse",
|
"set_reserve_warehouse",
|
||||||
"supplied_items",
|
"supplied_items",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_53",
|
"column_break_53",
|
||||||
"tax_category",
|
|
||||||
"column_break_50",
|
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
|
"column_break_50",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"section_break_52",
|
"section_break_52",
|
||||||
"taxes",
|
"taxes",
|
||||||
"totals",
|
"totals",
|
||||||
@@ -1256,13 +1257,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-17 17:28:07.729943",
|
"modified": "2022-12-12 18:36:37.455134",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
|||||||
@@ -57,44 +57,96 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
});
|
});
|
||||||
}, __("Tools"));
|
}, __("Tools"));
|
||||||
|
|
||||||
frm.add_custom_button(__('Download PDF'), () => {
|
frm.add_custom_button(
|
||||||
var suppliers = [];
|
__("Download PDF"),
|
||||||
const fields = [{
|
() => {
|
||||||
fieldtype: 'Link',
|
frappe.prompt(
|
||||||
label: __('Select a Supplier'),
|
[
|
||||||
fieldname: 'supplier',
|
{
|
||||||
options: 'Supplier',
|
fieldtype: "Link",
|
||||||
reqd: 1,
|
label: "Select a Supplier",
|
||||||
get_query: () => {
|
fieldname: "supplier",
|
||||||
return {
|
options: "Supplier",
|
||||||
filters: [
|
reqd: 1,
|
||||||
["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})]
|
default: frm.doc.suppliers?.length == 1 ? frm.doc.suppliers[0].supplier : "",
|
||||||
]
|
get_query: () => {
|
||||||
}
|
return {
|
||||||
}
|
filters: [
|
||||||
}];
|
[
|
||||||
|
"Supplier",
|
||||||
frappe.prompt(fields, data => {
|
"name",
|
||||||
var child = locals[cdt][cdn]
|
"in",
|
||||||
|
frm.doc.suppliers.map((row) => {
|
||||||
var w = window.open(
|
return row.supplier;
|
||||||
frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
|
}),
|
||||||
+"doctype="+encodeURIComponent(frm.doc.doctype)
|
],
|
||||||
+"&name="+encodeURIComponent(frm.doc.name)
|
],
|
||||||
+"&supplier="+encodeURIComponent(data.supplier)
|
};
|
||||||
+"&no_letterhead=0"));
|
},
|
||||||
if(!w) {
|
},
|
||||||
frappe.msgprint(__("Please enable pop-ups")); return;
|
{
|
||||||
}
|
fieldtype: "Section Break",
|
||||||
|
label: "Print Settings",
|
||||||
|
fieldname: "print_settings",
|
||||||
|
collapsible: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Link",
|
||||||
|
label: "Print Format",
|
||||||
|
fieldname: "print_format",
|
||||||
|
options: "Print Format",
|
||||||
|
placeholder: "Standard",
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
doc_type: "Request for Quotation",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Link",
|
||||||
|
label: "Language",
|
||||||
|
fieldname: "language",
|
||||||
|
options: "Language",
|
||||||
|
default: frappe.boot.lang,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Link",
|
||||||
|
label: "Letter Head",
|
||||||
|
fieldname: "letter_head",
|
||||||
|
options: "Letter Head",
|
||||||
|
default: frm.doc.letter_head,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
(data) => {
|
||||||
|
var w = window.open(
|
||||||
|
frappe.urllib.get_full_url(
|
||||||
|
"/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" +
|
||||||
|
new URLSearchParams({
|
||||||
|
doctype: frm.doc.doctype,
|
||||||
|
name: frm.doc.name,
|
||||||
|
supplier: data.supplier,
|
||||||
|
print_format: data.print_format || "Standard",
|
||||||
|
language: data.language || frappe.boot.lang,
|
||||||
|
letter_head: data.letter_head || frm.doc.letter_head || "",
|
||||||
|
}).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!w) {
|
||||||
|
frappe.msgprint(__("Please enable pop-ups"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Download PDF for Supplier",
|
||||||
|
"Download"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
'Download PDF for Supplier',
|
__("Tools")
|
||||||
'Download');
|
);
|
||||||
},
|
|
||||||
__("Tools"));
|
|
||||||
|
|
||||||
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
make_supplier_quotation: function(frm) {
|
make_supplier_quotation: function(frm) {
|
||||||
|
|||||||
@@ -389,10 +389,17 @@ def create_rfq_items(sq_doc, supplier, data):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pdf(doctype, name, supplier):
|
def get_pdf(doctype, name, supplier, print_format=None, language=None, letter_head=None):
|
||||||
doc = get_rfq_doc(doctype, name, supplier)
|
# permissions get checked in `download_pdf`
|
||||||
if doc:
|
if doc := get_rfq_doc(doctype, name, supplier):
|
||||||
download_pdf(doctype, name, doc=doc)
|
download_pdf(
|
||||||
|
doctype,
|
||||||
|
name,
|
||||||
|
print_format,
|
||||||
|
doc=doc,
|
||||||
|
language=language,
|
||||||
|
letter_head=letter_head or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_rfq_doc(doctype, name, supplier):
|
def get_rfq_doc(doctype, name, supplier):
|
||||||
|
|||||||
@@ -40,12 +40,13 @@
|
|||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_34",
|
"column_break_34",
|
||||||
"tax_category",
|
|
||||||
"column_break_36",
|
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
|
"column_break_36",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"section_break_38",
|
"section_break_38",
|
||||||
"taxes",
|
"taxes",
|
||||||
"totals",
|
"totals",
|
||||||
@@ -830,6 +831,12 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
@@ -837,7 +844,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-17 17:27:32.179686",
|
"modified": "2022-12-12 18:35:39.740974",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation",
|
"name": "Supplier Quotation",
|
||||||
|
|||||||
@@ -347,16 +347,21 @@ class StatusUpdater(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
|
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
|
||||||
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
|
if qty_or_amount == "qty":
|
||||||
|
msg = _("Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.")
|
||||||
|
else:
|
||||||
|
msg = _("Overbilling of {0} {1} ignored for item {2} because you have {3} role.")
|
||||||
|
|
||||||
msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format(
|
frappe.msgprint(
|
||||||
action,
|
msg.format(
|
||||||
_(item["target_ref_field"].title()),
|
_(item["target_ref_field"].title()),
|
||||||
frappe.bold(item["reduce_by"]),
|
frappe.bold(item["reduce_by"]),
|
||||||
frappe.bold(item.get("item_code")),
|
frappe.bold(item.get("item_code")),
|
||||||
role,
|
role,
|
||||||
|
),
|
||||||
|
indicator="orange",
|
||||||
|
alert=True,
|
||||||
)
|
)
|
||||||
frappe.msgprint(msg, indicator="orange", alert=True)
|
|
||||||
|
|
||||||
def update_qty(self, update_modified=True):
|
def update_qty(self, update_modified=True):
|
||||||
"""Updates qty or amount at row level
|
"""Updates qty or amount at row level
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-06-30 13:09:14.228756",
|
"modified": "2022-12-15 11:11:02.131986",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Appointment",
|
"name": "Appointment",
|
||||||
@@ -121,16 +121,6 @@
|
|||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Guest",
|
|
||||||
"share": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
@@ -170,5 +160,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,9 @@ from collections import Counter
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.desk.form.assign_to import add as add_assignment
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.share import add_docshare
|
||||||
from frappe.utils import get_url, getdate, now
|
from frappe.utils import get_url, getdate, now
|
||||||
from frappe.utils.verified_command import get_signed_params
|
from frappe.utils.verified_command import get_signed_params
|
||||||
|
|
||||||
@@ -130,21 +132,18 @@ class Appointment(Document):
|
|||||||
self.party = lead.name
|
self.party = lead.name
|
||||||
|
|
||||||
def auto_assign(self):
|
def auto_assign(self):
|
||||||
from frappe.desk.form.assign_to import add as add_assignemnt
|
|
||||||
|
|
||||||
existing_assignee = self.get_assignee_from_latest_opportunity()
|
existing_assignee = self.get_assignee_from_latest_opportunity()
|
||||||
if existing_assignee:
|
if existing_assignee:
|
||||||
# If the latest opportunity is assigned to someone
|
# If the latest opportunity is assigned to someone
|
||||||
# Assign the appointment to the same
|
# Assign the appointment to the same
|
||||||
add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [existing_assignee]})
|
self.assign_agent(existing_assignee)
|
||||||
return
|
return
|
||||||
if self._assign:
|
if self._assign:
|
||||||
return
|
return
|
||||||
available_agents = _get_agents_sorted_by_asc_workload(getdate(self.scheduled_time))
|
available_agents = _get_agents_sorted_by_asc_workload(getdate(self.scheduled_time))
|
||||||
for agent in available_agents:
|
for agent in available_agents:
|
||||||
if _check_agent_availability(agent, self.scheduled_time):
|
if _check_agent_availability(agent, self.scheduled_time):
|
||||||
agent = agent[0]
|
self.assign_agent(agent[0])
|
||||||
add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [agent]})
|
|
||||||
break
|
break
|
||||||
|
|
||||||
def get_assignee_from_latest_opportunity(self):
|
def get_assignee_from_latest_opportunity(self):
|
||||||
@@ -199,9 +198,15 @@ class Appointment(Document):
|
|||||||
params = {"email": self.customer_email, "appointment": self.name}
|
params = {"email": self.customer_email, "appointment": self.name}
|
||||||
return get_url(verify_route + "?" + get_signed_params(params))
|
return get_url(verify_route + "?" + get_signed_params(params))
|
||||||
|
|
||||||
|
def assign_agent(self, agent):
|
||||||
|
if not frappe.has_permission(doc=self, user=agent):
|
||||||
|
add_docshare(self.doctype, self.name, agent, flags={"ignore_share_permission": True})
|
||||||
|
|
||||||
|
add_assignment({"doctype": self.doctype, "name": self.name, "assign_to": [agent]})
|
||||||
|
|
||||||
|
|
||||||
def _get_agents_sorted_by_asc_workload(date):
|
def _get_agents_sorted_by_asc_workload(date):
|
||||||
appointments = frappe.db.get_list("Appointment", fields="*")
|
appointments = frappe.get_all("Appointment", fields="*")
|
||||||
agent_list = _get_agent_list_as_strings()
|
agent_list = _get_agent_list_as_strings()
|
||||||
if not appointments:
|
if not appointments:
|
||||||
return agent_list
|
return agent_list
|
||||||
@@ -226,7 +231,7 @@ def _get_agent_list_as_strings():
|
|||||||
|
|
||||||
|
|
||||||
def _check_agent_availability(agent_email, scheduled_time):
|
def _check_agent_availability(agent_email, scheduled_time):
|
||||||
appointemnts_at_scheduled_time = frappe.get_list(
|
appointemnts_at_scheduled_time = frappe.get_all(
|
||||||
"Appointment", filters={"scheduled_time": scheduled_time}
|
"Appointment", filters={"scheduled_time": scheduled_time}
|
||||||
)
|
)
|
||||||
for appointment in appointemnts_at_scheduled_time:
|
for appointment in appointemnts_at_scheduled_time:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2019-08-27 10:56:48.309824",
|
"creation": "2019-08-27 10:56:48.309824",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@@ -101,7 +102,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"modified": "2019-11-26 12:14:17.669366",
|
"links": [],
|
||||||
|
"modified": "2022-12-15 11:10:13.517742",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Appointment Booking Settings",
|
"name": "Appointment Booking Settings",
|
||||||
@@ -117,13 +119,6 @@
|
|||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"email": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"role": "Guest",
|
|
||||||
"share": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
@@ -147,5 +142,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -427,6 +427,7 @@ scheduler_events = {
|
|||||||
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
||||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||||
"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
|
"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
|
||||||
|
"erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries",
|
||||||
],
|
],
|
||||||
"monthly_long": [
|
"monthly_long": [
|
||||||
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
|
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
|
||||||
|
|||||||
@@ -1154,6 +1154,36 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
except frappe.MandatoryError:
|
except frappe.MandatoryError:
|
||||||
self.fail("Batch generation causing failing in Work Order")
|
self.fail("Batch generation causing failing in Work Order")
|
||||||
|
|
||||||
|
@change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1})
|
||||||
|
def test_auto_serial_no_creation(self):
|
||||||
|
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||||
|
|
||||||
|
fg_item = frappe.generate_hash(length=20)
|
||||||
|
child_item = frappe.generate_hash(length=20)
|
||||||
|
|
||||||
|
bom_tree = {fg_item: {child_item: {}}}
|
||||||
|
|
||||||
|
create_nested_bom(bom_tree, prefix="")
|
||||||
|
|
||||||
|
item = frappe.get_doc("Item", fg_item)
|
||||||
|
item.has_serial_no = 1
|
||||||
|
item.serial_no_series = f"{item.name}.#####"
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True)
|
||||||
|
serial_nos = wo_order.serial_no
|
||||||
|
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||||
|
stock_entry.set_work_order_details()
|
||||||
|
stock_entry.set_serial_no_batch_for_finished_good()
|
||||||
|
for row in stock_entry.items:
|
||||||
|
if row.item_code == fg_item:
|
||||||
|
self.assertTrue(row.serial_no)
|
||||||
|
self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos)))
|
||||||
|
|
||||||
|
except frappe.MandatoryError:
|
||||||
|
self.fail("Batch generation causing failing in Work Order")
|
||||||
|
|
||||||
@change_settings(
|
@change_settings(
|
||||||
"Manufacturing Settings",
|
"Manufacturing Settings",
|
||||||
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
|
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_payment_request() {
|
make_payment_request() {
|
||||||
var me = this;
|
let me = this;
|
||||||
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
|
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
|
||||||
? "Inward" : "Outward";
|
? "Inward" : "Outward";
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc){
|
if(!r.exc){
|
||||||
var doc = frappe.model.sync(r.message);
|
frappe.model.sync(r.message);
|
||||||
frappe.set_route("Form", r.message.doctype, r.message.name);
|
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,12 +43,13 @@
|
|||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_36",
|
"column_break_36",
|
||||||
"tax_category",
|
|
||||||
"column_break_34",
|
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
|
"column_break_34",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"section_break_36",
|
"section_break_36",
|
||||||
"taxes",
|
"taxes",
|
||||||
"section_break_39",
|
"section_break_39",
|
||||||
@@ -1059,13 +1060,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
"idx": 82,
|
"idx": 82,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-17 17:20:54.984348",
|
"modified": "2022-12-12 18:32:28.671332",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation",
|
"name": "Quotation",
|
||||||
|
|||||||
@@ -58,12 +58,13 @@
|
|||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_38",
|
"column_break_38",
|
||||||
"tax_category",
|
|
||||||
"column_break_49",
|
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
|
"column_break_49",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"section_break_40",
|
"section_break_40",
|
||||||
"taxes",
|
"taxes",
|
||||||
"section_break_43",
|
"section_break_43",
|
||||||
@@ -1630,13 +1631,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-17 17:22:00.413878",
|
"modified": "2022-12-12 18:34:00.681780",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order",
|
"name": "Sales Order",
|
||||||
|
|||||||
@@ -70,9 +70,6 @@ class Company(NestedSet):
|
|||||||
|
|
||||||
self.abbr = self.abbr.strip()
|
self.abbr = self.abbr.strip()
|
||||||
|
|
||||||
# if self.get('__islocal') and len(self.abbr) > 5:
|
|
||||||
# frappe.throw(_("Abbreviation cannot have more than 5 characters"))
|
|
||||||
|
|
||||||
if not self.abbr.strip():
|
if not self.abbr.strip():
|
||||||
frappe.throw(_("Abbreviation is mandatory"))
|
frappe.throw(_("Abbreviation is mandatory"))
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class TransactionDeletionRecord(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_doctypes_to_be_ignored():
|
def get_doctypes_to_be_ignored():
|
||||||
doctypes_to_be_ignored_list = [
|
doctypes_to_be_ignored = [
|
||||||
"Account",
|
"Account",
|
||||||
"Cost Center",
|
"Cost Center",
|
||||||
"Warehouse",
|
"Warehouse",
|
||||||
@@ -223,4 +223,7 @@ def get_doctypes_to_be_ignored():
|
|||||||
"Customer",
|
"Customer",
|
||||||
"Supplier",
|
"Supplier",
|
||||||
]
|
]
|
||||||
return doctypes_to_be_ignored_list
|
|
||||||
|
doctypes_to_be_ignored.extend(frappe.get_hooks("company_data_to_be_ignored") or [])
|
||||||
|
|
||||||
|
return doctypes_to_be_ignored
|
||||||
|
|||||||
@@ -57,12 +57,13 @@
|
|||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"column_break_43",
|
"column_break_43",
|
||||||
"tax_category",
|
|
||||||
"column_break_39",
|
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
|
"column_break_39",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"section_break_41",
|
"section_break_41",
|
||||||
"taxes",
|
"taxes",
|
||||||
"section_break_44",
|
"section_break_44",
|
||||||
@@ -1388,13 +1389,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-truck",
|
"icon": "fa fa-truck",
|
||||||
"idx": 146,
|
"idx": 146,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-17 17:22:42.860790",
|
"modified": "2022-12-12 18:38:53.067799",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note",
|
"name": "Delivery Note",
|
||||||
|
|||||||
@@ -192,13 +192,13 @@ class PickList(Document):
|
|||||||
|
|
||||||
if item_map.get(key):
|
if item_map.get(key):
|
||||||
item_map[key].qty += item.qty
|
item_map[key].qty += item.qty
|
||||||
item_map[key].stock_qty += item.stock_qty
|
item_map[key].stock_qty += flt(item.stock_qty, item.precision("stock_qty"))
|
||||||
else:
|
else:
|
||||||
item_map[key] = item
|
item_map[key] = item
|
||||||
|
|
||||||
# maintain count of each item (useful to limit get query)
|
# maintain count of each item (useful to limit get query)
|
||||||
self.item_count_map.setdefault(item_code, 0)
|
self.item_count_map.setdefault(item_code, 0)
|
||||||
self.item_count_map[item_code] += item.stock_qty
|
self.item_count_map[item_code] += flt(item.stock_qty, item.precision("stock_qty"))
|
||||||
|
|
||||||
return item_map.values()
|
return item_map.values()
|
||||||
|
|
||||||
|
|||||||
@@ -58,12 +58,13 @@
|
|||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
"taxes_charges_section",
|
"taxes_charges_section",
|
||||||
|
"tax_category",
|
||||||
"taxes_and_charges",
|
"taxes_and_charges",
|
||||||
"shipping_col",
|
"shipping_col",
|
||||||
"tax_category",
|
|
||||||
"column_break_53",
|
|
||||||
"shipping_rule",
|
"shipping_rule",
|
||||||
|
"column_break_53",
|
||||||
"incoterm",
|
"incoterm",
|
||||||
|
"named_place",
|
||||||
"taxes_section",
|
"taxes_section",
|
||||||
"taxes",
|
"taxes",
|
||||||
"totals",
|
"totals",
|
||||||
@@ -1225,13 +1226,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Incoterm",
|
"label": "Incoterm",
|
||||||
"options": "Incoterm"
|
"options": "Incoterm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "incoterm",
|
||||||
|
"fieldname": "named_place",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Named Place"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-truck",
|
"icon": "fa fa-truck",
|
||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-17 17:29:30.067536",
|
"modified": "2022-12-12 18:40:32.447752",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
|||||||
@@ -766,13 +766,13 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def auto_fetch_serial_number(
|
def auto_fetch_serial_number(
|
||||||
qty: float,
|
qty: int,
|
||||||
item_code: str,
|
item_code: str,
|
||||||
warehouse: str,
|
warehouse: str,
|
||||||
posting_date: Optional[str] = None,
|
posting_date: Optional[str] = None,
|
||||||
batch_nos: Optional[Union[str, List[str]]] = None,
|
batch_nos: Optional[Union[str, List[str]]] = None,
|
||||||
for_doctype: Optional[str] = None,
|
for_doctype: Optional[str] = None,
|
||||||
exclude_sr_nos: Optional[List[str]] = None,
|
exclude_sr_nos=None,
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
|
|
||||||
filters = frappe._dict({"item_code": item_code, "warehouse": warehouse})
|
filters = frappe._dict({"item_code": item_code, "warehouse": warehouse})
|
||||||
|
|||||||
@@ -4,12 +4,24 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
cint,
|
||||||
|
comma_or,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
format_time,
|
||||||
|
formatdate,
|
||||||
|
getdate,
|
||||||
|
nowdate,
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
@@ -2239,16 +2251,16 @@ class StockEntry(StockController):
|
|||||||
d.qty -= process_loss_dict[d.item_code][1]
|
d.qty -= process_loss_dict[d.item_code][1]
|
||||||
|
|
||||||
def set_serial_no_batch_for_finished_good(self):
|
def set_serial_no_batch_for_finished_good(self):
|
||||||
serial_nos = ""
|
serial_nos = []
|
||||||
if self.pro_doc.serial_no:
|
if self.pro_doc.serial_no:
|
||||||
serial_nos = self.get_serial_nos_for_fg()
|
serial_nos = self.get_serial_nos_for_fg() or []
|
||||||
|
|
||||||
for row in self.items:
|
for row in self.items:
|
||||||
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
|
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
|
||||||
if serial_nos:
|
if serial_nos:
|
||||||
row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
|
row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
|
||||||
|
|
||||||
def get_serial_nos_for_fg(self, args):
|
def get_serial_nos_for_fg(self):
|
||||||
fields = [
|
fields = [
|
||||||
"`tabStock Entry`.`name`",
|
"`tabStock Entry`.`name`",
|
||||||
"`tabStock Entry Detail`.`qty`",
|
"`tabStock Entry Detail`.`qty`",
|
||||||
@@ -2264,9 +2276,7 @@ class StockEntry(StockController):
|
|||||||
]
|
]
|
||||||
|
|
||||||
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||||
|
return self.get_available_serial_nos(stock_entries)
|
||||||
if self.pro_doc.serial_no:
|
|
||||||
return self.get_available_serial_nos(stock_entries)
|
|
||||||
|
|
||||||
def get_available_serial_nos(self, stock_entries):
|
def get_available_serial_nos(self, stock_entries):
|
||||||
used_serial_nos = []
|
used_serial_nos = []
|
||||||
@@ -2705,3 +2715,62 @@ def get_stock_entry_data(work_order):
|
|||||||
)
|
)
|
||||||
.orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
|
.orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
|
||||||
).run(as_dict=1)
|
).run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
|
def audit_incorrect_valuation_entries():
|
||||||
|
# Audit of stock transfer entries having incorrect valuation
|
||||||
|
from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
|
||||||
|
|
||||||
|
stock_entries = get_incorrect_stock_entries()
|
||||||
|
|
||||||
|
for stock_entry, values in stock_entries.items():
|
||||||
|
reposting_data = frappe._dict(
|
||||||
|
{
|
||||||
|
"posting_date": values.posting_date,
|
||||||
|
"posting_time": values.posting_time,
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"voucher_no": stock_entry,
|
||||||
|
"company": values.company,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
create_repost_item_valuation_entry(reposting_data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_incorrect_stock_entries() -> Dict:
|
||||||
|
stock_entry = frappe.qb.DocType("Stock Entry")
|
||||||
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
transfer_purposes = [
|
||||||
|
"Material Transfer",
|
||||||
|
"Material Transfer for Manufacture",
|
||||||
|
"Send to Subcontractor",
|
||||||
|
]
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(stock_entry)
|
||||||
|
.inner_join(stock_ledger_entry)
|
||||||
|
.on(stock_entry.name == stock_ledger_entry.voucher_no)
|
||||||
|
.select(
|
||||||
|
stock_entry.name,
|
||||||
|
stock_entry.company,
|
||||||
|
stock_entry.posting_date,
|
||||||
|
stock_entry.posting_time,
|
||||||
|
Sum(stock_ledger_entry.stock_value_difference).as_("stock_value"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(stock_entry.docstatus == 1)
|
||||||
|
& (stock_entry.purpose.isin(transfer_purposes))
|
||||||
|
& (stock_ledger_entry.modified > add_days(today(), -2))
|
||||||
|
)
|
||||||
|
.groupby(stock_ledger_entry.voucher_detail_no)
|
||||||
|
.having(Sum(stock_ledger_entry.stock_value_difference) != 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = query.run(as_dict=True)
|
||||||
|
stock_entries = {}
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
if abs(row.stock_value) > 0.1 and row.name not in stock_entries:
|
||||||
|
stock_entries.setdefault(row.name, row)
|
||||||
|
|
||||||
|
return stock_entries
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.permissions import add_user_permission, remove_user_permission
|
from frappe.permissions import add_user_permission, remove_user_permission
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, flt, nowdate, nowtime, today
|
from frappe.utils import add_days, flt, now, nowdate, nowtime, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
from erpnext.stock.doctype.item.test_item import (
|
from erpnext.stock.doctype.item.test_item import (
|
||||||
@@ -17,6 +17,8 @@ from erpnext.stock.doctype.item.test_item import (
|
|||||||
from erpnext.stock.doctype.serial_no.serial_no import * # noqa
|
from erpnext.stock.doctype.serial_no.serial_no import * # noqa
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry import (
|
from erpnext.stock.doctype.stock_entry.stock_entry import (
|
||||||
FinishedGoodError,
|
FinishedGoodError,
|
||||||
|
audit_incorrect_valuation_entries,
|
||||||
|
get_incorrect_stock_entries,
|
||||||
move_sample_to_retention_warehouse,
|
move_sample_to_retention_warehouse,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
@@ -1614,6 +1616,44 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertRaises(BatchExpiredError, se.save)
|
self.assertRaises(BatchExpiredError, se.save)
|
||||||
|
|
||||||
|
def test_audit_incorrect_stock_entries(self):
|
||||||
|
item_code = "Test Incorrect Valuation Rate Item - 001"
|
||||||
|
create_item(item_code=item_code, is_stock_item=1)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
posting_date=add_days(nowdate(), -10),
|
||||||
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
to_warehouse="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
transfer_entry = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
purpose="Material Transfer",
|
||||||
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
from_warehouse="_Test Warehouse - _TC",
|
||||||
|
to_warehouse="_Test Warehouse 1 - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
sle_name = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry", {"voucher_no": transfer_entry.name, "actual_qty": (">", 0)}, "name"
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Stock Ledger Entry", sle_name, {"modified": add_days(now(), -1), "stock_value_difference": 10}
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_entries = get_incorrect_stock_entries()
|
||||||
|
self.assertTrue(transfer_entry.name in stock_entries)
|
||||||
|
|
||||||
|
audit_incorrect_valuation_entries()
|
||||||
|
|
||||||
|
stock_entries = get_incorrect_stock_entries()
|
||||||
|
self.assertFalse(transfer_entry.name in stock_entries)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -715,8 +715,8 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None):
|
|||||||
def get_stock_balance_for(
|
def get_stock_balance_for(
|
||||||
item_code: str,
|
item_code: str,
|
||||||
warehouse: str,
|
warehouse: str,
|
||||||
posting_date: str,
|
posting_date,
|
||||||
posting_time: str,
|
posting_time,
|
||||||
batch_no: Optional[str] = None,
|
batch_no: Optional[str] = None,
|
||||||
with_valuation_rate: bool = True,
|
with_valuation_rate: bool = True,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -828,9 +828,9 @@ def insert_item_price(args):
|
|||||||
):
|
):
|
||||||
if frappe.has_permission("Item Price", "write"):
|
if frappe.has_permission("Item Price", "write"):
|
||||||
price_list_rate = (
|
price_list_rate = (
|
||||||
(args.rate + args.discount_amount) / args.get("conversion_factor")
|
(flt(args.rate) + flt(args.discount_amount)) / args.get("conversion_factor")
|
||||||
if args.get("conversion_factor")
|
if args.get("conversion_factor")
|
||||||
else (args.rate + args.discount_amount)
|
else (flt(args.rate) + flt(args.discount_amount))
|
||||||
)
|
)
|
||||||
|
|
||||||
item_price = frappe.db.get_value(
|
item_price = frappe.db.get_value(
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ def get_item_info(filters):
|
|||||||
item.safety_stock,
|
item.safety_stock,
|
||||||
item.lead_time_days,
|
item.lead_time_days,
|
||||||
)
|
)
|
||||||
.where(item.is_stock_item == 1)
|
.where((item.is_stock_item == 1) & (item.disabled == 0))
|
||||||
)
|
)
|
||||||
|
|
||||||
if brand := filters.get("brand"):
|
if brand := filters.get("brand"):
|
||||||
|
|||||||
@@ -1849,6 +1849,8 @@ Outstanding Amt,Offener Betrag,
|
|||||||
Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen,
|
Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen,
|
||||||
Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}),
|
Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}),
|
||||||
Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet),
|
Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet),
|
||||||
|
Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Annahme bzw. Lieferung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
|
||||||
|
Overbilling of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Abrechnung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
|
||||||
Overdue,Überfällig,
|
Overdue,Überfällig,
|
||||||
Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1},
|
Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1},
|
||||||
Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:,
|
Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:,
|
||||||
@@ -9916,4 +9918,3 @@ Cost and Freight,Kosten und Fracht,
|
|||||||
Delivered at Place,Geliefert benannter Ort,
|
Delivered at Place,Geliefert benannter Ort,
|
||||||
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
|
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
|
||||||
Delivered Duty Paid,Geliefert verzollt,
|
Delivered Duty Paid,Geliefert verzollt,
|
||||||
{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben."
|
|
||||||
|
|||||||
|
Can't render this file because it is too large.
|
@@ -2,8 +2,6 @@ frappe.ready(async () => {
|
|||||||
initialise_select_date();
|
initialise_select_date();
|
||||||
})
|
})
|
||||||
|
|
||||||
window.holiday_list = [];
|
|
||||||
|
|
||||||
async function initialise_select_date() {
|
async function initialise_select_date() {
|
||||||
navigate_to_page(1);
|
navigate_to_page(1);
|
||||||
await get_global_variables();
|
await get_global_variables();
|
||||||
@@ -20,7 +18,6 @@ async function get_global_variables() {
|
|||||||
window.timezones = (await frappe.call({
|
window.timezones = (await frappe.call({
|
||||||
method:'erpnext.www.book_appointment.index.get_timezones'
|
method:'erpnext.www.book_appointment.index.get_timezones'
|
||||||
})).message;
|
})).message;
|
||||||
window.holiday_list = window.appointment_settings.holiday_list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup_timezone_selector() {
|
function setup_timezone_selector() {
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ def get_context(context):
|
|||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_appointment_settings():
|
def get_appointment_settings():
|
||||||
settings = frappe.get_doc("Appointment Booking Settings")
|
settings = frappe.get_cached_value(
|
||||||
settings.holiday_list = frappe.get_doc("Holiday List", settings.holiday_list)
|
"Appointment Booking Settings",
|
||||||
|
None,
|
||||||
|
["advance_booking_days", "appointment_duration", "success_redirect_url"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
@@ -106,7 +110,7 @@ def create_appointment(date, time, tz, contact):
|
|||||||
appointment.customer_details = contact.get("notes", None)
|
appointment.customer_details = contact.get("notes", None)
|
||||||
appointment.customer_email = contact.get("email", None)
|
appointment.customer_email = contact.get("email", None)
|
||||||
appointment.status = "Open"
|
appointment.status = "Open"
|
||||||
appointment.insert()
|
appointment.insert(ignore_permissions=True)
|
||||||
return appointment
|
return appointment
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import frappe
|
|||||||
from frappe.utils.verified_command import verify_request
|
from frappe.utils.verified_command import verify_request
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def get_context(context):
|
def get_context(context):
|
||||||
if not verify_request():
|
if not verify_request():
|
||||||
context.success = False
|
context.success = False
|
||||||
|
|||||||
Reference in New Issue
Block a user