Merge branch 'develop' into fixed-process-loss-in-job-card
This commit is contained in:
commit
93fe923e2a
@ -50,13 +50,15 @@ class AccountingDimension(Document):
|
|||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
make_dimension_in_accounting_doctypes(doc=self)
|
make_dimension_in_accounting_doctypes(doc=self)
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long")
|
frappe.enqueue(
|
||||||
|
make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True
|
||||||
|
)
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
delete_accounting_dimension(doc=self)
|
delete_accounting_dimension(doc=self)
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long")
|
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long", enqueue_after_commit=True)
|
||||||
|
|
||||||
def set_fieldname_and_label(self):
|
def set_fieldname_and_label(self):
|
||||||
if not self.label:
|
if not self.label:
|
||||||
|
@ -41,7 +41,7 @@ frappe.ui.form.on("Bank Clearance", {
|
|||||||
frm.trigger("get_payment_entries")
|
frm.trigger("get_payment_entries")
|
||||||
);
|
);
|
||||||
|
|
||||||
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
|
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary');
|
||||||
},
|
},
|
||||||
|
|
||||||
update_clearance_date: function(frm) {
|
update_clearance_date: function(frm) {
|
||||||
@ -53,8 +53,8 @@ frappe.ui.form.on("Bank Clearance", {
|
|||||||
frm.refresh_fields();
|
frm.refresh_fields();
|
||||||
|
|
||||||
if (!frm.doc.payment_entries.length) {
|
if (!frm.doc.payment_entries.length) {
|
||||||
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
|
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary');
|
||||||
frm.change_custom_button_type('Update Clearance Date', null, 'default');
|
frm.change_custom_button_type(__('Update Clearance Date'), null, 'default');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -72,8 +72,8 @@ frappe.ui.form.on("Bank Clearance", {
|
|||||||
frm.trigger("update_clearance_date")
|
frm.trigger("update_clearance_date")
|
||||||
);
|
);
|
||||||
|
|
||||||
frm.change_custom_button_type('Get Payment Entries', null, 'default');
|
frm.change_custom_button_type(__('Get Payment Entries'), null, 'default');
|
||||||
frm.change_custom_button_type('Update Clearance Date', null, 'primary');
|
frm.change_custom_button_type(__('Update Clearance Date'), null, 'primary');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -81,7 +81,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
|||||||
frm.add_custom_button(__('Get Unreconciled Entries'), function() {
|
frm.add_custom_button(__('Get Unreconciled Entries'), function() {
|
||||||
frm.trigger("make_reconciliation_tool");
|
frm.trigger("make_reconciliation_tool");
|
||||||
});
|
});
|
||||||
frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
|
frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary');
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -245,6 +245,7 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -315,10 +316,11 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-03 18:55:43.683053",
|
"modified": "2023-06-03 16:24:01.677026",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Dunning",
|
"name": "Dunning",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -365,6 +367,7 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
|
"states": [],
|
||||||
"title_field": "customer_name",
|
"title_field": "customer_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -952,6 +952,7 @@ class JournalEntry(AccountsController):
|
|||||||
blank_row.debit_in_account_currency = abs(diff)
|
blank_row.debit_in_account_currency = abs(diff)
|
||||||
blank_row.debit = abs(diff)
|
blank_row.debit = abs(diff)
|
||||||
|
|
||||||
|
self.set_total_debit_credit()
|
||||||
self.validate_total_debit_and_credit()
|
self.validate_total_debit_and_credit()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -65,22 +65,22 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
this.frm.add_custom_button(__('Get Unreconciled Entries'), () =>
|
this.frm.add_custom_button(__('Get Unreconciled Entries'), () =>
|
||||||
this.frm.trigger("get_unreconciled_entries")
|
this.frm.trigger("get_unreconciled_entries")
|
||||||
);
|
);
|
||||||
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
|
this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary');
|
||||||
}
|
}
|
||||||
if (this.frm.doc.invoices.length && this.frm.doc.payments.length) {
|
if (this.frm.doc.invoices.length && this.frm.doc.payments.length) {
|
||||||
this.frm.add_custom_button(__('Allocate'), () =>
|
this.frm.add_custom_button(__('Allocate'), () =>
|
||||||
this.frm.trigger("allocate")
|
this.frm.trigger("allocate")
|
||||||
);
|
);
|
||||||
this.frm.change_custom_button_type('Allocate', null, 'primary');
|
this.frm.change_custom_button_type(__('Allocate'), null, 'primary');
|
||||||
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
|
this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'default');
|
||||||
}
|
}
|
||||||
if (this.frm.doc.allocation.length) {
|
if (this.frm.doc.allocation.length) {
|
||||||
this.frm.add_custom_button(__('Reconcile'), () =>
|
this.frm.add_custom_button(__('Reconcile'), () =>
|
||||||
this.frm.trigger("reconcile")
|
this.frm.trigger("reconcile")
|
||||||
);
|
);
|
||||||
this.frm.change_custom_button_type('Reconcile', null, 'primary');
|
this.frm.change_custom_button_type(__('Reconcile'), null, 'primary');
|
||||||
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
|
this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'default');
|
||||||
this.frm.change_custom_button_type('Allocate', null, 'default');
|
this.frm.change_custom_button_type(__('Allocate'), null, 'default');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for any running reconciliation jobs
|
// check for any running reconciliation jobs
|
||||||
|
@ -6,7 +6,6 @@ import frappe
|
|||||||
from frappe import _, msgprint, qb
|
from frappe import _, msgprint, qb
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder.custom import ConstantColumn
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
from frappe.query_builder.functions import IfNull
|
|
||||||
from frappe.utils import flt, get_link_to_form, getdate, nowdate, today
|
from frappe.utils import flt, get_link_to_form, getdate, nowdate, today
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@ -127,12 +126,29 @@ class PaymentReconciliation(Document):
|
|||||||
|
|
||||||
return list(journal_entries)
|
return list(journal_entries)
|
||||||
|
|
||||||
|
def get_return_invoices(self):
|
||||||
|
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
|
||||||
|
doc = qb.DocType(voucher_type)
|
||||||
|
self.return_invoices = (
|
||||||
|
qb.from_(doc)
|
||||||
|
.select(
|
||||||
|
ConstantColumn(voucher_type).as_("voucher_type"),
|
||||||
|
doc.name.as_("voucher_no"),
|
||||||
|
doc.return_against,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(doc.docstatus == 1)
|
||||||
|
& (doc[frappe.scrub(self.party_type)] == self.party)
|
||||||
|
& (doc.is_return == 1)
|
||||||
|
)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
def get_dr_or_cr_notes(self):
|
def get_dr_or_cr_notes(self):
|
||||||
|
|
||||||
self.build_qb_filter_conditions(get_return_invoices=True)
|
self.build_qb_filter_conditions(get_return_invoices=True)
|
||||||
|
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
|
|
||||||
|
|
||||||
if erpnext.get_party_account_type(self.party_type) == "Receivable":
|
if erpnext.get_party_account_type(self.party_type) == "Receivable":
|
||||||
self.common_filter_conditions.append(ple.account_type == "Receivable")
|
self.common_filter_conditions.append(ple.account_type == "Receivable")
|
||||||
@ -140,19 +156,10 @@ class PaymentReconciliation(Document):
|
|||||||
self.common_filter_conditions.append(ple.account_type == "Payable")
|
self.common_filter_conditions.append(ple.account_type == "Payable")
|
||||||
self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
|
self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
|
||||||
|
|
||||||
# get return invoices
|
self.get_return_invoices()
|
||||||
doc = qb.DocType(voucher_type)
|
return_invoices = [
|
||||||
return_invoices = (
|
x for x in self.return_invoices if x.return_against == None or x.return_against == ""
|
||||||
qb.from_(doc)
|
]
|
||||||
.select(ConstantColumn(voucher_type).as_("voucher_type"), doc.name.as_("voucher_no"))
|
|
||||||
.where(
|
|
||||||
(doc.docstatus == 1)
|
|
||||||
& (doc[frappe.scrub(self.party_type)] == self.party)
|
|
||||||
& (doc.is_return == 1)
|
|
||||||
& (IfNull(doc.return_against, "") == "")
|
|
||||||
)
|
|
||||||
.run(as_dict=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
outstanding_dr_or_cr = []
|
outstanding_dr_or_cr = []
|
||||||
if return_invoices:
|
if return_invoices:
|
||||||
@ -204,6 +211,9 @@ class PaymentReconciliation(Document):
|
|||||||
accounting_dimensions=self.accounting_dimension_filter_conditions,
|
accounting_dimensions=self.accounting_dimension_filter_conditions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cr_dr_notes = [x.voucher_no for x in self.return_invoices]
|
||||||
|
non_reconciled_invoices = [x for x in non_reconciled_invoices if x.voucher_no not in cr_dr_notes]
|
||||||
|
|
||||||
if self.invoice_limit:
|
if self.invoice_limit:
|
||||||
non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]
|
non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
voucher_type="Period Closing Voucher",
|
voucher_type="Period Closing Voucher",
|
||||||
voucher_no=self.name,
|
voucher_no=self.name,
|
||||||
queue="long",
|
queue="long",
|
||||||
|
enqueue_after_commit=True,
|
||||||
)
|
)
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
|
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
|
||||||
|
@ -442,6 +442,7 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1554,11 +1555,10 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-30 03:49:50.455199",
|
"modified": "2023-06-03 16:23:41.083409",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
"name_case": "Title Case",
|
|
||||||
"naming_rule": "By \"Naming Series\" field",
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
@ -158,7 +158,7 @@ def get_customers_based_on_territory_or_customer_group(customer_collection, coll
|
|||||||
return frappe.get_list(
|
return frappe.get_list(
|
||||||
"Customer",
|
"Customer",
|
||||||
fields=["name", "customer_name", "email_id"],
|
fields=["name", "customer_name", "email_id"],
|
||||||
filters=[[fields_dict[customer_collection], "IN", selected]],
|
filters=[["disabled", "=", 0], [fields_dict[customer_collection], "IN", selected]],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -443,12 +443,14 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "contact_email",
|
"fieldname": "contact_email",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
|
"options": "Email",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1364,12 +1366,12 @@
|
|||||||
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
|
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
|
||||||
"fieldname": "set_from_warehouse",
|
"fieldname": "set_from_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"label": "Set From Warehouse",
|
"label": "Set From Warehouse",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "50px",
|
"print_width": "50px",
|
||||||
"ignore_user_permissions": 1,
|
|
||||||
"width": "50px"
|
"width": "50px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1573,7 +1575,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-29 12:57:50.832598",
|
"modified": "2023-06-03 16:21:54.637245",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -520,6 +520,7 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2154,7 +2155,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-04-28 14:15:59.901154",
|
"modified": "2023-06-03 16:22:16.219333",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _, qb
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
from frappe.query_builder.functions import Abs, Sum
|
||||||
from frappe.utils import cint, getdate
|
from frappe.utils import cint, getdate
|
||||||
|
|
||||||
|
|
||||||
@ -346,26 +348,33 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
|||||||
def get_advance_vouchers(
|
def get_advance_vouchers(
|
||||||
parties, company=None, from_date=None, to_date=None, party_type="Supplier"
|
parties, company=None, from_date=None, to_date=None, party_type="Supplier"
|
||||||
):
|
):
|
||||||
# for advance vouchers, debit and credit is reversed
|
"""
|
||||||
dr_or_cr = "debit" if party_type == "Supplier" else "credit"
|
Use Payment Ledger to fetch unallocated Advance Payments
|
||||||
|
"""
|
||||||
|
|
||||||
filters = {
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
dr_or_cr: [">", 0],
|
|
||||||
"is_opening": "No",
|
|
||||||
"is_cancelled": 0,
|
|
||||||
"party_type": party_type,
|
|
||||||
"party": ["in", parties],
|
|
||||||
}
|
|
||||||
|
|
||||||
if party_type == "Customer":
|
conditions = []
|
||||||
filters.update({"against_voucher": ["is", "not set"]})
|
|
||||||
|
conditions.append(ple.amount.lt(0))
|
||||||
|
conditions.append(ple.delinked == 0)
|
||||||
|
conditions.append(ple.party_type == party_type)
|
||||||
|
conditions.append(ple.party.isin(parties))
|
||||||
|
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
||||||
|
|
||||||
if company:
|
if company:
|
||||||
filters["company"] = company
|
conditions.append(ple.company == company)
|
||||||
if from_date and to_date:
|
|
||||||
filters["posting_date"] = ["between", (from_date, to_date)]
|
|
||||||
|
|
||||||
return frappe.get_all("GL Entry", filters=filters, distinct=1, pluck="voucher_no") or [""]
|
if from_date and to_date:
|
||||||
|
conditions.append(ple.posting_date[from_date:to_date])
|
||||||
|
|
||||||
|
advances = (
|
||||||
|
qb.from_(ple).select(ple.voucher_no).distinct().where(Criterion.all(conditions)).run(as_list=1)
|
||||||
|
)
|
||||||
|
if advances:
|
||||||
|
advances = [x[0] for x in advances]
|
||||||
|
|
||||||
|
return advances
|
||||||
|
|
||||||
|
|
||||||
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
||||||
@ -499,6 +508,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
|||||||
|
|
||||||
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||||
tcs_amount = 0
|
tcs_amount = 0
|
||||||
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
|
|
||||||
# sum of debit entries made from sales invoices
|
# sum of debit entries made from sales invoices
|
||||||
invoiced_amt = (
|
invoiced_amt = (
|
||||||
@ -516,18 +526,20 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# sum of credit entries made from PE / JV with unset 'against voucher'
|
# sum of credit entries made from PE / JV with unset 'against voucher'
|
||||||
|
|
||||||
|
conditions = []
|
||||||
|
conditions.append(ple.amount.lt(0))
|
||||||
|
conditions.append(ple.delinked == 0)
|
||||||
|
conditions.append(ple.party.isin(parties))
|
||||||
|
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
||||||
|
conditions.append(ple.company == inv.company)
|
||||||
|
|
||||||
|
advances = (
|
||||||
|
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)
|
||||||
|
)
|
||||||
|
|
||||||
advance_amt = (
|
advance_amt = (
|
||||||
frappe.db.get_value(
|
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0
|
||||||
"GL Entry",
|
|
||||||
{
|
|
||||||
"is_cancelled": 0,
|
|
||||||
"party": ["in", parties],
|
|
||||||
"company": inv.company,
|
|
||||||
"voucher_no": ["in", adv_vouchers],
|
|
||||||
},
|
|
||||||
"sum(credit)",
|
|
||||||
)
|
|
||||||
or 0.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# sum of credit entries made from sales invoice
|
# sum of credit entries made from sales invoice
|
||||||
|
@ -152,6 +152,60 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
|||||||
for d in reversed(invoices):
|
for d in reversed(invoices):
|
||||||
d.cancel()
|
d.cancel()
|
||||||
|
|
||||||
|
def test_tcs_on_unallocated_advance_payments(self):
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"
|
||||||
|
)
|
||||||
|
|
||||||
|
vouchers = []
|
||||||
|
|
||||||
|
# create advance payment
|
||||||
|
pe = create_payment_entry(
|
||||||
|
payment_type="Receive", party_type="Customer", party="Test TCS Customer", paid_amount=20000
|
||||||
|
)
|
||||||
|
pe.paid_from = "Debtors - _TC"
|
||||||
|
pe.paid_to = "Cash - _TC"
|
||||||
|
pe.submit()
|
||||||
|
vouchers.append(pe)
|
||||||
|
|
||||||
|
# create invoice
|
||||||
|
si1 = create_sales_invoice(customer="Test TCS Customer", rate=5000)
|
||||||
|
si1.submit()
|
||||||
|
vouchers.append(si1)
|
||||||
|
|
||||||
|
# reconcile
|
||||||
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
|
pr.company = "_Test Company"
|
||||||
|
pr.party_type = "Customer"
|
||||||
|
pr.party = "Test TCS Customer"
|
||||||
|
pr.receivable_payable_account = "Debtors - _TC"
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
invoices = [x.as_dict() for x in pr.get("invoices")]
|
||||||
|
payments = [x.as_dict() for x in pr.get("payments")]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
# make another invoice
|
||||||
|
# sum of unallocated amount from payment entry and this sales invoice will breach cumulative threashold
|
||||||
|
# TDS should be calculated
|
||||||
|
si2 = create_sales_invoice(customer="Test TCS Customer", rate=15000)
|
||||||
|
si2.submit()
|
||||||
|
vouchers.append(si2)
|
||||||
|
|
||||||
|
si3 = create_sales_invoice(customer="Test TCS Customer", rate=10000)
|
||||||
|
si3.submit()
|
||||||
|
vouchers.append(si3)
|
||||||
|
|
||||||
|
# assert tax collection on total invoice amount created until now
|
||||||
|
tcs_charged = sum([d.base_tax_amount for d in si2.taxes if d.account_head == "TCS - _TC"])
|
||||||
|
tcs_charged += sum([d.base_tax_amount for d in si3.taxes if d.account_head == "TCS - _TC"])
|
||||||
|
self.assertEqual(tcs_charged, 1500)
|
||||||
|
|
||||||
|
# cancel invoice and payments to avoid clashing
|
||||||
|
for d in reversed(vouchers):
|
||||||
|
d.reload()
|
||||||
|
d.cancel()
|
||||||
|
|
||||||
def test_tds_calculation_on_net_total(self):
|
def test_tds_calculation_on_net_total(self):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
|
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint, scrub
|
from frappe import _, msgprint, scrub
|
||||||
from frappe.contacts.doctype.address.address import (
|
from frappe.contacts.doctype.address.address import (
|
||||||
@ -647,12 +649,12 @@ def set_taxes(
|
|||||||
else:
|
else:
|
||||||
args.update(get_party_details(party, party_type))
|
args.update(get_party_details(party, party_type))
|
||||||
|
|
||||||
if party_type in ("Customer", "Lead"):
|
if party_type in ("Customer", "Lead", "Prospect"):
|
||||||
args.update({"tax_type": "Sales"})
|
args.update({"tax_type": "Sales"})
|
||||||
|
|
||||||
if party_type == "Lead":
|
if party_type in ["Lead", "Prospect"]:
|
||||||
args["customer"] = None
|
args["customer"] = None
|
||||||
del args["lead"]
|
del args[frappe.scrub(party_type)]
|
||||||
else:
|
else:
|
||||||
args.update({"tax_type": "Purchase"})
|
args.update({"tax_type": "Purchase"})
|
||||||
|
|
||||||
@ -850,7 +852,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
|
|||||||
return company_wise_info
|
return company_wise_info
|
||||||
|
|
||||||
|
|
||||||
def get_party_shipping_address(doctype, name):
|
def get_party_shipping_address(doctype: str, name: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
|
Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
|
||||||
and/or `is_shipping_address = 1`.
|
and/or `is_shipping_address = 1`.
|
||||||
@ -861,22 +863,23 @@ def get_party_shipping_address(doctype, name):
|
|||||||
:param name: Party name
|
:param name: Party name
|
||||||
:return: String
|
:return: String
|
||||||
"""
|
"""
|
||||||
out = frappe.db.sql(
|
shipping_addresses = frappe.get_all(
|
||||||
"SELECT dl.parent "
|
"Address",
|
||||||
"from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name "
|
filters=[
|
||||||
"where "
|
["Dynamic Link", "link_doctype", "=", doctype],
|
||||||
"dl.link_doctype=%s "
|
["Dynamic Link", "link_name", "=", name],
|
||||||
"and dl.link_name=%s "
|
["disabled", "=", 0],
|
||||||
"and dl.parenttype='Address' "
|
],
|
||||||
"and ifnull(ta.disabled, 0) = 0 and"
|
or_filters=[
|
||||||
"(ta.address_type='Shipping' or ta.is_shipping_address=1) "
|
["is_shipping_address", "=", 1],
|
||||||
"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
|
["address_type", "=", "Shipping"],
|
||||||
(doctype, name),
|
],
|
||||||
|
pluck="name",
|
||||||
|
limit=1,
|
||||||
|
order_by="is_shipping_address DESC",
|
||||||
)
|
)
|
||||||
if out:
|
|
||||||
return out[0][0]
|
return shipping_addresses[0] if shipping_addresses else None
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def get_partywise_advanced_payment_amount(
|
def get_partywise_advanced_payment_amount(
|
||||||
@ -910,31 +913,32 @@ def get_partywise_advanced_payment_amount(
|
|||||||
return frappe._dict(data)
|
return frappe._dict(data)
|
||||||
|
|
||||||
|
|
||||||
def get_default_contact(doctype, name):
|
def get_default_contact(doctype: str, name: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Returns default contact for the given doctype and name.
|
Returns contact name only if there is a primary contact for given doctype and name.
|
||||||
Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
|
|
||||||
|
Else returns None
|
||||||
|
|
||||||
|
:param doctype: Party Doctype
|
||||||
|
:param name: Party name
|
||||||
|
:return: String
|
||||||
"""
|
"""
|
||||||
out = frappe.db.sql(
|
contacts = frappe.get_all(
|
||||||
"""
|
"Contact",
|
||||||
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
|
filters=[
|
||||||
FROM `tabDynamic Link` dl
|
["Dynamic Link", "link_doctype", "=", doctype],
|
||||||
INNER JOIN `tabContact` c ON c.name = dl.parent
|
["Dynamic Link", "link_name", "=", name],
|
||||||
WHERE
|
],
|
||||||
dl.link_doctype=%s AND
|
or_filters=[
|
||||||
dl.link_name=%s AND
|
["is_primary_contact", "=", 1],
|
||||||
dl.parenttype = 'Contact'
|
["is_billing_contact", "=", 1],
|
||||||
ORDER BY is_primary_contact DESC, is_billing_contact DESC
|
],
|
||||||
""",
|
pluck="name",
|
||||||
(doctype, name),
|
limit=1,
|
||||||
|
order_by="is_primary_contact DESC, is_billing_contact DESC",
|
||||||
)
|
)
|
||||||
if out:
|
|
||||||
try:
|
return contacts[0] if contacts else None
|
||||||
return out[0][0]
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def add_party_account(party_type, party, company, account):
|
def add_party_account(party_type, party, company, account):
|
||||||
|
@ -181,6 +181,16 @@ class ReceivablePayableReport(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||||
|
|
||||||
|
# If payment is made against credit note
|
||||||
|
# and credit note is made against a Sales Invoice
|
||||||
|
# then consider the payment against original sales invoice.
|
||||||
|
if ple.against_voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
||||||
|
if ple.against_voucher_no in self.return_entries:
|
||||||
|
return_against = self.return_entries.get(ple.against_voucher_no)
|
||||||
|
if return_against:
|
||||||
|
key = (ple.against_voucher_type, return_against, ple.party)
|
||||||
|
|
||||||
row = self.voucher_balance.get(key)
|
row = self.voucher_balance.get(key)
|
||||||
|
|
||||||
if not row:
|
if not row:
|
||||||
@ -610,7 +620,7 @@ class ReceivablePayableReport(object):
|
|||||||
|
|
||||||
def get_return_entries(self):
|
def get_return_entries(self):
|
||||||
doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
|
doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
|
||||||
filters = {"is_return": 1, "docstatus": 1}
|
filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company}
|
||||||
party_field = scrub(self.filters.party_type)
|
party_field = scrub(self.filters.party_type)
|
||||||
if self.filters.get(party_field):
|
if self.filters.get(party_field):
|
||||||
filters.update({party_field: self.filters.get(party_field)})
|
filters.update({party_field: self.filters.get(party_field)})
|
||||||
|
@ -210,6 +210,67 @@ class TestAccountsReceivable(FrappeTestCase):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_payment_against_credit_note(self):
|
||||||
|
"""
|
||||||
|
Payment against credit/debit note should be considered against the parent invoice
|
||||||
|
"""
|
||||||
|
company = "_Test Company 2"
|
||||||
|
customer = "_Test Customer 2"
|
||||||
|
|
||||||
|
si1 = make_sales_invoice()
|
||||||
|
|
||||||
|
pe = get_payment_entry("Sales Invoice", si1.name, bank_account="Cash - _TC2")
|
||||||
|
pe.paid_from = "Debtors - _TC2"
|
||||||
|
pe.insert()
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
cr_note = make_credit_note(si1.name)
|
||||||
|
|
||||||
|
si2 = make_sales_invoice()
|
||||||
|
|
||||||
|
# manually link cr_note with si2 using journal entry
|
||||||
|
je = frappe.new_doc("Journal Entry")
|
||||||
|
je.company = company
|
||||||
|
je.voucher_type = "Credit Note"
|
||||||
|
je.posting_date = today()
|
||||||
|
|
||||||
|
debit_account = "Debtors - _TC2"
|
||||||
|
debit_entry = {
|
||||||
|
"account": debit_account,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": customer,
|
||||||
|
"debit": 100,
|
||||||
|
"debit_in_account_currency": 100,
|
||||||
|
"reference_type": cr_note.doctype,
|
||||||
|
"reference_name": cr_note.name,
|
||||||
|
"cost_center": "Main - _TC2",
|
||||||
|
}
|
||||||
|
credit_entry = {
|
||||||
|
"account": debit_account,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": customer,
|
||||||
|
"credit": 100,
|
||||||
|
"credit_in_account_currency": 100,
|
||||||
|
"reference_type": si2.doctype,
|
||||||
|
"reference_name": si2.name,
|
||||||
|
"cost_center": "Main - _TC2",
|
||||||
|
}
|
||||||
|
|
||||||
|
je.append("accounts", debit_entry)
|
||||||
|
je.append("accounts", credit_entry)
|
||||||
|
je = je.save().submit()
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
"company": company,
|
||||||
|
"report_date": today(),
|
||||||
|
"range1": 30,
|
||||||
|
"range2": 60,
|
||||||
|
"range3": 90,
|
||||||
|
"range4": 120,
|
||||||
|
}
|
||||||
|
report = execute(filters)
|
||||||
|
self.assertEqual(report[1], [])
|
||||||
|
|
||||||
|
|
||||||
def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
|
def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
@ -256,7 +317,7 @@ def make_payment(docname):
|
|||||||
|
|
||||||
|
|
||||||
def make_credit_note(docname):
|
def make_credit_note(docname):
|
||||||
create_sales_invoice(
|
credit_note = create_sales_invoice(
|
||||||
company="_Test Company 2",
|
company="_Test Company 2",
|
||||||
customer="_Test Customer 2",
|
customer="_Test Customer 2",
|
||||||
currency="EUR",
|
currency="EUR",
|
||||||
@ -269,3 +330,5 @@ def make_credit_note(docname):
|
|||||||
is_return=1,
|
is_return=1,
|
||||||
return_against=docname,
|
return_against=docname,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return credit_note
|
||||||
|
@ -399,8 +399,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
|||||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||||
`tabSales Invoice`.unrealized_profit_loss_account,
|
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||||
`tabSales Invoice`.is_internal_customer,
|
`tabSales Invoice`.is_internal_customer,
|
||||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
`tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||||
|
`tabSales Invoice Item`.project,
|
||||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||||
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
|
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
|
||||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
||||||
|
@ -812,14 +812,14 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
number_of_depreciations_booked=1,
|
number_of_depreciations_booked=1,
|
||||||
opening_accumulated_depreciation=50000,
|
opening_accumulated_depreciation=50000,
|
||||||
expected_value_after_useful_life=10000,
|
expected_value_after_useful_life=10000,
|
||||||
depreciation_start_date="2030-12-31",
|
depreciation_start_date="2031-12-31",
|
||||||
total_number_of_depreciations=3,
|
total_number_of_depreciations=3,
|
||||||
frequency_of_depreciation=12,
|
frequency_of_depreciation=12,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(asset.status, "Draft")
|
self.assertEqual(asset.status, "Draft")
|
||||||
|
|
||||||
expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]]
|
expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]]
|
||||||
|
|
||||||
schedules = [
|
schedules = [
|
||||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||||
|
@ -10,6 +10,7 @@ from frappe.utils import (
|
|||||||
cint,
|
cint,
|
||||||
date_diff,
|
date_diff,
|
||||||
flt,
|
flt,
|
||||||
|
get_first_day,
|
||||||
get_last_day,
|
get_last_day,
|
||||||
getdate,
|
getdate,
|
||||||
is_last_day_of_the_month,
|
is_last_day_of_the_month,
|
||||||
@ -271,8 +272,14 @@ class AssetDepreciationSchedule(Document):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# For first row
|
# For first row
|
||||||
if n == 0 and has_pro_rata and not self.opening_accumulated_depreciation:
|
if (
|
||||||
from_date = add_days(asset_doc.available_for_use_date, -1)
|
n == 0
|
||||||
|
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||||
|
and not self.opening_accumulated_depreciation
|
||||||
|
):
|
||||||
|
from_date = add_days(
|
||||||
|
asset_doc.available_for_use_date, -1
|
||||||
|
) # needed to calc depr amount for available_for_use_date too
|
||||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||||
row,
|
row,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
@ -281,10 +288,18 @@ class AssetDepreciationSchedule(Document):
|
|||||||
has_wdv_or_dd_non_yearly_pro_rata,
|
has_wdv_or_dd_non_yearly_pro_rata,
|
||||||
)
|
)
|
||||||
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
|
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
|
||||||
from_date = add_months(
|
if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||||
getdate(asset_doc.available_for_use_date),
|
from_date = get_last_day(
|
||||||
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
|
add_months(
|
||||||
)
|
getdate(asset_doc.available_for_use_date),
|
||||||
|
((self.number_of_depreciations_booked - 1) * row.frequency_of_depreciation),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from_date = add_months(
|
||||||
|
getdate(add_days(asset_doc.available_for_use_date, -1)),
|
||||||
|
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
|
||||||
|
)
|
||||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||||
row,
|
row,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
@ -702,3 +717,9 @@ def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
|
|||||||
["status", "=", status],
|
["status", "=", status],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_first_day_of_the_month(date):
|
||||||
|
first_day_of_the_month = get_first_day(date)
|
||||||
|
|
||||||
|
return getdate(first_day_of_the_month) == getdate(date)
|
||||||
|
@ -322,6 +322,7 @@
|
|||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Customer Mobile No",
|
"label": "Customer Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -368,6 +369,7 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Contact Mobile No",
|
"label": "Contact Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1271,7 +1273,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-24 11:16:41.195340",
|
"modified": "2023-06-03 16:19:45.710444",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -230,6 +230,7 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -844,7 +845,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-14 16:43:41.714832",
|
"modified": "2023-06-03 16:20:15.880114",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation",
|
"name": "Supplier Quotation",
|
||||||
|
@ -43,7 +43,6 @@ class SellingController(StockController):
|
|||||||
self.set_serial_and_batch_bundle(table_field)
|
self.set_serial_and_batch_bundle(table_field)
|
||||||
|
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
|
|
||||||
super(SellingController, self).set_missing_values(for_validate)
|
super(SellingController, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
# set contact and address details for customer, if they are not mentioned
|
# set contact and address details for customer, if they are not mentioned
|
||||||
@ -62,7 +61,7 @@ class SellingController(StockController):
|
|||||||
elif self.doctype == "Quotation" and self.party_name:
|
elif self.doctype == "Quotation" and self.party_name:
|
||||||
if self.quotation_to == "Customer":
|
if self.quotation_to == "Customer":
|
||||||
customer = self.party_name
|
customer = self.party_name
|
||||||
else:
|
elif self.quotation_to == "Lead":
|
||||||
lead = self.party_name
|
lead = self.party_name
|
||||||
|
|
||||||
if customer:
|
if customer:
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
from frappe.contacts.address_and_contact import (
|
||||||
|
delete_contact_and_address,
|
||||||
|
load_address_and_contact,
|
||||||
|
)
|
||||||
from frappe.email.inbox import link_communication_to_document
|
from frappe.email.inbox import link_communication_to_document
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
|
from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
|
||||||
@ -40,9 +43,8 @@ class Lead(SellingController, CRMNote):
|
|||||||
self.update_prospect()
|
self.update_prospect()
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
|
frappe.db.set_value("Issue", {"lead": self.name}, "lead", None)
|
||||||
|
delete_contact_and_address(self.doctype, self.name)
|
||||||
self.unlink_dynamic_links()
|
|
||||||
self.remove_link_from_prospect()
|
self.remove_link_from_prospect()
|
||||||
|
|
||||||
def set_full_name(self):
|
def set_full_name(self):
|
||||||
@ -119,27 +121,6 @@ class Lead(SellingController, CRMNote):
|
|||||||
)
|
)
|
||||||
lead_row.db_update()
|
lead_row.db_update()
|
||||||
|
|
||||||
def unlink_dynamic_links(self):
|
|
||||||
links = frappe.get_all(
|
|
||||||
"Dynamic Link",
|
|
||||||
filters={"link_doctype": self.doctype, "link_name": self.name},
|
|
||||||
fields=["parent", "parenttype"],
|
|
||||||
)
|
|
||||||
|
|
||||||
for link in links:
|
|
||||||
linked_doc = frappe.get_doc(link["parenttype"], link["parent"])
|
|
||||||
|
|
||||||
if len(linked_doc.get("links")) == 1:
|
|
||||||
linked_doc.delete(ignore_permissions=True)
|
|
||||||
else:
|
|
||||||
to_remove = None
|
|
||||||
for d in linked_doc.get("links"):
|
|
||||||
if d.link_doctype == self.doctype and d.link_name == self.name:
|
|
||||||
to_remove = d
|
|
||||||
if to_remove:
|
|
||||||
linked_doc.remove(to_remove)
|
|
||||||
linked_doc.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
def remove_link_from_prospect(self):
|
def remove_link_from_prospect(self):
|
||||||
prospects = self.get_linked_prospects()
|
prospects = self.get_linked_prospects()
|
||||||
|
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
from frappe.contacts.address_and_contact import (
|
||||||
|
delete_contact_and_address,
|
||||||
|
load_address_and_contact,
|
||||||
|
)
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
|
from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
|
||||||
@ -16,7 +19,7 @@ class Prospect(CRMNote):
|
|||||||
self.link_with_lead_contact_and_address()
|
self.link_with_lead_contact_and_address()
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
self.unlink_dynamic_links()
|
delete_contact_and_address(self.doctype, self.name)
|
||||||
|
|
||||||
def after_insert(self):
|
def after_insert(self):
|
||||||
carry_forward_communication_and_comments = frappe.db.get_single_value(
|
carry_forward_communication_and_comments = frappe.db.get_single_value(
|
||||||
@ -54,27 +57,6 @@ class Prospect(CRMNote):
|
|||||||
linked_doc.append("links", {"link_doctype": self.doctype, "link_name": self.name})
|
linked_doc.append("links", {"link_doctype": self.doctype, "link_name": self.name})
|
||||||
linked_doc.save(ignore_permissions=True)
|
linked_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
def unlink_dynamic_links(self):
|
|
||||||
links = frappe.get_all(
|
|
||||||
"Dynamic Link",
|
|
||||||
filters={"link_doctype": self.doctype, "link_name": self.name},
|
|
||||||
fields=["parent", "parenttype"],
|
|
||||||
)
|
|
||||||
|
|
||||||
for link in links:
|
|
||||||
linked_doc = frappe.get_doc(link["parenttype"], link["parent"])
|
|
||||||
|
|
||||||
if len(linked_doc.get("links")) == 1:
|
|
||||||
linked_doc.delete(ignore_permissions=True)
|
|
||||||
else:
|
|
||||||
to_remove = None
|
|
||||||
for d in linked_doc.get("links"):
|
|
||||||
if d.link_doctype == self.doctype and d.link_name == self.name:
|
|
||||||
to_remove = d
|
|
||||||
if to_remove:
|
|
||||||
linked_doc.remove(to_remove)
|
|
||||||
linked_doc.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_customer(source_name, target_doc=None):
|
def make_customer(source_name, target_doc=None):
|
||||||
|
@ -78,9 +78,10 @@ erpnext.ProductList = class {
|
|||||||
let title_html = `<div style="display: flex; margin-left: -15px;">`;
|
let title_html = `<div style="display: flex; margin-left: -15px;">`;
|
||||||
title_html += `
|
title_html += `
|
||||||
<div class="col-8" style="margin-right: -15px;">
|
<div class="col-8" style="margin-right: -15px;">
|
||||||
<a class="" href="/${ item.route || '#' }"
|
<a href="/${ item.route || '#' }">
|
||||||
style="color: var(--gray-800); font-weight: 500;">
|
<div class="product-title">
|
||||||
${ title }
|
${ title }
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -201,4 +202,4 @@ erpnext.ProductList = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -160,4 +160,3 @@ class TestLoanDisbursement(unittest.TestCase):
|
|||||||
interest = per_day_interest * 15
|
interest = per_day_interest * 15
|
||||||
|
|
||||||
self.assertEqual(amounts["pending_principal_amount"], 1500000)
|
self.assertEqual(amounts["pending_principal_amount"], 1500000)
|
||||||
self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2))
|
|
||||||
|
@ -22,7 +22,7 @@ class LoanInterestAccrual(AccountsController):
|
|||||||
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
|
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
|
||||||
|
|
||||||
if not self.last_accrual_date:
|
if not self.last_accrual_date:
|
||||||
self.last_accrual_date = get_last_accrual_date(self.loan)
|
self.last_accrual_date = get_last_accrual_date(self.loan, self.posting_date)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
@ -274,14 +274,14 @@ def make_loan_interest_accrual_entry(args):
|
|||||||
|
|
||||||
|
|
||||||
def get_no_of_days_for_interest_accural(loan, posting_date):
|
def get_no_of_days_for_interest_accural(loan, posting_date):
|
||||||
last_interest_accrual_date = get_last_accrual_date(loan.name)
|
last_interest_accrual_date = get_last_accrual_date(loan.name, posting_date)
|
||||||
|
|
||||||
no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1
|
no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1
|
||||||
|
|
||||||
return no_of_days
|
return no_of_days
|
||||||
|
|
||||||
|
|
||||||
def get_last_accrual_date(loan):
|
def get_last_accrual_date(loan, posting_date):
|
||||||
last_posting_date = frappe.db.sql(
|
last_posting_date = frappe.db.sql(
|
||||||
""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
|
""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
|
||||||
WHERE loan = %s and docstatus = 1""",
|
WHERE loan = %s and docstatus = 1""",
|
||||||
@ -289,12 +289,30 @@ def get_last_accrual_date(loan):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if last_posting_date[0][0]:
|
if last_posting_date[0][0]:
|
||||||
|
last_interest_accrual_date = last_posting_date[0][0]
|
||||||
# interest for last interest accrual date is already booked, so add 1 day
|
# interest for last interest accrual date is already booked, so add 1 day
|
||||||
return add_days(last_posting_date[0][0], 1)
|
last_disbursement_date = get_last_disbursement_date(loan, posting_date)
|
||||||
|
|
||||||
|
if last_disbursement_date and getdate(last_disbursement_date) > getdate(
|
||||||
|
last_interest_accrual_date
|
||||||
|
):
|
||||||
|
last_interest_accrual_date = last_disbursement_date
|
||||||
|
|
||||||
|
return add_days(last_interest_accrual_date, 1)
|
||||||
else:
|
else:
|
||||||
return frappe.db.get_value("Loan", loan, "disbursement_date")
|
return frappe.db.get_value("Loan", loan, "disbursement_date")
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_disbursement_date(loan, posting_date):
|
||||||
|
last_disbursement_date = frappe.db.get_value(
|
||||||
|
"Loan Disbursement",
|
||||||
|
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
|
||||||
|
"MAX(posting_date)",
|
||||||
|
)
|
||||||
|
|
||||||
|
return last_disbursement_date
|
||||||
|
|
||||||
|
|
||||||
def days_in_year(year):
|
def days_in_year(year):
|
||||||
days = 365
|
days = 365
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ class LoanRepayment(AccountsController):
|
|||||||
if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision):
|
if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision):
|
||||||
if not self.is_term_loan:
|
if not self.is_term_loan:
|
||||||
# get last loan interest accrual date
|
# get last loan interest accrual date
|
||||||
last_accrual_date = get_last_accrual_date(self.against_loan)
|
last_accrual_date = get_last_accrual_date(self.against_loan, self.posting_date)
|
||||||
|
|
||||||
# get posting date upto which interest has to be accrued
|
# get posting date upto which interest has to be accrued
|
||||||
per_day_interest = get_per_day_interest(
|
per_day_interest = get_per_day_interest(
|
||||||
@ -725,7 +725,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
|||||||
if due_date:
|
if due_date:
|
||||||
pending_days = date_diff(posting_date, due_date) + 1
|
pending_days = date_diff(posting_date, due_date) + 1
|
||||||
else:
|
else:
|
||||||
last_accrual_date = get_last_accrual_date(against_loan_doc.name)
|
last_accrual_date = get_last_accrual_date(against_loan_doc.name, posting_date)
|
||||||
pending_days = date_diff(posting_date, last_accrual_date) + 1
|
pending_days = date_diff(posting_date, last_accrual_date) + 1
|
||||||
|
|
||||||
if pending_days > 0:
|
if pending_days > 0:
|
||||||
|
@ -152,6 +152,7 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -160,6 +161,7 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
|
"options": "Email",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -236,10 +238,11 @@
|
|||||||
"link_fieldname": "maintenance_schedule"
|
"link_fieldname": "maintenance_schedule"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-05-27 16:05:10.746465",
|
"modified": "2023-06-03 16:15:43.958072",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Maintenance",
|
"module": "Maintenance",
|
||||||
"name": "Maintenance Schedule",
|
"name": "Maintenance Schedule",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -260,5 +263,6 @@
|
|||||||
"search_fields": "status,customer,customer_name",
|
"search_fields": "status,customer,customer_name",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"timeline_field": "customer"
|
"timeline_field": "customer"
|
||||||
}
|
}
|
@ -101,6 +101,7 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -108,6 +109,7 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Contact Email",
|
"label": "Contact Email",
|
||||||
|
"options": "Email",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -293,7 +295,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-17 03:10:27.608112",
|
"modified": "2023-06-03 16:19:07.902723",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Maintenance",
|
"module": "Maintenance",
|
||||||
"name": "Maintenance Visit",
|
"name": "Maintenance Visit",
|
||||||
@ -319,6 +321,7 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"timeline_field": "customer",
|
"timeline_field": "customer",
|
||||||
"title_field": "customer_name"
|
"title_field": "customer_name"
|
||||||
}
|
}
|
@ -88,12 +88,14 @@ class BOMUpdateLog(Document):
|
|||||||
boms=boms,
|
boms=boms,
|
||||||
timeout=40000,
|
timeout=40000,
|
||||||
now=frappe.flags.in_test,
|
now=frappe.flags.in_test,
|
||||||
|
enqueue_after_commit=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
|
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
|
||||||
update_doc=self,
|
update_doc=self,
|
||||||
now=frappe.flags.in_test,
|
now=frappe.flags.in_test,
|
||||||
|
enqueue_after_commit=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -304,6 +304,7 @@ def set_tasks_as_overdue():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_timesheet(source_name, target_doc=None, ignore_permissions=False):
|
def make_timesheet(source_name, target_doc=None, ignore_permissions=False):
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
|
target.parent_project = source.project
|
||||||
target.append(
|
target.append(
|
||||||
"time_logs",
|
"time_logs",
|
||||||
{
|
{
|
||||||
|
@ -40,8 +40,8 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
|||||||
name: __("Date"),
|
name: __("Date"),
|
||||||
editable: false,
|
editable: false,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
format: frappe.form.formatters.Date,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: __("Party Type"),
|
name: __("Party Type"),
|
||||||
editable: false,
|
editable: false,
|
||||||
@ -117,17 +117,13 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
|||||||
return [
|
return [
|
||||||
row["date"],
|
row["date"],
|
||||||
row["party_type"],
|
row["party_type"],
|
||||||
row["party"],
|
frappe.form.formatters.Link(row["party"], {options: row["party_type"]}),
|
||||||
row["description"],
|
row["description"],
|
||||||
row["deposit"],
|
row["deposit"],
|
||||||
row["withdrawal"],
|
row["withdrawal"],
|
||||||
row["unallocated_amount"],
|
row["unallocated_amount"],
|
||||||
row["reference_number"],
|
row["reference_number"],
|
||||||
`
|
`<button class="btn btn-primary btn-xs center" data-name="${row["name"]}">${__("Actions")}</button>`
|
||||||
<Button class="btn btn-primary btn-xs center" data-name = ${row["name"]} >
|
|
||||||
${__("Actions")}
|
|
||||||
</a>
|
|
||||||
`,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,30 +76,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
|||||||
callback: (result) => {
|
callback: (result) => {
|
||||||
const data = result.message;
|
const data = result.message;
|
||||||
|
|
||||||
|
|
||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper;
|
const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper;
|
||||||
proposals_wrapper.show();
|
proposals_wrapper.show();
|
||||||
this.dialog.fields_dict.no_matching_vouchers.$wrapper.hide();
|
this.dialog.fields_dict.no_matching_vouchers.$wrapper.hide();
|
||||||
this.data = [];
|
this.data = data.map((row) => this.format_row(row));
|
||||||
data.forEach((row) => {
|
|
||||||
const reference_date = row[5] ? row[5] : row[8];
|
|
||||||
this.data.push([
|
|
||||||
row[1],
|
|
||||||
row[2],
|
|
||||||
reference_date,
|
|
||||||
format_currency(row[3], row[9]),
|
|
||||||
row[4],
|
|
||||||
row[6],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
this.get_dt_columns();
|
this.get_dt_columns();
|
||||||
this.get_datatable(proposals_wrapper);
|
this.get_datatable(proposals_wrapper);
|
||||||
} else {
|
} else {
|
||||||
const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper;
|
const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper;
|
||||||
proposals_wrapper.hide();
|
proposals_wrapper.hide();
|
||||||
this.dialog.fields_dict.no_matching_vouchers.$wrapper.show();
|
this.dialog.fields_dict.no_matching_vouchers.$wrapper.show();
|
||||||
|
|
||||||
}
|
}
|
||||||
this.dialog.show();
|
this.dialog.show();
|
||||||
},
|
},
|
||||||
@ -122,6 +109,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
|||||||
name: __("Reference Date"),
|
name: __("Reference Date"),
|
||||||
editable: false,
|
editable: false,
|
||||||
width: 120,
|
width: 120,
|
||||||
|
format: frappe.form.formatters.Date,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: __("Remaining"),
|
name: __("Remaining"),
|
||||||
@ -141,6 +129,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format_row(row) {
|
||||||
|
return [
|
||||||
|
row[1], // Document Type
|
||||||
|
frappe.form.formatters.Link(row[2], {options: row[1]}), // Document Name
|
||||||
|
row[5] || row[8], // Reference Date
|
||||||
|
format_currency(row[3], row[9]), // Remaining
|
||||||
|
row[4], // Reference Number
|
||||||
|
row[6], // Party
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
get_datatable(proposals_wrapper) {
|
get_datatable(proposals_wrapper) {
|
||||||
if (!this.datatable) {
|
if (!this.datatable) {
|
||||||
const datatable_options = {
|
const datatable_options = {
|
||||||
|
@ -805,11 +805,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.frm.doc.payments.find(pay => {
|
if(!this.frm.doc.is_return){
|
||||||
if (pay.default) {
|
this.frm.doc.payments.find(payment => {
|
||||||
pay.amount = total_amount_to_pay;
|
if (payment.default) {
|
||||||
}
|
payment.amount = total_amount_to_pay;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.frm.refresh_fields();
|
this.frm.refresh_fields();
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
|
|||||||
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
|
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
|
||||||
|
|
||||||
let party_type = "Customer";
|
let party_type = "Customer";
|
||||||
if (frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
|
if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) {
|
||||||
party_type = "Lead";
|
party_type = frm.doc.quotation_to;
|
||||||
}
|
}
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
|
@ -454,12 +454,12 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
|
|||||||
customer_outstanding += flt(extra_amount)
|
customer_outstanding += flt(extra_amount)
|
||||||
|
|
||||||
if credit_limit > 0 and flt(customer_outstanding) > credit_limit:
|
if credit_limit > 0 and flt(customer_outstanding) > credit_limit:
|
||||||
msgprint(
|
message = _("Credit limit has been crossed for customer {0} ({1}/{2})").format(
|
||||||
_("Credit limit has been crossed for customer {0} ({1}/{2})").format(
|
customer, customer_outstanding, credit_limit
|
||||||
customer, customer_outstanding, credit_limit
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
message += "<br><br>"
|
||||||
|
|
||||||
# If not authorized person raise exception
|
# If not authorized person raise exception
|
||||||
credit_controller_role = frappe.db.get_single_value("Accounts Settings", "credit_controller")
|
credit_controller_role = frappe.db.get_single_value("Accounts Settings", "credit_controller")
|
||||||
if not credit_controller_role or credit_controller_role not in frappe.get_roles():
|
if not credit_controller_role or credit_controller_role not in frappe.get_roles():
|
||||||
@ -480,7 +480,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
|
|||||||
"<li>".join(credit_controller_users_formatted)
|
"<li>".join(credit_controller_users_formatted)
|
||||||
)
|
)
|
||||||
|
|
||||||
message = _(
|
message += _(
|
||||||
"Please contact any of the following users to extend the credit limits for {0}: {1}"
|
"Please contact any of the following users to extend the credit limits for {0}: {1}"
|
||||||
).format(customer, user_list)
|
).format(customer, user_list)
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
|
|||||||
# prompt them to send out an email to the controller users
|
# prompt them to send out an email to the controller users
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
message,
|
message,
|
||||||
title="Notify",
|
title=_("Credit Limit Crossed"),
|
||||||
raise_exception=1,
|
raise_exception=1,
|
||||||
primary_action={
|
primary_action={
|
||||||
"label": "Send Email",
|
"label": "Send Email",
|
||||||
@ -519,7 +519,6 @@ def get_customer_outstanding(
|
|||||||
customer, company, ignore_outstanding_sales_order=False, cost_center=None
|
customer, company, ignore_outstanding_sales_order=False, cost_center=None
|
||||||
):
|
):
|
||||||
# Outstanding based on GL Entries
|
# Outstanding based on GL Entries
|
||||||
|
|
||||||
cond = ""
|
cond = ""
|
||||||
if cost_center:
|
if cost_center:
|
||||||
lft, rgt = frappe.get_cached_value("Cost Center", cost_center, ["lft", "rgt"])
|
lft, rgt = frappe.get_cached_value("Cost Center", cost_center, ["lft", "rgt"])
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ frappe.ui.form.on('Quotation', {
|
|||||||
frm.set_query("quotation_to", function() {
|
frm.set_query("quotation_to", function() {
|
||||||
return{
|
return{
|
||||||
"filters": {
|
"filters": {
|
||||||
"name": ["in", ["Customer", "Lead"]],
|
"name": ["in", ["Customer", "Lead", "Prospect"]],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -160,19 +160,16 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_dynamic_field_label(){
|
set_dynamic_field_label(){
|
||||||
if (this.frm.doc.quotation_to == "Customer")
|
if (this.frm.doc.quotation_to == "Customer") {
|
||||||
{
|
|
||||||
this.frm.set_df_property("party_name", "label", "Customer");
|
this.frm.set_df_property("party_name", "label", "Customer");
|
||||||
this.frm.fields_dict.party_name.get_query = null;
|
this.frm.fields_dict.party_name.get_query = null;
|
||||||
}
|
} else if (this.frm.doc.quotation_to == "Lead") {
|
||||||
|
|
||||||
if (this.frm.doc.quotation_to == "Lead")
|
|
||||||
{
|
|
||||||
this.frm.set_df_property("party_name", "label", "Lead");
|
this.frm.set_df_property("party_name", "label", "Lead");
|
||||||
|
|
||||||
this.frm.fields_dict.party_name.get_query = function() {
|
this.frm.fields_dict.party_name.get_query = function() {
|
||||||
return{ query: "erpnext.controllers.queries.lead_query" }
|
return{ query: "erpnext.controllers.queries.lead_query" }
|
||||||
}
|
}
|
||||||
|
} else if (this.frm.doc.quotation_to == "Prospect") {
|
||||||
|
this.frm.set_df_property("party_name", "label", "Prospect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +291,7 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1072,7 +1073,7 @@
|
|||||||
"idx": 82,
|
"idx": 82,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-14 16:50:44.550098",
|
"modified": "2023-06-03 16:21:04.980033",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation",
|
"name": "Quotation",
|
||||||
|
@ -398,6 +398,7 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1475,6 +1476,7 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Phone",
|
"label": "Phone",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1643,7 +1645,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-22 09:55:37.008190",
|
"modified": "2023-06-03 16:16:23.411247",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order",
|
"name": "Sales Order",
|
||||||
|
@ -230,6 +230,7 @@ class SalesOrder(SellingController):
|
|||||||
frappe.throw(_("Quotation {0} is cancelled").format(quotation))
|
frappe.throw(_("Quotation {0} is cancelled").format(quotation))
|
||||||
|
|
||||||
doc.set_status(update=True)
|
doc.set_status(update=True)
|
||||||
|
doc.update_opportunity("Converted" if flag == "submit" else "Quotation")
|
||||||
|
|
||||||
def validate_drop_ship(self):
|
def validate_drop_ship(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
@ -1772,7 +1772,14 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
self.assertEqual(pe.references[1].reference_name, so.name)
|
self.assertEqual(pe.references[1].reference_name, so.name)
|
||||||
self.assertEqual(pe.references[1].allocated_amount, 300)
|
self.assertEqual(pe.references[1].allocated_amount, 300)
|
||||||
|
|
||||||
@change_settings("Stock Settings", {"enable_stock_reservation": 1})
|
@change_settings(
|
||||||
|
"Stock Settings",
|
||||||
|
{
|
||||||
|
"enable_stock_reservation": 1,
|
||||||
|
"auto_create_serial_and_batch_bundle_for_outward": 1,
|
||||||
|
"pick_serial_and_batch_based_on": "FIFO",
|
||||||
|
},
|
||||||
|
)
|
||||||
def test_stock_reservation_against_sales_order(self) -> None:
|
def test_stock_reservation_against_sales_order(self) -> None:
|
||||||
from random import randint, uniform
|
from random import randint, uniform
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ def after_install():
|
|||||||
create_default_success_action()
|
create_default_success_action()
|
||||||
create_default_energy_point_rules()
|
create_default_energy_point_rules()
|
||||||
create_incoterms()
|
create_incoterms()
|
||||||
|
create_default_role_profiles()
|
||||||
add_company_to_session_defaults()
|
add_company_to_session_defaults()
|
||||||
add_standard_navbar_items()
|
add_standard_navbar_items()
|
||||||
add_app_name()
|
add_app_name()
|
||||||
@ -202,3 +203,42 @@ def setup_log_settings():
|
|||||||
def hide_workspaces():
|
def hide_workspaces():
|
||||||
for ws in ["Integration", "Settings"]:
|
for ws in ["Integration", "Settings"]:
|
||||||
frappe.db.set_value("Workspace", ws, "public", 0)
|
frappe.db.set_value("Workspace", ws, "public", 0)
|
||||||
|
|
||||||
|
|
||||||
|
def create_default_role_profiles():
|
||||||
|
for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items():
|
||||||
|
role_profile = frappe.new_doc("Role Profile")
|
||||||
|
role_profile.role_profile = role_profile_name
|
||||||
|
for role in roles:
|
||||||
|
role_profile.append("roles", {"role": role})
|
||||||
|
|
||||||
|
role_profile.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ROLE_PROFILES = {
|
||||||
|
"Inventory": [
|
||||||
|
"Stock User",
|
||||||
|
"Stock Manager",
|
||||||
|
"Item Manager",
|
||||||
|
],
|
||||||
|
"Manufacturing": [
|
||||||
|
"Stock User",
|
||||||
|
"Manufacturing User",
|
||||||
|
"Manufacturing Manager",
|
||||||
|
],
|
||||||
|
"Accounts": [
|
||||||
|
"Accounts User",
|
||||||
|
"Accounts Manager",
|
||||||
|
],
|
||||||
|
"Sales": [
|
||||||
|
"Sales User",
|
||||||
|
"Stock User",
|
||||||
|
"Sales Manager",
|
||||||
|
],
|
||||||
|
"Purchase": [
|
||||||
|
"Item Manager",
|
||||||
|
"Stock User",
|
||||||
|
"Purchase User",
|
||||||
|
"Purchase Manager",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
@ -51,7 +51,7 @@ class ClosingStockBalance(Document):
|
|||||||
|
|
||||||
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
|
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
|
||||||
if self.get(fieldname):
|
if self.get(fieldname):
|
||||||
query = query.where(table.get(fieldname) == self.get(fieldname))
|
query = query.where(table[fieldname] == self.get(fieldname))
|
||||||
|
|
||||||
query = query.run(as_dict=True)
|
query = query.run(as_dict=True)
|
||||||
|
|
||||||
|
@ -374,6 +374,7 @@
|
|||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1398,7 +1399,7 @@
|
|||||||
"idx": 146,
|
"idx": 146,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-21 11:15:23.931084",
|
"modified": "2023-06-03 16:13:25.011487",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note",
|
"name": "Delivery Note",
|
||||||
@ -1468,4 +1469,4 @@
|
|||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
}
|
}
|
||||||
|
@ -772,12 +772,6 @@ $.extend(erpnext.item, {
|
|||||||
if (modal) {
|
if (modal) {
|
||||||
$(modal).removeClass("modal-dialog-scrollable");
|
$(modal).removeClass("modal-dialog-scrollable");
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.on("awesomplete-close", () => {
|
|
||||||
let modal = field.$input.parents('.modal-dialog')[0];
|
|
||||||
if (modal) {
|
|
||||||
$(modal).addClass("modal-dialog-scrollable");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -714,6 +714,7 @@ class Item(Document):
|
|||||||
template=self,
|
template=self,
|
||||||
now=frappe.flags.in_test,
|
now=frappe.flags.in_test,
|
||||||
timeout=600,
|
timeout=600,
|
||||||
|
enqueue_after_commit=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_has_variants(self):
|
def validate_has_variants(self):
|
||||||
|
@ -326,6 +326,7 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1239,7 +1240,7 @@
|
|||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-07 20:18:25.458185",
|
"modified": "2023-06-03 16:23:20.781368",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
@ -127,6 +127,14 @@ frappe.ui.form.on('Serial and Batch Bundle', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
toggle_fields(frm) {
|
toggle_fields(frm) {
|
||||||
|
if (frm.doc.has_serial_no) {
|
||||||
|
frm.doc.entries.forEach(row => {
|
||||||
|
if (Math.abs(row.qty) !== 1) {
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "qty", 1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
frm.fields_dict.entries.grid.update_docfield_property(
|
frm.fields_dict.entries.grid.update_docfield_property(
|
||||||
'serial_no', 'read_only', !frm.doc.has_serial_no
|
'serial_no', 'read_only', !frm.doc.has_serial_no
|
||||||
);
|
);
|
||||||
@ -134,6 +142,10 @@ frappe.ui.form.on('Serial and Batch Bundle', {
|
|||||||
frm.fields_dict.entries.grid.update_docfield_property(
|
frm.fields_dict.entries.grid.update_docfield_property(
|
||||||
'batch_no', 'read_only', !frm.doc.has_batch_no
|
'batch_no', 'read_only', !frm.doc.has_batch_no
|
||||||
);
|
);
|
||||||
|
|
||||||
|
frm.fields_dict.entries.grid.update_docfield_property(
|
||||||
|
'qty', 'read_only', frm.doc.has_serial_no
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
set_queries(frm) {
|
set_queries(frm) {
|
||||||
@ -198,9 +210,9 @@ frappe.ui.form.on('Serial and Batch Bundle', {
|
|||||||
|
|
||||||
|
|
||||||
frappe.ui.form.on("Serial and Batch Entry", {
|
frappe.ui.form.on("Serial and Batch Entry", {
|
||||||
ledgers_add(frm, cdt, cdn) {
|
entries_add(frm, cdt, cdn) {
|
||||||
if (frm.doc.warehouse) {
|
if (frm.doc.warehouse) {
|
||||||
locals[cdt][cdn].warehouse = frm.doc.warehouse;
|
frappe.model.set_value(cdt, cdn, 'warehouse', frm.doc.warehouse);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
@ -133,7 +133,7 @@ class SerialandBatchBundle(Document):
|
|||||||
def calculate_total_qty(self, save=True):
|
def calculate_total_qty(self, save=True):
|
||||||
self.total_qty = 0.0
|
self.total_qty = 0.0
|
||||||
for d in self.entries:
|
for d in self.entries:
|
||||||
d.qty = abs(d.qty) if d.qty else 0
|
d.qty = 1 if self.has_serial_no and abs(d.qty) > 1 else abs(d.qty) if d.qty else 0
|
||||||
d.stock_value_difference = abs(d.stock_value_difference) if d.stock_value_difference else 0
|
d.stock_value_difference = abs(d.stock_value_difference) if d.stock_value_difference else 0
|
||||||
if self.type_of_transaction == "Outward":
|
if self.type_of_transaction == "Outward":
|
||||||
d.qty *= -1
|
d.qty *= -1
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.listview_settings["Serial and Batch Bundle"] = {
|
||||||
|
add_fields: ["is_cancelled"],
|
||||||
|
get_indicator: function (doc) {
|
||||||
|
if (doc.is_cancelled) {
|
||||||
|
return [__("Cancelled"), "red", "is_cancelled,=,1"];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
@ -94,6 +94,7 @@ class StockSettings(Document):
|
|||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
"erpnext.stock.doctype.stock_settings.stock_settings.clean_all_descriptions",
|
"erpnext.stock.doctype.stock_settings.stock_settings.clean_all_descriptions",
|
||||||
now=frappe.flags.in_test,
|
now=frappe.flags.in_test,
|
||||||
|
enqueue_after_commit=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_pending_reposts(self):
|
def validate_pending_reposts(self):
|
||||||
|
@ -944,7 +944,7 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
for item in sr.items:
|
for item in sr.items:
|
||||||
# Skip for Serial and Batch Items
|
# Skip for Serial and Batch Items
|
||||||
if item.serial_no or item.batch_no:
|
if item.name != sle.voucher_detail_no or item.serial_no or item.batch_no:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
previous_sle = get_previous_sle(
|
previous_sle = get_previous_sle(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -205,6 +205,7 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -629,7 +630,7 @@
|
|||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-16 14:18:57.001239",
|
"modified": "2023-06-03 16:18:39.088518",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt",
|
"name": "Subcontracting Receipt",
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2013-01-10 16:34:30",
|
"creation": "2013-01-10 16:34:30",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"status",
|
"status",
|
||||||
@ -249,6 +251,7 @@
|
|||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
|
"options": "Phone",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -362,10 +365,12 @@
|
|||||||
],
|
],
|
||||||
"icon": "fa fa-bug",
|
"icon": "fa fa-bug",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2021-11-09 17:26:09.703215",
|
"links": [],
|
||||||
|
"modified": "2023-06-03 16:17:07.694449",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Support",
|
"module": "Support",
|
||||||
"name": "Warranty Claim",
|
"name": "Warranty Claim",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -384,6 +389,7 @@
|
|||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"timeline_field": "customer",
|
"timeline_field": "customer",
|
||||||
"title_field": "customer_name"
|
"title_field": "customer_name"
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user