fix: merge conflict
This commit is contained in:
commit
8b2fef11b1
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.2.1'
|
||||
__version__ = '13.3.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -7,26 +7,30 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"auto_accounting_for_stock",
|
||||
"acc_frozen_upto",
|
||||
"frozen_accounts_modifier",
|
||||
"determine_address_tax_category_from",
|
||||
"accounts_transactions_settings_section",
|
||||
"over_billing_allowance",
|
||||
"role_allowed_to_over_bill",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"make_payment_via_journal_entry",
|
||||
"column_break_11",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"unlink_payment_on_cancellation_of_invoice",
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"automatically_fetch_payment_terms",
|
||||
"delete_linked_ledger_entries",
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"tax_settings_section",
|
||||
"determine_address_tax_category_from",
|
||||
"column_break_19",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"period_closing_settings_section",
|
||||
"acc_frozen_upto",
|
||||
"frozen_accounts_modifier",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"deferred_accounting_settings_section",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_based_on",
|
||||
"column_break_18",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_via_journal_entry",
|
||||
"submit_journal_entries",
|
||||
"print_settings",
|
||||
@ -40,15 +44,6 @@
|
||||
"use_custom_cash_flow"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, the system will post accounting entries for inventory automatically",
|
||||
"fieldname": "auto_accounting_for_stock",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Make Accounting Entry For Every Stock Movement"
|
||||
},
|
||||
{
|
||||
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
|
||||
"fieldname": "acc_frozen_upto",
|
||||
@ -94,6 +89,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "make_payment_via_journal_entry",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Make Payment via Journal Entry"
|
||||
},
|
||||
{
|
||||
@ -234,6 +230,29 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Bill ",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_closing_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Period Closing Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts_transactions_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Transactions Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -241,7 +260,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-11 18:52:05.601996",
|
||||
"modified": "2021-04-30 15:25:10.381008",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -239,6 +239,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
"withdrawal",
|
||||
"description",
|
||||
"reference_number",
|
||||
"bank_account"
|
||||
],
|
||||
},
|
||||
});
|
||||
|
@ -146,7 +146,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal && !doc.import_file\n",
|
||||
"description": "Must be a publicly accessible Google Sheets URL",
|
||||
"description": "Must be a publicly accessible Google Sheets URL and adding Bank Account column is necessary for importing via Google Sheets",
|
||||
"fieldname": "google_sheets_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Import from Google Sheets"
|
||||
@ -202,7 +202,7 @@
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-10 19:29:59.027325",
|
||||
"modified": "2021-05-12 14:17:37.777246",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Statement Import",
|
||||
@ -224,4 +224,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,13 @@ class BankStatementImport(DataImport):
|
||||
|
||||
def start_import(self):
|
||||
|
||||
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
|
||||
self.import_file, self.google_sheets_url
|
||||
)
|
||||
|
||||
if 'Bank Account' not in json.dumps(preview):
|
||||
frappe.throw(_("Please add the Bank Account column"))
|
||||
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
@ -67,6 +74,7 @@ class BankStatementImport(DataImport):
|
||||
data_import=self.name,
|
||||
bank_account=self.bank_account,
|
||||
import_file_path=self.import_file,
|
||||
google_sheets_url=self.google_sheets_url,
|
||||
bank=self.bank,
|
||||
template_options=self.template_options,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
@ -90,18 +98,20 @@ def download_errored_template(data_import_name):
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
||||
data_import.export_errored_rows()
|
||||
|
||||
def start_import(data_import, bank_account, import_file_path, bank, template_options):
|
||||
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
|
||||
"""This method runs in background job"""
|
||||
|
||||
update_mapping_db(bank, template_options)
|
||||
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import)
|
||||
file = import_file_path if import_file_path else google_sheets_url
|
||||
|
||||
import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records")
|
||||
import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
|
||||
data = import_file.raw_data
|
||||
|
||||
add_bank_account(data, bank_account)
|
||||
write_files(import_file, data)
|
||||
if import_file_path:
|
||||
add_bank_account(data, bank_account)
|
||||
write_files(import_file, data)
|
||||
|
||||
try:
|
||||
i = Importer(data_import.reference_doctype, data_import=data_import)
|
||||
|
@ -1,87 +1,39 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2014-08-29 16:02:39.740505",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2014-08-29 16:02:39.740505",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"field_order": [
|
||||
"company",
|
||||
"account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"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": "2016-07-11 03:28:03.348246",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Party Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-07 18:13:08.833822",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Party Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -114,7 +114,7 @@ class PaymentReconciliation(Document):
|
||||
'party_type': self.party_type,
|
||||
'voucher_type': voucher_type,
|
||||
'account': self.receivable_payable_account
|
||||
}, as_dict=1, debug=1)
|
||||
}, as_dict=1)
|
||||
|
||||
def add_payment_entries(self, entries):
|
||||
self.set('payments', [])
|
||||
|
@ -461,7 +461,17 @@ def get_stock_availability(item_code, warehouse):
|
||||
order by posting_date desc, posting_time desc
|
||||
limit 1""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
|
||||
if sle_qty and pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
return sle_qty
|
||||
|
||||
def get_pos_reserved_qty(item_code, warehouse):
|
||||
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
||||
where p.name = p_item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
@ -470,14 +480,8 @@ def get_stock_availability(item_code, warehouse):
|
||||
and p_item.item_code = %s
|
||||
and p_item.warehouse = %s
|
||||
""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||
|
||||
if sle_qty and pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
return sle_qty
|
||||
|
||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
|
@ -1380,7 +1380,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-30 22:45:58.334107",
|
||||
"modified": "2021-04-30 22:45:58.334107",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -356,11 +356,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
},
|
||||
|
||||
items_on_form_rendered: function() {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
packed_items_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
make_sales_return: function() {
|
||||
|
@ -1111,7 +1111,7 @@ class SalesInvoice(SellingController):
|
||||
if not item.serial_no:
|
||||
continue
|
||||
|
||||
for serial_no in item.serial_no.split("\n"):
|
||||
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)
|
||||
|
||||
@ -1755,15 +1755,10 @@ def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, wa
|
||||
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
|
||||
def get_delivery_note_details(internal_reference):
|
||||
so_item_map = {}
|
||||
|
||||
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
|
||||
filters={'parent': internal_reference})
|
||||
|
||||
for d in si_item_details:
|
||||
so_item_map.setdefault(d.name, d.so_detail)
|
||||
|
||||
return so_item_map
|
||||
return {d.name: d.so_detail for d in si_item_details if d.so_detail}
|
||||
|
||||
def get_sales_invoice_details(internal_reference):
|
||||
dn_item_map = {}
|
||||
|
@ -21,7 +21,10 @@ def get_party_details(inv):
|
||||
else:
|
||||
party_type = 'Supplier'
|
||||
party = inv.supplier
|
||||
|
||||
|
||||
if not party:
|
||||
frappe.throw(_("Please select {0} first").format(party_type))
|
||||
|
||||
return party_type, party
|
||||
|
||||
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
@ -324,7 +327,7 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post
|
||||
net_total, ldc.certificate_limit
|
||||
):
|
||||
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"condition": "doc.auto_created",
|
||||
"creation": "2018-04-25 14:19:05.440361",
|
||||
"days_in_advance": 0,
|
||||
|
@ -364,7 +364,7 @@ class ReceivablePayableReport(object):
|
||||
payment_terms_details = frappe.db.sql("""
|
||||
select
|
||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
@ -394,7 +394,7 @@ class ReceivablePayableReport(object):
|
||||
"due_date": d.due_date,
|
||||
"invoiced": invoiced,
|
||||
"invoice_grand_total": row.invoiced,
|
||||
"payment_term": d.description,
|
||||
"payment_term": d.description or d.payment_term,
|
||||
"paid": d.paid_amount + d.discounted_amount,
|
||||
"credit_note": 0.0,
|
||||
"outstanding": invoiced - d.paid_amount - d.discounted_amount
|
||||
|
@ -5,7 +5,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, cint
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
|
||||
get_filtered_list_for_consolidated_report)
|
||||
|
||||
def execute(filters=None):
|
||||
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
||||
@ -132,6 +133,10 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit
|
||||
if filters.get('accumulated_values'):
|
||||
period_list = [period_list[-1]]
|
||||
|
||||
# from consolidated financial statement
|
||||
if filters.get('accumulated_in_group_company'):
|
||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
if asset:
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, get_filtered_list_for_consolidated_report)
|
||||
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from six import iteritems
|
||||
@ -67,9 +67,9 @@ def execute(filters=None):
|
||||
section_data.append(account_data)
|
||||
|
||||
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
|
||||
period_list, company_currency, summary_data)
|
||||
period_list, company_currency, summary_data, filters)
|
||||
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data)
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters)
|
||||
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
|
||||
|
||||
chart = get_chart_data(columns, data)
|
||||
@ -162,18 +162,26 @@ def get_start_date(period, accumulated_values, company):
|
||||
|
||||
return start_date
|
||||
|
||||
def add_total_row_account(out, data, label, period_list, currency, summary_data, consolidated = False):
|
||||
def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False):
|
||||
total_row = {
|
||||
"account_name": "'" + _("{0}").format(label) + "'",
|
||||
"account": "'" + _("{0}").format(label) + "'",
|
||||
"currency": currency
|
||||
}
|
||||
|
||||
summary_data[label] = 0
|
||||
|
||||
# from consolidated financial statement
|
||||
if filters.get('accumulated_in_group_company'):
|
||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||
|
||||
for row in data:
|
||||
if row.get("parent_account"):
|
||||
for period in period_list:
|
||||
key = period if consolidated else period['key']
|
||||
total_row.setdefault(key, 0.0)
|
||||
total_row[key] += row.get(key, 0.0)
|
||||
summary_data[label] += row.get(key)
|
||||
|
||||
total_row.setdefault("total", 0.0)
|
||||
total_row["total"] += row["total"]
|
||||
@ -181,7 +189,6 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data,
|
||||
out.append(total_row)
|
||||
out.append({})
|
||||
|
||||
summary_data[label] = total_row["total"]
|
||||
|
||||
def get_report_summary(summary_data, currency):
|
||||
report_summary = []
|
||||
|
@ -2,118 +2,128 @@
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Filter Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Fiscal Year", "Date Range"],
|
||||
"default": ["Fiscal Year"],
|
||||
"reqd": 1,
|
||||
on_change: function() {
|
||||
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Filter Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Fiscal Year", "Date Range"],
|
||||
"default": ["Fiscal Year"],
|
||||
"reqd": 1,
|
||||
on_change: function() {
|
||||
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||
|
||||
frappe.query_report.refresh();
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"period_start_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"period_end_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"report",
|
||||
"label": __("Report"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
|
||||
"default": "Balance Sheet",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list(),
|
||||
"default": frappe.defaults.get_user_default("Currency")
|
||||
},
|
||||
{
|
||||
"fieldname":"accumulated_in_group_company",
|
||||
"label": __("Accumulated Values in Group Company"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (data && column.fieldname=="account") {
|
||||
value = data.account_name || value;
|
||||
|
||||
column.link_onclick =
|
||||
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
||||
column.is_tree = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"period_start_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"period_end_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"report",
|
||||
"label": __("Report"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
|
||||
"default": "Balance Sheet",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list(),
|
||||
"default": frappe.defaults.get_user_default("Currency")
|
||||
},
|
||||
{
|
||||
"fieldname":"accumulated_in_group_company",
|
||||
"label": __("Accumulated Values in Group Company"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (!data.parent_account) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
if (!data.parent_account) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
onload: function() {
|
||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
onload: function() {
|
||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -94,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
|
||||
|
||||
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
|
||||
|
||||
report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True)
|
||||
report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True)
|
||||
|
||||
return data, None, chart, report_summary
|
||||
|
||||
@ -149,9 +149,9 @@ def get_cash_flow_data(fiscal_year, companies, filters):
|
||||
section_data.append(account_data)
|
||||
|
||||
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
|
||||
companies, company_currency, summary_data, True)
|
||||
companies, company_currency, summary_data, filters, True)
|
||||
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, True)
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True)
|
||||
|
||||
report_summary = get_cash_flow_summary(summary_data, company_currency)
|
||||
|
||||
@ -329,8 +329,9 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
|
||||
has_value = False
|
||||
total = 0
|
||||
row = frappe._dict({
|
||||
"account_name": _(d.account_name),
|
||||
"account": _(d.account_name),
|
||||
"account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
|
||||
if d.account_number else _(d.account_name)),
|
||||
"account": _(d.name),
|
||||
"parent_account": _(d.parent_account),
|
||||
"indent": flt(d.indent),
|
||||
"year_start_date": start_date,
|
||||
|
@ -119,10 +119,10 @@ def validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year):
|
||||
|
||||
def validate_dates(from_date, to_date):
|
||||
if not from_date or not to_date:
|
||||
frappe.throw("From Date and To Date are mandatory")
|
||||
frappe.throw(_("From Date and To Date are mandatory"))
|
||||
|
||||
if to_date < from_date:
|
||||
frappe.throw("To Date cannot be less than From Date")
|
||||
frappe.throw(_("To Date cannot be less than From Date"))
|
||||
|
||||
def get_months(start_date, end_date):
|
||||
diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month)
|
||||
@ -522,4 +522,12 @@ def get_columns(periodicity, period_list, accumulated_values=1, company=None):
|
||||
"width": 150
|
||||
})
|
||||
|
||||
return columns
|
||||
return columns
|
||||
|
||||
def get_filtered_list_for_consolidated_report(filters, period_list):
|
||||
filtered_summary_list = []
|
||||
for period in period_list:
|
||||
if period == filters.get('company'):
|
||||
filtered_summary_list.append(period)
|
||||
|
||||
return filtered_summary_list
|
||||
|
@ -116,22 +116,19 @@ def validate_filters(filters):
|
||||
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format(
|
||||
company=filters.get("company"),
|
||||
from_date=filters.get("from_date"),
|
||||
to_date=filters.get("to_date"))
|
||||
conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s"
|
||||
|
||||
if filters.get("pos_profile"):
|
||||
conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile"))
|
||||
conditions += " AND pos_profile = %(pos_profile)s"
|
||||
|
||||
if filters.get("owner"):
|
||||
conditions += " AND owner = %(owner)s".format(owner=filters.get("owner"))
|
||||
conditions += " AND owner = %(owner)s"
|
||||
|
||||
if filters.get("customer"):
|
||||
conditions += " AND customer = %(customer)s".format(customer=filters.get("customer"))
|
||||
conditions += " AND customer = %(customer)s"
|
||||
|
||||
if filters.get("is_return"):
|
||||
conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return"))
|
||||
conditions += " AND is_return = %(is_return)s"
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
conditions += """
|
||||
|
@ -5,7 +5,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
|
||||
get_filtered_list_for_consolidated_report)
|
||||
|
||||
def execute(filters=None):
|
||||
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
||||
@ -33,13 +34,17 @@ def execute(filters=None):
|
||||
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
|
||||
|
||||
currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
|
||||
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency)
|
||||
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters)
|
||||
|
||||
return columns, data, None, chart, report_summary
|
||||
|
||||
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False):
|
||||
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False):
|
||||
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
|
||||
|
||||
# from consolidated financial statement
|
||||
if filters.get('accumulated_in_group_company'):
|
||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
if income:
|
||||
|
@ -195,8 +195,7 @@ class Asset(AccountsController):
|
||||
# If depreciation is already completed (for double declining balance)
|
||||
if skip_row: continue
|
||||
|
||||
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
|
||||
d.total_number_of_depreciations, d)
|
||||
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d)
|
||||
|
||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||
schedule_date = add_months(d.depreciation_start_date,
|
||||
@ -208,7 +207,7 @@ class Asset(AccountsController):
|
||||
|
||||
# For first row
|
||||
if has_pro_rata and n==0:
|
||||
depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
|
||||
self.available_for_use_date, d.depreciation_start_date)
|
||||
|
||||
# For first depr schedule date will be the start date
|
||||
@ -220,7 +219,7 @@ class Asset(AccountsController):
|
||||
to_date = add_months(self.available_for_use_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
|
||||
depreciation_amount, days, months = get_pro_rata_amt(d,
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(d,
|
||||
depreciation_amount, schedule_date, to_date)
|
||||
|
||||
monthly_schedule_date = add_months(schedule_date, 1)
|
||||
@ -365,24 +364,6 @@ class Asset(AccountsController):
|
||||
def get_value_after_depreciation(self, idx):
|
||||
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
|
||||
|
||||
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
|
||||
precision = self.precision("gross_purchase_amount")
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
|
||||
|
||||
if not depreciation_left:
|
||||
frappe.msgprint(_("All the depreciations has been booked"))
|
||||
depreciation_amount = flt(row.expected_value_after_useful_life)
|
||||
return depreciation_amount
|
||||
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
else:
|
||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
|
||||
|
||||
return depreciation_amount
|
||||
|
||||
def validate_expected_value_after_useful_life(self):
|
||||
for row in self.get('finance_books'):
|
||||
accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
|
||||
@ -575,6 +556,13 @@ class Asset(AccountsController):
|
||||
|
||||
return 100 * (1 - flt(depreciation_rate, float_precision))
|
||||
|
||||
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
|
||||
days = date_diff(to_date, from_date)
|
||||
months = month_diff(to_date, from_date)
|
||||
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
||||
|
||||
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||
|
||||
def update_maintenance_status():
|
||||
assets = frappe.get_all(
|
||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
|
||||
@ -758,15 +746,20 @@ def make_asset_movement(assets, purpose=None):
|
||||
def is_cwip_accounting_enabled(asset_category):
|
||||
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
|
||||
|
||||
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
|
||||
days = date_diff(to_date, from_date)
|
||||
months = month_diff(to_date, from_date)
|
||||
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
||||
|
||||
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||
|
||||
def get_total_days(date, frequency):
|
||||
period_start_date = add_months(date,
|
||||
cint(frequency) * -1)
|
||||
|
||||
return date_diff(date, period_start_date)
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_depreciation_amount(asset, depreciable_value, row):
|
||||
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
else:
|
||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
||||
|
||||
return depreciation_amount
|
@ -635,6 +635,45 @@ class TestAsset(unittest.TestCase):
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
|
||||
|
||||
def test_discounted_wdv_depreciation_rate_for_indian_region(self):
|
||||
# set indian company
|
||||
company_flag = frappe.flags.company
|
||||
frappe.flags.company = "_Test Company"
|
||||
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=8000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2030-06-12'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 1000,
|
||||
"depreciation_method": "Written Down Value",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.save(ignore_permissions=True)
|
||||
|
||||
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||
|
||||
expected_schedules = [
|
||||
["2030-12-31", 1106.85, 1106.85],
|
||||
["2031-12-31", 3446.58, 4553.43],
|
||||
["2032-12-31", 1723.29, 6276.72],
|
||||
["2033-06-12", 723.28, 7000.00]
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
|
||||
for d in asset.get("schedules")]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
# reset indian company
|
||||
frappe.flags.company = company_flag
|
||||
|
||||
def create_asset_data():
|
||||
if not frappe.db.exists("Asset Category", "Computers"):
|
||||
create_asset_category()
|
||||
|
73
erpnext/change_log/v13/v13_3_0.md
Normal file
73
erpnext/change_log/v13/v13_3_0.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Version 13.3.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
|
||||
- Purchase receipt creation from purchase invoice ([#25126](https://github.com/frappe/erpnext/pull/25126))
|
||||
- New Document Transaction Deletion ([#25354](https://github.com/frappe/erpnext/pull/25354))
|
||||
- Employee Referral ([#24997](https://github.com/frappe/erpnext/pull/24997))
|
||||
- Add Create Expense Claim button in Delivery Trip ([#25526](https://github.com/frappe/erpnext/pull/25526))
|
||||
- Reduced rate of asset depreciation as per IT Act ([#25648](https://github.com/frappe/erpnext/pull/25648))
|
||||
- Improve DATEV export ([#25238](https://github.com/frappe/erpnext/pull/25238))
|
||||
- Add pick batch button ([#25413](https://github.com/frappe/erpnext/pull/25413))
|
||||
- Enable custom field search on POS ([#25421](https://github.com/frappe/erpnext/pull/25421))
|
||||
- New check field in subscriptions for (not) submitting invoices ([#25394](https://github.com/frappe/erpnext/pull/25394))
|
||||
- Show POS reserved stock in stock projected qty report ([#25593](https://github.com/frappe/erpnext/pull/25593))
|
||||
- e-way bill validity field ([#25555](https://github.com/frappe/erpnext/pull/25555))
|
||||
- Significant reduction in time taken to save sales documents ([#25475](https://github.com/frappe/erpnext/pull/25475))
|
||||
|
||||
### Fixes
|
||||
|
||||
- Bank statement import via google sheet ([#25677](https://github.com/frappe/erpnext/pull/25677))
|
||||
- Invoices not getting fetched during payment reconciliation ([#25598](https://github.com/frappe/erpnext/pull/25598))
|
||||
- Error on applying TDS without party ([#25632](https://github.com/frappe/erpnext/pull/25632))
|
||||
- Allow to cancel loan with cancelled repayment entry ([#25507](https://github.com/frappe/erpnext/pull/25507))
|
||||
- Can't open general ledger from consolidated financial report ([#25542](https://github.com/frappe/erpnext/pull/25542))
|
||||
- Add 'Partially Received' to Status drop-down list in Material Request ([#24857](https://github.com/frappe/erpnext/pull/24857))
|
||||
- Updated item filters for material request ([#25531](https://github.com/frappe/erpnext/pull/25531))
|
||||
- Added validation in stock entry to check duplicate serial nos ([#25611](https://github.com/frappe/erpnext/pull/25611))
|
||||
- Update shopify api version ([#25600](https://github.com/frappe/erpnext/pull/25600))
|
||||
- Dialog variable assignment after definition in POS ([#25680](https://github.com/frappe/erpnext/pull/25680))
|
||||
- Added tax_types list ([#25587](https://github.com/frappe/erpnext/pull/25587))
|
||||
- Include search fields in Project Link field query ([#25505](https://github.com/frappe/erpnext/pull/25505))
|
||||
- Item stock levels displaying inconsistently ([#25506](https://github.com/frappe/erpnext/pull/25506))
|
||||
- Change today to now to get data for reposting ([#25703](https://github.com/frappe/erpnext/pull/25703))
|
||||
- Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet ([#25700](https://github.com/frappe/erpnext/pull/25700))
|
||||
- Minor fixes in loan ([#25546](https://github.com/frappe/erpnext/pull/25546))
|
||||
- Fieldname when updating docfield property ([#25516](https://github.com/frappe/erpnext/pull/25516))
|
||||
- Use get_serial_nos for splitting ([#25590](https://github.com/frappe/erpnext/pull/25590))
|
||||
- Show item's full name on hover over item in POS ([#25554](https://github.com/frappe/erpnext/pull/25554))
|
||||
- Stock ledger entry created against draft stock entry ([#25540](https://github.com/frappe/erpnext/pull/25540))
|
||||
- Incorrect expense account set in pos invoice ([#25543](https://github.com/frappe/erpnext/pull/25543))
|
||||
- Stock balance and batch-wise balance history report showing different closing stock ([#25575](https://github.com/frappe/erpnext/pull/25575))
|
||||
- Make strings translatable ([#25521](https://github.com/frappe/erpnext/pull/25521))
|
||||
- Serial no changed after saving stock reconciliation ([#25541](https://github.com/frappe/erpnext/pull/25541))
|
||||
- Ignore fraction difference while making round off gl entry ([#25438](https://github.com/frappe/erpnext/pull/25438))
|
||||
- Sync shopify customer addresses ([#25481](https://github.com/frappe/erpnext/pull/25481))
|
||||
- Total stock summary report not working ([#25551](https://github.com/frappe/erpnext/pull/25551))
|
||||
- Rename field has not updated value of deposit and withdrawal fields ([#25545](https://github.com/frappe/erpnext/pull/25545))
|
||||
- Unexpected keyword argument 'merge_logs' ([#25489](https://github.com/frappe/erpnext/pull/25489))
|
||||
- Validation message of quality inspection in purchase receipt ([#25667](https://github.com/frappe/erpnext/pull/25667))
|
||||
- Added is_stock_item filter ([#25530](https://github.com/frappe/erpnext/pull/25530))
|
||||
- Fetch total stock at company in PO ([#25532](https://github.com/frappe/erpnext/pull/25532))
|
||||
- Updated filters for process statement of accounts ([#25384](https://github.com/frappe/erpnext/pull/25384))
|
||||
- Incorrect expense account set in pos invoice ([#25571](https://github.com/frappe/erpnext/pull/25571))
|
||||
- Client script breaking while settings tax labels ([#25653](https://github.com/frappe/erpnext/pull/25653))
|
||||
- Empty payment term column in accounts receivable report ([#25556](https://github.com/frappe/erpnext/pull/25556))
|
||||
- Designation insufficient permission on lead doctype. ([#25331](https://github.com/frappe/erpnext/pull/25331))
|
||||
- Force https for shopify webhook registration ([#25630](https://github.com/frappe/erpnext/pull/25630))
|
||||
- Patch regional fields for old companies ([#25673](https://github.com/frappe/erpnext/pull/25673))
|
||||
- Woocommerce order sync issue ([#25692](https://github.com/frappe/erpnext/pull/25692))
|
||||
- Allow to receive same serial numbers multiple times ([#25471](https://github.com/frappe/erpnext/pull/25471))
|
||||
- Update Allocated amount after Paid Amount is changed in PE ([#25515](https://github.com/frappe/erpnext/pull/25515))
|
||||
- Updating Standard Notification's channel field ([#25564](https://github.com/frappe/erpnext/pull/25564))
|
||||
- Report summary showing inflated values when values are accumulated in Group Company ([#25577](https://github.com/frappe/erpnext/pull/25577))
|
||||
- UI fixes related to overflowing payment section ([#25652](https://github.com/frappe/erpnext/pull/25652))
|
||||
- List invoices in Payment Reconciliation Payment ([#25524](https://github.com/frappe/erpnext/pull/25524))
|
||||
- Ageing errors in PSOA ([#25490](https://github.com/frappe/erpnext/pull/25490))
|
||||
- Prevent spurious defaults for items when making prec from dnote ([#25559](https://github.com/frappe/erpnext/pull/25559))
|
||||
- Stock reconciliation getting time out error during submission ([#25557](https://github.com/frappe/erpnext/pull/25557))
|
||||
- Timesheet filter date exclusive issue ([#25626](https://github.com/frappe/erpnext/pull/25626))
|
||||
- Update cost center in the item table fetched from POS Profile ([#25609](https://github.com/frappe/erpnext/pull/25609))
|
||||
- Updated modified time in purchase invoice to pull new fields ([#25678](https://github.com/frappe/erpnext/pull/25678))
|
||||
- Stock and Accounts Settings form refactor ([#25534](https://github.com/frappe/erpnext/pull/25534))
|
||||
- Payment amount showing in foreign currency ([#25292](https://github.com/frappe/erpnext/pull/25292))
|
@ -838,9 +838,10 @@ class BuyingController(StockController):
|
||||
if not self.get("items"):
|
||||
return
|
||||
|
||||
earliest_schedule_date = min([d.schedule_date for d in self.get("items")])
|
||||
if earliest_schedule_date:
|
||||
self.schedule_date = earliest_schedule_date
|
||||
if any(d.schedule_date for d in self.get("items")):
|
||||
# Select earliest schedule_date.
|
||||
self.schedule_date = min(d.schedule_date for d in self.get("items")
|
||||
if d.schedule_date is not None)
|
||||
|
||||
if self.schedule_date:
|
||||
for d in self.get('items'):
|
||||
|
@ -292,11 +292,14 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
|
||||
|
||||
fields = get_fields("Project", ["name"])
|
||||
fields = get_fields("Project", ["name", "project_name"])
|
||||
searchfields = frappe.get_meta("Project").get_search_fields()
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabProject`
|
||||
where `tabProject`.status not in ("Completed", "Cancelled")
|
||||
and {cond} `tabProject`.name like %(txt)s {match_cond}
|
||||
where
|
||||
`tabProject`.status not in ("Completed", "Cancelled")
|
||||
and {cond} {match_cond} {scond}
|
||||
order by
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
idx desc,
|
||||
@ -304,6 +307,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
limit {start}, {page_len}""".format(
|
||||
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
|
||||
cond=cond,
|
||||
scond=searchfields,
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len), {
|
||||
|
@ -100,6 +100,10 @@ status_map = {
|
||||
["Queued", "eval:self.status == 'Queued'"],
|
||||
["Failed", "eval:self.status == 'Failed'"],
|
||||
["Cancelled", "eval:self.docstatus == 2"],
|
||||
],
|
||||
"Transaction Deletion Record": [
|
||||
["Draft", None],
|
||||
["Completed", "eval:self.docstatus == 1"],
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -379,8 +379,7 @@ class StockController(AccountsController):
|
||||
link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection)
|
||||
frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError)
|
||||
|
||||
qa_failed = any([r.status=="Rejected" for r in qa_doc.readings])
|
||||
if qa_failed:
|
||||
if qa_doc.status != 'Accepted':
|
||||
frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
|
||||
.format(d.idx, d.item_code), QualityInspectionRejectedError)
|
||||
elif qa_required :
|
||||
|
@ -335,13 +335,13 @@ def get_url(shopify_settings):
|
||||
|
||||
if not last_order_id:
|
||||
if shopify_settings.sync_based_on == 'Date':
|
||||
url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format(
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
|
||||
get_datetime(shopify_settings.from_date)), shopify_settings)
|
||||
else:
|
||||
url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
|
||||
shopify_settings.from_order_id), shopify_settings)
|
||||
else:
|
||||
url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
|
||||
|
||||
return url
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, base64, hashlib, hmac, json
|
||||
from frappe.utils import cstr
|
||||
from frappe import _
|
||||
|
||||
def verify_request():
|
||||
@ -146,22 +147,19 @@ def rename_address(address, customer):
|
||||
|
||||
def link_items(items_list, woocommerce_settings, sys_lang):
|
||||
for item_data in items_list:
|
||||
item_woo_com_id = item_data.get("product_id")
|
||||
item_woo_com_id = cstr(item_data.get("product_id"))
|
||||
|
||||
if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}):
|
||||
#Edit Item
|
||||
item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id})
|
||||
else:
|
||||
if not frappe.db.get_value("Item", {"woocommerce_id": item_woo_com_id}, 'name'):
|
||||
#Create Item
|
||||
item = frappe.new_doc("Item")
|
||||
item.item_code = _("woocommerce - {0}", sys_lang).format(item_woo_com_id)
|
||||
item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
|
||||
item.item_group = _("WooCommerce Products", sys_lang)
|
||||
|
||||
item.item_name = item_data.get("name")
|
||||
item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id"))
|
||||
item.woocommerce_id = item_data.get("product_id")
|
||||
item.item_group = _("WooCommerce Products", sys_lang)
|
||||
item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
|
||||
item.flags.ignore_mandatory = True
|
||||
item.save()
|
||||
item.item_name = item_data.get("name")
|
||||
item.woocommerce_id = item_woo_com_id
|
||||
item.flags.ignore_mandatory = True
|
||||
item.save()
|
||||
|
||||
def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
|
||||
new_sales_order = frappe.new_doc("Sales Order")
|
||||
@ -194,12 +192,12 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
||||
|
||||
for item in order.get("line_items"):
|
||||
woocomm_item_id = item.get("product_id")
|
||||
found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
|
||||
found_item = frappe.get_doc("Item", {"woocommerce_id": cstr(woocomm_item_id)})
|
||||
|
||||
ordered_items_tax = item.get("total_tax")
|
||||
|
||||
new_sales_order.append("items",{
|
||||
"item_code": found_item.item_code,
|
||||
new_sales_order.append("items", {
|
||||
"item_code": found_item.name,
|
||||
"item_name": found_item.item_name,
|
||||
"description": found_item.item_name,
|
||||
"delivery_date": new_sales_order.delivery_date,
|
||||
@ -207,7 +205,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
||||
"qty": item.get("quantity"),
|
||||
"rate": item.get("price"),
|
||||
"warehouse": woocommerce_settings.warehouse or default_warehouse
|
||||
})
|
||||
})
|
||||
|
||||
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
|
||||
|
||||
|
@ -30,14 +30,14 @@ class ShopifySettings(Document):
|
||||
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
|
||||
# url = get_shopify_url('admin/webhooks.json', self)
|
||||
created_webhooks = [d.method for d in self.webhooks]
|
||||
url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
|
||||
url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
|
||||
for method in webhooks:
|
||||
session = get_request_session()
|
||||
try:
|
||||
res = session.post(url, data=json.dumps({
|
||||
"webhook": {
|
||||
"topic": method,
|
||||
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
|
||||
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
|
||||
"format": "json"
|
||||
}
|
||||
}), headers=get_header(self))
|
||||
@ -56,7 +56,7 @@ class ShopifySettings(Document):
|
||||
deleted_webhooks = []
|
||||
|
||||
for d in self.webhooks:
|
||||
url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
|
||||
url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
|
||||
try:
|
||||
res = session.delete(url, headers=get_header(self))
|
||||
res.raise_for_status()
|
||||
|
@ -32,10 +32,12 @@ def create_customer(shopify_customer, shopify_settings):
|
||||
raise e
|
||||
|
||||
def create_customer_address(customer, shopify_customer):
|
||||
if not shopify_customer.get("addresses"):
|
||||
return
|
||||
addresses = shopify_customer.get("addresses", [])
|
||||
|
||||
for i, address in enumerate(shopify_customer.get("addresses")):
|
||||
if not addresses and "default_address" in shopify_customer:
|
||||
addresses.append(shopify_customer["default_address"])
|
||||
|
||||
for i, address in enumerate(addresses):
|
||||
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
|
||||
try :
|
||||
frappe.get_doc({
|
||||
|
@ -8,7 +8,7 @@ from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings impo
|
||||
shopify_variants_attr_list = ["option1", "option2", "option3"]
|
||||
|
||||
def sync_item_from_shopify(shopify_settings, item):
|
||||
url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
|
||||
url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
|
||||
session = get_request_session()
|
||||
|
||||
try:
|
||||
|
@ -28,7 +28,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key='secret'):
|
||||
|
||||
return innerfn
|
||||
|
||||
def get_webhook_address(connector_name, method, exclude_uri=False):
|
||||
def get_webhook_address(connector_name, method, exclude_uri=False, force_https=False):
|
||||
endpoint = "erpnext.erpnext_integrations.connectors.{0}.{1}".format(connector_name, method)
|
||||
|
||||
if exclude_uri:
|
||||
@ -39,7 +39,11 @@ def get_webhook_address(connector_name, method, exclude_uri=False):
|
||||
except RuntimeError:
|
||||
url = "http://localhost:8000"
|
||||
|
||||
server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint)
|
||||
url_data = urlparse(url)
|
||||
scheme = "https" if force_https else url_data.scheme
|
||||
netloc = url_data.netloc
|
||||
|
||||
server_url = f"{scheme}://{netloc}/api/method/{endpoint}"
|
||||
|
||||
return server_url
|
||||
|
||||
|
@ -426,7 +426,8 @@ regional_overrides = {
|
||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
|
||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
|
||||
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
|
||||
},
|
||||
'United Arab Emirates': {
|
||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
||||
|
@ -182,6 +182,10 @@
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Sales User"
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
@ -191,4 +195,4 @@
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
"column_break_5",
|
||||
"expense_approver",
|
||||
"approval_status",
|
||||
"delivery_trip",
|
||||
"is_paid",
|
||||
"expense_details",
|
||||
"expenses",
|
||||
@ -365,13 +366,20 @@
|
||||
"label": "Total Taxes and Charges",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.delivery_trip",
|
||||
"fieldname": "delivery_trip",
|
||||
"fieldtype": "Link",
|
||||
"label": "Delivery Trip",
|
||||
"options": "Delivery Trip"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified": "2021-05-04 05:35:12.040199",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Claim",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"creation": "2017-08-11 03:17:11.769210",
|
||||
"days_in_advance": 0,
|
||||
"docstatus": 0,
|
||||
|
@ -29,7 +29,10 @@ frappe.ui.form.on("BOM", {
|
||||
|
||||
frm.set_query("item", function() {
|
||||
return {
|
||||
query: "erpnext.manufacturing.doctype.bom.bom.item_query"
|
||||
query: "erpnext.manufacturing.doctype.bom.bom.item_query",
|
||||
filters: {
|
||||
"is_stock_item": 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -973,6 +973,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not has_variants:
|
||||
query_filters["has_variants"] = 0
|
||||
|
||||
if filters and filters.get("is_stock_item"):
|
||||
query_filters["is_stock_item"] = 1
|
||||
|
||||
return frappe.get_all("Item",
|
||||
fields = fields, filters=query_filters,
|
||||
or_filters = or_cond_filters, order_by=order_by,
|
||||
|
@ -774,4 +774,7 @@ erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
|
||||
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
|
||||
erpnext.patches.v13_0.update_shipment_status
|
||||
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
|
||||
erpnext.patches.v12_0.add_ewaybill_validity_field
|
||||
erpnext.patches.v13_0.germany_make_custom_fields
|
||||
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
|
||||
erpnext.patches.v13_0.set_pos_closing_as_failed
|
||||
|
16
erpnext/patches/v12_0/add_ewaybill_validity_field.py
Normal file
16
erpnext/patches/v12_0/add_ewaybill_validity_field.py
Normal file
@ -0,0 +1,16 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_fields = {
|
||||
'Sales Invoice': [
|
||||
dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
|
||||
depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=True)
|
@ -44,9 +44,11 @@ def execute():
|
||||
# make current item's tax map
|
||||
item_tax_map = {}
|
||||
for d in old_item_taxes[item_code]:
|
||||
item_tax_map[d.tax_type] = d.tax_rate
|
||||
if d.tax_type not in item_tax_map:
|
||||
item_tax_map[d.tax_type] = d.tax_rate
|
||||
|
||||
item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code)
|
||||
tax_types = []
|
||||
item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code, tax_types=tax_types)
|
||||
|
||||
# update the item tax table
|
||||
frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code)
|
||||
@ -68,7 +70,7 @@ def execute():
|
||||
and item_tax_template is NULL""".format(dt), as_dict=1):
|
||||
item_tax_map = json.loads(d.item_tax_rate)
|
||||
item_tax_template_name = get_item_tax_template(item_tax_templates,
|
||||
item_tax_map, d.item_code, d.parenttype, d.parent)
|
||||
item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types)
|
||||
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = False
|
||||
@ -78,7 +80,7 @@ def execute():
|
||||
settings.determine_address_tax_category_from = "Billing Address"
|
||||
settings.save()
|
||||
|
||||
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None):
|
||||
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None):
|
||||
# search for previously created item tax template by comparing tax maps
|
||||
for template, item_tax_template_map in iteritems(item_tax_templates):
|
||||
if item_tax_map == item_tax_template_map:
|
||||
@ -126,7 +128,9 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp
|
||||
account_type = frappe.get_cached_value("Account", tax_type, "account_type")
|
||||
|
||||
if tax_type and account_type in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
|
||||
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
|
||||
if tax_type not in tax_types:
|
||||
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
|
||||
tax_types.append(tax_type)
|
||||
item_tax_templates.setdefault(item_tax_template.title, {})
|
||||
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
|
||||
if item_tax_template.get("taxes"):
|
||||
|
31
erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
Normal file
31
erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
"""Move account number into the new custom field debtor_creditor_number.
|
||||
|
||||
German companies used to use a dedicated payable/receivable account for
|
||||
every party to mimick party accounts in the external accounting software
|
||||
"DATEV". This is no longer necessary. The reference ID for DATEV will be
|
||||
stored in a new custom field "debtor_creditor_number".
|
||||
"""
|
||||
company_list = frappe.get_all('Company', filters={'country': 'Germany'})
|
||||
|
||||
for company in company_list:
|
||||
party_account_list = frappe.get_all('Party Account', filters={'company': company.name}, fields=['name', 'account', 'debtor_creditor_number'])
|
||||
for party_account in party_account_list:
|
||||
if (not party_account.account) or party_account.debtor_creditor_number:
|
||||
# account empty or debtor_creditor_number already filled
|
||||
continue
|
||||
|
||||
account_number = frappe.db.get_value('Account', party_account.account, 'account_number')
|
||||
if not account_number:
|
||||
continue
|
||||
|
||||
frappe.db.set_value('Party Account', party_account.name, 'debtor_creditor_number', account_number)
|
||||
frappe.db.set_value('Party Account', party_account.name, 'account', '')
|
20
erpnext/patches/v13_0/germany_make_custom_fields.py
Normal file
20
erpnext/patches/v13_0/germany_make_custom_fields.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from erpnext.regional.germany.setup import make_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
"""Execute the make_custom_fields method for german companies.
|
||||
|
||||
It is usually run once at setup of a new company. Since it's new, run it
|
||||
once for existing companies as well.
|
||||
"""
|
||||
company_list = frappe.get_all('Company', filters = {'country': 'Germany'})
|
||||
if not company_list:
|
||||
return
|
||||
|
||||
make_custom_fields()
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"condition": "doc.docstatus==1",
|
||||
"creation": "2018-05-15 18:52:36.362838",
|
||||
"date_changed": "bonus_payment_date",
|
||||
|
@ -458,7 +458,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"max_attachments": 4,
|
||||
"modified": "2020-09-02 11:54:01.223620",
|
||||
"modified": "2021-04-28 16:36:11.654632",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Project",
|
||||
@ -495,11 +495,11 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "customer, status, priority, is_active",
|
||||
"search_fields": "project_name,customer, status, priority, is_active",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"timeline_field": "customer",
|
||||
"title_field": "project_name",
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
@ -209,7 +209,7 @@ def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time
|
||||
if parent:
|
||||
condition = "AND parent = %(parent)s"
|
||||
if from_time and to_time:
|
||||
condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s"
|
||||
condition += "AND CAST(from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
|
||||
|
||||
return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
|
||||
from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1
|
||||
|
@ -4,6 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = [], []
|
||||
@ -52,8 +53,8 @@ def get_rows(filters):
|
||||
|
||||
def calculate_cost_and_profit(data):
|
||||
for row in data:
|
||||
row.fractional_cost = row.base_gross_pay * row.utilization
|
||||
row.profit = row.base_grand_total - row.base_gross_pay * row.utilization
|
||||
row.fractional_cost = flt(row.base_gross_pay) * flt(row.utilization)
|
||||
row.profit = flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization)
|
||||
return data
|
||||
|
||||
def get_conditions(filters):
|
||||
|
@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
|
||||
this.toggle_item_grid_columns(company_currency);
|
||||
|
||||
if(this.frm.fields_dict["operations"]) {
|
||||
if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
|
||||
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
|
||||
this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations");
|
||||
|
||||
@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["scrap_items"]) {
|
||||
if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) {
|
||||
this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items");
|
||||
this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items");
|
||||
|
||||
@ -1351,13 +1351,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["taxes"]) {
|
||||
if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
|
||||
this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes");
|
||||
|
||||
this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes");
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["advances"]) {
|
||||
if (this.frm.doc.advances && this.frm.doc.advances.length > 0) {
|
||||
this.frm.set_currency_labels(["advance_amount", "allocated_amount"],
|
||||
this.frm.doc.party_account_currency, "advances");
|
||||
}
|
||||
|
@ -48,31 +48,24 @@ $.extend(erpnext, {
|
||||
return cint(frappe.boot.sysdefaults.allow_stale);
|
||||
},
|
||||
|
||||
setup_serial_no: function() {
|
||||
var grid_row = cur_frm.open_grid_row();
|
||||
if(!grid_row || !grid_row.grid_form.fields_dict.serial_no ||
|
||||
grid_row.grid_form.fields_dict.serial_no.get_status()!=="Write") return;
|
||||
setup_serial_or_batch_no: function() {
|
||||
let grid_row = cur_frm.open_grid_row();
|
||||
if (!grid_row || !grid_row.grid_form.fields_dict.serial_no ||
|
||||
grid_row.grid_form.fields_dict.serial_no.get_status() !== "Write") return;
|
||||
|
||||
var $btn = $('<button class="btn btn-sm btn-default">'+__("Add Serial No")+'</button>')
|
||||
.appendTo($("<div>")
|
||||
.css({"margin-bottom": "10px", "margin-top": "10px"})
|
||||
.appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper));
|
||||
frappe.model.get_value('Item', {'name': grid_row.doc.item_code},
|
||||
['has_serial_no', 'has_batch_no'], ({has_serial_no, has_batch_no}) => {
|
||||
Object.assign(grid_row.doc, {has_serial_no, has_batch_no});
|
||||
|
||||
var me = this;
|
||||
$btn.on("click", function() {
|
||||
let callback = '';
|
||||
let on_close = '';
|
||||
|
||||
frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no',
|
||||
(data) => {
|
||||
if(data) {
|
||||
grid_row.doc.has_serial_no = data.has_serial_no;
|
||||
me.show_serial_batch_selector(grid_row.frm, grid_row.doc,
|
||||
callback, on_close, true);
|
||||
}
|
||||
if (has_serial_no) {
|
||||
attach_selector_button(__("Add Serial No"),
|
||||
grid_row.grid_form.fields_dict.serial_no.$wrapper, this, grid_row);
|
||||
} else if (has_batch_no) {
|
||||
attach_selector_button(__("Pick Batch No"),
|
||||
grid_row.grid_form.fields_dict.batch_no.$wrapper, this, grid_row);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
route_to_adjustment_jv: (args) => {
|
||||
@ -743,3 +736,14 @@ $(document).on('app_ready', function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function attach_selector_button(inner_text, append_loction, context, grid_row) {
|
||||
let $btn_div = $("<div>").css({"margin-bottom": "10px", "margin-top": "10px"})
|
||||
.appendTo(append_loction);
|
||||
let $btn = $(`<button class="btn btn-sm btn-default">${inner_text}</button>`)
|
||||
.appendTo($btn_div);
|
||||
|
||||
$btn.on("click", function() {
|
||||
context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
|
||||
});
|
||||
}
|
||||
|
@ -129,11 +129,20 @@
|
||||
@extend .pointer-no-select;
|
||||
border-radius: var(--border-radius-md);
|
||||
box-shadow: var(--shadow-base);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.02, 1.02);
|
||||
}
|
||||
|
||||
.item-qty-pill {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
margin: var(--margin-sm);
|
||||
justify-content: flex-end;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.item-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -766,9 +775,10 @@
|
||||
> .payment-modes {
|
||||
display: flex;
|
||||
padding-bottom: var(--padding-sm);
|
||||
margin-bottom: var(--margin-xs);
|
||||
margin-bottom: var(--margin-sm);
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
> .payment-mode-wrapper {
|
||||
min-width: 40%;
|
||||
@ -825,9 +835,24 @@
|
||||
> .fields-numpad-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
justify-content: flex-end;
|
||||
|
||||
> .fields-section {
|
||||
flex: 1;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-bottom: var(--margin-md);
|
||||
|
||||
.invoice-fields {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
> .number-pad {
|
||||
@ -835,6 +860,7 @@
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
max-width: 50%;
|
||||
|
||||
.numpad-container {
|
||||
display: grid;
|
||||
@ -861,6 +887,7 @@
|
||||
margin-bottom: var(--margin-sm);
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
> .totals {
|
||||
display: flex;
|
||||
|
@ -1,11 +1,24 @@
|
||||
import os
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_custom_roles_for_reports()
|
||||
|
||||
|
||||
def make_custom_fields():
|
||||
custom_fields = {
|
||||
'Party Account': [
|
||||
dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number',
|
||||
fieldtype='Data', insert_after='account', translatable=0)
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields)
|
||||
|
||||
|
||||
def add_custom_roles_for_reports():
|
||||
"""Add Access Control to UAE VAT 201."""
|
||||
if not frappe.db.get_value('Custom Role', dict(report='DATEV')):
|
||||
@ -16,4 +29,4 @@ def add_custom_roles_for_reports():
|
||||
dict(role='Accounts User'),
|
||||
dict(role='Accounts Manager')
|
||||
]
|
||||
)).insert()
|
||||
)).insert()
|
||||
|
@ -56,10 +56,10 @@ def get_datev_csv(data, filters, csv_class):
|
||||
)
|
||||
|
||||
if not six.PY2:
|
||||
data = data.encode('latin_1')
|
||||
data = data.encode('latin_1', errors='replace')
|
||||
|
||||
header = get_header(filters, csv_class)
|
||||
header = ';'.join(header).encode('latin_1')
|
||||
header = ';'.join(header).encode('latin_1', errors='replace')
|
||||
|
||||
# 1st Row: Header with meta data
|
||||
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
|
||||
|
@ -71,13 +71,14 @@ def validate_einvoice_fields(doc):
|
||||
|
||||
def raise_document_name_too_long_error():
|
||||
title = _('Document ID Too Long')
|
||||
msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ')
|
||||
msg += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
|
||||
msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice')
|
||||
msg += ', '
|
||||
msg += _('document id {} exceed 16 letters.').format(bold(_('should not')))
|
||||
msg += '<br><br>'
|
||||
msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
|
||||
msg += _('You must {} your {} in order to have document id of {} length 16.').format(
|
||||
bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
|
||||
)
|
||||
msg += _('Please account for ammended documents too. ')
|
||||
msg += _('Please account for ammended documents too.')
|
||||
frappe.throw(msg, title=title)
|
||||
|
||||
def read_json(name):
|
||||
@ -847,6 +848,7 @@ class GSPConnector():
|
||||
res = self.make_request('post', self.generate_ewaybill_url, headers, data)
|
||||
if res.get('success'):
|
||||
self.invoice.ewaybill = res.get('result').get('EwbNo')
|
||||
self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill')
|
||||
self.invoice.eway_bill_cancelled = 0
|
||||
self.invoice.update(args)
|
||||
self.invoice.flags.updater_reference = {
|
||||
@ -944,6 +946,7 @@ class GSPConnector():
|
||||
|
||||
self.invoice.irn = res.get('Irn')
|
||||
self.invoice.ewaybill = res.get('EwbNo')
|
||||
self.invoice.eway_bill_validity = res.get('EwbValidTill')
|
||||
self.invoice.ack_no = res.get('AckNo')
|
||||
self.invoice.ack_date = res.get('AckDt')
|
||||
self.invoice.signed_einvoice = dec_signed_invoice
|
||||
@ -960,6 +963,7 @@ class GSPConnector():
|
||||
'label': _('IRN Generated')
|
||||
}
|
||||
self.update_invoice()
|
||||
|
||||
def attach_qrcode_image(self):
|
||||
qrcode = self.invoice.signed_qr_code
|
||||
doctype = self.invoice.doctype
|
||||
|
@ -422,6 +422,9 @@ def make_custom_fields(update=True):
|
||||
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
|
||||
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
|
||||
|
||||
dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
|
||||
depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'),
|
||||
|
||||
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
|
||||
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
|
||||
|
||||
|
@ -879,3 +879,24 @@ def update_taxable_values(doc, method):
|
||||
if total_charges != additional_taxes:
|
||||
diff = additional_taxes - total_charges
|
||||
doc.get('items')[item_count - 1].taxable_value += diff
|
||||
|
||||
def get_depreciation_amount(asset, depreciable_value, row):
|
||||
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
else:
|
||||
rate_of_depreciation = row.rate_of_depreciation
|
||||
# if its the first depreciation
|
||||
if depreciable_value == asset.gross_purchase_amount:
|
||||
# as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
|
||||
diff = date_diff(asset.available_for_use_date, row.depreciation_start_date)
|
||||
if diff <= 180:
|
||||
rate_of_depreciation = rate_of_depreciation / 2
|
||||
frappe.msgprint(
|
||||
_('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.'))
|
||||
|
||||
depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
|
||||
|
||||
return depreciation_amount
|
@ -3,9 +3,9 @@
|
||||
Provide a report and downloadable CSV according to the German DATEV format.
|
||||
|
||||
- Query report showing only the columns that contain data, formatted nicely for
|
||||
dispay to the user.
|
||||
dispay to the user.
|
||||
- CSV download functionality `download_datev_csv` that provides a CSV file with
|
||||
all required columns. Used to import the data into the DATEV Software.
|
||||
all required columns. Used to import the data into the DATEV Software.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@ -88,6 +88,32 @@ COLUMNS = [
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "Beleginfo - Art 2",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 3",
|
||||
"fieldname": "Beleginfo - Art 3",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 3",
|
||||
"fieldname": "Beleginfo - Inhalt 3",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "Beleginfo - Art 3",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 4",
|
||||
"fieldname": "Beleginfo - Art 4",
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 4",
|
||||
"fieldname": "Beleginfo - Inhalt 4",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
}
|
||||
]
|
||||
|
||||
@ -120,10 +146,8 @@ def validate(filters):
|
||||
validate_fiscal_year(from_date, to_date, company)
|
||||
|
||||
if not frappe.db.exists('DATEV Settings', filters.get('company')):
|
||||
frappe.log_error(_('Please create {} for Company {}.').format(
|
||||
'<a href="desk#List/DATEV%20Settings/List">{}</a>'.format(_('DATEV Settings')),
|
||||
frappe.bold(filters.get('company'))
|
||||
))
|
||||
msg = 'Please create DATEV Settings for Company {}'.format(filters.get('company'))
|
||||
frappe.log_error(msg, title='DATEV Settings missing')
|
||||
return False
|
||||
|
||||
return True
|
||||
@ -169,7 +193,11 @@ def get_transactions(filters, as_dict=1):
|
||||
gl.voucher_type as 'Beleginfo - Art 1',
|
||||
gl.voucher_no as 'Beleginfo - Inhalt 1',
|
||||
gl.against_voucher_type as 'Beleginfo - Art 2',
|
||||
gl.against_voucher as 'Beleginfo - Inhalt 2'
|
||||
gl.against_voucher as 'Beleginfo - Inhalt 2',
|
||||
gl.party_type as 'Beleginfo - Art 3',
|
||||
gl.party as 'Beleginfo - Inhalt 3',
|
||||
case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4',
|
||||
par.debtor_creditor_number as 'Beleginfo - Inhalt 4'
|
||||
|
||||
FROM `tabGL Entry` gl
|
||||
|
||||
@ -177,6 +205,19 @@ def get_transactions(filters, as_dict=1):
|
||||
left join `tabAccount` acc
|
||||
on gl.account = acc.name
|
||||
|
||||
left join `tabCustomer` cus
|
||||
on gl.party_type = 'Customer'
|
||||
and gl.party = cus.name
|
||||
|
||||
left join `tabSupplier` sup
|
||||
on gl.party_type = 'Supplier'
|
||||
and gl.party = sup.name
|
||||
|
||||
left join `tabParty Account` par
|
||||
on par.parent = gl.party
|
||||
and par.parenttype = gl.party_type
|
||||
and par.company = %(company)s
|
||||
|
||||
WHERE gl.company = %(company)s
|
||||
AND DATE(gl.posting_date) >= %(from_date)s
|
||||
AND DATE(gl.posting_date) <= %(to_date)s
|
||||
@ -196,40 +237,56 @@ def get_customers(filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
|
||||
acc.account_number as 'Konto',
|
||||
CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
|
||||
CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
|
||||
CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
|
||||
CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
|
||||
par.debtor_creditor_number as 'Konto',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Company' THEN cus.customer_name
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp Unternehmen)',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name)))
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp natürl. Person)',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1)
|
||||
ELSE null
|
||||
END as 'Vorname (Adressatentyp natürl. Person)',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Individual' THEN '1'
|
||||
WHEN 'Company' THEN '2'
|
||||
ELSE '0'
|
||||
END as 'Adressatentyp',
|
||||
adr.address_line1 as 'Straße',
|
||||
adr.pincode as 'Postleitzahl',
|
||||
adr.city as 'Ort',
|
||||
UPPER(country.code) as 'Land',
|
||||
adr.address_line2 as 'Adresszusatz',
|
||||
con.email_id as 'E-Mail',
|
||||
coalesce(con.mobile_no, con.phone) as 'Telefon',
|
||||
adr.email_id as 'E-Mail',
|
||||
adr.phone as 'Telefon',
|
||||
adr.fax as 'Fax',
|
||||
cus.website as 'Internet',
|
||||
cus.tax_id as 'Steuernummer'
|
||||
|
||||
FROM `tabParty Account` par
|
||||
FROM `tabCustomer` cus
|
||||
|
||||
left join `tabAccount` acc
|
||||
on acc.name = par.account
|
||||
left join `tabParty Account` par
|
||||
on par.parent = cus.name
|
||||
and par.parenttype = 'Customer'
|
||||
and par.company = %(company)s
|
||||
|
||||
left join `tabCustomer` cus
|
||||
on cus.name = par.parent
|
||||
left join `tabDynamic Link` dyn_adr
|
||||
on dyn_adr.link_name = cus.name
|
||||
and dyn_adr.link_doctype = 'Customer'
|
||||
and dyn_adr.parenttype = 'Address'
|
||||
|
||||
left join `tabAddress` adr
|
||||
on adr.name = cus.customer_primary_address
|
||||
on adr.name = dyn_adr.parent
|
||||
and adr.is_primary_address = '1'
|
||||
|
||||
left join `tabCountry` country
|
||||
on country.name = adr.country
|
||||
|
||||
left join `tabContact` con
|
||||
on con.name = cus.customer_primary_contact
|
||||
|
||||
WHERE par.company = %(company)s
|
||||
AND par.parenttype = 'Customer'""", filters, as_dict=1)
|
||||
WHERE adr.is_primary_address = '1'
|
||||
""", filters, as_dict=1)
|
||||
|
||||
|
||||
def get_suppliers(filters):
|
||||
@ -242,35 +299,48 @@ def get_suppliers(filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
|
||||
acc.account_number as 'Konto',
|
||||
CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
|
||||
CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
|
||||
CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
|
||||
CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
|
||||
par.debtor_creditor_number as 'Konto',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Company' THEN sup.supplier_name
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp Unternehmen)',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name)))
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp natürl. Person)',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1)
|
||||
ELSE null
|
||||
END as 'Vorname (Adressatentyp natürl. Person)',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Individual' THEN '1'
|
||||
WHEN 'Company' THEN '2'
|
||||
ELSE '0'
|
||||
END as 'Adressatentyp',
|
||||
adr.address_line1 as 'Straße',
|
||||
adr.pincode as 'Postleitzahl',
|
||||
adr.city as 'Ort',
|
||||
UPPER(country.code) as 'Land',
|
||||
adr.address_line2 as 'Adresszusatz',
|
||||
con.email_id as 'E-Mail',
|
||||
coalesce(con.mobile_no, con.phone) as 'Telefon',
|
||||
adr.email_id as 'E-Mail',
|
||||
adr.phone as 'Telefon',
|
||||
adr.fax as 'Fax',
|
||||
sup.website as 'Internet',
|
||||
sup.tax_id as 'Steuernummer',
|
||||
case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
|
||||
|
||||
FROM `tabParty Account` par
|
||||
FROM `tabSupplier` sup
|
||||
|
||||
left join `tabAccount` acc
|
||||
on acc.name = par.account
|
||||
|
||||
left join `tabSupplier` sup
|
||||
on sup.name = par.parent
|
||||
left join `tabParty Account` par
|
||||
on par.parent = sup.name
|
||||
and par.parenttype = 'Supplier'
|
||||
and par.company = %(company)s
|
||||
|
||||
left join `tabDynamic Link` dyn_adr
|
||||
on dyn_adr.link_name = sup.name
|
||||
and dyn_adr.link_doctype = 'Supplier'
|
||||
and dyn_adr.parenttype = 'Address'
|
||||
|
||||
|
||||
left join `tabAddress` adr
|
||||
on adr.name = dyn_adr.parent
|
||||
and adr.is_primary_address = '1'
|
||||
@ -278,17 +348,8 @@ def get_suppliers(filters):
|
||||
left join `tabCountry` country
|
||||
on country.name = adr.country
|
||||
|
||||
left join `tabDynamic Link` dyn_con
|
||||
on dyn_con.link_name = sup.name
|
||||
and dyn_con.link_doctype = 'Supplier'
|
||||
and dyn_con.parenttype = 'Contact'
|
||||
|
||||
left join `tabContact` con
|
||||
on con.name = dyn_con.parent
|
||||
and con.is_primary_contact = '1'
|
||||
|
||||
WHERE par.company = %(company)s
|
||||
AND par.parenttype = 'Supplier'""", filters, as_dict=1)
|
||||
WHERE adr.is_primary_address = '1'
|
||||
""", filters, as_dict=1)
|
||||
|
||||
|
||||
def get_account_names(filters):
|
||||
|
@ -490,7 +490,7 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F
|
||||
outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0
|
||||
|
||||
# Outstanding based on Sales Order
|
||||
outstanding_based_on_so = 0.0
|
||||
outstanding_based_on_so = 0
|
||||
|
||||
# if credit limit check is bypassed at sales order level,
|
||||
# we should not consider outstanding Sales Orders, when customer credit balance report is run
|
||||
@ -501,9 +501,11 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F
|
||||
where customer=%s and docstatus = 1 and company=%s
|
||||
and per_billed < 100 and status != 'Closed'""", (customer, company))
|
||||
|
||||
outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0
|
||||
outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0
|
||||
|
||||
# Outstanding based on Delivery Note, which are not created against Sales Order
|
||||
outstanding_based_on_dn = 0
|
||||
|
||||
unmarked_delivery_note_items = frappe.db.sql("""select
|
||||
dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total
|
||||
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
|
||||
@ -515,15 +517,29 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F
|
||||
and ifnull(dn_item.against_sales_invoice, '') = ''
|
||||
""", (customer, company), as_dict=True)
|
||||
|
||||
outstanding_based_on_dn = 0.0
|
||||
if not unmarked_delivery_note_items:
|
||||
return outstanding_based_on_gle + outstanding_based_on_so
|
||||
|
||||
si_amounts = frappe.db.sql("""
|
||||
SELECT
|
||||
dn_detail, sum(amount) from `tabSales Invoice Item`
|
||||
WHERE
|
||||
docstatus = 1
|
||||
and dn_detail in ({})
|
||||
GROUP BY dn_detail""".format(", ".join(
|
||||
frappe.db.escape(dn_item.name)
|
||||
for dn_item in unmarked_delivery_note_items
|
||||
))
|
||||
)
|
||||
|
||||
si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts}
|
||||
|
||||
for dn_item in unmarked_delivery_note_items:
|
||||
si_amount = frappe.db.sql("""select sum(amount)
|
||||
from `tabSales Invoice Item`
|
||||
where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0]
|
||||
dn_amount = flt(dn_item.amount)
|
||||
si_amount = flt(si_amounts.get(dn_item.name))
|
||||
|
||||
if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total:
|
||||
outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \
|
||||
if dn_amount > si_amount and dn_item.base_net_total:
|
||||
outstanding_based_on_dn += ((dn_amount - si_amount)
|
||||
/ dn_item.base_net_total) * dn_item.base_grand_total
|
||||
|
||||
return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn
|
||||
|
@ -56,10 +56,6 @@ erpnext.PointOfSale.Controller = class {
|
||||
dialog.fields_dict.balance_details.grid.refresh();
|
||||
});
|
||||
}
|
||||
const pos_profile_query = {
|
||||
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
|
||||
filters: { company: frappe.defaults.get_default('company') }
|
||||
}
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Create POS Opening Entry'),
|
||||
static: true,
|
||||
@ -105,6 +101,10 @@ erpnext.PointOfSale.Controller = class {
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
dialog.show();
|
||||
const pos_profile_query = {
|
||||
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
|
||||
filters: { company: dialog.fields_dict.company.get_value() }
|
||||
};
|
||||
}
|
||||
|
||||
async prepare_app_defaults(data) {
|
||||
|
@ -81,13 +81,26 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
|
||||
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
|
||||
|
||||
let qty_to_display = actual_qty;
|
||||
|
||||
if (Math.round(qty_to_display) > 999) {
|
||||
qty_to_display = Math.round(qty_to_display)/1000;
|
||||
qty_to_display = qty_to_display.toFixed(1) + 'K';
|
||||
}
|
||||
|
||||
function get_item_image_html() {
|
||||
if (!me.hide_images && item_image) {
|
||||
return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
|
||||
return `<div class="item-qty-pill">
|
||||
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
|
||||
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
|
||||
</div>`;
|
||||
} else {
|
||||
return `<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
|
||||
return `<div class="item-qty-pill">
|
||||
<span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
|
||||
</div>
|
||||
<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,13 +108,12 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
`<div class="item-wrapper"
|
||||
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
|
||||
data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
|
||||
title="Avaiable Qty: ${actual_qty}">
|
||||
title="${item.item_name}">
|
||||
|
||||
${get_item_image_html()}
|
||||
|
||||
<div class="item-detail">
|
||||
<div class="item-name">
|
||||
<span class="indicator ${indicator_color}"></span>
|
||||
${frappe.ellipsis(item.item_name, 18)}
|
||||
</div>
|
||||
<div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
|
||||
@ -316,4 +328,4 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
toggle_component(show) {
|
||||
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -169,9 +169,9 @@ frappe.ui.form.on("Company", {
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions",
|
||||
method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request",
|
||||
args: {
|
||||
company_name: data.company_name
|
||||
company: data.company_name
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r, rt) {
|
||||
|
@ -613,4 +613,13 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad
|
||||
if out:
|
||||
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_transaction_deletion_request(company):
|
||||
tdr = frappe.get_doc({
|
||||
'doctype': 'Transaction Deletion Record',
|
||||
'company': company
|
||||
})
|
||||
tdr.insert()
|
||||
tdr.submit()
|
||||
|
@ -1,117 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import cint
|
||||
from frappe import _
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
|
||||
import functools
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_company_transactions(company_name):
|
||||
frappe.only_for("System Manager")
|
||||
doc = frappe.get_doc("Company", company_name)
|
||||
|
||||
if frappe.session.user != doc.owner and frappe.session.user != 'Administrator':
|
||||
frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
|
||||
frappe.PermissionError)
|
||||
|
||||
delete_bins(company_name)
|
||||
delete_lead_addresses(company_name)
|
||||
|
||||
for doctype in frappe.db.sql_list("""select parent from
|
||||
tabDocField where fieldtype='Link' and options='Company'"""):
|
||||
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
|
||||
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
||||
"Purchase Taxes and Charges Template", "POS Profile", "BOM",
|
||||
"Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account",
|
||||
"Item Default", "Customer", "Supplier", "GST Account"):
|
||||
delete_for_doctype(doctype, company_name)
|
||||
|
||||
# reset company values
|
||||
doc.total_monthly_sales = 0
|
||||
doc.sales_monthly_history = None
|
||||
doc.save()
|
||||
# Clear notification counts
|
||||
clear_notifications()
|
||||
|
||||
def delete_for_doctype(doctype, company_name):
|
||||
meta = frappe.get_meta(doctype)
|
||||
company_fieldname = meta.get("fields", {"fieldtype": "Link",
|
||||
"options": "Company"})[0].fieldname
|
||||
|
||||
if not meta.issingle:
|
||||
if not meta.istable:
|
||||
# delete communication
|
||||
delete_communications(doctype, company_name, company_fieldname)
|
||||
|
||||
# delete children
|
||||
for df in meta.get_table_fields():
|
||||
frappe.db.sql("""delete from `tab{0}` where parent in
|
||||
(select name from `tab{1}` where `{2}`=%s)""".format(df.options,
|
||||
doctype, company_fieldname), company_name)
|
||||
|
||||
#delete version log
|
||||
frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
|
||||
(select name from `tab{0}` where `{1}`=%s)""".format(doctype,
|
||||
company_fieldname), (doctype, company_name))
|
||||
|
||||
# delete parent
|
||||
frappe.db.sql("""delete from `tab{0}`
|
||||
where {1}= %s """.format(doctype, company_fieldname), company_name)
|
||||
|
||||
# reset series
|
||||
naming_series = meta.get_field("naming_series")
|
||||
if naming_series and naming_series.options:
|
||||
prefixes = sorted(naming_series.options.split("\n"),
|
||||
key=functools.cmp_to_key(lambda a, b: len(b) - len(a)))
|
||||
|
||||
for prefix in prefixes:
|
||||
if prefix:
|
||||
last = frappe.db.sql("""select max(name) from `tab{0}`
|
||||
where name like %s""".format(doctype), prefix + "%")
|
||||
if last and last[0][0]:
|
||||
last = cint(last[0][0].replace(prefix, ""))
|
||||
else:
|
||||
last = 0
|
||||
|
||||
frappe.db.sql("""update tabSeries set current = %s
|
||||
where name=%s""", (last, prefix))
|
||||
|
||||
def delete_bins(company_name):
|
||||
frappe.db.sql("""delete from tabBin where warehouse in
|
||||
(select name from tabWarehouse where company=%s)""", company_name)
|
||||
|
||||
def delete_lead_addresses(company_name):
|
||||
"""Delete addresses to which leads are linked"""
|
||||
leads = frappe.get_all("Lead", filters={"company": company_name})
|
||||
leads = [ "'%s'"%row.get("name") for row in leads ]
|
||||
addresses = []
|
||||
if leads:
|
||||
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
||||
in ({leads})""".format(leads=",".join(leads)))
|
||||
|
||||
if addresses:
|
||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||
|
||||
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
||||
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
||||
|
||||
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
||||
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
||||
|
||||
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
||||
|
||||
def delete_communications(doctype, company_name, company_fieldname):
|
||||
reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
|
||||
reference_doc_names = [r.name for r in reference_docs]
|
||||
|
||||
communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]})
|
||||
communication_names = [c.name for c in communications]
|
||||
|
||||
frappe.delete_doc("Communication", communication_names, ignore_permissions=True)
|
@ -86,15 +86,6 @@ class TestCompany(unittest.TestCase):
|
||||
self.delete_mode_of_payment(template)
|
||||
frappe.delete_doc("Company", template)
|
||||
|
||||
def test_delete_communication(self):
|
||||
from erpnext.setup.doctype.company.delete_company_transactions import delete_communications
|
||||
company = create_child_company()
|
||||
lead = create_test_lead_in_company(company)
|
||||
communication = create_company_communication("Lead", lead)
|
||||
delete_communications("Lead", "Test Company", "company")
|
||||
self.assertFalse(frappe.db.exists("Communcation", communication))
|
||||
self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication}))
|
||||
|
||||
def delete_mode_of_payment(self, company):
|
||||
frappe.db.sql(""" delete from `tabMode of Payment Account`
|
||||
where company =%s """, (company))
|
||||
|
@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestTransactionDeletionRecord(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_company('Dunder Mifflin Paper Co')
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_doctypes_contain_company_field(self):
|
||||
tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
|
||||
for doctype in tdr.doctypes:
|
||||
contains_company = False
|
||||
doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields']
|
||||
for doctype_field in doctype_fields:
|
||||
if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company':
|
||||
contains_company = True
|
||||
break
|
||||
self.assertTrue(contains_company)
|
||||
|
||||
def test_no_of_docs_is_correct(self):
|
||||
for i in range(5):
|
||||
create_task('Dunder Mifflin Paper Co')
|
||||
tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
|
||||
for doctype in tdr.doctypes:
|
||||
if doctype.doctype_name == 'Task':
|
||||
self.assertEqual(doctype.no_of_docs, 5)
|
||||
|
||||
def test_deletion_is_successful(self):
|
||||
create_task('Dunder Mifflin Paper Co')
|
||||
create_transaction_deletion_request('Dunder Mifflin Paper Co')
|
||||
tasks_containing_company = frappe.get_all('Task',
|
||||
filters = {
|
||||
'company' : 'Dunder Mifflin Paper Co'
|
||||
})
|
||||
self.assertEqual(tasks_containing_company, [])
|
||||
|
||||
def create_company(company_name):
|
||||
company = frappe.get_doc({
|
||||
'doctype': 'Company',
|
||||
'company_name': company_name,
|
||||
'default_currency': 'INR'
|
||||
})
|
||||
company.insert(ignore_if_duplicate = True)
|
||||
|
||||
def create_transaction_deletion_request(company):
|
||||
tdr = frappe.get_doc({
|
||||
'doctype': 'Transaction Deletion Record',
|
||||
'company': company
|
||||
})
|
||||
tdr.insert()
|
||||
tdr.submit()
|
||||
return tdr
|
||||
|
||||
|
||||
def create_task(company):
|
||||
task = frappe.get_doc({
|
||||
'doctype': 'Task',
|
||||
'company': company,
|
||||
'subject': 'Delete'
|
||||
})
|
||||
task.insert()
|
@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Transaction Deletion Record', {
|
||||
onload: function(frm) {
|
||||
if (frm.doc.docstatus == 0) {
|
||||
let doctypes_to_be_ignored_array;
|
||||
frappe.call({
|
||||
method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored',
|
||||
callback: function(r) {
|
||||
doctypes_to_be_ignored_array = r.message;
|
||||
populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm);
|
||||
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
|
||||
frm.refresh_field('doctypes_to_be_ignored');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true;
|
||||
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
|
||||
frm.refresh_field('doctypes_to_be_ignored');
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
|
||||
frm.refresh_field('doctypes_to_be_ignored');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) {
|
||||
if (!(frm.doc.doctypes_to_be_ignored)) {
|
||||
var i;
|
||||
for (i = 0; i < doctypes_to_be_ignored_array.length; i++) {
|
||||
frm.add_child('doctypes_to_be_ignored', {
|
||||
doctype_name: doctypes_to_be_ignored_array[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "TDL.####",
|
||||
"creation": "2021-04-06 20:17:18.404716",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"doctypes",
|
||||
"doctypes_to_be_ignored",
|
||||
"amended_from",
|
||||
"status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Summary",
|
||||
"options": "Transaction Deletion Record Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "doctypes_to_be_ignored",
|
||||
"fieldtype": "Table",
|
||||
"label": "Excluded DocTypes",
|
||||
"options": "Transaction Deletion Record Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Transaction Deletion Record",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nCompleted"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-08 23:13:48.049879",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Transaction Deletion Record",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.utils import cint
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
|
||||
class TransactionDeletionRecord(Document):
|
||||
def validate(self):
|
||||
frappe.only_for('System Manager')
|
||||
company_obj = frappe.get_doc('Company', self.company)
|
||||
if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator':
|
||||
frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'),
|
||||
frappe.PermissionError)
|
||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||
for doctype in self.doctypes_to_be_ignored:
|
||||
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
||||
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed"))
|
||||
|
||||
def before_submit(self):
|
||||
if not self.doctypes_to_be_ignored:
|
||||
self.populate_doctypes_to_be_ignored_table()
|
||||
|
||||
self.delete_bins()
|
||||
self.delete_lead_addresses()
|
||||
|
||||
company_obj = frappe.get_doc('Company', self.company)
|
||||
# reset company values
|
||||
company_obj.total_monthly_sales = 0
|
||||
company_obj.sales_monthly_history = None
|
||||
company_obj.save()
|
||||
# Clear notification counts
|
||||
clear_notifications()
|
||||
|
||||
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
||||
tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
|
||||
doctypes_to_be_ignored_list = singles
|
||||
for doctype in self.doctypes_to_be_ignored:
|
||||
doctypes_to_be_ignored_list.append(doctype.doctype_name)
|
||||
|
||||
docfields = frappe.get_all('DocField',
|
||||
filters = {
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Company',
|
||||
'parent': ['not in', doctypes_to_be_ignored_list]},
|
||||
fields=['parent', 'fieldname'])
|
||||
|
||||
for docfield in docfields:
|
||||
if docfield['parent'] != self.doctype:
|
||||
no_of_docs = frappe.db.count(docfield['parent'], {
|
||||
docfield['fieldname'] : self.company
|
||||
})
|
||||
|
||||
if no_of_docs > 0:
|
||||
self.delete_version_log(docfield['parent'], docfield['fieldname'])
|
||||
self.delete_communications(docfield['parent'], docfield['fieldname'])
|
||||
|
||||
# populate DocTypes table
|
||||
if docfield['parent'] not in tables:
|
||||
self.append('doctypes', {
|
||||
'doctype_name' : docfield['parent'],
|
||||
'no_of_docs' : no_of_docs
|
||||
})
|
||||
|
||||
# delete the docs linked with the specified company
|
||||
frappe.db.delete(docfield['parent'], {
|
||||
docfield['fieldname'] : self.company
|
||||
})
|
||||
|
||||
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
||||
if naming_series:
|
||||
if '#' in naming_series:
|
||||
self.update_naming_series(naming_series, docfield['parent'])
|
||||
|
||||
def populate_doctypes_to_be_ignored_table(self):
|
||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||
for doctype in doctypes_to_be_ignored_list:
|
||||
self.append('doctypes_to_be_ignored', {
|
||||
'doctype_name' : doctype
|
||||
})
|
||||
|
||||
def update_naming_series(self, naming_series, doctype_name):
|
||||
if '.' in naming_series:
|
||||
prefix, hashes = naming_series.rsplit('.', 1)
|
||||
else:
|
||||
prefix, hashes = naming_series.rsplit('{', 1)
|
||||
last = frappe.db.sql("""select max(name) from `tab{0}`
|
||||
where name like %s""".format(doctype_name), prefix + '%')
|
||||
if last and last[0][0]:
|
||||
last = cint(last[0][0].replace(prefix, ''))
|
||||
else:
|
||||
last = 0
|
||||
|
||||
frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix))
|
||||
|
||||
def delete_version_log(self, doctype, company_fieldname):
|
||||
frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
|
||||
(select name from `tab{0}` where `{1}`=%s)""".format(doctype,
|
||||
company_fieldname), (doctype, self.company))
|
||||
|
||||
def delete_communications(self, doctype, company_fieldname):
|
||||
reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company})
|
||||
reference_doc_names = [r.name for r in reference_docs]
|
||||
|
||||
communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]})
|
||||
communication_names = [c.name for c in communications]
|
||||
|
||||
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
|
||||
|
||||
def delete_bins(self):
|
||||
frappe.db.sql("""delete from tabBin where warehouse in
|
||||
(select name from tabWarehouse where company=%s)""", self.company)
|
||||
|
||||
def delete_lead_addresses(self):
|
||||
"""Delete addresses to which leads are linked"""
|
||||
leads = frappe.get_all('Lead', filters={'company': self.company})
|
||||
leads = ["'%s'" % row.get("name") for row in leads]
|
||||
addresses = []
|
||||
if leads:
|
||||
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
||||
in ({leads})""".format(leads=",".join(leads)))
|
||||
|
||||
if addresses:
|
||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||
|
||||
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
||||
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
||||
|
||||
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
||||
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
||||
|
||||
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_doctypes_to_be_ignored():
|
||||
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
|
||||
'Party Account', 'Employee', 'Sales Taxes and Charges Template',
|
||||
'Purchase Taxes and Charges Template', 'POS Profile', 'BOM',
|
||||
'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment',
|
||||
'Item Default', 'Customer', 'Supplier', 'GST Account']
|
||||
return doctypes_to_be_ignored_list
|
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.listview_settings['Transaction Deletion Record'] = {
|
||||
get_indicator: function(doc) {
|
||||
if (doc.docstatus == 0) {
|
||||
return [__("Draft"), "red"];
|
||||
} else {
|
||||
return [__("Completed"), "green"];
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-04-07 07:34:00.124124",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"doctype_name",
|
||||
"no_of_docs"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "doctype_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "no_of_docs",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Number of Docs"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-08 23:10:46.166744",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Transaction Deletion Record Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TransactionDeletionRecordItem(Document):
|
||||
pass
|
@ -428,7 +428,6 @@ def install_post_company_fixtures(args=None):
|
||||
frappe.local.flags.ignore_update_nsm = True
|
||||
make_records(records[1:])
|
||||
frappe.local.flags.ignore_update_nsm = False
|
||||
|
||||
rebuild_tree("Department", "parent_department")
|
||||
|
||||
|
||||
|
@ -273,11 +273,11 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
},
|
||||
|
||||
items_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
packed_items_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
close_delivery_note: function(doc){
|
||||
|
@ -41,6 +41,15 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.employee) {
|
||||
frm.add_custom_button(__('Expense Claim'), function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.make_expense_claim',
|
||||
frm: cur_frm,
|
||||
});
|
||||
}, __("Create"));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) {
|
||||
frm.add_custom_button(__("Notify Customers via Email"), function () {
|
||||
frm.trigger('notify_customers');
|
||||
|
@ -21,6 +21,7 @@
|
||||
"column_break_4",
|
||||
"vehicle",
|
||||
"departure_time",
|
||||
"employee",
|
||||
"delivery_service_stops",
|
||||
"delivery_stops",
|
||||
"calculate_arrival_time",
|
||||
@ -176,11 +177,19 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Driver Email",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "driver.employee",
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"label": "Employee",
|
||||
"options": "Employee",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-01-26 22:37:14.824021",
|
||||
"modified": "2021-04-30 21:21:36.610142",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Trip",
|
||||
|
@ -11,6 +11,7 @@ from frappe import _
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, get_datetime, get_link_to_form
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
|
||||
class DeliveryTrip(Document):
|
||||
@ -394,3 +395,15 @@ def get_driver_email(driver):
|
||||
employee = frappe.db.get_value("Driver", driver, "employee")
|
||||
email = frappe.db.get_value("Employee", employee, "prefered_email")
|
||||
return {"email": email}
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_expense_claim(source_name, target_doc=None):
|
||||
doc = get_mapped_doc("Delivery Trip", source_name,
|
||||
{"Delivery Trip": {
|
||||
"doctype": "Expense Claim",
|
||||
"field_map": {
|
||||
"name" : "delivery_trip"
|
||||
}
|
||||
}}, target_doc)
|
||||
|
||||
return doc
|
@ -7,7 +7,7 @@ import unittest
|
||||
|
||||
import erpnext
|
||||
import frappe
|
||||
from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers
|
||||
from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers, make_expense_claim
|
||||
from erpnext.tests.utils import create_test_contact_and_address
|
||||
from frappe.utils import add_days, flt, now_datetime, nowdate
|
||||
|
||||
@ -28,6 +28,10 @@ class TestDeliveryTrip(unittest.TestCase):
|
||||
frappe.db.sql("delete from `tabEmail Template`")
|
||||
frappe.db.sql("delete from `tabDelivery Trip`")
|
||||
|
||||
def test_expense_claim_fields_are_fetched_properly(self):
|
||||
expense_claim = make_expense_claim(self.delivery_trip.name)
|
||||
self.assertEqual(self.delivery_trip.name, expense_claim.delivery_trip)
|
||||
|
||||
def test_delivery_trip_notify_customers(self):
|
||||
notify_customers(delivery_trip=self.delivery_trip.name)
|
||||
self.delivery_trip.load_from_db()
|
||||
|
@ -181,7 +181,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred\nReceived",
|
||||
"options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nPartially Received\nOrdered\nIssued\nTransferred\nReceived",
|
||||
"print_hide": 1,
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
|
@ -13,8 +13,9 @@ from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from six import iteritems
|
||||
from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
class TestPurchaseReceipt(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -144,6 +145,62 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
|
||||
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
|
||||
|
||||
def test_duplicate_serial_nos(self):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
item = frappe.db.exists("Item", {'item_name': 'Test Serialized Item 123'})
|
||||
if not item:
|
||||
item = create_item("Test Serialized Item 123")
|
||||
item.has_serial_no = 1
|
||||
item.serial_no_series = "TSI123-.####"
|
||||
item.save()
|
||||
else:
|
||||
item = frappe.get_doc("Item", {'item_name': 'Test Serialized Item 123'})
|
||||
|
||||
# First make purchase receipt
|
||||
pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
|
||||
pr.load_from_db()
|
||||
|
||||
serial_nos = frappe.db.get_value('Stock Ledger Entry',
|
||||
{'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no')
|
||||
|
||||
serial_nos = get_serial_nos(serial_nos)
|
||||
|
||||
self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos)
|
||||
|
||||
# Then tried to receive same serial nos in difference company
|
||||
pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
|
||||
serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
|
||||
warehouse = 'Stores - _TC1')
|
||||
|
||||
self.assertRaises(SerialNoDuplicateError, pr_different_company.submit)
|
||||
|
||||
# Then made delivery note to remove the serial nos from stock
|
||||
dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos))
|
||||
dn.load_from_db()
|
||||
self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos)
|
||||
|
||||
posting_date = add_days(today(), -3)
|
||||
|
||||
# Try to receive same serial nos again in the same company with backdated.
|
||||
pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
|
||||
posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True)
|
||||
|
||||
self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit)
|
||||
|
||||
# Try to receive same serial nos with different company with backdated.
|
||||
pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
|
||||
posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
|
||||
warehouse = 'Stores - _TC1')
|
||||
|
||||
self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit)
|
||||
|
||||
# Receive the same serial nos after the delivery note posting date and time
|
||||
make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos))
|
||||
|
||||
# Raise the error for backdated deliver note entry cancel
|
||||
self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel)
|
||||
|
||||
def test_purchase_receipt_gl_entry(self):
|
||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
|
||||
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
|
||||
@ -565,30 +622,6 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
new_pr_doc.cancel()
|
||||
|
||||
def test_not_accept_duplicate_serial_no(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0, "has_batch_no": 0})
|
||||
if not item_code:
|
||||
item = make_item("Test Serial Item 1", dict(has_serial_no=1, has_batch_no=0))
|
||||
item_code = item.name
|
||||
|
||||
serial_no = random_string(5)
|
||||
pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
|
||||
dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no)
|
||||
|
||||
pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True)
|
||||
self.assertRaises(SerialNoDuplicateError, pr2.submit)
|
||||
|
||||
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
|
||||
serial_no=serial_no, basic_rate=100, do_not_submit=True)
|
||||
se.submit()
|
||||
|
||||
se.cancel()
|
||||
dn.cancel()
|
||||
pr1.cancel()
|
||||
|
||||
def test_auto_asset_creation(self):
|
||||
asset_item = "Test Asset Item"
|
||||
|
||||
|
@ -27,10 +27,11 @@ class TestQualityInspection(unittest.TestCase):
|
||||
dn.reload()
|
||||
self.assertRaises(QualityInspectionRejectedError, dn.submit)
|
||||
|
||||
frappe.db.set_value("Quality Inspection Reading", {"parent": qa.name}, "status", "Accepted")
|
||||
frappe.db.set_value("Quality Inspection", qa.name, "status", "Accepted")
|
||||
dn.reload()
|
||||
dn.submit()
|
||||
|
||||
qa.reload()
|
||||
qa.cancel()
|
||||
dn.reload()
|
||||
dn.cancel()
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, get_link_to_form, add_to_date, today
|
||||
from frappe.utils import cint, get_link_to_form, add_to_date, now, today
|
||||
from erpnext.stock.stock_ledger import repost_future_sle
|
||||
from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced
|
||||
from frappe.utils.user import get_users_with_role
|
||||
@ -127,7 +127,7 @@ def repost_entries():
|
||||
check_if_stock_and_account_balance_synced(today(), d.name)
|
||||
|
||||
def get_repost_item_valuation_entries():
|
||||
date = add_to_date(today(), hours=-3)
|
||||
date = add_to_date(now(), hours=-3)
|
||||
|
||||
return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
|
||||
WHERE status != 'Completed' and creation <= %s and docstatus = 1
|
||||
|
@ -243,7 +243,7 @@ def validate_serial_no(sle, item_det):
|
||||
if frappe.db.exists("Serial No", serial_no):
|
||||
sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
|
||||
"delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
|
||||
"purchase_document_no", "company"], as_dict=1)
|
||||
"purchase_document_no", "company", "status"], as_dict=1)
|
||||
|
||||
if sr.item_code!=sle.item_code:
|
||||
if not allow_serial_nos_with_different_item(serial_no, sle):
|
||||
@ -266,6 +266,9 @@ def validate_serial_no(sle, item_det):
|
||||
frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no,
|
||||
sle.warehouse), SerialNoWarehouseError)
|
||||
|
||||
if not sr.purchase_document_no:
|
||||
frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError)
|
||||
|
||||
if sle.voucher_type in ("Delivery Note", "Sales Invoice"):
|
||||
|
||||
if sr.batch_no and sr.batch_no != sle.batch_no:
|
||||
@ -382,19 +385,6 @@ def has_serial_no_exists(sn, sle):
|
||||
if sn.company != sle.company:
|
||||
return False
|
||||
|
||||
status = False
|
||||
if sn.purchase_document_no:
|
||||
if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and
|
||||
sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]):
|
||||
status = True
|
||||
|
||||
# If status is receipt then system will allow to in-ward the delivered serial no
|
||||
if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry",
|
||||
sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")):
|
||||
status = False
|
||||
|
||||
return status
|
||||
|
||||
def allow_serial_nos_with_different_item(sle_serial_no, sle):
|
||||
"""
|
||||
Allows same serial nos for raw materials and finished goods
|
||||
|
@ -996,7 +996,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
},
|
||||
|
||||
items_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
toggle_related_fields: function(doc) {
|
||||
|
@ -76,6 +76,7 @@ class StockEntry(StockController):
|
||||
self.validate_difference_account()
|
||||
self.set_job_card_data()
|
||||
self.set_purpose_for_stock_entry()
|
||||
self.validate_duplicate_serial_no()
|
||||
|
||||
if not self.from_bom:
|
||||
self.fg_completed_qty = 0.0
|
||||
@ -587,6 +588,22 @@ class StockEntry(StockController):
|
||||
self.purpose = frappe.get_cached_value('Stock Entry Type',
|
||||
self.stock_entry_type, 'purpose')
|
||||
|
||||
def validate_duplicate_serial_no(self):
|
||||
warehouse_wise_serial_nos = {}
|
||||
|
||||
# In case of repack the source and target serial nos could be same
|
||||
for warehouse in ['s_warehouse', 't_warehouse']:
|
||||
serial_nos = []
|
||||
for row in self.items:
|
||||
if not (row.serial_no and row.get(warehouse)): continue
|
||||
|
||||
for sn in get_serial_nos(row.serial_no):
|
||||
if sn in serial_nos:
|
||||
frappe.throw(_('The serial no {0} has added multiple times in the stock entry {1}')
|
||||
.format(frappe.bold(sn), self.name))
|
||||
|
||||
serial_nos.append(sn)
|
||||
|
||||
def validate_purchase_order(self):
|
||||
"""Throw exception if more raw material is transferred against Purchase Order than in
|
||||
the raw materials supplied table"""
|
||||
|
@ -72,7 +72,7 @@ class StockReconciliation(StockController):
|
||||
|
||||
if item_dict.get("serial_nos"):
|
||||
item.current_serial_no = item_dict.get("serial_nos")
|
||||
if self.purpose == "Stock Reconciliation":
|
||||
if self.purpose == "Stock Reconciliation" and not item.serial_no:
|
||||
item.serial_no = item.current_serial_no
|
||||
|
||||
item.current_qty = item_dict.get("qty")
|
||||
|
@ -5,40 +5,44 @@
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_defaults_section",
|
||||
"item_naming_by",
|
||||
"item_group",
|
||||
"stock_uom",
|
||||
"default_warehouse",
|
||||
"sample_retention_warehouse",
|
||||
"column_break_4",
|
||||
"valuation_method",
|
||||
"sample_retention_warehouse",
|
||||
"use_naming_series",
|
||||
"naming_series_prefix",
|
||||
"section_break_9",
|
||||
"over_delivery_receipt_allowance",
|
||||
"role_allowed_to_over_deliver_receive",
|
||||
"action_if_quality_inspection_is_not_submitted",
|
||||
"show_barcode_field",
|
||||
"clean_description_html",
|
||||
"disable_serial_no_and_batch_selector",
|
||||
"section_break_7",
|
||||
"column_break_12",
|
||||
"auto_insert_price_list_rate_if_missing",
|
||||
"allow_negative_stock",
|
||||
"column_break_10",
|
||||
"show_barcode_field",
|
||||
"clean_description_html",
|
||||
"action_if_quality_inspection_is_not_submitted",
|
||||
"section_break_7",
|
||||
"automatically_set_serial_nos_based_on_fifo",
|
||||
"set_qty_in_transactions_based_on_serial_no_input",
|
||||
"column_break_10",
|
||||
"disable_serial_no_and_batch_selector",
|
||||
"auto_material_request",
|
||||
"auto_indent",
|
||||
"column_break_27",
|
||||
"reorder_email_notify",
|
||||
"inter_warehouse_transfer_settings_section",
|
||||
"allow_from_dn",
|
||||
"column_break_31",
|
||||
"allow_from_pr",
|
||||
"control_historical_stock_transactions_section",
|
||||
"role_allowed_to_create_edit_back_dated_transactions",
|
||||
"column_break_26",
|
||||
"stock_frozen_upto",
|
||||
"stock_frozen_upto_days",
|
||||
"stock_auth_role",
|
||||
"batch_id_sb",
|
||||
"use_naming_series",
|
||||
"naming_series_prefix"
|
||||
"column_break_26",
|
||||
"role_allowed_to_create_edit_back_dated_transactions",
|
||||
"stock_auth_role"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -102,23 +106,24 @@
|
||||
"default": "1",
|
||||
"fieldname": "show_barcode_field",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Barcode Field"
|
||||
"label": "Show Barcode Field in Stock Transactions"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "clean_description_html",
|
||||
"fieldtype": "Check",
|
||||
"label": "Convert Item Description to Clean HTML"
|
||||
"label": "Convert Item Description to Clean HTML in Transactions"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Serialised and Batch Setting"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "auto_insert_price_list_rate_if_missing",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Insert Price List Rate If Missing"
|
||||
"label": "Auto Insert Item Price If Missing"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -179,16 +184,11 @@
|
||||
"label": "Role Allowed to Edit Frozen Stock",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "batch_id_sb",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Batch Identification"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_naming_series",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Naming Series"
|
||||
"label": "Have Default Naming Series for Batch ID?"
|
||||
},
|
||||
{
|
||||
"default": "BATCH-",
|
||||
@ -242,6 +242,28 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Deliver/Receive",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_defaults_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Item Defaults"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Stock Transactions Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_27",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_31",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -249,7 +271,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-11 18:48:14.513055",
|
||||
"modified": "2021-04-30 17:27:42.709231",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
@ -79,7 +79,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
||||
get_price_list_rate(args, item, out)
|
||||
|
||||
if args.customer and cint(args.is_pos):
|
||||
out.update(get_pos_profile_item_details(args.company, args))
|
||||
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
|
||||
|
||||
if (args.get("doctype") == "Material Request" and
|
||||
args.get("material_request_type") == "Material Transfer"):
|
||||
@ -470,7 +470,9 @@ def get_item_tax_template(args, item, out):
|
||||
item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
|
||||
item_group = item_group_doc.parent_item_group
|
||||
|
||||
def _get_item_tax_template(args, taxes, out={}, for_validate=False):
|
||||
def _get_item_tax_template(args, taxes, out=None, for_validate=False):
|
||||
if out is None:
|
||||
out = {}
|
||||
taxes_with_validity = []
|
||||
taxes_with_no_validity = []
|
||||
|
||||
@ -933,10 +935,10 @@ def get_bin_details(item_code, warehouse, company=None):
|
||||
return bin_details
|
||||
|
||||
def get_company_total_stock(item_code, company):
|
||||
return frappe.db.sql("""SELECT sum(actual_qty) from
|
||||
(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
|
||||
WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'"""
|
||||
.format(company, item_code))[0][0]
|
||||
return frappe.db.sql("""SELECT sum(actual_qty) from
|
||||
(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
|
||||
WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""",
|
||||
(company, item_code))[0][0]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):
|
||||
|
@ -70,7 +70,7 @@ def get_stock_ledger_entries(filters):
|
||||
return frappe.db.sql("""
|
||||
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
|
||||
from `tabStock Ledger Entry`
|
||||
where docstatus < 2 and ifnull(batch_no, '') != '' %s
|
||||
where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s
|
||||
group by voucher_no, batch_no, item_code, warehouse
|
||||
order by item_code, warehouse""" %
|
||||
conditions, as_dict=1)
|
||||
|
0
erpnext/stock/report/serial_no_ledger/__init__.py
Normal file
0
erpnext/stock/report/serial_no_ledger/__init__.py
Normal file
52
erpnext/stock/report/serial_no_ledger/serial_no_ledger.js
Normal file
52
erpnext/stock/report/serial_no_ledger/serial_no_ledger.js
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Serial No Ledger"] = {
|
||||
"filters": [
|
||||
{
|
||||
'label': __('Item Code'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'item_code',
|
||||
'reqd': 1,
|
||||
'options': 'Item',
|
||||
get_query: function() {
|
||||
return {
|
||||
filters: {
|
||||
'has_serial_no': 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'label': __('Serial No'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'serial_no',
|
||||
'options': 'Serial No',
|
||||
'reqd': 1
|
||||
},
|
||||
{
|
||||
'label': __('Warehouse'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'warehouse',
|
||||
'options': 'Warehouse',
|
||||
get_query: function() {
|
||||
let company = frappe.query_report.get_filter_value('company');
|
||||
|
||||
if (company) {
|
||||
return {
|
||||
filters: {
|
||||
'company': company
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'label': __('As On Date'),
|
||||
'fieldtype': 'Date',
|
||||
'fieldname': 'posting_date',
|
||||
'default': frappe.datetime.get_today()
|
||||
},
|
||||
]
|
||||
};
|
33
erpnext/stock/report/serial_no_ledger/serial_no_ledger.json
Normal file
33
erpnext/stock/report/serial_no_ledger/serial_no_ledger.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-04-20 13:32:41.523219",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{}",
|
||||
"modified": "2021-04-20 13:33:19.015829",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Serial No Ledger",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Stock Ledger Entry",
|
||||
"report_name": "Serial No Ledger",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Sales User"
|
||||
}
|
||||
]
|
||||
}
|
53
erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
Normal file
53
erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
from erpnext.stock.stock_ledger import get_stock_ledger_entries
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [{
|
||||
'label': _('Posting Date'),
|
||||
'fieldtype': 'Date',
|
||||
'fieldname': 'posting_date'
|
||||
}, {
|
||||
'label': _('Posting Time'),
|
||||
'fieldtype': 'Time',
|
||||
'fieldname': 'posting_time'
|
||||
}, {
|
||||
'label': _('Voucher Type'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'voucher_type',
|
||||
'options': 'DocType',
|
||||
'width': 220
|
||||
}, {
|
||||
'label': _('Voucher No'),
|
||||
'fieldtype': 'Dynamic Link',
|
||||
'fieldname': 'voucher_no',
|
||||
'options': 'voucher_type',
|
||||
'width': 220
|
||||
}, {
|
||||
'label': _('Company'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'company',
|
||||
'options': 'Company',
|
||||
'width': 220
|
||||
}, {
|
||||
'label': _('Warehouse'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'warehouse',
|
||||
'options': 'Warehouse',
|
||||
'width': 220
|
||||
}]
|
||||
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
return get_stock_ledger_entries(filters, '<=', order="asc") or []
|
||||
|
@ -165,7 +165,7 @@ def get_stock_ledger_entries(filters, items):
|
||||
select
|
||||
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
|
||||
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
||||
sle.item_code as name, sle.voucher_no, sle.stock_value
|
||||
sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
|
||||
from
|
||||
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
||||
where sle.docstatus < 2 %s %s
|
||||
@ -193,7 +193,7 @@ def get_item_warehouse_map(filters, sle):
|
||||
|
||||
qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)]
|
||||
|
||||
if d.voucher_type == "Stock Reconciliation":
|
||||
if d.voucher_type == "Stock Reconciliation" and not d.batch_no:
|
||||
qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty)
|
||||
else:
|
||||
qty_diff = flt(d.actual_qty)
|
||||
|
@ -14,7 +14,14 @@ frappe.query_reports["Stock Projected Qty"] = {
|
||||
"fieldname":"warehouse",
|
||||
"label": __("Warehouse"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse"
|
||||
"options": "Warehouse",
|
||||
"get_query": () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frappe.query_report.get_filter_value('company')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"item_code",
|
||||
|
@ -6,6 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, today
|
||||
from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty
|
||||
|
||||
def execute(filters=None):
|
||||
is_reposting_item_valuation_in_progress()
|
||||
@ -49,9 +50,13 @@ def execute(filters=None):
|
||||
if (re_order_level or re_order_qty) and re_order_level > bin.projected_qty:
|
||||
shortage_qty = re_order_level - flt(bin.projected_qty)
|
||||
|
||||
reserved_qty_for_pos = get_pos_reserved_qty(bin.item_code, bin.warehouse)
|
||||
if reserved_qty_for_pos:
|
||||
bin.projected_qty -= reserved_qty_for_pos
|
||||
|
||||
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
|
||||
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
|
||||
bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract,
|
||||
bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, reserved_qty_for_pos,
|
||||
bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
|
||||
|
||||
if include_uom:
|
||||
@ -74,9 +79,11 @@ def get_columns():
|
||||
{"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"},
|
||||
{"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
|
||||
{"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
|
||||
{"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
|
||||
{"label": _("Reserved for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
|
||||
"width": 100, "convertible": "qty"},
|
||||
{"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
|
||||
{"label": _("Reserved for Sub Contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
|
||||
"width": 100, "convertible": "qty"},
|
||||
{"label": _("Reserved for POS Transactions"), "fieldname": "reserved_qty_for_pos", "fieldtype": "Float",
|
||||
"width": 100, "convertible": "qty"},
|
||||
{"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
|
||||
{"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"},
|
||||
|
@ -51,7 +51,7 @@ def get_total_stock(filters):
|
||||
INNER JOIN `tabWarehouse` warehouse
|
||||
ON warehouse.name = ledger.warehouse
|
||||
WHERE
|
||||
actual_qty != 0 %s""" % (columns, conditions))
|
||||
ledger.actual_qty != 0 %s""" % (columns, conditions))
|
||||
|
||||
def validate_filters(filters):
|
||||
if filters.get("group_by") == 'Company' and \
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user