fix: merge conflict

This commit is contained in:
Nabin Hait 2021-05-17 11:37:14 +05:30
commit 8b2fef11b1
101 changed files with 1632 additions and 670 deletions

View File

@ -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'''

View File

@ -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",

View File

@ -239,6 +239,7 @@ frappe.ui.form.on("Bank Statement Import", {
"withdrawal",
"description",
"reference_number",
"bank_account"
],
},
});

View File

@ -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",

View File

@ -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,16 +98,18 @@ 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
if import_file_path:
add_bank_account(data, bank_account)
write_files(import_file, data)

View File

@ -1,87 +1,39 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"actions": [],
"creation": "2014-08-29 16:02:39.740505",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"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
"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
"options": "Account"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"index_web_pages_for_search": 1,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:03.348246",
"links": [],
"modified": "2021-04-07 18:13:08.833822",
"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
"sort_order": "DESC"
}

View File

@ -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', [])

View File

@ -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
@ -471,13 +481,7 @@ def get_stock_availability(item_code, warehouse):
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):

View File

@ -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",

View File

@ -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() {

View File

@ -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 = {}

View File

@ -22,6 +22,9 @@ def get_party_details(inv):
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):

View File

@ -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,

View File

@ -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

View File

@ -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:

View File

@ -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 = []

View File

@ -2,7 +2,8 @@
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Consolidated Financial Statement"] = {
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Consolidated Financial Statement"] = {
"filters": [
{
"fieldname":"company",
@ -94,6 +95,14 @@ frappe.query_reports["Consolidated Financial Statement"] = {
}
],
"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;
}
value = default_formatter(value, row, column, data);
if (!data.parent_account) {
@ -116,4 +125,5 @@ frappe.query_reports["Consolidated Financial Statement"] = {
});
});
}
}
}
});

View File

@ -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,

View File

@ -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)
@ -523,3 +523,11 @@ def get_columns(periodicity, period_list, accumulated_values=1, company=None):
})
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

View File

@ -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 += """

View File

@ -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:

View File

@ -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

View File

@ -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()

View 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))

View File

@ -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'):

View File

@ -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), {

View File

@ -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"],
]
}

View File

@ -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 :

View File

@ -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

View File

@ -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,20 +147,17 @@ 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.woocommerce_id = item_woo_com_id
item.flags.ignore_mandatory = True
item.save()
@ -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,

View File

@ -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()

View File

@ -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({

View File

@ -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:

View File

@ -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

View File

@ -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',

View File

@ -182,6 +182,10 @@
"share": 1,
"submit": 0,
"write": 1
},
{
"read": 1,
"role": "Sales User"
}
],
"quick_entry": 1,

View File

@ -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",

View File

@ -1,5 +1,6 @@
{
"attach_print": 0,
"channel": "Email",
"creation": "2017-08-11 03:17:11.769210",
"days_in_advance": 0,
"docstatus": 0,

View File

@ -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
}
};
});

View File

@ -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,

View File

@ -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

View 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)

View File

@ -44,9 +44,11 @@ def execute():
# make current item's tax map
item_tax_map = {}
for d in old_item_taxes[item_code]:
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'):
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"):

View 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', '')

View 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()

View File

@ -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",

View File

@ -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,7 +495,7 @@
}
],
"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",

View File

@ -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

View File

@ -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):

View File

@ -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");
}

View File

@ -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);
});
}

View File

@ -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;

View File

@ -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')):

View File

@ -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.

View File

@ -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

View File

@ -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'),

View File

@ -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

View File

@ -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,29 +299,42 @@ 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
@ -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):

View File

@ -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

View File

@ -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) {

View File

@ -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>

View File

@ -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) {

View File

@ -614,3 +614,12 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
else:
return None
@frappe.whitelist()
def create_transaction_deletion_request(company):
tdr = frappe.get_doc({
'doctype': 'Transaction Deletion Record',
'company': company
})
tdr.insert()
tdr.submit()

View File

@ -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)

View File

@ -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))

View File

@ -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()

View File

@ -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]
});
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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"];
}
}
};

View File

@ -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
}

View File

@ -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

View File

@ -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")

View File

@ -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){

View File

@ -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');

View File

@ -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",

View File

@ -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

View File

@ -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()

View File

@ -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,

View File

@ -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"

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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"""

View File

@ -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")

View File

@ -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",

View File

@ -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 = []
@ -935,8 +937,8 @@ def get_bin_details(item_code, warehouse, company=None):
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]
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):

View File

@ -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)

View 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()
},
]
};

View 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"
}
]
}

View 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 []

View File

@ -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)

View File

@ -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",

View File

@ -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"},

View File

@ -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