Merge branch 'develop' of https://github.com/frappe/erpnext into #34282-Record-advance-payment-as-a-liability
This commit is contained in:
commit
7ec9d76545
@ -154,7 +154,6 @@
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"onScan": true,
|
||||
"html2canvas": true,
|
||||
"extend_cscript": true,
|
||||
"localforage": true
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.0.0-dev"
|
||||
__version__ = "15.0.0-dev"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -38,6 +38,7 @@ def make_closing_entries(closing_entries, voucher_name):
|
||||
"closing_date": closing_date,
|
||||
}
|
||||
)
|
||||
cle.flags.ignore_permissions = True
|
||||
cle.submit()
|
||||
|
||||
|
||||
|
@ -50,13 +50,15 @@ class AccountingDimension(Document):
|
||||
if frappe.flags.in_test:
|
||||
make_dimension_in_accounting_doctypes(doc=self)
|
||||
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):
|
||||
if frappe.flags.in_test:
|
||||
delete_accounting_dimension(doc=self)
|
||||
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):
|
||||
if not self.label:
|
||||
|
@ -21,8 +21,6 @@
|
||||
"allow_multi_currency_invoices_against_single_party_account",
|
||||
"journals_section",
|
||||
"merge_similar_account_heads",
|
||||
"report_setting_section",
|
||||
"use_custom_cash_flow",
|
||||
"deferred_accounting_settings_section",
|
||||
"book_deferred_entries_based_on",
|
||||
"column_break_18",
|
||||
@ -36,6 +34,7 @@
|
||||
"book_tax_discount_loss",
|
||||
"print_settings",
|
||||
"show_inclusive_tax_in_print",
|
||||
"show_taxes_as_table_in_print",
|
||||
"column_break_12",
|
||||
"show_payment_schedule_in_print",
|
||||
"currency_exchange_section",
|
||||
@ -173,13 +172,6 @@
|
||||
"fieldtype": "Int",
|
||||
"label": "Stale Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Only select this if you have set up the Cash Flow Mapper documents",
|
||||
"fieldname": "use_custom_cash_flow",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Custom Cash Flow Format"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Payment Terms from orders will be fetched into the invoices as is",
|
||||
@ -338,11 +330,6 @@
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "POS"
|
||||
},
|
||||
{
|
||||
"fieldname": "report_setting_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Report Setting"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
|
||||
@ -390,6 +377,12 @@
|
||||
"fieldname": "auto_reconcile_payments",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Reconcile Payments"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_taxes_as_table_in_print",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Taxes as Table in Print"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -397,7 +390,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-21 13:11:37.130743",
|
||||
"modified": "2023-06-13 18:47:46.430291",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -41,7 +41,7 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
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) {
|
||||
@ -53,8 +53,8 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
frm.refresh_fields();
|
||||
|
||||
if (!frm.doc.payment_entries.length) {
|
||||
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(__('Get Payment Entries'), null, 'primary');
|
||||
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.change_custom_button_type('Get Payment Entries', null, 'default');
|
||||
frm.change_custom_button_type('Update Clearance Date', null, 'primary');
|
||||
frm.change_custom_button_type(__('Get Payment Entries'), null, 'default');
|
||||
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.trigger("make_reconciliation_tool");
|
||||
});
|
||||
frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
|
||||
frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary');
|
||||
|
||||
},
|
||||
|
||||
|
@ -125,14 +125,27 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
if not args.account:
|
||||
return
|
||||
|
||||
for budget_against in ["project", "cost_center"] + get_accounting_dimensions():
|
||||
default_dimensions = [
|
||||
{
|
||||
"fieldname": "project",
|
||||
"document_type": "Project",
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"document_type": "Cost Center",
|
||||
},
|
||||
]
|
||||
|
||||
for dimension in default_dimensions + get_accounting_dimensions(as_list=False):
|
||||
budget_against = dimension.get("fieldname")
|
||||
|
||||
if (
|
||||
args.get(budget_against)
|
||||
and args.account
|
||||
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
|
||||
):
|
||||
|
||||
doctype = frappe.unscrub(budget_against)
|
||||
doctype = dimension.get("document_type")
|
||||
|
||||
if frappe.get_cached_value("DocType", doctype, "is_tree"):
|
||||
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
|
||||
|
@ -1,6 +0,0 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Cash Flow Mapper', {
|
||||
|
||||
});
|
@ -1,275 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:section_name",
|
||||
"beta": 0,
|
||||
"creation": "2018-02-08 10:00:14.066519",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Section Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_header",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Section Header",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "e.g Adjustments for:",
|
||||
"fieldname": "section_leader",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Section Leader",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_subtotal",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Section Subtotal",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_footer",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Section Footer",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Accounts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cash Flow Mapping Template Details",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "position",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Position",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-02-15 18:28:55.034933",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cash Flow Mapper",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "name",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CashFlowMapper(Document):
|
||||
pass
|
@ -1,25 +0,0 @@
|
||||
DEFAULT_MAPPERS = [
|
||||
{
|
||||
"doctype": "Cash Flow Mapper",
|
||||
"section_footer": "Net cash generated by operating activities",
|
||||
"section_header": "Cash flows from operating activities",
|
||||
"section_leader": "Adjustments for",
|
||||
"section_name": "Operating Activities",
|
||||
"position": 0,
|
||||
"section_subtotal": "Cash generated from operations",
|
||||
},
|
||||
{
|
||||
"doctype": "Cash Flow Mapper",
|
||||
"position": 1,
|
||||
"section_footer": "Net cash used in investing activities",
|
||||
"section_header": "Cash flows from investing activities",
|
||||
"section_name": "Investing Activities",
|
||||
},
|
||||
{
|
||||
"doctype": "Cash Flow Mapper",
|
||||
"position": 2,
|
||||
"section_footer": "Net cash used in financing activites",
|
||||
"section_header": "Cash flows from financing activities",
|
||||
"section_name": "Financing Activities",
|
||||
},
|
||||
]
|
@ -1,8 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestCashFlowMapper(unittest.TestCase):
|
||||
pass
|
@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Cash Flow Mapping', {
|
||||
refresh: function(frm) {
|
||||
frm.events.disable_unchecked_fields(frm);
|
||||
},
|
||||
reset_check_fields: function(frm) {
|
||||
frm.fields.filter(field => field.df.fieldtype === 'Check')
|
||||
.map(field => frm.set_df_property(field.df.fieldname, 'read_only', 0));
|
||||
},
|
||||
has_checked_field(frm) {
|
||||
const val = frm.fields.filter(field => field.value === 1);
|
||||
return val.length ? 1 : 0;
|
||||
},
|
||||
_disable_unchecked_fields: function(frm) {
|
||||
// get value of clicked field
|
||||
frm.fields.filter(field => field.value === 0)
|
||||
.map(field => frm.set_df_property(field.df.fieldname, 'read_only', 1));
|
||||
},
|
||||
disable_unchecked_fields: function(frm) {
|
||||
frm.events.reset_check_fields(frm);
|
||||
const checked = frm.events.has_checked_field(frm);
|
||||
if (checked) {
|
||||
frm.events._disable_unchecked_fields(frm);
|
||||
}
|
||||
},
|
||||
is_working_capital: function(frm) {
|
||||
frm.events.disable_unchecked_fields(frm);
|
||||
},
|
||||
is_finance_cost: function(frm) {
|
||||
frm.events.disable_unchecked_fields(frm);
|
||||
},
|
||||
is_income_tax_liability: function(frm) {
|
||||
frm.events.disable_unchecked_fields(frm);
|
||||
},
|
||||
is_income_tax_expense: function(frm) {
|
||||
frm.events.disable_unchecked_fields(frm);
|
||||
},
|
||||
is_finance_cost_adjustment: function(frm) {
|
||||
frm.events.disable_unchecked_fields(frm);
|
||||
}
|
||||
});
|
@ -1,359 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:mapping_name",
|
||||
"beta": 0,
|
||||
"creation": "2018-02-08 09:28:44.678364",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mapping_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Label",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Accounts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cash Flow Mapping Accounts",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sb_1",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Select Maximum Of 1",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "is_finance_cost",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Finance Cost",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "is_working_capital",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Working Capital",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "is_finance_cost_adjustment",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Finance Cost Adjustment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "is_income_tax_liability",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Income Tax Liability",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "is_income_tax_expense",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Income Tax Expense",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-02-15 08:25:18.693533",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cash Flow Mapping",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "name",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CashFlowMapping(Document):
|
||||
def validate(self):
|
||||
self.validate_checked_options()
|
||||
|
||||
def validate_checked_options(self):
|
||||
checked_fields = [
|
||||
d for d in self.meta.fields if d.fieldtype == "Check" and self.get(d.fieldname) == 1
|
||||
]
|
||||
if len(checked_fields) > 1:
|
||||
frappe.throw(
|
||||
_("You can only select a maximum of one option from the list of check boxes."),
|
||||
title=_("Error"),
|
||||
)
|
@ -1,28 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
class TestCashFlowMapping(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if frappe.db.exists("Cash Flow Mapping", "Test Mapping"):
|
||||
frappe.delete_doc("Cash Flow Mappping", "Test Mapping")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.delete_doc("Cash Flow Mapping", "Test Mapping")
|
||||
|
||||
def test_multiple_selections_not_allowed(self):
|
||||
doc = frappe.new_doc("Cash Flow Mapping")
|
||||
doc.mapping_name = "Test Mapping"
|
||||
doc.label = "Test label"
|
||||
doc.append("accounts", {"account": "Accounts Receivable - _TC"})
|
||||
doc.is_working_capital = 1
|
||||
doc.is_finance_cost = 1
|
||||
|
||||
self.assertRaises(frappe.ValidationError, doc.insert)
|
||||
|
||||
doc.is_finance_cost = 0
|
||||
doc.insert()
|
@ -1,73 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:account",
|
||||
"beta": 0,
|
||||
"creation": "2018-02-08 09:25:34.353995",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-02-08 09:25:34.353995",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cash Flow Mapping Accounts",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CashFlowMappingAccounts(Document):
|
||||
pass
|
@ -1,6 +0,0 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Cash Flow Mapping Template', {
|
||||
|
||||
});
|
@ -1,123 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-02-08 10:20:18.316801",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "template_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Template Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mapping",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Cash Flow Mapping",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cash Flow Mapping Template Details",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-02-08 10:20:18.316801",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cash Flow Mapping Template",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CashFlowMappingTemplate(Document):
|
||||
pass
|
@ -1,8 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestCashFlowMappingTemplate(unittest.TestCase):
|
||||
pass
|
@ -1,6 +0,0 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Cash Flow Mapping Template Details', {
|
||||
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2018-02-08 10:18:48.513608",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"mapping"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "mapping",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Mapping",
|
||||
"options": "Cash Flow Mapping",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-21 03:34:57.902332",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cash Flow Mapping Template Details",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CashFlowMappingTemplateDetails(Document):
|
||||
pass
|
@ -1,8 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestCashFlowMappingTemplateDetails(unittest.TestCase):
|
||||
pass
|
@ -245,6 +245,7 @@
|
||||
"fieldname": "contact_mobile",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Mobile No",
|
||||
"options": "Phone",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -315,10 +316,11 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 18:55:43.683053",
|
||||
"modified": "2023-06-03 16:24:01.677026",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Dunning",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@ -365,6 +367,7 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"title_field": "customer_name",
|
||||
"track_changes": 1
|
||||
}
|
@ -35,6 +35,21 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
|
||||
}
|
||||
},
|
||||
|
||||
validate_rounding_loss: function(frm) {
|
||||
let allowance = frm.doc.rounding_loss_allowance;
|
||||
if (!(allowance > 0 && allowance < 1)) {
|
||||
frappe.throw(__("Rounding Loss Allowance should be between 0 and 1"));
|
||||
}
|
||||
},
|
||||
|
||||
rounding_loss_allowance: function(frm) {
|
||||
frm.events.validate_rounding_loss(frm);
|
||||
},
|
||||
|
||||
validate: function(frm) {
|
||||
frm.events.validate_rounding_loss(frm);
|
||||
},
|
||||
|
||||
get_entries: function(frm, account) {
|
||||
frappe.call({
|
||||
method: "get_accounts_data",
|
||||
@ -126,7 +141,8 @@ var get_account_details = function(frm, cdt, cdn) {
|
||||
company: frm.doc.company,
|
||||
posting_date: frm.doc.posting_date,
|
||||
party_type: row.party_type,
|
||||
party: row.party
|
||||
party: row.party,
|
||||
rounding_loss_allowance: frm.doc.rounding_loss_allowance
|
||||
},
|
||||
callback: function(r){
|
||||
$.extend(row, r.message);
|
||||
|
@ -8,6 +8,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"posting_date",
|
||||
"rounding_loss_allowance",
|
||||
"column_break_2",
|
||||
"company",
|
||||
"section_break_4",
|
||||
@ -96,11 +97,18 @@
|
||||
{
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0.05",
|
||||
"description": "Only values between 0 and 1 are allowed. \nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account",
|
||||
"fieldname": "rounding_loss_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Rounding Loss Allowance"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-12-29 19:38:24.416529",
|
||||
"modified": "2023-06-12 21:02:09.818208",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Exchange Rate Revaluation",
|
||||
|
@ -12,13 +12,19 @@ from frappe.utils import flt, get_link_to_form
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_balance_on
|
||||
from erpnext.accounts.utils import get_currency_precision
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
|
||||
class ExchangeRateRevaluation(Document):
|
||||
def validate(self):
|
||||
self.validate_rounding_loss_allowance()
|
||||
self.set_total_gain_loss()
|
||||
|
||||
def validate_rounding_loss_allowance(self):
|
||||
if not (self.rounding_loss_allowance > 0 and self.rounding_loss_allowance < 1):
|
||||
frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
|
||||
|
||||
def set_total_gain_loss(self):
|
||||
total_gain_loss = 0
|
||||
|
||||
@ -91,7 +97,12 @@ class ExchangeRateRevaluation(Document):
|
||||
def get_accounts_data(self):
|
||||
self.validate_mandatory()
|
||||
account_details = self.get_account_balance_from_gle(
|
||||
company=self.company, posting_date=self.posting_date, account=None, party_type=None, party=None
|
||||
company=self.company,
|
||||
posting_date=self.posting_date,
|
||||
account=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
rounding_loss_allowance=self.rounding_loss_allowance,
|
||||
)
|
||||
accounts_with_new_balance = self.calculate_new_account_balance(
|
||||
self.company, self.posting_date, account_details
|
||||
@ -103,7 +114,9 @@ class ExchangeRateRevaluation(Document):
|
||||
return accounts_with_new_balance
|
||||
|
||||
@staticmethod
|
||||
def get_account_balance_from_gle(company, posting_date, account, party_type, party):
|
||||
def get_account_balance_from_gle(
|
||||
company, posting_date, account, party_type, party, rounding_loss_allowance
|
||||
):
|
||||
account_details = []
|
||||
|
||||
if company and posting_date:
|
||||
@ -170,6 +183,23 @@ class ExchangeRateRevaluation(Document):
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
# round off balance based on currency precision
|
||||
# and consider debit-credit difference allowance
|
||||
currency_precision = get_currency_precision()
|
||||
rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
|
||||
for acc in account_details:
|
||||
acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
|
||||
if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
|
||||
acc.balance_in_account_currency = 0
|
||||
|
||||
acc.balance = flt(acc.balance, currency_precision)
|
||||
if abs(acc.balance) <= rounding_loss_allowance:
|
||||
acc.balance = 0
|
||||
|
||||
acc.zero_balance = (
|
||||
True if (acc.balance == 0 or acc.balance_in_account_currency == 0) else False
|
||||
)
|
||||
|
||||
return account_details
|
||||
|
||||
@staticmethod
|
||||
@ -521,7 +551,9 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details(company, posting_date, account, party_type=None, party=None):
|
||||
def get_account_details(
|
||||
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float = None
|
||||
):
|
||||
if not (company and posting_date):
|
||||
frappe.throw(_("Company and Posting Date is mandatory"))
|
||||
|
||||
@ -539,7 +571,12 @@ def get_account_details(company, posting_date, account, party_type=None, party=N
|
||||
"account_currency": account_currency,
|
||||
}
|
||||
account_balance = ExchangeRateRevaluation.get_account_balance_from_gle(
|
||||
company=company, posting_date=posting_date, account=account, party_type=party_type, party=party
|
||||
company=company,
|
||||
posting_date=posting_date,
|
||||
account=account,
|
||||
party_type=party_type,
|
||||
party=party,
|
||||
rounding_loss_allowance=rounding_loss_allowance,
|
||||
)
|
||||
|
||||
if account_balance and (
|
||||
|
@ -12,7 +12,7 @@ from frappe.utils import add_days, add_years, cstr, getdate
|
||||
class FiscalYear(Document):
|
||||
@frappe.whitelist()
|
||||
def set_as_default(self):
|
||||
frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
|
||||
frappe.db.set_single_value("Global Defaults", "current_fiscal_year", self.name)
|
||||
global_defaults = frappe.get_doc("Global Defaults")
|
||||
global_defaults.check_permission("write")
|
||||
global_defaults.on_update()
|
||||
|
@ -575,7 +575,7 @@ $.extend(erpnext.journal_entry, {
|
||||
};
|
||||
if(!frm.doc.multi_currency) {
|
||||
$.extend(filters, {
|
||||
account_currency: frappe.get_doc(":Company", frm.doc.company).default_currency
|
||||
account_currency: ['in', [frappe.get_doc(":Company", frm.doc.company).default_currency, null]]
|
||||
});
|
||||
}
|
||||
return { filters: filters };
|
||||
|
@ -952,6 +952,7 @@ class JournalEntry(AccountsController):
|
||||
blank_row.debit_in_account_currency = abs(diff)
|
||||
blank_row.debit = abs(diff)
|
||||
|
||||
self.set_total_debit_credit()
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -105,8 +105,8 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
|
||||
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0
|
||||
frappe.db.set_single_value(
|
||||
"Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0
|
||||
)
|
||||
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
|
||||
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
|
||||
|
@ -28,7 +28,7 @@ frappe.ui.form.on("Journal Entry Template", {
|
||||
|
||||
if(!frm.doc.multi_currency) {
|
||||
$.extend(filters, {
|
||||
account_currency: frappe.get_doc(":Company", frm.doc.company).default_currency
|
||||
account_currency: ['in', [frappe.get_doc(":Company", frm.doc.company).default_currency, null]]
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -178,19 +178,57 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
def validate_allocated_amount(self):
|
||||
for d in self.get("references"):
|
||||
if self.payment_type == "Internal Transfer":
|
||||
return
|
||||
|
||||
latest_references = get_outstanding_reference_documents(
|
||||
{
|
||||
"posting_date": self.posting_date,
|
||||
"company": self.company,
|
||||
"party_type": self.party_type,
|
||||
"payment_type": self.payment_type,
|
||||
"party": self.party,
|
||||
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
|
||||
}
|
||||
)
|
||||
|
||||
# Group latest_references by (voucher_type, voucher_no)
|
||||
latest_lookup = {}
|
||||
for d in latest_references:
|
||||
d = frappe._dict(d)
|
||||
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
||||
|
||||
for d in self.get("references").copy():
|
||||
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
||||
|
||||
# The reference has already been fully paid
|
||||
if not latest:
|
||||
frappe.throw(
|
||||
_("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
|
||||
)
|
||||
# The reference has already been partly paid
|
||||
elif (
|
||||
latest.outstanding_amount < latest.invoice_amount
|
||||
and d.outstanding_amount != latest.outstanding_amount
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount."
|
||||
).format(d.reference_doctype, d.reference_name)
|
||||
)
|
||||
|
||||
d.outstanding_amount = latest.outstanding_amount
|
||||
|
||||
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||
|
||||
if (flt(d.allocated_amount)) > 0:
|
||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
||||
)
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
# Check for negative outstanding invoices as well
|
||||
if flt(d.allocated_amount) < 0:
|
||||
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
||||
)
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
def delink_advance_entry_references(self):
|
||||
for reference in self.references:
|
||||
@ -396,7 +434,7 @@ class PaymentEntry(AccountsController):
|
||||
for k, v in no_oustanding_refs.items():
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry."
|
||||
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
|
||||
).format(
|
||||
_(k),
|
||||
frappe.bold(", ".join(d.reference_name for d in v)),
|
||||
@ -1535,7 +1573,7 @@ def get_orders_to_be_billed(
|
||||
if voucher_type:
|
||||
doc = frappe.get_doc({"doctype": voucher_type})
|
||||
condition = ""
|
||||
if cost_center and doc and hasattr(doc, "cost_center"):
|
||||
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||
condition = " and cost_center='%s'" % cost_center
|
||||
|
||||
orders = []
|
||||
@ -1581,13 +1619,15 @@ def get_orders_to_be_billed(
|
||||
|
||||
order_list = []
|
||||
for d in orders:
|
||||
if filters.get("oustanding_amt_greater_than") and flt(d.outstanding_amount) < flt(
|
||||
filters.get("outstanding_amt_greater_than")
|
||||
):
|
||||
continue
|
||||
|
||||
if filters.get("oustanding_amt_less_than") and flt(d.outstanding_amount) > flt(
|
||||
filters.get("outstanding_amt_less_than")
|
||||
if (
|
||||
filters
|
||||
and filters.get("outstanding_amt_greater_than")
|
||||
and filters.get("outstanding_amt_less_than")
|
||||
and not (
|
||||
flt(filters.get("outstanding_amt_greater_than"))
|
||||
<= flt(d.outstanding_amount)
|
||||
<= flt(filters.get("outstanding_amt_less_than"))
|
||||
)
|
||||
):
|
||||
continue
|
||||
|
||||
|
@ -1013,6 +1013,30 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
employee = make_employee("test_payment_entry@salary.com", company="_Test Company")
|
||||
create_payment_entry(party_type="Employee", party=employee, save=True)
|
||||
|
||||
def test_duplicate_payment_entry_allocate_amount(self):
|
||||
si = create_sales_invoice()
|
||||
|
||||
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||
pe_draft.insert()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name)
|
||||
pe.submit()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||
|
||||
def test_duplicate_payment_entry_partial_allocate_amount(self):
|
||||
si = create_sales_invoice()
|
||||
|
||||
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||
pe_draft.insert()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name)
|
||||
pe.received_amount = si.total / 2
|
||||
pe.references[0].allocated_amount = si.total / 2
|
||||
pe.submit()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
@ -75,22 +75,22 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
this.frm.add_custom_button(__('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) {
|
||||
this.frm.add_custom_button(__('Allocate'), () =>
|
||||
this.frm.trigger("allocate")
|
||||
);
|
||||
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(__('Allocate'), null, 'primary');
|
||||
this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'default');
|
||||
}
|
||||
if (this.frm.doc.allocation.length) {
|
||||
this.frm.add_custom_button(__('Reconcile'), () =>
|
||||
this.frm.trigger("reconcile")
|
||||
);
|
||||
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('Allocate', null, 'default');
|
||||
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(__('Allocate'), null, 'default');
|
||||
}
|
||||
|
||||
// check for any running reconciliation jobs
|
||||
|
@ -6,7 +6,6 @@ import frappe
|
||||
from frappe import _, msgprint, qb
|
||||
from frappe.model.document import Document
|
||||
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
|
||||
|
||||
import erpnext
|
||||
@ -144,12 +143,29 @@ class PaymentReconciliation(Document):
|
||||
|
||||
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):
|
||||
|
||||
self.build_qb_filter_conditions(get_return_invoices=True)
|
||||
|
||||
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":
|
||||
self.common_filter_conditions.append(ple.account_type == "Receivable")
|
||||
@ -157,19 +173,10 @@ class PaymentReconciliation(Document):
|
||||
self.common_filter_conditions.append(ple.account_type == "Payable")
|
||||
self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
|
||||
|
||||
# get return invoices
|
||||
doc = qb.DocType(voucher_type)
|
||||
return_invoices = (
|
||||
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)
|
||||
)
|
||||
self.get_return_invoices()
|
||||
return_invoices = [
|
||||
x for x in self.return_invoices if x.return_against == None or x.return_against == ""
|
||||
]
|
||||
|
||||
outstanding_dr_or_cr = []
|
||||
if return_invoices:
|
||||
@ -221,6 +228,15 @@ class PaymentReconciliation(Document):
|
||||
accounting_dimensions=self.accounting_dimension_filter_conditions,
|
||||
)
|
||||
|
||||
cr_dr_notes = (
|
||||
[x.voucher_no for x in self.return_invoices]
|
||||
if self.party_type in ["Customer", "Supplier"]
|
||||
else []
|
||||
)
|
||||
# Filter out cr/dr notes from outstanding invoices list
|
||||
# Happens when non-standalone cr/dr notes are linked with another invoice through journal entry
|
||||
non_reconciled_invoices = [x for x in non_reconciled_invoices if x.voucher_no not in cr_dr_notes]
|
||||
|
||||
if 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_no=self.name,
|
||||
queue="long",
|
||||
enqueue_after_commit=True,
|
||||
)
|
||||
frappe.msgprint(
|
||||
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
|
||||
|
@ -442,6 +442,7 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Mobile No",
|
||||
"options": "Phone",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -1554,11 +1555,10 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-30 03:49:50.455199",
|
||||
"modified": "2023-06-03 16:23:41.083409",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, bold
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
|
||||
|
||||
@ -16,12 +16,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
update_multi_mode_option,
|
||||
)
|
||||
from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty, get_pos_reserved_batch_qty
|
||||
from erpnext.stock.doctype.serial_no.serial_no import (
|
||||
get_delivered_serial_nos,
|
||||
get_pos_reserved_serial_nos,
|
||||
get_serial_nos,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
|
||||
class POSInvoice(SalesInvoice):
|
||||
@ -71,6 +66,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.apply_loyalty_points()
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
self.submit_serial_batch_bundle()
|
||||
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
|
||||
@ -112,6 +108,29 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
update_coupon_code_count(self.coupon_code, "cancelled")
|
||||
|
||||
self.delink_serial_and_batch_bundle()
|
||||
|
||||
def delink_serial_and_batch_bundle(self):
|
||||
for row in self.items:
|
||||
if row.serial_and_batch_bundle:
|
||||
if not self.consolidated_invoice:
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Bundle",
|
||||
row.serial_and_batch_bundle,
|
||||
{"is_cancelled": 1, "voucher_no": ""},
|
||||
)
|
||||
|
||||
row.db_set("serial_and_batch_bundle", None)
|
||||
|
||||
def submit_serial_batch_bundle(self):
|
||||
for item in self.items:
|
||||
if item.serial_and_batch_bundle:
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
|
||||
|
||||
if doc.docstatus == 0:
|
||||
doc.flags.ignore_voucher_validation = True
|
||||
doc.submit()
|
||||
|
||||
def check_phone_payments(self):
|
||||
for pay in self.payments:
|
||||
if pay.type == "Phone" and pay.amount >= 0:
|
||||
@ -129,88 +148,6 @@ class POSInvoice(SalesInvoice):
|
||||
if paid_amt and pay.amount != paid_amt:
|
||||
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||
|
||||
def validate_pos_reserved_serial_nos(self, item):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
filters = {"item_code": item.item_code, "warehouse": item.warehouse}
|
||||
if item.batch_no:
|
||||
filters["batch_no"] = item.batch_no
|
||||
|
||||
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||
|
||||
bold_invalid_serial_nos = frappe.bold(", ".join(invalid_serial_nos))
|
||||
if len(invalid_serial_nos) == 1:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no."
|
||||
).format(item.idx, bold_invalid_serial_nos),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
elif invalid_serial_nos:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Serial Nos. {} have already been transacted into another POS Invoice. Please select valid serial no."
|
||||
).format(item.idx, bold_invalid_serial_nos),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
|
||||
def validate_pos_reserved_batch_qty(self, item):
|
||||
filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no": item.batch_no}
|
||||
|
||||
available_batch_qty = get_batch_qty(item.batch_no, item.warehouse, item.item_code)
|
||||
reserved_batch_qty = get_pos_reserved_batch_qty(filters)
|
||||
|
||||
bold_item_name = frappe.bold(item.item_name)
|
||||
bold_extra_batch_qty_needed = frappe.bold(
|
||||
abs(available_batch_qty - reserved_batch_qty - item.stock_qty)
|
||||
)
|
||||
bold_invalid_batch_no = frappe.bold(item.batch_no)
|
||||
|
||||
if (available_batch_qty - reserved_batch_qty) == 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Batch No. {} of item {} has no stock available. Please select valid batch no."
|
||||
).format(item.idx, bold_invalid_batch_no, bold_item_name),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
|
||||
).format(
|
||||
item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed
|
||||
),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
|
||||
def validate_delivered_serial_nos(self, item):
|
||||
delivered_serial_nos = get_delivered_serial_nos(item.serial_no)
|
||||
|
||||
if delivered_serial_nos:
|
||||
bold_delivered_serial_nos = frappe.bold(", ".join(delivered_serial_nos))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no."
|
||||
).format(item.idx, bold_delivered_serial_nos),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
|
||||
def validate_invalid_serial_nos(self, item):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
error_msg = []
|
||||
invalid_serials, msg = "", ""
|
||||
for serial_no in serial_nos:
|
||||
if not frappe.db.exists("Serial No", serial_no):
|
||||
invalid_serials = invalid_serials + (", " if invalid_serials else "") + serial_no
|
||||
msg = _("Row #{}: Following Serial numbers for item {} are <b>Invalid</b>: {}").format(
|
||||
item.idx, frappe.bold(item.get("item_code")), frappe.bold(invalid_serials)
|
||||
)
|
||||
if invalid_serials:
|
||||
error_msg.append(msg)
|
||||
|
||||
if error_msg:
|
||||
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
if self.is_return:
|
||||
return
|
||||
@ -223,13 +160,7 @@ class POSInvoice(SalesInvoice):
|
||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.serial_no:
|
||||
self.validate_pos_reserved_serial_nos(d)
|
||||
self.validate_delivered_serial_nos(d)
|
||||
self.validate_invalid_serial_nos(d)
|
||||
elif d.batch_no:
|
||||
self.validate_pos_reserved_batch_qty(d)
|
||||
else:
|
||||
if not d.serial_and_batch_bundle:
|
||||
if is_negative_stock_allowed(item_code=d.item_code):
|
||||
return
|
||||
|
||||
@ -258,36 +189,15 @@ class POSInvoice(SalesInvoice):
|
||||
def validate_serialised_or_batched_item(self):
|
||||
error_msg = []
|
||||
for d in self.get("items"):
|
||||
serialized = d.get("has_serial_no")
|
||||
batched = d.get("has_batch_no")
|
||||
no_serial_selected = not d.get("serial_no")
|
||||
no_batch_selected = not d.get("batch_no")
|
||||
error_msg = ""
|
||||
if d.get("has_serial_no") and not d.serial_and_batch_bundle:
|
||||
error_msg = f"Row #{d.idx}: Please select Serial No. for item {bold(d.item_code)}"
|
||||
|
||||
msg = ""
|
||||
item_code = frappe.bold(d.item_code)
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
if serialized and batched and (no_batch_selected or no_serial_selected):
|
||||
msg = _(
|
||||
"Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction."
|
||||
).format(d.idx, item_code)
|
||||
elif serialized and no_serial_selected:
|
||||
msg = _(
|
||||
"Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction."
|
||||
).format(d.idx, item_code)
|
||||
elif batched and no_batch_selected:
|
||||
msg = _(
|
||||
"Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction."
|
||||
).format(d.idx, item_code)
|
||||
elif serialized and not no_serial_selected and len(serial_nos) != d.qty:
|
||||
msg = _("Row #{}: You must select {} serial numbers for item {}.").format(
|
||||
d.idx, frappe.bold(cint(d.qty)), item_code
|
||||
)
|
||||
|
||||
if msg:
|
||||
error_msg.append(msg)
|
||||
elif d.get("has_batch_no") and not d.serial_and_batch_bundle:
|
||||
error_msg = f"Row #{d.idx}: Please select Batch No. for item {bold(d.item_code)}"
|
||||
|
||||
if error_msg:
|
||||
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||
frappe.throw(error_msg, title=_("Serial / Batch Bundle Missing"), as_list=True)
|
||||
|
||||
def validate_return_items_qty(self):
|
||||
if not self.get("is_return"):
|
||||
@ -652,7 +562,7 @@ def get_bundle_availability(bundle_item_code, warehouse):
|
||||
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
|
||||
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||
|
||||
max_available_bundles = available_qty / item.stock_qty
|
||||
max_available_bundles = available_qty / item.qty
|
||||
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
||||
"Item", item.item_code, "is_stock_item"
|
||||
):
|
||||
|
@ -5,12 +5,18 @@ import copy
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
@ -25,7 +31,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
if frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
frappe.db.set_value("Selling Settings", None, "validate_selling_price", 0)
|
||||
frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0)
|
||||
|
||||
def test_timestamp_change(self):
|
||||
w = create_pos_invoice(do_not_save=1)
|
||||
@ -249,7 +255,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -260,11 +266,11 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
serial_no=[serial_nos[0]],
|
||||
rate=1000,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].serial_no = serial_nos[0]
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
@ -276,7 +282,9 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
pos_return.insert()
|
||||
pos_return.submit()
|
||||
self.assertEqual(pos_return.get("items")[0].serial_no, serial_nos[0])
|
||||
self.assertEqual(
|
||||
get_serial_nos_from_bundle(pos_return.get("items")[0].serial_and_batch_bundle)[0], serial_nos[0]
|
||||
)
|
||||
|
||||
def test_partial_pos_returns(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
@ -289,7 +297,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -300,12 +308,12 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
serial_no=serial_nos,
|
||||
qty=2,
|
||||
rate=1000,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[1]
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
@ -317,14 +325,27 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
# partial return 1
|
||||
pos_return1.get("items")[0].qty = -1
|
||||
pos_return1.get("items")[0].serial_no = serial_nos[0]
|
||||
|
||||
bundle_id = frappe.get_doc(
|
||||
"Serial and Batch Bundle", pos_return1.get("items")[0].serial_and_batch_bundle
|
||||
)
|
||||
|
||||
bundle_id.remove(bundle_id.entries[1])
|
||||
bundle_id.save()
|
||||
|
||||
bundle_id.load_from_db()
|
||||
|
||||
serial_no = bundle_id.entries[0].serial_no
|
||||
self.assertEqual(serial_no, serial_nos[0])
|
||||
|
||||
pos_return1.insert()
|
||||
pos_return1.submit()
|
||||
|
||||
# partial return 2
|
||||
pos_return2 = make_sales_return(pos.name)
|
||||
self.assertEqual(pos_return2.get("items")[0].qty, -1)
|
||||
self.assertEqual(pos_return2.get("items")[0].serial_no, serial_nos[1])
|
||||
serial_no = get_serial_nos_from_bundle(pos_return2.get("items")[0].serial_and_batch_bundle)[0]
|
||||
self.assertEqual(serial_no, serial_nos[1])
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
pos = create_pos_invoice(
|
||||
@ -368,7 +389,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -380,10 +401,10 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].serial_no = serial_nos[0]
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
@ -401,10 +422,10 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
@ -423,7 +444,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company",
|
||||
@ -435,11 +456,11 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
update_stock=1,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
si.get("items")[0].serial_no = serial_nos[0]
|
||||
si.update_stock = 1
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
@ -453,10 +474,10 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
@ -473,7 +494,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
serial_nos = se.get("items")[0].serial_no + "wrong"
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0] + "wrong"
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -486,14 +507,13 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
qty=2,
|
||||
serial_nos=[serial_nos],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.get("items")[0].serial_no = serial_nos
|
||||
pos.insert()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos.submit)
|
||||
self.assertRaises(frappe.ValidationError, pos.insert)
|
||||
|
||||
def test_value_error_on_serial_no_validation(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
@ -504,7 +524,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
serial_nos = se.get("items")[0].serial_no
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
# make a pos invoice
|
||||
pos = create_pos_invoice(
|
||||
@ -517,11 +537,11 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
pos.set("payments", [])
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
@ -547,12 +567,12 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].has_serial_no = 1
|
||||
pos2.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
# Value error should not be triggered on validation
|
||||
pos2.save()
|
||||
|
||||
@ -702,7 +722,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
)
|
||||
|
||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||
frappe.db.set_single_value("Selling Settings", "validate_selling_price", 1)
|
||||
|
||||
item = "Test Selling Price Validation"
|
||||
make_item(item, {"is_stock_item": 1})
|
||||
@ -748,16 +768,16 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
self.assertEqual(rounded_total, 400)
|
||||
|
||||
def test_pos_batch_item_qty_validation(self):
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||
BatchNegativeStockError,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_batch_item_with_batch,
|
||||
)
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
create_batch_item_with_batch("_BATCH ITEM", "TestBatch 01")
|
||||
item = frappe.get_doc("Item", "_BATCH ITEM")
|
||||
batch = frappe.get_doc("Batch", "TestBatch 01")
|
||||
batch.submit()
|
||||
item.batch_no = "TestBatch 01"
|
||||
item.save()
|
||||
|
||||
se = make_stock_entry(
|
||||
target="_Test Warehouse - _TC",
|
||||
@ -767,16 +787,28 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
batch_no="TestBatch 01",
|
||||
)
|
||||
|
||||
pos_inv1 = create_pos_invoice(item=item.name, rate=300, qty=1, do_not_submit=1)
|
||||
pos_inv1.items[0].batch_no = "TestBatch 01"
|
||||
pos_inv1 = create_pos_invoice(
|
||||
item=item.name, rate=300, qty=1, do_not_submit=1, batch_no="TestBatch 01"
|
||||
)
|
||||
pos_inv1.save()
|
||||
pos_inv1.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(item=item.name, rate=300, qty=2, do_not_submit=1)
|
||||
pos_inv2.items[0].batch_no = "TestBatch 01"
|
||||
pos_inv2.save()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos_inv2.submit)
|
||||
sn_doc = SerialBatchCreation(
|
||||
{
|
||||
"item_code": item.name,
|
||||
"warehouse": pos_inv2.items[0].warehouse,
|
||||
"voucher_type": "Delivery Note",
|
||||
"qty": 2,
|
||||
"avg_rate": 300,
|
||||
"batches": frappe._dict({"TestBatch 01": 2}),
|
||||
"type_of_transaction": "Outward",
|
||||
"company": pos_inv2.company,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertRaises(BatchNegativeStockError, sn_doc.make_serial_and_batch_bundle)
|
||||
|
||||
# teardown
|
||||
pos_inv1.reload()
|
||||
@ -785,9 +817,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos_inv2.reload()
|
||||
pos_inv2.delete()
|
||||
se.cancel()
|
||||
batch.reload()
|
||||
batch.cancel()
|
||||
batch.delete()
|
||||
|
||||
def test_ignore_pricing_rule(self):
|
||||
from erpnext.accounts.doctype.pricing_rule.test_pricing_rule import make_pricing_rule
|
||||
@ -838,18 +867,18 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
frappe.db.savepoint("before_test_delivered_serial_no_case")
|
||||
try:
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
|
||||
|
||||
dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=serial_no)
|
||||
dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=[serial_no])
|
||||
delivered_serial_no = get_serial_nos_from_bundle(dn.get("items")[0].serial_and_batch_bundle)[0]
|
||||
|
||||
delivery_document_no = frappe.db.get_value("Serial No", serial_no, "delivery_document_no")
|
||||
self.assertEquals(delivery_document_no, dn.name)
|
||||
self.assertEqual(serial_no, delivered_serial_no)
|
||||
|
||||
init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=serial_no,
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=True,
|
||||
@ -861,42 +890,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
frappe.db.rollback(save_point="before_test_delivered_serial_no_case")
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_returned_serial_no_case(self):
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
|
||||
init_user_and_profile,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
||||
from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
frappe.db.savepoint("before_test_returned_serial_no_case")
|
||||
try:
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||
|
||||
init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=serial_no,
|
||||
qty=1,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
pos_return = make_sales_return(pos_inv.name)
|
||||
pos_return.flags.ignore_validate = True
|
||||
pos_return.insert()
|
||||
pos_return.submit()
|
||||
|
||||
pos_reserved_serial_nos = get_pos_reserved_serial_nos(
|
||||
{"item_code": "_Test Serialized Item With Series", "warehouse": "_Test Warehouse - _TC"}
|
||||
)
|
||||
self.assertTrue(serial_no not in pos_reserved_serial_nos)
|
||||
|
||||
finally:
|
||||
frappe.db.rollback(save_point="before_test_returned_serial_no_case")
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
|
||||
def create_pos_invoice(**args):
|
||||
args = frappe._dict(args)
|
||||
@ -926,6 +919,40 @@ def create_pos_invoice(**args):
|
||||
|
||||
pos_inv.set_missing_values()
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
type_of_transaction = args.type_of_transaction or "Outward"
|
||||
|
||||
if pos_inv.is_return:
|
||||
type_of_transaction = "Inward"
|
||||
|
||||
qty = args.get("qty") or 1
|
||||
qty *= -1 if type_of_transaction == "Outward" else 1
|
||||
batches = {}
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
|
||||
bundle_id = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": qty,
|
||||
"batches": batches,
|
||||
"voucher_type": "Delivery Note",
|
||||
"serial_nos": args.serial_no,
|
||||
"posting_date": pos_inv.posting_date,
|
||||
"posting_time": pos_inv.posting_time,
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
if not bundle_id:
|
||||
msg = f"Serial No {args.serial_no} not available for Item {args.item}"
|
||||
frappe.throw(_(msg))
|
||||
|
||||
pos_inv.append(
|
||||
"items",
|
||||
{
|
||||
@ -936,8 +963,7 @@ def create_pos_invoice(**args):
|
||||
"income_account": args.income_account or "Sales - _TC",
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
"batch_no": args.batch_no,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -79,6 +79,7 @@
|
||||
"warehouse",
|
||||
"target_warehouse",
|
||||
"quality_inspection",
|
||||
"serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"col_break5",
|
||||
"allow_zero_valuation_rate",
|
||||
@ -628,10 +629,11 @@
|
||||
{
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break5",
|
||||
@ -648,10 +650,12 @@
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Serial No",
|
||||
"oldfieldname": "serial_no",
|
||||
"oldfieldtype": "Small Text"
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_tax_rate",
|
||||
@ -817,11 +821,19 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-02 12:52:39.125295",
|
||||
"modified": "2023-03-12 13:36:40.160468",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
|
@ -184,6 +184,8 @@ class POSInvoiceMergeLog(Document):
|
||||
item.base_amount = item.base_net_amount
|
||||
item.price_list_rate = 0
|
||||
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
||||
if item.serial_and_batch_bundle:
|
||||
si_item.serial_and_batch_bundle = item.serial_and_batch_bundle
|
||||
items.append(si_item)
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
@ -385,7 +387,7 @@ def split_invoices(invoices):
|
||||
]
|
||||
for pos_invoice in pos_return_docs:
|
||||
for item in pos_invoice.items:
|
||||
if not item.serial_no:
|
||||
if not item.serial_no and not item.serial_and_batch_bundle:
|
||||
continue
|
||||
|
||||
return_against_is_added = any(
|
||||
|
@ -13,6 +13,9 @@ from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_inv
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
consolidate_pos_invoices,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_serial_nos_from_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
@ -410,13 +413,13 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
|
||||
try:
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
|
||||
|
||||
init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=serial_no,
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
@ -430,7 +433,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
|
||||
pos_inv2 = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=serial_no,
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=1,
|
||||
|
@ -469,7 +469,7 @@
|
||||
"options": "UOM"
|
||||
},
|
||||
{
|
||||
"description": "If rate is zero them item will be treated as \"Free Item\"",
|
||||
"description": "If rate is zero then item will be treated as \"Free Item\"",
|
||||
"fieldname": "free_item_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Free Item Rate"
|
||||
@ -670,4 +670,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title"
|
||||
}
|
||||
}
|
||||
|
@ -237,10 +237,6 @@ def apply_pricing_rule(args, doc=None):
|
||||
item_list = args.get("items")
|
||||
args.pop("items")
|
||||
|
||||
set_serial_nos_based_on_fifo = frappe.db.get_single_value(
|
||||
"Stock Settings", "automatically_set_serial_nos_based_on_fifo"
|
||||
)
|
||||
|
||||
item_code_list = tuple(item.get("item_code") for item in item_list)
|
||||
query_items = frappe.get_all(
|
||||
"Item",
|
||||
@ -258,28 +254,9 @@ def apply_pricing_rule(args, doc=None):
|
||||
data = get_pricing_rule_for_item(args_copy, doc=doc)
|
||||
out.append(data)
|
||||
|
||||
if (
|
||||
serialized_items.get(item.get("item_code"))
|
||||
and not item.get("serial_no")
|
||||
and set_serial_nos_based_on_fifo
|
||||
and not args.get("is_return")
|
||||
):
|
||||
out[0].update(get_serial_no_for_item(args_copy))
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def get_serial_no_for_item(args):
|
||||
from erpnext.stock.get_item_details import get_serial_no
|
||||
|
||||
item_details = frappe._dict(
|
||||
{"doctype": args.doctype, "name": args.name, "serial_no": args.serial_no}
|
||||
)
|
||||
if args.get("parenttype") in ("Sales Invoice", "Delivery Note") and flt(args.stock_qty) > 0:
|
||||
item_details.serial_no = get_serial_no(args)
|
||||
return item_details
|
||||
|
||||
|
||||
def update_pricing_rule_uom(pricing_rule, args):
|
||||
child_doc = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}.get(
|
||||
pricing_rule.apply_on
|
||||
|
@ -158,7 +158,7 @@ def get_customers_based_on_territory_or_customer_group(customer_collection, coll
|
||||
return frappe.get_list(
|
||||
"Customer",
|
||||
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",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Mobile No",
|
||||
"options": "Phone",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "contact_email",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Contact Email",
|
||||
"options": "Email",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@ -1574,7 +1576,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-05 17:40:35.320635",
|
||||
"modified": "2023-06-03 16:21:54.637245",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -105,9 +105,6 @@ class PurchaseInvoice(BuyingController):
|
||||
# validate service stop date to lie in between start and end date
|
||||
validate_service_stop_date(self)
|
||||
|
||||
if self._action == "submit" and self.update_stock:
|
||||
self.make_batches("warehouse")
|
||||
|
||||
self.validate_release_date()
|
||||
self.check_conversion_rate()
|
||||
self.validate_credit_to_acc()
|
||||
@ -516,10 +513,6 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
|
||||
# this sequence because outstanding may get -negative
|
||||
self.make_gl_entries()
|
||||
|
||||
@ -1460,6 +1453,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Payment Ledger Entry",
|
||||
"Tax Withheld Vouchers",
|
||||
"Serial and Batch Bundle",
|
||||
)
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
|
@ -26,6 +26,11 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||
get_taxes,
|
||||
make_purchase_receipt,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
|
||||
from erpnext.stock.tests.test_utils import StockTestMixin
|
||||
|
||||
@ -37,7 +42,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
|
||||
frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
@ -637,13 +642,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
gle_filters={"account": "Stock In Hand - TCP1"},
|
||||
)
|
||||
|
||||
# assert loss booked in COGS
|
||||
self.assertGLEs(
|
||||
return_pi,
|
||||
[{"credit": 0, "debit": 200}],
|
||||
gle_filters={"account": "Cost of Goods Sold - TCP1"},
|
||||
)
|
||||
|
||||
def test_return_with_lcv(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
|
||||
@ -888,14 +886,20 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
rejected_warehouse="_Test Rejected Warehouse - _TC",
|
||||
allow_zero_valuation_rate=1,
|
||||
)
|
||||
pi.load_from_db()
|
||||
|
||||
serial_no = get_serial_nos_from_bundle(pi.get("items")[0].serial_and_batch_bundle)[0]
|
||||
rejected_serial_no = get_serial_nos_from_bundle(
|
||||
pi.get("items")[0].rejected_serial_and_batch_bundle
|
||||
)[0]
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"),
|
||||
frappe.db.get_value("Serial No", serial_no, "warehouse"),
|
||||
pi.get("items")[0].warehouse,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Serial No", pi.get("items")[0].rejected_serial_no, "warehouse"),
|
||||
frappe.db.get_value("Serial No", rejected_serial_no, "warehouse"),
|
||||
pi.get("items")[0].rejected_warehouse,
|
||||
)
|
||||
|
||||
@ -1221,9 +1225,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1
|
||||
)
|
||||
frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1)
|
||||
|
||||
original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account")
|
||||
frappe.db.set_value(
|
||||
@ -1358,8 +1360,8 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
pay.reload()
|
||||
pay.cancel()
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
|
||||
frappe.db.set_single_value(
|
||||
"Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
|
||||
)
|
||||
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
||||
|
||||
@ -1652,7 +1654,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
)
|
||||
|
||||
pi.load_from_db()
|
||||
batch_no = pi.items[0].batch_no
|
||||
batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle)
|
||||
self.assertTrue(batch_no)
|
||||
|
||||
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1))
|
||||
@ -1706,6 +1708,21 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
|
||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||
|
||||
def test_gl_entries_for_standalone_debit_note(self):
|
||||
make_purchase_invoice(qty=5, rate=500, update_stock=True)
|
||||
|
||||
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
|
||||
|
||||
# override the rate with valuation rate
|
||||
sle = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["stock_value_difference", "actual_qty"],
|
||||
filters={"voucher_no": returned_inv.name},
|
||||
)[0]
|
||||
|
||||
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
|
||||
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
@ -1794,6 +1811,32 @@ def make_purchase_invoice(**args):
|
||||
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
|
||||
pi.cost_center = args.parent_cost_center
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
batches = {}
|
||||
qty = args.qty or 5
|
||||
item_code = args.item or args.item_code or "_Test Item"
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
|
||||
serial_nos = args.get("serial_no") or []
|
||||
|
||||
bundle_id = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": item_code,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": qty,
|
||||
"batches": batches,
|
||||
"voucher_type": "Purchase Invoice",
|
||||
"serial_nos": serial_nos,
|
||||
"type_of_transaction": "Inward",
|
||||
"posting_date": args.posting_date or today(),
|
||||
"posting_time": args.posting_time,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
pi.append(
|
||||
"items",
|
||||
{
|
||||
@ -1808,12 +1851,11 @@ def make_purchase_invoice(**args):
|
||||
"discount_account": args.discount_account or None,
|
||||
"discount_amount": args.discount_amount or 0,
|
||||
"conversion_factor": 1.0,
|
||||
"serial_no": args.serial_no,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
"stock_uom": args.uom or "_Test UOM",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"project": args.project,
|
||||
"rejected_warehouse": args.rejected_warehouse or "",
|
||||
"rejected_serial_no": args.rejected_serial_no or "",
|
||||
"asset_location": args.location or "",
|
||||
"allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0,
|
||||
},
|
||||
@ -1857,6 +1899,31 @@ def make_purchase_invoice_against_cost_center(**args):
|
||||
if args.supplier_warehouse:
|
||||
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
batches = {}
|
||||
qty = args.qty or 5
|
||||
item_code = args.item or args.item_code or "_Test Item"
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
|
||||
serial_nos = args.get("serial_no") or []
|
||||
|
||||
bundle_id = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": item_code,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": qty,
|
||||
"batches": batches,
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"serial_nos": serial_nos,
|
||||
"posting_date": args.posting_date or today(),
|
||||
"posting_time": args.posting_time,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
pi.append(
|
||||
"items",
|
||||
{
|
||||
@ -1867,12 +1934,11 @@ def make_purchase_invoice_against_cost_center(**args):
|
||||
"rejected_qty": args.rejected_qty or 0,
|
||||
"rate": args.rate or 50,
|
||||
"conversion_factor": 1.0,
|
||||
"serial_no": args.serial_no,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
"stock_uom": "_Test UOM",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"project": args.project,
|
||||
"rejected_warehouse": args.rejected_warehouse or "",
|
||||
"rejected_serial_no": args.rejected_serial_no or "",
|
||||
},
|
||||
)
|
||||
if not args.do_not_save:
|
||||
|
@ -64,9 +64,11 @@
|
||||
"warehouse",
|
||||
"from_warehouse",
|
||||
"quality_inspection",
|
||||
"serial_and_batch_bundle",
|
||||
"serial_no",
|
||||
"col_br_wh",
|
||||
"rejected_warehouse",
|
||||
"rejected_serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"rejected_serial_no",
|
||||
"manufacture_details",
|
||||
@ -436,9 +438,10 @@
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"no_copy": 1,
|
||||
"options": "Batch"
|
||||
"options": "Batch",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_br_wh",
|
||||
@ -448,8 +451,9 @@
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"no_copy": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
@ -457,7 +461,8 @@
|
||||
"fieldtype": "Text",
|
||||
"label": "Rejected Serial No",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting",
|
||||
@ -875,12 +880,30 @@
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "rejected_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Rejected Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-29 13:01:20.438217",
|
||||
"modified": "2023-04-01 20:08:54.545160",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -520,6 +520,7 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Mobile No",
|
||||
"options": "Phone",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -2154,7 +2155,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-04-28 14:15:59.901154",
|
||||
"modified": "2023-06-03 16:22:16.219333",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -39,13 +39,8 @@ from erpnext.controllers.accounts_controller import (
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
||||
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
|
||||
from erpnext.stock.doctype.serial_no.serial_no import (
|
||||
get_delivery_note_serial_no,
|
||||
get_serial_nos,
|
||||
update_serial_nos_after_submit,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
|
||||
|
||||
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||
|
||||
@ -132,9 +127,6 @@ class SalesInvoice(SellingController):
|
||||
if not self.is_opening:
|
||||
self.is_opening = "No"
|
||||
|
||||
if self._action != "submit" and self.update_stock and not self.is_return:
|
||||
set_batch_nos(self, "warehouse", True)
|
||||
|
||||
if self.redeem_loyalty_points:
|
||||
lp = frappe.get_doc("Loyalty Program", self.loyalty_program)
|
||||
self.loyalty_redemption_account = (
|
||||
@ -265,8 +257,6 @@ class SalesInvoice(SellingController):
|
||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||
if self.update_stock == 1:
|
||||
self.update_stock_ledger()
|
||||
if self.is_return and self.update_stock:
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
@ -279,8 +269,6 @@ class SalesInvoice(SellingController):
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.check_credit_limit()
|
||||
|
||||
self.update_serial_no()
|
||||
|
||||
if not cint(self.is_pos) == 1 and not self.is_return:
|
||||
self.update_against_document_in_jv()
|
||||
|
||||
@ -364,7 +352,6 @@ class SalesInvoice(SellingController):
|
||||
if not self.is_return:
|
||||
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.update_serial_no(in_cancel=True)
|
||||
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||
@ -403,6 +390,7 @@ class SalesInvoice(SellingController):
|
||||
"Repost Payment Ledger",
|
||||
"Repost Payment Ledger Items",
|
||||
"Payment Ledger Entry",
|
||||
"Serial and Batch Bundle",
|
||||
)
|
||||
|
||||
def update_status_updater_args(self):
|
||||
@ -1016,10 +1004,16 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def check_prev_docstatus(self):
|
||||
for d in self.get("items"):
|
||||
if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
|
||||
if (
|
||||
d.sales_order
|
||||
and frappe.db.get_value("Sales Order", d.sales_order, "docstatus", cache=True) != 1
|
||||
):
|
||||
frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
|
||||
|
||||
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
|
||||
if (
|
||||
d.delivery_note
|
||||
and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus", cache=True) != 1
|
||||
):
|
||||
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
@ -1529,20 +1523,6 @@ class SalesInvoice(SellingController):
|
||||
self.set("write_off_amount", reference_doc.get("write_off_amount"))
|
||||
self.due_date = None
|
||||
|
||||
def update_serial_no(self, in_cancel=False):
|
||||
"""update Sales Invoice refrence in Serial No"""
|
||||
invoice = None if (in_cancel or self.is_return) else self.name
|
||||
if in_cancel and self.is_return:
|
||||
invoice = self.return_against
|
||||
|
||||
for item in self.items:
|
||||
if not item.serial_no:
|
||||
continue
|
||||
|
||||
for serial_no in get_serial_nos(item.serial_no):
|
||||
if serial_no and frappe.db.get_value("Serial No", serial_no, "item_code") == item.item_code:
|
||||
frappe.db.set_value("Serial No", serial_no, "sales_invoice", invoice)
|
||||
|
||||
def validate_serial_numbers(self):
|
||||
"""
|
||||
validate serial number agains Delivery Note and Sales Invoice
|
||||
|
@ -30,6 +30,11 @@ from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
|
||||
get_qty_after_transaction,
|
||||
@ -1058,7 +1063,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(pos.write_off_amount, 10)
|
||||
|
||||
def test_pos_with_no_gl_entry_for_change_amount(self):
|
||||
frappe.db.set_value("Accounts Settings", None, "post_change_gl_entries", 0)
|
||||
frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 0)
|
||||
|
||||
make_pos_profile(
|
||||
company="_Test Company with perpetual inventory",
|
||||
@ -1108,7 +1113,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True)
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "post_change_gl_entries", 1)
|
||||
frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 1)
|
||||
|
||||
def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False):
|
||||
if validate_without_change_gle:
|
||||
@ -1348,55 +1353,47 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
se.load_from_db()
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
si.update_stock = 1
|
||||
si.get("items")[0].item_code = "_Test Serialized Item With Series"
|
||||
si.get("items")[0].qty = 1
|
||||
si.get("items")[0].serial_no = serial_nos[0]
|
||||
si.get("items")[0].warehouse = se.get("items")[0].t_warehouse
|
||||
si.get("items")[0].serial_and_batch_bundle = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": si.get("items")[0].item_code,
|
||||
"warehouse": si.get("items")[0].warehouse,
|
||||
"company": si.company,
|
||||
"qty": 1,
|
||||
"voucher_type": "Stock Entry",
|
||||
"serial_nos": [serial_nos[0]],
|
||||
"posting_date": si.posting_date,
|
||||
"posting_time": si.posting_time,
|
||||
"type_of_transaction": "Outward",
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no"), si.name
|
||||
)
|
||||
|
||||
return si
|
||||
|
||||
def test_serialized_cancel(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
si = self.test_serialized()
|
||||
si.cancel()
|
||||
|
||||
serial_nos = get_serial_nos(si.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(si.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC"
|
||||
)
|
||||
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no"))
|
||||
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"))
|
||||
|
||||
def test_serialize_status(self):
|
||||
serial_no = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Serial No",
|
||||
"item_code": "_Test Serialized Item With Series",
|
||||
"serial_no": make_autoname("SR", "Serial No"),
|
||||
}
|
||||
)
|
||||
serial_no.save()
|
||||
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
si.update_stock = 1
|
||||
si.get("items")[0].item_code = "_Test Serialized Item With Series"
|
||||
si.get("items")[0].qty = 1
|
||||
si.get("items")[0].serial_no = serial_no.name
|
||||
si.insert()
|
||||
|
||||
self.assertRaises(SerialNoWarehouseError, si.submit)
|
||||
|
||||
def test_serial_numbers_against_delivery_note(self):
|
||||
"""
|
||||
@ -1404,20 +1401,22 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
serial numbers are same
|
||||
"""
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
se.load_from_db()
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
|
||||
|
||||
dn = create_delivery_note(item=se.get("items")[0].item_code, serial_no=serial_nos[0])
|
||||
dn = create_delivery_note(item=se.get("items")[0].item_code, serial_no=[serial_nos])
|
||||
dn.submit()
|
||||
dn.load_from_db()
|
||||
|
||||
serial_nos = get_serial_nos_from_bundle(dn.get("items")[0].serial_and_batch_bundle)[0]
|
||||
self.assertTrue(get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0])
|
||||
|
||||
si = make_sales_invoice(dn.name)
|
||||
si.save()
|
||||
|
||||
self.assertEqual(si.get("items")[0].serial_no, dn.get("items")[0].serial_no)
|
||||
|
||||
def test_return_sales_invoice(self):
|
||||
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
|
||||
|
||||
@ -2453,7 +2452,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"Check mapping (expense account) of inter company SI to PI in absence of default warehouse."
|
||||
# setup
|
||||
old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1)
|
||||
|
||||
old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company 1")
|
||||
frappe.local.enable_perpetual_inventory["_Test Company 1"] = 1
|
||||
@ -2507,7 +2506,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
# tear down
|
||||
frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
|
||||
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock)
|
||||
|
||||
def test_sle_for_target_warehouse(self):
|
||||
se = make_stock_entry(
|
||||
@ -2573,7 +2572,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"posting_date": si.posting_date,
|
||||
"posting_time": si.posting_time,
|
||||
"qty": -1 * flt(d.get("stock_qty")),
|
||||
"serial_no": d.serial_no,
|
||||
"serial_and_batch_bundle": d.serial_and_batch_bundle,
|
||||
"company": si.company,
|
||||
"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name,
|
||||
@ -2899,7 +2898,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
party_link = create_party_link("Supplier", supplier, customer)
|
||||
|
||||
# enable common party accounting
|
||||
frappe.db.set_value("Accounts Settings", None, "enable_common_party_accounting", 1)
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1)
|
||||
|
||||
# create a sales invoice
|
||||
si = create_sales_invoice(customer=customer, parent_cost_center="_Test Cost Center - _TC")
|
||||
@ -2926,7 +2925,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(jv[0], si.grand_total)
|
||||
|
||||
party_link.delete()
|
||||
frappe.db.set_value("Accounts Settings", None, "enable_common_party_accounting", 0)
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0)
|
||||
|
||||
def test_payment_statuses(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
@ -2982,7 +2981,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
# Sales Invoice with Payment Schedule
|
||||
si_with_payment_schedule = create_sales_invoice(do_not_submit=True)
|
||||
si_with_payment_schedule.extend(
|
||||
si_with_payment_schedule.set(
|
||||
"payment_schedule",
|
||||
[
|
||||
{
|
||||
@ -3046,7 +3045,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertRaises(frappe.ValidationError, si.save)
|
||||
|
||||
def test_sales_invoice_submission_post_account_freezing_date(self):
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", add_days(getdate(), 1))
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", add_days(getdate(), 1))
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.posting_date = add_days(getdate(), 1)
|
||||
si.save()
|
||||
@ -3055,7 +3054,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.posting_date = getdate()
|
||||
si.submit()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def test_over_billing_case_against_delivery_note(self):
|
||||
"""
|
||||
@ -3067,7 +3066,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
over_billing_allowance = frappe.db.get_single_value(
|
||||
"Accounts Settings", "over_billing_allowance"
|
||||
)
|
||||
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
|
||||
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 0)
|
||||
|
||||
dn = create_delivery_note()
|
||||
dn.submit()
|
||||
@ -3083,7 +3082,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
self.assertTrue("cannot overbill" in str(err.exception).lower())
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance)
|
||||
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", over_billing_allowance)
|
||||
|
||||
def test_multi_currency_deferred_revenue_via_journal_entry(self):
|
||||
deferred_account = create_account(
|
||||
@ -3122,7 +3121,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", getdate("2019-01-31"))
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", getdate("2019-01-31"))
|
||||
|
||||
pda1 = frappe.get_doc(
|
||||
dict(
|
||||
@ -3167,14 +3166,14 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
acc_settings.submit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def test_standalone_serial_no_return(self):
|
||||
si = create_sales_invoice(
|
||||
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
|
||||
)
|
||||
si.reload()
|
||||
self.assertTrue(si.items[0].serial_no)
|
||||
self.assertTrue(get_serial_nos_from_bundle(si.items[0].serial_and_batch_bundle))
|
||||
|
||||
def test_sales_invoice_with_disabled_account(self):
|
||||
try:
|
||||
@ -3217,9 +3216,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1
|
||||
)
|
||||
frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1)
|
||||
|
||||
jv = make_journal_entry("_Test Receivable USD - _TC", "_Test Bank - _TC", -7000, save=False)
|
||||
|
||||
@ -3262,8 +3259,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, nowdate())
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
|
||||
frappe.db.set_single_value(
|
||||
"Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
|
||||
)
|
||||
|
||||
def test_batch_expiry_for_sales_invoice_return(self):
|
||||
@ -3283,11 +3280,11 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
pr = make_purchase_receipt(qty=1, item_code=item.name)
|
||||
|
||||
batch_no = pr.items[0].batch_no
|
||||
batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
|
||||
si = create_sales_invoice(qty=1, item_code=item.name, update_stock=1, batch_no=batch_no)
|
||||
|
||||
si.load_from_db()
|
||||
batch_no = si.items[0].batch_no
|
||||
batch_no = get_batch_from_bundle(si.items[0].serial_and_batch_bundle)
|
||||
self.assertTrue(batch_no)
|
||||
|
||||
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
|
||||
@ -3445,6 +3442,33 @@ def create_sales_invoice(**args):
|
||||
si.naming_series = args.naming_series or "T-SINV-"
|
||||
si.cost_center = args.parent_cost_center
|
||||
|
||||
bundle_id = None
|
||||
if si.update_stock and (args.get("batch_no") or args.get("serial_no")):
|
||||
batches = {}
|
||||
qty = args.qty or 1
|
||||
item_code = args.item or args.item_code or "_Test Item"
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
|
||||
serial_nos = args.get("serial_no") or []
|
||||
|
||||
bundle_id = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": item_code,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": qty,
|
||||
"batches": batches,
|
||||
"voucher_type": "Sales Invoice",
|
||||
"serial_nos": serial_nos,
|
||||
"type_of_transaction": "Outward" if not args.is_return else "Inward",
|
||||
"posting_date": si.posting_date or today(),
|
||||
"posting_time": si.posting_time,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
si.append(
|
||||
"items",
|
||||
{
|
||||
@ -3464,10 +3488,9 @@ def create_sales_invoice(**args):
|
||||
"discount_amount": args.discount_amount or 0,
|
||||
"asset": args.asset or None,
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
"conversion_factor": args.get("conversion_factor", 1),
|
||||
"incoming_rate": args.incoming_rate or 0,
|
||||
"batch_no": args.batch_no or None,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
},
|
||||
)
|
||||
|
||||
@ -3477,6 +3500,8 @@ def create_sales_invoice(**args):
|
||||
si.submit()
|
||||
else:
|
||||
si.payment_schedule = []
|
||||
|
||||
si.load_from_db()
|
||||
else:
|
||||
si.payment_schedule = []
|
||||
|
||||
@ -3511,7 +3536,6 @@ def create_sales_invoice_against_cost_center(**args):
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -81,6 +81,7 @@
|
||||
"warehouse",
|
||||
"target_warehouse",
|
||||
"quality_inspection",
|
||||
"serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"incoming_rate",
|
||||
"col_break5",
|
||||
@ -600,10 +601,10 @@
|
||||
{
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"print_hide": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break5",
|
||||
@ -620,10 +621,11 @@
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"oldfieldname": "serial_no",
|
||||
"oldfieldtype": "Small Text"
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_group",
|
||||
@ -885,12 +887,20 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Item Scanned",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-12-28 16:17:33.484531",
|
||||
"modified": "2023-03-12 13:42:24.303113",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -15,7 +15,7 @@ test_records = frappe.get_test_records("Tax Rule")
|
||||
class TestTaxRule(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0)
|
||||
frappe.db.set_single_value("Shopping Cart Settings", "enabled", 0)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, getdate
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import cint, flt, getdate
|
||||
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
@ -346,26 +348,33 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
def get_advance_vouchers(
|
||||
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 = {
|
||||
dr_or_cr: [">", 0],
|
||||
"is_opening": "No",
|
||||
"is_cancelled": 0,
|
||||
"party_type": party_type,
|
||||
"party": ["in", parties],
|
||||
}
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
if party_type == "Customer":
|
||||
filters.update({"against_voucher": ["is", "not set"]})
|
||||
conditions = []
|
||||
|
||||
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:
|
||||
filters["company"] = company
|
||||
if from_date and to_date:
|
||||
filters["posting_date"] = ["between", (from_date, to_date)]
|
||||
conditions.append(ple.company == company)
|
||||
|
||||
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):
|
||||
@ -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):
|
||||
tcs_amount = 0
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
# sum of debit entries made from sales invoices
|
||||
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'
|
||||
|
||||
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 = (
|
||||
frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"party": ["in", parties],
|
||||
"company": inv.company,
|
||||
"voucher_no": ["in", adv_vouchers],
|
||||
},
|
||||
"sum(credit)",
|
||||
)
|
||||
or 0.0
|
||||
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0
|
||||
)
|
||||
|
||||
# sum of credit entries made from sales invoice
|
||||
@ -569,7 +581,12 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
|
||||
tds_amount = 0
|
||||
limit_consumed = frappe.db.get_value(
|
||||
"Purchase Invoice",
|
||||
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
|
||||
{
|
||||
"supplier": ("in", parties),
|
||||
"apply_tds": 1,
|
||||
"docstatus": 1,
|
||||
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
|
||||
},
|
||||
"sum(tax_withholding_net_total)",
|
||||
)
|
||||
|
||||
@ -584,10 +601,10 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
|
||||
|
||||
|
||||
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
||||
if current_amount < (certificate_limit - deducted_amount):
|
||||
if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0:
|
||||
return current_amount * rate / 100
|
||||
else:
|
||||
ltds_amount = certificate_limit - deducted_amount
|
||||
ltds_amount = certificate_limit - flt(deducted_amount)
|
||||
tds_amount = current_amount - ltds_amount
|
||||
|
||||
return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
|
||||
@ -598,9 +615,9 @@ def is_valid_certificate(
|
||||
):
|
||||
valid = False
|
||||
|
||||
if (
|
||||
getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)
|
||||
) and certificate_limit > deducted_amount:
|
||||
available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount)
|
||||
|
||||
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
|
||||
valid = True
|
||||
|
||||
return valid
|
||||
|
@ -152,6 +152,60 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in reversed(invoices):
|
||||
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):
|
||||
frappe.db.set_value(
|
||||
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
|
||||
|
@ -2,6 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.contacts.doctype.address.address import (
|
||||
@ -680,12 +682,12 @@ def set_taxes(
|
||||
else:
|
||||
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"})
|
||||
|
||||
if party_type == "Lead":
|
||||
if party_type in ["Lead", "Prospect"]:
|
||||
args["customer"] = None
|
||||
del args["lead"]
|
||||
del args[frappe.scrub(party_type)]
|
||||
else:
|
||||
args.update({"tax_type": "Purchase"})
|
||||
|
||||
@ -883,7 +885,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
|
||||
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.
|
||||
and/or `is_shipping_address = 1`.
|
||||
@ -894,22 +896,23 @@ def get_party_shipping_address(doctype, name):
|
||||
:param name: Party name
|
||||
:return: String
|
||||
"""
|
||||
out = frappe.db.sql(
|
||||
"SELECT dl.parent "
|
||||
"from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name "
|
||||
"where "
|
||||
"dl.link_doctype=%s "
|
||||
"and dl.link_name=%s "
|
||||
"and dl.parenttype='Address' "
|
||||
"and ifnull(ta.disabled, 0) = 0 and"
|
||||
"(ta.address_type='Shipping' or ta.is_shipping_address=1) "
|
||||
"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
|
||||
(doctype, name),
|
||||
shipping_addresses = frappe.get_all(
|
||||
"Address",
|
||||
filters=[
|
||||
["Dynamic Link", "link_doctype", "=", doctype],
|
||||
["Dynamic Link", "link_name", "=", name],
|
||||
["disabled", "=", 0],
|
||||
],
|
||||
or_filters=[
|
||||
["is_shipping_address", "=", 1],
|
||||
["address_type", "=", "Shipping"],
|
||||
],
|
||||
pluck="name",
|
||||
limit=1,
|
||||
order_by="is_shipping_address DESC",
|
||||
)
|
||||
if out:
|
||||
return out[0][0]
|
||||
else:
|
||||
return ""
|
||||
|
||||
return shipping_addresses[0] if shipping_addresses else None
|
||||
|
||||
|
||||
def get_partywise_advanced_payment_amount(
|
||||
@ -943,31 +946,32 @@ def get_partywise_advanced_payment_amount(
|
||||
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.
|
||||
Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
|
||||
Returns contact name only if there is a primary contact for given doctype and name.
|
||||
|
||||
Else returns None
|
||||
|
||||
:param doctype: Party Doctype
|
||||
:param name: Party name
|
||||
:return: String
|
||||
"""
|
||||
out = frappe.db.sql(
|
||||
"""
|
||||
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
|
||||
FROM `tabDynamic Link` dl
|
||||
INNER JOIN `tabContact` c ON c.name = dl.parent
|
||||
WHERE
|
||||
dl.link_doctype=%s AND
|
||||
dl.link_name=%s AND
|
||||
dl.parenttype = 'Contact'
|
||||
ORDER BY is_primary_contact DESC, is_billing_contact DESC
|
||||
""",
|
||||
(doctype, name),
|
||||
contacts = frappe.get_all(
|
||||
"Contact",
|
||||
filters=[
|
||||
["Dynamic Link", "link_doctype", "=", doctype],
|
||||
["Dynamic Link", "link_name", "=", name],
|
||||
],
|
||||
or_filters=[
|
||||
["is_primary_contact", "=", 1],
|
||||
["is_billing_contact", "=", 1],
|
||||
],
|
||||
pluck="name",
|
||||
limit=1,
|
||||
order_by="is_primary_contact DESC, is_billing_contact DESC",
|
||||
)
|
||||
if out:
|
||||
try:
|
||||
return out[0][0]
|
||||
except Exception:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
return contacts[0] if contacts else None
|
||||
|
||||
|
||||
def add_party_account(party_type, party, company, account):
|
||||
|
@ -181,6 +181,16 @@ class ReceivablePayableReport(object):
|
||||
return
|
||||
|
||||
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)
|
||||
|
||||
if not row:
|
||||
@ -610,7 +620,7 @@ class ReceivablePayableReport(object):
|
||||
|
||||
def get_return_entries(self):
|
||||
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)
|
||||
if 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):
|
||||
frappe.set_user("Administrator")
|
||||
@ -256,7 +317,7 @@ def make_payment(docname):
|
||||
|
||||
|
||||
def make_credit_note(docname):
|
||||
create_sales_invoice(
|
||||
credit_note = create_sales_invoice(
|
||||
company="_Test Company 2",
|
||||
customer="_Test Customer 2",
|
||||
currency="EUR",
|
||||
@ -269,3 +330,5 @@ def make_credit_note(docname):
|
||||
is_return=1,
|
||||
return_against=docname,
|
||||
)
|
||||
|
||||
return credit_note
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr
|
||||
from frappe.utils import cstr
|
||||
|
||||
from erpnext.accounts.report.financial_statements import (
|
||||
get_columns,
|
||||
@ -20,11 +20,6 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if cint(frappe.db.get_single_value("Accounts Settings", "use_custom_cash_flow")):
|
||||
from erpnext.accounts.report.cash_flow.custom_cash_flow import execute as execute_custom
|
||||
|
||||
return execute_custom(filters=filters)
|
||||
|
||||
period_list = get_period_list(
|
||||
filters.from_fiscal_year,
|
||||
filters.to_fiscal_year,
|
||||
|
@ -1,567 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import add_to_date, flt, get_date_str
|
||||
|
||||
from erpnext.accounts.report.financial_statements import get_columns, get_data, get_period_list
|
||||
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
|
||||
get_net_profit_loss,
|
||||
)
|
||||
|
||||
|
||||
def get_mapper_for(mappers, position):
|
||||
mapper_list = list(filter(lambda x: x["position"] == position, mappers))
|
||||
return mapper_list[0] if mapper_list else []
|
||||
|
||||
|
||||
def get_mappers_from_db():
|
||||
return frappe.get_all(
|
||||
"Cash Flow Mapper",
|
||||
fields=[
|
||||
"section_name",
|
||||
"section_header",
|
||||
"section_leader",
|
||||
"section_subtotal",
|
||||
"section_footer",
|
||||
"name",
|
||||
"position",
|
||||
],
|
||||
order_by="position",
|
||||
)
|
||||
|
||||
|
||||
def get_accounts_in_mappers(mapping_names):
|
||||
cfm = frappe.qb.DocType("Cash Flow Mapping")
|
||||
cfma = frappe.qb.DocType("Cash Flow Mapping Accounts")
|
||||
result = (
|
||||
frappe.qb.select(
|
||||
cfma.name,
|
||||
cfm.label,
|
||||
cfm.is_working_capital,
|
||||
cfm.is_income_tax_liability,
|
||||
cfm.is_income_tax_expense,
|
||||
cfm.is_finance_cost,
|
||||
cfm.is_finance_cost_adjustment,
|
||||
cfma.account,
|
||||
)
|
||||
.from_(cfm)
|
||||
.join(cfma)
|
||||
.on(cfm.name == cfma.parent)
|
||||
.where(cfma.parent.isin(mapping_names))
|
||||
).run()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def setup_mappers(mappers):
|
||||
cash_flow_accounts = []
|
||||
|
||||
for mapping in mappers:
|
||||
mapping["account_types"] = []
|
||||
mapping["tax_liabilities"] = []
|
||||
mapping["tax_expenses"] = []
|
||||
mapping["finance_costs"] = []
|
||||
mapping["finance_costs_adjustments"] = []
|
||||
doc = frappe.get_doc("Cash Flow Mapper", mapping["name"])
|
||||
mapping_names = [item.name for item in doc.accounts]
|
||||
|
||||
if not mapping_names:
|
||||
continue
|
||||
|
||||
accounts = get_accounts_in_mappers(mapping_names)
|
||||
|
||||
account_types = [
|
||||
dict(
|
||||
name=account[0],
|
||||
account_name=account[7],
|
||||
label=account[1],
|
||||
is_working_capital=account[2],
|
||||
is_income_tax_liability=account[3],
|
||||
is_income_tax_expense=account[4],
|
||||
)
|
||||
for account in accounts
|
||||
if not account[3]
|
||||
]
|
||||
|
||||
finance_costs_adjustments = [
|
||||
dict(
|
||||
name=account[0],
|
||||
account_name=account[7],
|
||||
label=account[1],
|
||||
is_finance_cost=account[5],
|
||||
is_finance_cost_adjustment=account[6],
|
||||
)
|
||||
for account in accounts
|
||||
if account[6]
|
||||
]
|
||||
|
||||
tax_liabilities = [
|
||||
dict(
|
||||
name=account[0],
|
||||
account_name=account[7],
|
||||
label=account[1],
|
||||
is_income_tax_liability=account[3],
|
||||
is_income_tax_expense=account[4],
|
||||
)
|
||||
for account in accounts
|
||||
if account[3]
|
||||
]
|
||||
|
||||
tax_expenses = [
|
||||
dict(
|
||||
name=account[0],
|
||||
account_name=account[7],
|
||||
label=account[1],
|
||||
is_income_tax_liability=account[3],
|
||||
is_income_tax_expense=account[4],
|
||||
)
|
||||
for account in accounts
|
||||
if account[4]
|
||||
]
|
||||
|
||||
finance_costs = [
|
||||
dict(name=account[0], account_name=account[7], label=account[1], is_finance_cost=account[5])
|
||||
for account in accounts
|
||||
if account[5]
|
||||
]
|
||||
|
||||
account_types_labels = sorted(
|
||||
set(
|
||||
(d["label"], d["is_working_capital"], d["is_income_tax_liability"], d["is_income_tax_expense"])
|
||||
for d in account_types
|
||||
),
|
||||
key=lambda x: x[1],
|
||||
)
|
||||
|
||||
fc_adjustment_labels = sorted(
|
||||
set(
|
||||
[
|
||||
(d["label"], d["is_finance_cost"], d["is_finance_cost_adjustment"])
|
||||
for d in finance_costs_adjustments
|
||||
if d["is_finance_cost_adjustment"]
|
||||
]
|
||||
),
|
||||
key=lambda x: x[2],
|
||||
)
|
||||
|
||||
unique_liability_labels = sorted(
|
||||
set(
|
||||
[
|
||||
(d["label"], d["is_income_tax_liability"], d["is_income_tax_expense"])
|
||||
for d in tax_liabilities
|
||||
]
|
||||
),
|
||||
key=lambda x: x[0],
|
||||
)
|
||||
|
||||
unique_expense_labels = sorted(
|
||||
set(
|
||||
[(d["label"], d["is_income_tax_liability"], d["is_income_tax_expense"]) for d in tax_expenses]
|
||||
),
|
||||
key=lambda x: x[0],
|
||||
)
|
||||
|
||||
unique_finance_costs_labels = sorted(
|
||||
set([(d["label"], d["is_finance_cost"]) for d in finance_costs]), key=lambda x: x[0]
|
||||
)
|
||||
|
||||
for label in account_types_labels:
|
||||
names = [d["account_name"] for d in account_types if d["label"] == label[0]]
|
||||
m = dict(label=label[0], names=names, is_working_capital=label[1])
|
||||
mapping["account_types"].append(m)
|
||||
|
||||
for label in fc_adjustment_labels:
|
||||
names = [d["account_name"] for d in finance_costs_adjustments if d["label"] == label[0]]
|
||||
m = dict(label=label[0], names=names)
|
||||
mapping["finance_costs_adjustments"].append(m)
|
||||
|
||||
for label in unique_liability_labels:
|
||||
names = [d["account_name"] for d in tax_liabilities if d["label"] == label[0]]
|
||||
m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
|
||||
mapping["tax_liabilities"].append(m)
|
||||
|
||||
for label in unique_expense_labels:
|
||||
names = [d["account_name"] for d in tax_expenses if d["label"] == label[0]]
|
||||
m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
|
||||
mapping["tax_expenses"].append(m)
|
||||
|
||||
for label in unique_finance_costs_labels:
|
||||
names = [d["account_name"] for d in finance_costs if d["label"] == label[0]]
|
||||
m = dict(label=label[0], names=names, is_finance_cost=label[1])
|
||||
mapping["finance_costs"].append(m)
|
||||
|
||||
cash_flow_accounts.append(mapping)
|
||||
|
||||
return cash_flow_accounts
|
||||
|
||||
|
||||
def add_data_for_operating_activities(
|
||||
filters, company_currency, profit_data, period_list, light_mappers, mapper, data
|
||||
):
|
||||
has_added_working_capital_header = False
|
||||
section_data = []
|
||||
|
||||
data.append(
|
||||
{
|
||||
"account_name": mapper["section_header"],
|
||||
"parent_account": None,
|
||||
"indent": 0.0,
|
||||
"account": mapper["section_header"],
|
||||
}
|
||||
)
|
||||
|
||||
if profit_data:
|
||||
profit_data.update(
|
||||
{"indent": 1, "parent_account": get_mapper_for(light_mappers, position=1)["section_header"]}
|
||||
)
|
||||
data.append(profit_data)
|
||||
section_data.append(profit_data)
|
||||
|
||||
data.append(
|
||||
{
|
||||
"account_name": mapper["section_leader"],
|
||||
"parent_account": None,
|
||||
"indent": 1.0,
|
||||
"account": mapper["section_leader"],
|
||||
}
|
||||
)
|
||||
|
||||
for account in mapper["account_types"]:
|
||||
if account["is_working_capital"] and not has_added_working_capital_header:
|
||||
data.append(
|
||||
{
|
||||
"account_name": "Movement in working capital",
|
||||
"parent_account": None,
|
||||
"indent": 1.0,
|
||||
"account": "",
|
||||
}
|
||||
)
|
||||
has_added_working_capital_header = True
|
||||
|
||||
account_data = _get_account_type_based_data(
|
||||
filters, account["names"], period_list, filters.accumulated_values
|
||||
)
|
||||
|
||||
if not account["is_working_capital"]:
|
||||
for key in account_data:
|
||||
if key != "total":
|
||||
account_data[key] *= -1
|
||||
|
||||
if account_data["total"] != 0:
|
||||
account_data.update(
|
||||
{
|
||||
"account_name": account["label"],
|
||||
"account": account["names"],
|
||||
"indent": 1.0,
|
||||
"parent_account": mapper["section_header"],
|
||||
"currency": company_currency,
|
||||
}
|
||||
)
|
||||
data.append(account_data)
|
||||
section_data.append(account_data)
|
||||
|
||||
_add_total_row_account(
|
||||
data, section_data, mapper["section_subtotal"], period_list, company_currency, indent=1
|
||||
)
|
||||
|
||||
# calculate adjustment for tax paid and add to data
|
||||
if not mapper["tax_liabilities"]:
|
||||
mapper["tax_liabilities"] = [
|
||||
dict(label="Income tax paid", names=[""], tax_liability=1, tax_expense=0)
|
||||
]
|
||||
|
||||
for account in mapper["tax_liabilities"]:
|
||||
tax_paid = calculate_adjustment(
|
||||
filters,
|
||||
mapper["tax_liabilities"],
|
||||
mapper["tax_expenses"],
|
||||
filters.accumulated_values,
|
||||
period_list,
|
||||
)
|
||||
|
||||
if tax_paid:
|
||||
tax_paid.update(
|
||||
{
|
||||
"parent_account": mapper["section_header"],
|
||||
"currency": company_currency,
|
||||
"account_name": account["label"],
|
||||
"indent": 1.0,
|
||||
}
|
||||
)
|
||||
data.append(tax_paid)
|
||||
section_data.append(tax_paid)
|
||||
|
||||
if not mapper["finance_costs_adjustments"]:
|
||||
mapper["finance_costs_adjustments"] = [dict(label="Interest Paid", names=[""])]
|
||||
|
||||
for account in mapper["finance_costs_adjustments"]:
|
||||
interest_paid = calculate_adjustment(
|
||||
filters,
|
||||
mapper["finance_costs_adjustments"],
|
||||
mapper["finance_costs"],
|
||||
filters.accumulated_values,
|
||||
period_list,
|
||||
)
|
||||
|
||||
if interest_paid:
|
||||
interest_paid.update(
|
||||
{
|
||||
"parent_account": mapper["section_header"],
|
||||
"currency": company_currency,
|
||||
"account_name": account["label"],
|
||||
"indent": 1.0,
|
||||
}
|
||||
)
|
||||
data.append(interest_paid)
|
||||
section_data.append(interest_paid)
|
||||
|
||||
_add_total_row_account(
|
||||
data, section_data, mapper["section_footer"], period_list, company_currency
|
||||
)
|
||||
|
||||
|
||||
def calculate_adjustment(
|
||||
filters, non_expense_mapper, expense_mapper, use_accumulated_values, period_list
|
||||
):
|
||||
liability_accounts = [d["names"] for d in non_expense_mapper]
|
||||
expense_accounts = [d["names"] for d in expense_mapper]
|
||||
|
||||
non_expense_closing = _get_account_type_based_data(filters, liability_accounts, period_list, 0)
|
||||
|
||||
non_expense_opening = _get_account_type_based_data(
|
||||
filters, liability_accounts, period_list, use_accumulated_values, opening_balances=1
|
||||
)
|
||||
|
||||
expense_data = _get_account_type_based_data(
|
||||
filters, expense_accounts, period_list, use_accumulated_values
|
||||
)
|
||||
|
||||
data = _calculate_adjustment(non_expense_closing, non_expense_opening, expense_data)
|
||||
return data
|
||||
|
||||
|
||||
def _calculate_adjustment(non_expense_closing, non_expense_opening, expense_data):
|
||||
account_data = {}
|
||||
for month in non_expense_opening.keys():
|
||||
if non_expense_opening[month] and non_expense_closing[month]:
|
||||
account_data[month] = (
|
||||
non_expense_opening[month] - expense_data[month] + non_expense_closing[month]
|
||||
)
|
||||
elif expense_data[month]:
|
||||
account_data[month] = expense_data[month]
|
||||
|
||||
return account_data
|
||||
|
||||
|
||||
def add_data_for_other_activities(
|
||||
filters, company_currency, profit_data, period_list, light_mappers, mapper_list, data
|
||||
):
|
||||
for mapper in mapper_list:
|
||||
section_data = []
|
||||
data.append(
|
||||
{
|
||||
"account_name": mapper["section_header"],
|
||||
"parent_account": None,
|
||||
"indent": 0.0,
|
||||
"account": mapper["section_header"],
|
||||
}
|
||||
)
|
||||
|
||||
for account in mapper["account_types"]:
|
||||
account_data = _get_account_type_based_data(
|
||||
filters, account["names"], period_list, filters.accumulated_values
|
||||
)
|
||||
if account_data["total"] != 0:
|
||||
account_data.update(
|
||||
{
|
||||
"account_name": account["label"],
|
||||
"account": account["names"],
|
||||
"indent": 1,
|
||||
"parent_account": mapper["section_header"],
|
||||
"currency": company_currency,
|
||||
}
|
||||
)
|
||||
data.append(account_data)
|
||||
section_data.append(account_data)
|
||||
|
||||
_add_total_row_account(
|
||||
data, section_data, mapper["section_footer"], period_list, company_currency
|
||||
)
|
||||
|
||||
|
||||
def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper):
|
||||
data = []
|
||||
|
||||
operating_activities_mapper = get_mapper_for(light_mappers, position=1)
|
||||
other_mappers = [
|
||||
get_mapper_for(light_mappers, position=2),
|
||||
get_mapper_for(light_mappers, position=3),
|
||||
]
|
||||
|
||||
if operating_activities_mapper:
|
||||
add_data_for_operating_activities(
|
||||
filters,
|
||||
company_currency,
|
||||
profit_data,
|
||||
period_list,
|
||||
light_mappers,
|
||||
operating_activities_mapper,
|
||||
data,
|
||||
)
|
||||
|
||||
if all(other_mappers):
|
||||
add_data_for_other_activities(
|
||||
filters, company_currency, profit_data, period_list, light_mappers, other_mappers, data
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters.periodicity:
|
||||
filters.periodicity = "Monthly"
|
||||
period_list = get_period_list(
|
||||
filters.from_fiscal_year,
|
||||
filters.to_fiscal_year,
|
||||
filters.period_start_date,
|
||||
filters.period_end_date,
|
||||
filters.filter_based_on,
|
||||
filters.periodicity,
|
||||
company=filters.company,
|
||||
)
|
||||
|
||||
mappers = get_mappers_from_db()
|
||||
|
||||
cash_flow_accounts = setup_mappers(mappers)
|
||||
|
||||
# compute net profit / loss
|
||||
income = get_data(
|
||||
filters.company,
|
||||
"Income",
|
||||
"Credit",
|
||||
period_list,
|
||||
filters=filters,
|
||||
accumulated_values=filters.accumulated_values,
|
||||
ignore_closing_entries=True,
|
||||
ignore_accumulated_values_for_fy=True,
|
||||
)
|
||||
|
||||
expense = get_data(
|
||||
filters.company,
|
||||
"Expense",
|
||||
"Debit",
|
||||
period_list,
|
||||
filters=filters,
|
||||
accumulated_values=filters.accumulated_values,
|
||||
ignore_closing_entries=True,
|
||||
ignore_accumulated_values_for_fy=True,
|
||||
)
|
||||
|
||||
net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company)
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||
|
||||
data = compute_data(
|
||||
filters, company_currency, net_profit_loss, period_list, mappers, cash_flow_accounts
|
||||
)
|
||||
|
||||
_add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency)
|
||||
columns = get_columns(
|
||||
filters.periodicity, period_list, filters.accumulated_values, filters.company
|
||||
)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def _get_account_type_based_data(
|
||||
filters, account_names, period_list, accumulated_values, opening_balances=0
|
||||
):
|
||||
if not account_names or not account_names[0] or not type(account_names[0]) == str:
|
||||
# only proceed if account_names is a list of account names
|
||||
return {}
|
||||
|
||||
from erpnext.accounts.report.cash_flow.cash_flow import get_start_date
|
||||
|
||||
company = filters.company
|
||||
data = {}
|
||||
total = 0
|
||||
GLEntry = frappe.qb.DocType("GL Entry")
|
||||
Account = frappe.qb.DocType("Account")
|
||||
|
||||
for period in period_list:
|
||||
start_date = get_start_date(period, accumulated_values, company)
|
||||
|
||||
account_subquery = (
|
||||
frappe.qb.from_(Account)
|
||||
.where((Account.name.isin(account_names)) | (Account.parent_account.isin(account_names)))
|
||||
.select(Account.name)
|
||||
.as_("account_subquery")
|
||||
)
|
||||
|
||||
if opening_balances:
|
||||
date_info = dict(date=start_date)
|
||||
months_map = {"Monthly": -1, "Quarterly": -3, "Half-Yearly": -6}
|
||||
years_map = {"Yearly": -1}
|
||||
|
||||
if months_map.get(filters.periodicity):
|
||||
date_info.update(months=months_map[filters.periodicity])
|
||||
else:
|
||||
date_info.update(years=years_map[filters.periodicity])
|
||||
|
||||
if accumulated_values:
|
||||
start, end = add_to_date(start_date, years=-1), add_to_date(period["to_date"], years=-1)
|
||||
else:
|
||||
start, end = add_to_date(**date_info), add_to_date(**date_info)
|
||||
|
||||
start, end = get_date_str(start), get_date_str(end)
|
||||
|
||||
else:
|
||||
start, end = start_date if accumulated_values else period["from_date"], period["to_date"]
|
||||
start, end = get_date_str(start), get_date_str(end)
|
||||
|
||||
result = (
|
||||
frappe.qb.from_(GLEntry)
|
||||
.select(Sum(GLEntry.credit) - Sum(GLEntry.debit))
|
||||
.where(
|
||||
(GLEntry.company == company)
|
||||
& (GLEntry.posting_date >= start)
|
||||
& (GLEntry.posting_date <= end)
|
||||
& (GLEntry.voucher_type != "Period Closing Voucher")
|
||||
& (GLEntry.account.isin(account_subquery))
|
||||
)
|
||||
).run()
|
||||
|
||||
if result and result[0]:
|
||||
gl_sum = result[0][0]
|
||||
else:
|
||||
gl_sum = 0
|
||||
|
||||
total += flt(gl_sum)
|
||||
data.setdefault(period["key"], flt(gl_sum))
|
||||
|
||||
data["total"] = total
|
||||
return data
|
||||
|
||||
|
||||
def _add_total_row_account(out, data, label, period_list, currency, indent=0.0):
|
||||
total_row = {
|
||||
"indent": indent,
|
||||
"account_name": "'" + _("{0}").format(label) + "'",
|
||||
"account": "'" + _("{0}").format(label) + "'",
|
||||
"currency": currency,
|
||||
}
|
||||
for row in data:
|
||||
if row.get("parent_account"):
|
||||
for period in period_list:
|
||||
total_row.setdefault(period.key, 0.0)
|
||||
total_row[period.key] += row.get(period.key, 0.0)
|
||||
|
||||
total_row.setdefault("total", 0.0)
|
||||
total_row["total"] += row["total"]
|
||||
|
||||
out.append(total_row)
|
||||
out.append({})
|
@ -6,7 +6,7 @@ from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, getdate
|
||||
from frappe.utils import flt, getdate
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.report.balance_sheet.balance_sheet import (
|
||||
@ -58,11 +58,6 @@ def execute(filters=None):
|
||||
fiscal_year, companies, columns, filters
|
||||
)
|
||||
else:
|
||||
if cint(frappe.db.get_single_value("Accounts Settings", "use_custom_cash_flow")):
|
||||
from erpnext.accounts.report.cash_flow.custom_cash_flow import execute as execute_custom
|
||||
|
||||
return execute_custom(filters=filters)
|
||||
|
||||
data, report_summary = get_cash_flow_data(fiscal_year, companies, filters)
|
||||
|
||||
return columns, data, message, chart, report_summary
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb, scrub
|
||||
@ -702,6 +703,9 @@ class GrossProfitGenerator(object):
|
||||
}
|
||||
)
|
||||
|
||||
if row.serial_and_batch_bundle:
|
||||
args.update({"serial_and_batch_bundle": row.serial_and_batch_bundle})
|
||||
|
||||
average_buying_rate = get_incoming_rate(args)
|
||||
self.average_buying_rate[item_code] = flt(average_buying_rate)
|
||||
|
||||
@ -804,7 +808,7 @@ class GrossProfitGenerator(object):
|
||||
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
|
||||
`tabSales Invoice Item`.cost_center
|
||||
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.serial_and_batch_bundle
|
||||
{sales_person_cols}
|
||||
{payment_term_cols}
|
||||
from
|
||||
@ -856,30 +860,30 @@ class GrossProfitGenerator(object):
|
||||
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
|
||||
"""
|
||||
|
||||
parents = []
|
||||
grouped = OrderedDict()
|
||||
|
||||
for row in self.si_list:
|
||||
if row.parent not in parents:
|
||||
parents.append(row.parent)
|
||||
# initialize list with a header row for each new parent
|
||||
grouped.setdefault(row.parent, [self.get_invoice_row(row)]).append(
|
||||
row.update(
|
||||
{"indent": 1.0, "parent_invoice": row.parent, "invoice_or_item": row.item_code}
|
||||
) # descendant rows will have indent: 1.0 or greater
|
||||
)
|
||||
|
||||
parents_index = 0
|
||||
for index, row in enumerate(self.si_list):
|
||||
if parents_index < len(parents) and row.parent == parents[parents_index]:
|
||||
invoice = self.get_invoice_row(row)
|
||||
self.si_list.insert(index, invoice)
|
||||
parents_index += 1
|
||||
# if item is a bundle, add it's components as seperate rows
|
||||
if frappe.db.exists("Product Bundle", row.item_code):
|
||||
bundled_items = self.get_bundle_items(row)
|
||||
for x in bundled_items:
|
||||
bundle_item = self.get_bundle_item_row(row, x)
|
||||
grouped.get(row.parent).append(bundle_item)
|
||||
|
||||
else:
|
||||
# skipping the bundle items rows
|
||||
if not row.indent:
|
||||
row.indent = 1.0
|
||||
row.parent_invoice = row.parent
|
||||
row.invoice_or_item = row.item_code
|
||||
self.si_list.clear()
|
||||
|
||||
if frappe.db.exists("Product Bundle", row.item_code):
|
||||
self.add_bundle_items(row, index)
|
||||
for items in grouped.values():
|
||||
self.si_list.extend(items)
|
||||
|
||||
def get_invoice_row(self, row):
|
||||
# header row format
|
||||
return frappe._dict(
|
||||
{
|
||||
"parent_invoice": "",
|
||||
@ -908,13 +912,6 @@ class GrossProfitGenerator(object):
|
||||
}
|
||||
)
|
||||
|
||||
def add_bundle_items(self, product_bundle, index):
|
||||
bundle_items = self.get_bundle_items(product_bundle)
|
||||
|
||||
for i, item in enumerate(bundle_items):
|
||||
bundle_item = self.get_bundle_item_row(product_bundle, item)
|
||||
self.si_list.insert((index + i + 1), bundle_item)
|
||||
|
||||
def get_bundle_items(self, product_bundle):
|
||||
return frappe.get_all(
|
||||
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]
|
||||
|
@ -399,8 +399,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||
`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 Item`.project,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
|
||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
||||
|
@ -5,8 +5,9 @@
|
||||
"label": "Profit and Loss"
|
||||
}
|
||||
],
|
||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}}]",
|
||||
"content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
|
||||
"creation": "2020-03-02 15:41:59.515192",
|
||||
"custom_blocks": [],
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
"for_user": "",
|
||||
@ -1060,10 +1061,11 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2023-02-23 15:32:12.135355",
|
||||
"modified": "2023-05-30 13:23:29.316711",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
"number_cards": [],
|
||||
"owner": "Administrator",
|
||||
"parent_page": "",
|
||||
"public": 1,
|
||||
|
@ -41,6 +41,8 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
setup: function(frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ['Journal Entry'];
|
||||
|
||||
frm.make_methods = {
|
||||
'Asset Movement': () => {
|
||||
frappe.call({
|
||||
|
@ -513,18 +513,22 @@ def get_gl_entries_on_asset_disposal(
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
]
|
||||
|
||||
if accumulated_depr_amount:
|
||||
gl_entries.append(
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
)
|
||||
|
||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||
if profit_amount:
|
||||
get_profit_gl_entries(
|
||||
|
@ -812,14 +812,14 @@ class TestDepreciationMethods(AssetSetup):
|
||||
number_of_depreciations_booked=1,
|
||||
opening_accumulated_depreciation=50000,
|
||||
expected_value_after_useful_life=10000,
|
||||
depreciation_start_date="2030-12-31",
|
||||
depreciation_start_date="2031-12-31",
|
||||
total_number_of_depreciations=3,
|
||||
frequency_of_depreciation=12,
|
||||
)
|
||||
|
||||
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 = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
@ -1804,7 +1804,7 @@ def set_depreciation_settings_in_company(company=None):
|
||||
company.save()
|
||||
|
||||
# Enable booking asset depreciation entry automatically
|
||||
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
|
||||
frappe.db.set_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically", 1)
|
||||
|
||||
|
||||
def enable_cwip_accounting(asset_category, enable=1):
|
||||
|
@ -6,6 +6,7 @@ frappe.provide("erpnext.assets");
|
||||
|
||||
erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.stock.StockController {
|
||||
setup() {
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
|
||||
this.setup_posting_date_time_check();
|
||||
}
|
||||
|
||||
@ -64,6 +65,18 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
};
|
||||
});
|
||||
|
||||
me.frm.set_query("serial_and_batch_bundle", "stock_items", (doc, cdt, cdn) => {
|
||||
let row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
'item_code': row.item_code,
|
||||
'voucher_type': doc.doctype,
|
||||
'voucher_no': ["in", [doc.name, ""]],
|
||||
'is_cancelled': 0,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
me.frm.set_query("item_code", "stock_items", function() {
|
||||
return erpnext.queries.item({"is_stock_item": 1});
|
||||
});
|
||||
@ -99,6 +112,17 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let sbb_field = me.frm.get_docfield('stock_items', 'serial_and_batch_bundle');
|
||||
if (sbb_field) {
|
||||
sbb_field.get_route_options_for_new_doc = (row) => {
|
||||
return {
|
||||
'item_code': row.doc.item_code,
|
||||
'warehouse': row.doc.warehouse,
|
||||
'voucher_type': me.frm.doc.doctype,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
target_item_code() {
|
||||
|
@ -334,7 +334,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-12 15:09:40.771332",
|
||||
"modified": "2022-10-12 15:09:40.771332",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization",
|
||||
|
@ -65,6 +65,10 @@ class AssetCapitalization(StockController):
|
||||
self.calculate_totals()
|
||||
self.set_title()
|
||||
|
||||
def on_update(self):
|
||||
if self.stock_items:
|
||||
self.set_serial_and_batch_bundle(table_name="stock_items")
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_source_mandatory()
|
||||
|
||||
@ -74,7 +78,12 @@ class AssetCapitalization(StockController):
|
||||
self.update_target_asset()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
self.ignore_linked_doctypes = (
|
||||
"GL Entry",
|
||||
"Stock Ledger Entry",
|
||||
"Repost Item Valuation",
|
||||
"Serial and Batch Bundle",
|
||||
)
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
@ -316,9 +325,7 @@ class AssetCapitalization(StockController):
|
||||
for d in self.stock_items:
|
||||
sle = self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"actual_qty": -flt(d.stock_qty),
|
||||
},
|
||||
{"actual_qty": -flt(d.stock_qty), "serial_and_batch_bundle": d.serial_and_batch_bundle},
|
||||
)
|
||||
sl_entries.append(sle)
|
||||
|
||||
@ -328,8 +335,6 @@ class AssetCapitalization(StockController):
|
||||
{
|
||||
"item_code": self.target_item_code,
|
||||
"warehouse": self.target_warehouse,
|
||||
"batch_no": self.target_batch_no,
|
||||
"serial_no": self.target_serial_no,
|
||||
"actual_qty": flt(self.target_qty),
|
||||
"incoming_rate": flt(self.target_incoming_rate),
|
||||
},
|
||||
|
@ -16,6 +16,11 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
get_asset_depr_schedule_doc,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
|
||||
|
||||
class TestAssetCapitalization(unittest.TestCase):
|
||||
@ -371,14 +376,32 @@ def create_asset_capitalization(**args):
|
||||
asset_capitalization.set_posting_time = 1
|
||||
|
||||
if flt(args.stock_rate):
|
||||
bundle = None
|
||||
if args.stock_batch_no or args.stock_serial_no:
|
||||
bundle = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": args.stock_item,
|
||||
"warehouse": source_warehouse,
|
||||
"company": frappe.get_cached_value("Warehouse", source_warehouse, "company"),
|
||||
"qty": (flt(args.stock_qty) or 1) * -1,
|
||||
"voucher_type": "Asset Capitalization",
|
||||
"type_of_transaction": "Outward",
|
||||
"serial_nos": args.stock_serial_no,
|
||||
"posting_date": asset_capitalization.posting_date,
|
||||
"posting_time": asset_capitalization.posting_time,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
asset_capitalization.append(
|
||||
"stock_items",
|
||||
{
|
||||
"item_code": args.stock_item or "Capitalization Source Stock Item",
|
||||
"warehouse": source_warehouse,
|
||||
"stock_qty": flt(args.stock_qty) or 1,
|
||||
"batch_no": args.stock_batch_no,
|
||||
"serial_no": args.stock_serial_no,
|
||||
"serial_and_batch_bundle": bundle,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -17,8 +17,9 @@
|
||||
"valuation_rate",
|
||||
"amount",
|
||||
"batch_and_serial_no_section",
|
||||
"batch_no",
|
||||
"serial_and_batch_bundle",
|
||||
"column_break_13",
|
||||
"batch_no",
|
||||
"serial_no",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
@ -41,7 +42,10 @@
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch"
|
||||
"no_copy": 1,
|
||||
"options": "Batch",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
@ -100,7 +104,10 @@
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No"
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
@ -139,12 +146,20 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-08 15:56:20.230548",
|
||||
"modified": "2023-04-06 01:10:17.947952",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization Stock Item",
|
||||
@ -152,5 +167,6 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -96,7 +96,6 @@ class AssetCategory(Document):
|
||||
frappe.throw(msg, title=_("Missing Account"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_asset_category_account(
|
||||
fieldname, item=None, asset=None, account=None, asset_category=None, company=None
|
||||
):
|
||||
|
@ -10,6 +10,7 @@ from frappe.utils import (
|
||||
cint,
|
||||
date_diff,
|
||||
flt,
|
||||
get_first_day,
|
||||
get_last_day,
|
||||
getdate,
|
||||
is_last_day_of_the_month,
|
||||
@ -246,10 +247,6 @@ class AssetDepreciationSchedule(Document):
|
||||
if should_get_last_day:
|
||||
schedule_date = get_last_day(schedule_date)
|
||||
|
||||
# schedule date will be a year later from start date
|
||||
# so monthly schedule date is calculated by removing 11 months from it
|
||||
monthly_schedule_date = add_months(schedule_date, -row.frequency_of_depreciation + 1)
|
||||
|
||||
# if asset is being sold or scrapped
|
||||
if date_of_disposal:
|
||||
from_date = add_months(
|
||||
@ -276,9 +273,9 @@ class AssetDepreciationSchedule(Document):
|
||||
|
||||
# For first row
|
||||
if (
|
||||
(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||
n == 0
|
||||
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||
and not self.opening_accumulated_depreciation
|
||||
and n == 0
|
||||
):
|
||||
from_date = add_days(
|
||||
asset_doc.available_for_use_date, -1
|
||||
@ -290,11 +287,26 @@ class AssetDepreciationSchedule(Document):
|
||||
row.depreciation_start_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
# For first depr schedule date will be the start date
|
||||
# so monthly schedule date is calculated by removing
|
||||
# month difference between use date and start date
|
||||
monthly_schedule_date = add_months(row.depreciation_start_date, -months + 1)
|
||||
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
|
||||
if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||
from_date = get_last_day(
|
||||
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(
|
||||
row,
|
||||
depreciation_amount,
|
||||
from_date,
|
||||
row.depreciation_start_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
# For last row
|
||||
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
||||
@ -319,9 +331,7 @@ class AssetDepreciationSchedule(Document):
|
||||
depreciation_amount_without_pro_rata, depreciation_amount
|
||||
)
|
||||
|
||||
monthly_schedule_date = add_months(schedule_date, 1)
|
||||
schedule_date = add_days(schedule_date, days)
|
||||
last_schedule_date = schedule_date
|
||||
|
||||
if not depreciation_amount:
|
||||
continue
|
||||
@ -707,3 +717,9 @@ def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
|
||||
["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)
|
||||
|
@ -182,4 +182,4 @@ def set_depreciation_settings_in_company():
|
||||
company.save()
|
||||
|
||||
# Enable booking asset depreciation entry automatically
|
||||
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
|
||||
frappe.db.set_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically", 1)
|
||||
|
@ -28,6 +28,28 @@ frappe.ui.form.on('Asset Repair', {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
frm.set_query("serial_and_batch_bundle", "stock_items", (doc, cdt, cdn) => {
|
||||
let row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
'item_code': row.item_code,
|
||||
'voucher_type': doc.doctype,
|
||||
'voucher_no': ["in", [doc.name, ""]],
|
||||
'is_cancelled': 0,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let sbb_field = frm.get_docfield('stock_items', 'serial_and_batch_bundle');
|
||||
if (sbb_field) {
|
||||
sbb_field.get_route_options_for_new_doc = (row) => {
|
||||
return {
|
||||
'item_code': row.doc.item_code,
|
||||
'voucher_type': frm.doc.doctype,
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -147,6 +147,8 @@ class AssetRepair(AccountsController):
|
||||
)
|
||||
|
||||
for stock_item in self.get("stock_items"):
|
||||
self.validate_serial_no(stock_item)
|
||||
|
||||
stock_entry.append(
|
||||
"items",
|
||||
{
|
||||
@ -154,7 +156,7 @@ class AssetRepair(AccountsController):
|
||||
"item_code": stock_item.item_code,
|
||||
"qty": stock_item.consumed_quantity,
|
||||
"basic_rate": stock_item.valuation_rate,
|
||||
"serial_no": stock_item.serial_no,
|
||||
"serial_no": stock_item.serial_and_batch_bundle,
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project,
|
||||
},
|
||||
@ -165,6 +167,23 @@ class AssetRepair(AccountsController):
|
||||
|
||||
self.db_set("stock_entry", stock_entry.name)
|
||||
|
||||
def validate_serial_no(self, stock_item):
|
||||
if not stock_item.serial_and_batch_bundle and frappe.get_cached_value(
|
||||
"Item", stock_item.item_code, "has_serial_no"
|
||||
):
|
||||
msg = f"Serial No Bundle is mandatory for Item {stock_item.item_code}"
|
||||
frappe.throw(msg, title=_("Missing Serial No Bundle"))
|
||||
|
||||
if stock_item.serial_and_batch_bundle:
|
||||
values_to_update = {
|
||||
"type_of_transaction": "Outward",
|
||||
"voucher_type": "Stock Entry",
|
||||
}
|
||||
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Bundle", stock_item.serial_and_batch_bundle, values_to_update
|
||||
)
|
||||
|
||||
def increase_stock_quantity(self):
|
||||
if self.stock_entry:
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
|
@ -4,7 +4,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe.utils import flt, nowdate, nowtime, today
|
||||
|
||||
from erpnext.assets.doctype.asset.asset import (
|
||||
get_asset_account,
|
||||
@ -19,6 +19,10 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
||||
get_asset_depr_schedule_doc,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
|
||||
|
||||
class TestAssetRepair(unittest.TestCase):
|
||||
@ -84,19 +88,19 @@ class TestAssetRepair(unittest.TestCase):
|
||||
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
|
||||
|
||||
def test_serialized_item_consumption(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
stock_entry = make_serialized_item()
|
||||
serial_nos = stock_entry.get("items")[0].serial_no
|
||||
serial_no = serial_nos.split("\n")[0]
|
||||
bundle_id = stock_entry.get("items")[0].serial_and_batch_bundle
|
||||
serial_nos = get_serial_nos_from_bundle(bundle_id)
|
||||
serial_no = serial_nos[0]
|
||||
|
||||
# should not raise any error
|
||||
create_asset_repair(
|
||||
stock_consumption=1,
|
||||
item_code=stock_entry.get("items")[0].item_code,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
serial_no=serial_no,
|
||||
serial_no=[serial_no],
|
||||
submit=1,
|
||||
)
|
||||
|
||||
@ -108,7 +112,7 @@ class TestAssetRepair(unittest.TestCase):
|
||||
)
|
||||
|
||||
asset_repair.repair_status = "Completed"
|
||||
self.assertRaises(SerialNoRequiredError, asset_repair.submit)
|
||||
self.assertRaises(frappe.ValidationError, asset_repair.submit)
|
||||
|
||||
def test_increase_in_asset_value_due_to_stock_consumption(self):
|
||||
asset = create_asset(calculate_depreciation=1, submit=1)
|
||||
@ -290,13 +294,32 @@ def create_asset_repair(**args):
|
||||
asset_repair.warehouse = args.warehouse or create_warehouse(
|
||||
"Test Warehouse", company=asset.company
|
||||
)
|
||||
|
||||
bundle = None
|
||||
if args.serial_no:
|
||||
bundle = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": args.item_code,
|
||||
"warehouse": asset_repair.warehouse,
|
||||
"company": frappe.get_cached_value("Warehouse", asset_repair.warehouse, "company"),
|
||||
"qty": (flt(args.stock_qty) or 1) * -1,
|
||||
"voucher_type": "Asset Repair",
|
||||
"type_of_transaction": "Asset Repair",
|
||||
"serial_nos": args.serial_no,
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
asset_repair.append(
|
||||
"stock_items",
|
||||
{
|
||||
"item_code": args.item_code or "_Test Stock Item",
|
||||
"valuation_rate": args.rate if args.get("rate") is not None else 100,
|
||||
"consumed_quantity": args.qty or 1,
|
||||
"serial_no": args.serial_no,
|
||||
"serial_and_batch_bundle": bundle,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,8 @@
|
||||
"valuation_rate",
|
||||
"consumed_quantity",
|
||||
"total_value",
|
||||
"serial_no"
|
||||
"serial_no",
|
||||
"serial_and_batch_bundle"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -34,7 +35,9 @@
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No"
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
@ -42,12 +45,18 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"options": "Serial and Batch Bundle"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-08 17:37:20.028290",
|
||||
"modified": "2023-04-06 02:24:20.375870",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair Consumed Item",
|
||||
@ -55,5 +64,6 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -7,12 +7,14 @@
|
||||
],
|
||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Assets\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset Category\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fixed Asset Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
|
||||
"creation": "2020-03-02 15:43:27.634865",
|
||||
"custom_blocks": [],
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
"for_user": "",
|
||||
"hide_custom": 0,
|
||||
"icon": "assets",
|
||||
"idx": 0,
|
||||
"is_hidden": 0,
|
||||
"label": "Assets",
|
||||
"links": [
|
||||
{
|
||||
@ -183,13 +185,15 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2022-01-13 18:25:41.730628",
|
||||
"modified": "2023-05-24 14:47:20.243146",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Assets",
|
||||
"number_cards": [],
|
||||
"owner": "Administrator",
|
||||
"parent_page": "",
|
||||
"parent_page": "Accounting",
|
||||
"public": 1,
|
||||
"quick_lists": [],
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 4.0,
|
||||
@ -216,4 +220,4 @@
|
||||
}
|
||||
],
|
||||
"title": "Assets"
|
||||
}
|
||||
}
|
@ -322,6 +322,7 @@
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"label": "Customer Mobile No",
|
||||
"options": "Phone",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@ -368,6 +369,7 @@
|
||||
"fieldname": "contact_mobile",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Contact Mobile No",
|
||||
"options": "Phone",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -1271,7 +1273,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-24 11:16:41.195340",
|
||||
"modified": "2023-06-03 16:19:45.710444",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -92,7 +92,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
|
||||
frappe.db.set_value("Item", "_Test Item", "over_delivery_receipt_allowance", 0)
|
||||
frappe.db.set_value("Item", "_Test Item", "over_billing_allowance", 0)
|
||||
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
|
||||
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 0)
|
||||
|
||||
def test_update_remove_child_linked_to_mr(self):
|
||||
"""Test impact on linked PO and MR on deleting/updating row."""
|
||||
@ -581,7 +581,7 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
)
|
||||
|
||||
def test_group_same_items(self):
|
||||
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
|
||||
frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1)
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Order",
|
||||
@ -836,8 +836,8 @@ class TestPurchaseOrder(FrappeTestCase):
|
||||
)
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
|
||||
frappe.db.set_value("Selling Settings", None, "maintain_same_sales_rate", 1)
|
||||
frappe.db.set_value("Buying Settings", None, "maintain_same_rate", 1)
|
||||
frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1)
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
supplier = "_Test Internal Supplier 2"
|
||||
|
@ -156,7 +156,7 @@ class TestSupplier(FrappeTestCase):
|
||||
def test_serach_fields_for_supplier(self):
|
||||
from erpnext.controllers.queries import supplier_query
|
||||
|
||||
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Naming Series")
|
||||
frappe.db.set_single_value("Buying Settings", "supp_master_name", "Naming Series")
|
||||
|
||||
supplier_name = create_supplier(supplier_name="Test Supplier 1").name
|
||||
|
||||
@ -189,7 +189,7 @@ class TestSupplier(FrappeTestCase):
|
||||
self.assertEqual(data[0].supplier_type, "Company")
|
||||
self.assertTrue("supplier_type" in data[0])
|
||||
|
||||
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Supplier Name")
|
||||
frappe.db.set_single_value("Buying Settings", "supp_master_name", "Supplier Name")
|
||||
|
||||
|
||||
def create_supplier(**args):
|
||||
|
@ -230,6 +230,7 @@
|
||||
"fieldname": "contact_mobile",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Mobile No",
|
||||
"options": "Phone",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -844,7 +845,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-14 16:43:41.714832",
|
||||
"modified": "2023-06-03 16:20:15.880114",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
@ -7,12 +7,14 @@
|
||||
],
|
||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Buying\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]",
|
||||
"creation": "2020-01-28 11:50:26.195467",
|
||||
"custom_blocks": [],
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
"for_user": "",
|
||||
"hide_custom": 0,
|
||||
"icon": "buying",
|
||||
"idx": 0,
|
||||
"is_hidden": 0,
|
||||
"label": "Buying",
|
||||
"links": [
|
||||
{
|
||||
@ -509,16 +511,18 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2022-01-13 17:26:39.090190",
|
||||
"modified": "2023-05-24 14:47:20.535772",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying",
|
||||
"number_cards": [],
|
||||
"owner": "Administrator",
|
||||
"parent_page": "",
|
||||
"public": 1,
|
||||
"quick_lists": [],
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 6.0,
|
||||
"sequence_id": 5.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"color": "Green",
|
||||
|
@ -759,6 +759,7 @@ class AccountsController(TransactionBase):
|
||||
}
|
||||
)
|
||||
|
||||
update_gl_dict_with_regional_fields(self, gl_dict)
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
dimension_dict = frappe._dict()
|
||||
|
||||
@ -921,6 +922,9 @@ class AccountsController(TransactionBase):
|
||||
|
||||
return is_inclusive
|
||||
|
||||
def should_show_taxes_as_table_in_print(self):
|
||||
return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print"))
|
||||
|
||||
def validate_advance_entries(self):
|
||||
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
|
||||
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
|
||||
@ -2514,7 +2518,7 @@ def set_order_defaults(
|
||||
Returns a Sales/Purchase Order Item child item containing the default values
|
||||
"""
|
||||
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||
child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
|
||||
child_item = frappe.new_doc(child_doctype, parent_doc=p_doc, parentfield=child_docname)
|
||||
item = frappe.get_doc("Item", trans_item.get("item_code"))
|
||||
|
||||
for field in ("item_code", "item_name", "description", "item_group"):
|
||||
|
@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe import ValidationError, _, msgprint
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.utils import cint, cstr, flt, getdate
|
||||
from frappe.utils import cint, flt, getdate
|
||||
from frappe.utils.data import nowtime
|
||||
|
||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||
@ -26,6 +26,8 @@ class BuyingController(SubcontractingController):
|
||||
self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"]
|
||||
|
||||
def validate(self):
|
||||
self.set_rate_for_standalone_debit_note()
|
||||
|
||||
super(BuyingController, self).validate()
|
||||
if getattr(self, "supplier", None) and not self.supplier_name:
|
||||
self.supplier_name = frappe.db.get_value("Supplier", self.supplier, "supplier_name")
|
||||
@ -38,6 +40,7 @@ class BuyingController(SubcontractingController):
|
||||
self.set_supplier_address()
|
||||
self.validate_asset_return()
|
||||
self.validate_auto_repeat_subscription_dates()
|
||||
self.create_package_for_transfer()
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
self.validate_purchase_receipt_if_update_stock()
|
||||
@ -58,6 +61,7 @@ class BuyingController(SubcontractingController):
|
||||
|
||||
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
self.update_valuation_rate()
|
||||
self.set_serial_and_batch_bundle()
|
||||
|
||||
def onload(self):
|
||||
super(BuyingController, self).onload()
|
||||
@ -68,6 +72,60 @@ class BuyingController(SubcontractingController):
|
||||
),
|
||||
)
|
||||
|
||||
def create_package_for_transfer(self) -> None:
|
||||
"""Create serial and batch package for Sourece Warehouse in case of inter transfer."""
|
||||
|
||||
if self.is_internal_transfer() and (
|
||||
self.doctype == "Purchase Receipt" or (self.doctype == "Purchase Invoice" and self.update_stock)
|
||||
):
|
||||
field = "delivery_note_item" if self.doctype == "Purchase Receipt" else "sales_invoice_item"
|
||||
|
||||
doctype = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item"
|
||||
|
||||
ids = [d.get(field) for d in self.get("items") if d.get(field)]
|
||||
bundle_ids = {}
|
||||
if ids:
|
||||
for bundle in frappe.get_all(
|
||||
doctype, filters={"name": ("in", ids)}, fields=["serial_and_batch_bundle", "name"]
|
||||
):
|
||||
bundle_ids[bundle.name] = bundle.serial_and_batch_bundle
|
||||
|
||||
if not bundle_ids:
|
||||
return
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get(field) and not item.serial_and_batch_bundle and bundle_ids.get(item.get(field)):
|
||||
item.serial_and_batch_bundle = self.make_package_for_transfer(
|
||||
bundle_ids.get(item.get(field)),
|
||||
item.from_warehouse,
|
||||
type_of_transaction="Outward",
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
def set_rate_for_standalone_debit_note(self):
|
||||
if self.get("is_return") and self.get("update_stock") and not self.return_against:
|
||||
for row in self.items:
|
||||
|
||||
# override the rate with valuation rate
|
||||
row.rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
"posting_date": self.get("posting_date"),
|
||||
"posting_time": self.get("posting_time"),
|
||||
"qty": row.qty,
|
||||
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
row.discount_percentage = 0.0
|
||||
row.discount_amount = 0.0
|
||||
row.margin_rate_or_amount = 0.0
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
super(BuyingController, self).set_missing_values(for_validate)
|
||||
|
||||
@ -180,6 +238,7 @@ class BuyingController(SubcontractingController):
|
||||
address_dict = {
|
||||
"supplier_address": "address_display",
|
||||
"shipping_address": "shipping_address_display",
|
||||
"billing_address": "billing_address_display",
|
||||
}
|
||||
|
||||
for address_field, address_display_field in address_dict.items():
|
||||
@ -304,8 +363,7 @@ class BuyingController(SubcontractingController):
|
||||
"posting_date": self.get("posting_date") or self.get("transation_date"),
|
||||
"posting_time": posting_time,
|
||||
"qty": -1 * flt(d.get("stock_qty")),
|
||||
"serial_no": d.get("serial_no"),
|
||||
"batch_no": d.get("batch_no"),
|
||||
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
@ -440,7 +498,7 @@ class BuyingController(SubcontractingController):
|
||||
continue
|
||||
|
||||
if d.warehouse:
|
||||
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
pr_qty = flt(flt(d.qty) * flt(d.conversion_factor), d.precision("stock_qty"))
|
||||
|
||||
if pr_qty:
|
||||
|
||||
@ -462,7 +520,15 @@ class BuyingController(SubcontractingController):
|
||||
sl_entries.append(from_warehouse_sle)
|
||||
|
||||
sle = self.get_sl_entries(
|
||||
d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
|
||||
d,
|
||||
{
|
||||
"actual_qty": flt(pr_qty),
|
||||
"serial_and_batch_bundle": (
|
||||
d.serial_and_batch_bundle
|
||||
if not self.is_internal_transfer()
|
||||
else self.get_package_for_target_warehouse(d)
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
if self.is_return:
|
||||
@ -470,7 +536,13 @@ class BuyingController(SubcontractingController):
|
||||
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
||||
)
|
||||
|
||||
sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1})
|
||||
sle.update(
|
||||
{
|
||||
"outgoing_rate": outgoing_rate,
|
||||
"recalculate_rate": 1,
|
||||
"serial_and_batch_bundle": d.serial_and_batch_bundle,
|
||||
}
|
||||
)
|
||||
if d.from_warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = d.name
|
||||
else:
|
||||
@ -502,21 +574,31 @@ class BuyingController(SubcontractingController):
|
||||
d,
|
||||
{
|
||||
"warehouse": d.rejected_warehouse,
|
||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||
"actual_qty": flt(flt(d.rejected_qty) * flt(d.conversion_factor), d.precision("stock_qty")),
|
||||
"incoming_rate": 0.0,
|
||||
"serial_and_batch_bundle": d.rejected_serial_and_batch_bundle,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||
|
||||
self.make_sl_entries(
|
||||
sl_entries,
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
|
||||
def get_package_for_target_warehouse(self, item) -> str:
|
||||
if not item.serial_and_batch_bundle:
|
||||
return ""
|
||||
|
||||
return self.make_package_for_transfer(
|
||||
item.serial_and_batch_bundle,
|
||||
item.warehouse,
|
||||
)
|
||||
|
||||
def update_ordered_and_reserved_qty(self):
|
||||
po_map = {}
|
||||
for d in self.get("items"):
|
||||
|
@ -30,10 +30,16 @@ def set_print_templates_for_taxes(doc, settings):
|
||||
doc.print_templates.update(
|
||||
{
|
||||
"total": "templates/print_formats/includes/total.html",
|
||||
"taxes": "templates/print_formats/includes/taxes.html",
|
||||
}
|
||||
)
|
||||
|
||||
if not doc.should_show_taxes_as_table_in_print():
|
||||
doc.print_templates.update(
|
||||
{
|
||||
"taxes": "templates/print_formats/includes/taxes.html",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def format_columns(display_columns, compact_fields):
|
||||
compact_fields = compact_fields + ["image", "item_code", "item_name"]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user