mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-19 13:09:17 +00:00
Merge branch 'v11-pre-release' into version-11
This commit is contained in:
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '11.1.67'
|
__version__ = '11.1.68'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
|||||||
# GL Entry for crediting the amount in the deferred expense
|
# GL Entry for crediting the amount in the deferred expense
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
|
if amount == 0: return
|
||||||
|
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
doc.get_gl_dict({
|
doc.get_gl_dict({
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ class Account(NestedSet):
|
|||||||
if ancestors:
|
if ancestors:
|
||||||
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
|
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
|
||||||
return
|
return
|
||||||
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
|
|
||||||
|
if not frappe.db.get_value("Account",
|
||||||
|
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
|
||||||
|
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
|
||||||
else:
|
else:
|
||||||
descendants = get_descendants_of('Company', self.company)
|
descendants = get_descendants_of('Company', self.company)
|
||||||
if not descendants: return
|
if not descendants: return
|
||||||
@@ -114,21 +117,7 @@ class Account(NestedSet):
|
|||||||
|
|
||||||
if not parent_acc_name_map: return
|
if not parent_acc_name_map: return
|
||||||
|
|
||||||
for company in descendants:
|
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
||||||
if not parent_acc_name_map.get(company):
|
|
||||||
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
|
||||||
.format(company, parent_acc_name))
|
|
||||||
|
|
||||||
doc = frappe.copy_doc(self)
|
|
||||||
doc.flags.ignore_root_company_validation = True
|
|
||||||
doc.update({
|
|
||||||
"company": company,
|
|
||||||
"account_currency": None,
|
|
||||||
"parent_account": parent_acc_name_map[company]
|
|
||||||
})
|
|
||||||
doc.save()
|
|
||||||
frappe.msgprint(_("Account {0} is added in the child company {1}")
|
|
||||||
.format(doc.name, company))
|
|
||||||
|
|
||||||
def validate_group_or_ledger(self):
|
def validate_group_or_ledger(self):
|
||||||
if self.get("__islocal"):
|
if self.get("__islocal"):
|
||||||
@@ -170,6 +159,49 @@ class Account(NestedSet):
|
|||||||
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
||||||
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
||||||
|
|
||||||
|
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
||||||
|
for company in descendants:
|
||||||
|
if not parent_acc_name_map.get(company):
|
||||||
|
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
||||||
|
.format(company, parent_acc_name))
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
"account_name": self.account_name,
|
||||||
|
"company": company
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.account_number:
|
||||||
|
filters["account_number"] = self.account_number
|
||||||
|
|
||||||
|
child_account = frappe.db.get_value("Account", filters, 'name')
|
||||||
|
|
||||||
|
if not child_account:
|
||||||
|
doc = frappe.copy_doc(self)
|
||||||
|
doc.flags.ignore_root_company_validation = True
|
||||||
|
doc.update({
|
||||||
|
"company": company,
|
||||||
|
# parent account's currency should be passed down to child account's curreny
|
||||||
|
# if it is None, it picks it up from default company currency, which might be unintended
|
||||||
|
"account_currency": self.account_currency,
|
||||||
|
"parent_account": parent_acc_name_map[company]
|
||||||
|
})
|
||||||
|
|
||||||
|
doc.save()
|
||||||
|
frappe.msgprint(_("Account {0} is added in the child company {1}")
|
||||||
|
.format(doc.name, company))
|
||||||
|
elif child_account:
|
||||||
|
# update the parent company's value in child companies
|
||||||
|
doc = frappe.get_doc("Account", child_account)
|
||||||
|
parent_value_changed = False
|
||||||
|
for field in ['account_type', 'account_currency',
|
||||||
|
'freeze_account', 'balance_must_be']:
|
||||||
|
if doc.get(field) != self.get(field):
|
||||||
|
parent_value_changed = True
|
||||||
|
doc.set(field, self.get(field))
|
||||||
|
|
||||||
|
if parent_value_changed:
|
||||||
|
doc.save()
|
||||||
|
|
||||||
def convert_group_to_ledger(self):
|
def convert_group_to_ledger(self):
|
||||||
if self.check_if_child_exists():
|
if self.check_if_child_exists():
|
||||||
throw(_("Account with child nodes cannot be converted to ledger"))
|
throw(_("Account with child nodes cannot be converted to ledger"))
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
() => {
|
() => {
|
||||||
frm.set_party_account_based_on_party = false;
|
frm.set_party_account_based_on_party = false;
|
||||||
if (r.message.bank_account) {
|
if (r.message.bank_account) {
|
||||||
frm.set_value("bank_account", r.message.bank_account);
|
frm.set_value("party_bank_account", r.message.bank_account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1791,6 +1791,41 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"default": "Draft",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Status",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "\nDraft\nSubmitted\nCancelled",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@@ -2205,7 +2240,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2019-03-27 17:39:54.163016",
|
"modified": "2019-11-06 15:15:45.223497",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.validate_duplicate_entry()
|
self.validate_duplicate_entry()
|
||||||
self.validate_allocated_amount()
|
self.validate_allocated_amount()
|
||||||
self.ensure_supplier_is_not_blocked()
|
self.ensure_supplier_is_not_blocked()
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.setup_party_account_field()
|
self.setup_party_account_field()
|
||||||
@@ -69,6 +70,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@@ -78,6 +80,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
self.delink_advance_entry_references()
|
self.delink_advance_entry_references()
|
||||||
|
self.set_status()
|
||||||
|
|
||||||
def update_outstanding_amounts(self):
|
def update_outstanding_amounts(self):
|
||||||
self.set_missing_ref_details(force=True)
|
self.set_missing_ref_details(force=True)
|
||||||
@@ -274,6 +277,14 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
|
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
|
||||||
.format(d.reference_name, dr_or_cr))
|
.format(d.reference_name, dr_or_cr))
|
||||||
|
|
||||||
|
def set_status(self):
|
||||||
|
if self.docstatus == 2:
|
||||||
|
self.status = 'Cancelled'
|
||||||
|
elif self.docstatus == 1:
|
||||||
|
self.status = 'Submitted'
|
||||||
|
else:
|
||||||
|
self.status = 'Draft'
|
||||||
|
|
||||||
def set_amounts(self):
|
def set_amounts(self):
|
||||||
self.set_amounts_in_company_currency()
|
self.set_amounts_in_company_currency()
|
||||||
self.set_total_allocated_amount()
|
self.set_total_allocated_amount()
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ class SalesInvoice(SellingController):
|
|||||||
timesheet.calculate_percentage_billed()
|
timesheet.calculate_percentage_billed()
|
||||||
timesheet.flags.ignore_validate_update_after_submit = True
|
timesheet.flags.ignore_validate_update_after_submit = True
|
||||||
timesheet.set_status()
|
timesheet.set_status()
|
||||||
timesheet.save()
|
timesheet.save(ignore_permissions=True)
|
||||||
|
|
||||||
def update_time_sheet_detail(self, timesheet, args, sales_invoice):
|
def update_time_sheet_detail(self, timesheet, args, sales_invoice):
|
||||||
for data in timesheet.time_logs:
|
for data in timesheet.time_logs:
|
||||||
@@ -992,10 +992,8 @@ class SalesInvoice(SellingController):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for serial_no in item.serial_no.split("\n"):
|
for serial_no in item.serial_no.split("\n"):
|
||||||
if serial_no and frappe.db.exists('Serial No', serial_no):
|
if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
|
||||||
sno = frappe.get_doc('Serial No', serial_no)
|
frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
|
||||||
sno.sales_invoice = invoice
|
|
||||||
sno.db_update()
|
|
||||||
|
|
||||||
def validate_serial_numbers(self):
|
def validate_serial_numbers(self):
|
||||||
"""
|
"""
|
||||||
@@ -1041,8 +1039,9 @@ class SalesInvoice(SellingController):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for serial_no in item.serial_no.split("\n"):
|
for serial_no in item.serial_no.split("\n"):
|
||||||
sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice")
|
sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no,
|
||||||
if sales_invoice and self.name != sales_invoice:
|
["sales_invoice", "item_code"])
|
||||||
|
if sales_invoice and item_code == item.item_code and self.name != sales_invoice:
|
||||||
sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
|
sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
|
||||||
if sales_invoice_company == self.company:
|
if sales_invoice_company == self.company:
|
||||||
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
|
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
|
||||||
|
|||||||
@@ -187,7 +187,11 @@ class ReceivablePayableReport(object):
|
|||||||
self.data.append(row)
|
self.data.append(row)
|
||||||
|
|
||||||
def set_invoice_details(self, row):
|
def set_invoice_details(self, row):
|
||||||
row.update(self.invoice_details.get(row.voucher_no, {}))
|
invoice_details = self.invoice_details.get(row.voucher_no, {})
|
||||||
|
if row.due_date:
|
||||||
|
invoice_details.pop("due_date", None)
|
||||||
|
row.update(invoice_details)
|
||||||
|
|
||||||
if row.voucher_type == 'Sales Invoice':
|
if row.voucher_type == 'Sales Invoice':
|
||||||
if self.filters.show_delivery_notes:
|
if self.filters.show_delivery_notes:
|
||||||
self.set_delivery_notes(row)
|
self.set_delivery_notes(row)
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
self.filters.report_date) or {}
|
self.filters.report_date) or {}
|
||||||
|
|
||||||
for party, party_dict in iteritems(self.party_total):
|
for party, party_dict in iteritems(self.party_total):
|
||||||
|
if party_dict.outstanding <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
|
|
||||||
row.party = party
|
row.party = party
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||||
frappe.query_reports["Balance Sheet"] = erpnext.financial_statements;
|
frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements);
|
||||||
|
|
||||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||||
"fieldname": "accumulated_values",
|
"fieldname": "accumulated_values",
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
|||||||
if tax_acc not in income_accounts:
|
if tax_acc not in income_accounts:
|
||||||
tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2
|
tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2
|
||||||
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision)
|
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision)
|
||||||
|
total_tax += tax_amount
|
||||||
row.append(tax_amount)
|
row.append(tax_amount)
|
||||||
|
|
||||||
# total tax, grand total, outstanding amount & rounded total
|
# total tax, grand total, outstanding amount & rounded total
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ def get_data(filters):
|
|||||||
accumulate_values_into_parents(accounts, accounts_by_name)
|
accumulate_values_into_parents(accounts, accounts_by_name)
|
||||||
|
|
||||||
data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency)
|
data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency)
|
||||||
data = filter_out_zero_value_rows(data, parent_children_map,
|
data = filter_out_zero_value_rows(data, parent_children_map, show_zero_values=filters.get("show_zero_values"))
|
||||||
show_zero_values=filters.get("show_zero_values"))
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -175,33 +174,11 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters,
|
|||||||
|
|
||||||
d["closing_debit"] = d["opening_debit"] + d["debit"]
|
d["closing_debit"] = d["opening_debit"] + d["debit"]
|
||||||
d["closing_credit"] = d["opening_credit"] + d["credit"]
|
d["closing_credit"] = d["opening_credit"] + d["credit"]
|
||||||
total_row["debit"] += d["debit"]
|
|
||||||
total_row["credit"] += d["credit"]
|
|
||||||
|
|
||||||
if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense":
|
prepare_opening_closing(d)
|
||||||
d["opening_debit"] -= d["opening_credit"]
|
|
||||||
d["closing_debit"] -= d["closing_credit"]
|
|
||||||
|
|
||||||
# For opening
|
for field in value_fields:
|
||||||
check_opening_closing_has_negative_value(d, "opening_debit", "opening_credit")
|
total_row[field] += d[field]
|
||||||
|
|
||||||
# For closing
|
|
||||||
check_opening_closing_has_negative_value(d, "closing_debit", "closing_credit")
|
|
||||||
|
|
||||||
if d["root_type"] == "Liability" or d["root_type"] == "Income":
|
|
||||||
d["opening_credit"] -= d["opening_debit"]
|
|
||||||
d["closing_credit"] -= d["closing_debit"]
|
|
||||||
|
|
||||||
# For opening
|
|
||||||
check_opening_closing_has_negative_value(d, "opening_credit", "opening_debit")
|
|
||||||
|
|
||||||
# For closing
|
|
||||||
check_opening_closing_has_negative_value(d, "closing_credit", "closing_debit")
|
|
||||||
|
|
||||||
total_row["opening_debit"] += d["opening_debit"]
|
|
||||||
total_row["closing_debit"] += d["closing_debit"]
|
|
||||||
total_row["opening_credit"] += d["opening_credit"]
|
|
||||||
total_row["closing_credit"] += d["closing_credit"]
|
|
||||||
|
|
||||||
return total_row
|
return total_row
|
||||||
|
|
||||||
@@ -215,6 +192,10 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr
|
|||||||
data = []
|
data = []
|
||||||
|
|
||||||
for d in accounts:
|
for d in accounts:
|
||||||
|
# Prepare opening closing for group account
|
||||||
|
if parent_children_map.get(d.account):
|
||||||
|
prepare_opening_closing(d)
|
||||||
|
|
||||||
has_value = False
|
has_value = False
|
||||||
row = {
|
row = {
|
||||||
"account": d.name,
|
"account": d.name,
|
||||||
@@ -301,11 +282,16 @@ def get_columns():
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def check_opening_closing_has_negative_value(d, dr_or_cr, switch_to_column):
|
def prepare_opening_closing(row):
|
||||||
# If opening debit has negetive value then move it to opening credit and vice versa.
|
dr_or_cr = "debit" if row["root_type"] in ["Asset", "Equity", "Expense"] else "credit"
|
||||||
|
reverse_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
|
||||||
|
|
||||||
if d[dr_or_cr] < 0:
|
for col_type in ["opening", "closing"]:
|
||||||
d[switch_to_column] = abs(d[dr_or_cr])
|
valid_col = col_type + "_" + dr_or_cr
|
||||||
d[dr_or_cr] = 0.0
|
reverse_col = col_type + "_" + reverse_dr_or_cr
|
||||||
else:
|
row[valid_col] -= row[reverse_col]
|
||||||
d[switch_to_column] = 0.0
|
if row[valid_col] < 0:
|
||||||
|
row[reverse_col] = abs(row[valid_col])
|
||||||
|
row[valid_col] = 0.0
|
||||||
|
else:
|
||||||
|
row[reverse_col] = 0.0
|
||||||
@@ -1097,6 +1097,8 @@ def get_supplier_block_status(party_name):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name):
|
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name):
|
||||||
data = json.loads(trans_items)
|
data = json.loads(trans_items)
|
||||||
|
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
|
||||||
|
|
||||||
for d in data:
|
for d in data:
|
||||||
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
|
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
|
||||||
|
|
||||||
@@ -1118,18 +1120,22 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name):
|
|||||||
# if rate is greater than price_list_rate, set margin
|
# if rate is greater than price_list_rate, set margin
|
||||||
# or set discount
|
# or set discount
|
||||||
child_item.discount_percentage = 0
|
child_item.discount_percentage = 0
|
||||||
child_item.margin_type = "Amount"
|
|
||||||
child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
|
if parent_doctype in sales_doctypes:
|
||||||
child_item.precision("margin_rate_or_amount"))
|
child_item.margin_type = "Amount"
|
||||||
child_item.rate_with_margin = child_item.rate
|
child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
|
||||||
|
child_item.precision("margin_rate_or_amount"))
|
||||||
|
child_item.rate_with_margin = child_item.rate
|
||||||
else:
|
else:
|
||||||
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
|
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
|
||||||
child_item.precision("discount_percentage"))
|
child_item.precision("discount_percentage"))
|
||||||
child_item.discount_amount = flt(
|
child_item.discount_amount = flt(
|
||||||
child_item.price_list_rate) - flt(child_item.rate)
|
child_item.price_list_rate) - flt(child_item.rate)
|
||||||
child_item.margin_type = ""
|
|
||||||
child_item.margin_rate_or_amount = 0
|
if parent_doctype in sales_doctypes:
|
||||||
child_item.rate_with_margin = 0
|
child_item.margin_type = ""
|
||||||
|
child_item.margin_rate_or_amount = 0
|
||||||
|
child_item.rate_with_margin = 0
|
||||||
|
|
||||||
child_item.flags.ignore_validate_update_after_submit = True
|
child_item.flags.ignore_validate_update_after_submit = True
|
||||||
child_item.save()
|
child_item.save()
|
||||||
|
|||||||
@@ -152,6 +152,20 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
|
#Get searchfields from meta and use in Item Link field query
|
||||||
|
meta = frappe.get_meta("Item", cached=True)
|
||||||
|
searchfields = meta.get_search_fields()
|
||||||
|
|
||||||
|
if "description" in searchfields:
|
||||||
|
searchfields.remove("description")
|
||||||
|
|
||||||
|
columns = [field for field in searchfields if not field in ["name", "item_group", "description"]]
|
||||||
|
columns = ", ".join(columns)
|
||||||
|
|
||||||
|
searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"]
|
||||||
|
if not field in searchfields]
|
||||||
|
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||||
|
|
||||||
description_cond = ''
|
description_cond = ''
|
||||||
if frappe.db.count('Item', cache=True) < 50000:
|
if frappe.db.count('Item', cache=True) < 50000:
|
||||||
# scan description only if items are less than 50000
|
# scan description only if items are less than 50000
|
||||||
@@ -162,17 +176,14 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
|
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
|
||||||
tabItem.item_group,
|
tabItem.item_group,
|
||||||
if(length(tabItem.description) > 40, \
|
if(length(tabItem.description) > 40, \
|
||||||
concat(substr(tabItem.description, 1, 40), "..."), description) as decription
|
concat(substr(tabItem.description, 1, 40), "..."), description) as description,
|
||||||
|
{columns}
|
||||||
from tabItem
|
from tabItem
|
||||||
where tabItem.docstatus < 2
|
where tabItem.docstatus < 2
|
||||||
and tabItem.has_variants=0
|
and tabItem.has_variants=0
|
||||||
and tabItem.disabled=0
|
and tabItem.disabled=0
|
||||||
and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00')
|
and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00')
|
||||||
and (tabItem.`{key}` LIKE %(txt)s
|
and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
|
||||||
or tabItem.item_code LIKE %(txt)s
|
|
||||||
or tabItem.item_group LIKE %(txt)s
|
|
||||||
or tabItem.item_name LIKE %(txt)s
|
|
||||||
or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
|
|
||||||
{description_cond})
|
{description_cond})
|
||||||
{fcond} {mcond}
|
{fcond} {mcond}
|
||||||
order by
|
order by
|
||||||
@@ -182,6 +193,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
name, item_name
|
name, item_name
|
||||||
limit %(start)s, %(page_len)s """.format(
|
limit %(start)s, %(page_len)s """.format(
|
||||||
key=searchfield,
|
key=searchfield,
|
||||||
|
columns=columns,
|
||||||
|
scond=searchfields,
|
||||||
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
||||||
mcond=get_match_cond(doctype).replace('%', '%%'),
|
mcond=get_match_cond(doctype).replace('%', '%%'),
|
||||||
description_cond = description_cond),
|
description_cond = description_cond),
|
||||||
@@ -280,22 +293,31 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
"page_len": page_len
|
"page_len": page_len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
having_clause = "having sum(sle.actual_qty) > 0"
|
||||||
|
if filters.get("is_return"):
|
||||||
|
having_clause = ""
|
||||||
|
|
||||||
if args.get('warehouse'):
|
if args.get('warehouse'):
|
||||||
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
|
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
|
||||||
from `tabStock Ledger Entry` sle
|
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
|
||||||
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
from `tabStock Ledger Entry` sle
|
||||||
where
|
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
||||||
batch.disabled = 0
|
where
|
||||||
and sle.item_code = %(item_code)s
|
batch.disabled = 0
|
||||||
and sle.warehouse = %(warehouse)s
|
and sle.item_code = %(item_code)s
|
||||||
and (sle.batch_no like %(txt)s
|
and sle.warehouse = %(warehouse)s
|
||||||
or batch.manufacturing_date like %(txt)s)
|
and (sle.batch_no like %(txt)s
|
||||||
and batch.docstatus < 2
|
or batch.manufacturing_date like %(txt)s)
|
||||||
{0}
|
and batch.docstatus < 2
|
||||||
{match_conditions}
|
{cond}
|
||||||
group by batch_no having sum(sle.actual_qty) > 0
|
{match_conditions}
|
||||||
order by batch.expiry_date, sle.batch_no desc
|
group by batch_no {having_clause}
|
||||||
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args)
|
order by batch.expiry_date, sle.batch_no desc
|
||||||
|
limit %(start)s, %(page_len)s""".format(
|
||||||
|
cond=cond,
|
||||||
|
match_conditions=get_match_cond(doctype),
|
||||||
|
having_clause = having_clause
|
||||||
|
), args)
|
||||||
|
|
||||||
return batch_nos
|
return batch_nos
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1468,7 +1468,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2019-05-17 19:03:32.740910",
|
"modified": "2019-10-22 11:11:32.740910",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Opportunity",
|
"name": "Opportunity",
|
||||||
|
|||||||
@@ -233,7 +233,6 @@ scheduler_events = {
|
|||||||
],
|
],
|
||||||
"daily": [
|
"daily": [
|
||||||
"erpnext.stock.reorder_item.reorder_item",
|
"erpnext.stock.reorder_item.reorder_item",
|
||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
|
||||||
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
||||||
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
|
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
|
||||||
"erpnext.controllers.accounts_controller.update_invoice_status",
|
"erpnext.controllers.accounts_controller.update_invoice_status",
|
||||||
@@ -252,6 +251,7 @@ scheduler_events = {
|
|||||||
"erpnext.projects.doctype.project.project.send_project_status_email_to_users"
|
"erpnext.projects.doctype.project.project.send_project_status_email_to_users"
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
|
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
|
||||||
],
|
],
|
||||||
"monthly_long": [
|
"monthly_long": [
|
||||||
|
|||||||
@@ -157,10 +157,11 @@ class Employee(NestedSet):
|
|||||||
def validate_status(self):
|
def validate_status(self):
|
||||||
if self.status == 'Left':
|
if self.status == 'Left':
|
||||||
reports_to = frappe.db.get_all('Employee',
|
reports_to = frappe.db.get_all('Employee',
|
||||||
filters={'reports_to': self.name}
|
filters={'reports_to': self.name, 'status': "Active"},
|
||||||
|
fields = ['name','employee_name']
|
||||||
)
|
)
|
||||||
if reports_to:
|
if reports_to:
|
||||||
link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name) for employee in reports_to]
|
link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to]
|
||||||
throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee: ")
|
throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee: ")
|
||||||
+ ', '.join(link_to_employees), EmployeeLeftValidationError)
|
+ ', '.join(link_to_employees), EmployeeLeftValidationError)
|
||||||
if not self.relieving_date:
|
if not self.relieving_date:
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ class BOM(WebsiteGenerator):
|
|||||||
names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)]
|
names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)]
|
||||||
|
|
||||||
# split by (-) if cancelled
|
# split by (-) if cancelled
|
||||||
names = [cint(name.split('-')[-1]) for name in names]
|
if names:
|
||||||
|
names = [cint(name.split('-')[-1]) for name in names]
|
||||||
idx = max(names) + 1
|
idx = max(names) + 1
|
||||||
|
else:
|
||||||
|
idx = 1
|
||||||
else:
|
else:
|
||||||
idx = 1
|
idx = 1
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
"allow_guest_to_view": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
@@ -14,10 +15,12 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "item_code",
|
"fieldname": "item_code",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -41,14 +44,17 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "item_name",
|
"fieldname": "item_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -72,14 +78,17 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -103,14 +112,17 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -132,14 +144,17 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "quantity",
|
"fieldname": "quantity",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -162,14 +177,51 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
|
"fieldname": "uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "UOM",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "UOM",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "actual_qty",
|
"fieldname": "actual_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -192,14 +244,86 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
|
"fieldname": "item_details",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Item Description",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Description",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"oldfieldname": "description",
|
||||||
|
"oldfieldtype": "Small Text",
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"print_width": "300px",
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0,
|
||||||
|
"width": "300px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "min_order_qty",
|
"fieldname": "min_order_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -222,14 +346,17 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -252,14 +379,17 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "sales_order",
|
"fieldname": "sales_order",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -283,14 +413,17 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "requested_qty",
|
"fieldname": "requested_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -313,6 +446,7 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -326,7 +460,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-02-15 13:08:30.535963",
|
"modified": "2019-11-08 14:59:58.805613",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Material Request Plan Item",
|
"name": "Material Request Plan Item",
|
||||||
@@ -340,5 +474,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
||||||
@@ -122,6 +122,8 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
item.quantity = d.quantity;
|
item.quantity = d.quantity;
|
||||||
item.sales_order = d.sales_order;
|
item.sales_order = d.sales_order;
|
||||||
item.warehouse = d.warehouse;
|
item.warehouse = d.warehouse;
|
||||||
|
item.description = d.description;
|
||||||
|
item.uom = d.uom;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
refresh_field('mr_items');
|
refresh_field('mr_items');
|
||||||
|
|||||||
@@ -472,7 +472,9 @@ def get_material_request_items(row, sales_order, company, ignore_existing_ordere
|
|||||||
or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
|
or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
|
||||||
'actual_qty': actual_qty,
|
'actual_qty': actual_qty,
|
||||||
'min_order_qty': row['min_order_qty'],
|
'min_order_qty': row['min_order_qty'],
|
||||||
'sales_order': sales_order
|
'sales_order': sales_order,
|
||||||
|
'description': row.get("description"),
|
||||||
|
'uom': row.get("purchase_uom") or row.get("stock_uom")
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_sales_orders(self):
|
def get_sales_orders(self):
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ frappe.ui.form.on("Work Order", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
project: function(frm) {
|
project: function(frm) {
|
||||||
if(!erpnext.in_production_item_onchange) {
|
if(!erpnext.in_production_item_onchange && !frm.doc.bom_no) {
|
||||||
frm.trigger("production_item");
|
frm.trigger("production_item");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -607,3 +607,4 @@ erpnext.patches.v11_1.set_quotation_status
|
|||||||
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
|
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
|
||||||
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
|
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
|
||||||
erpnext.patches.v11_1.set_produced_qty_field_in_sales_order_for_work_order
|
erpnext.patches.v11_1.set_produced_qty_field_in_sales_order_for_work_order
|
||||||
|
erpnext.patches.v11_1.set_payment_entry_status
|
||||||
9
erpnext/patches/v11_1/set_payment_entry_status.py
Normal file
9
erpnext/patches/v11_1/set_payment_entry_status.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doctype("Payment Entry")
|
||||||
|
frappe.db.sql("""update `tabPayment Entry` set status = CASE
|
||||||
|
WHEN docstatus = 1 THEN 'Submitted'
|
||||||
|
WHEN docstatus = 2 THEN 'Cancelled'
|
||||||
|
ELSE 'Draft'
|
||||||
|
END;""")
|
||||||
@@ -713,7 +713,7 @@
|
|||||||
"depends_on": "",
|
"depends_on": "",
|
||||||
"fetch_if_empty": 0,
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "depends_on_tasks",
|
"fieldname": "depends_on_tasks",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Long Text",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import json
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, throw
|
from frappe import _, throw
|
||||||
from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate
|
from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today
|
||||||
from frappe.utils.nestedset import NestedSet
|
from frappe.utils.nestedset import NestedSet
|
||||||
|
|
||||||
|
|
||||||
@@ -201,6 +201,9 @@ def set_multiple_status(names, status):
|
|||||||
def set_tasks_as_overdue():
|
def set_tasks_as_overdue():
|
||||||
tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]})
|
tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]})
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
|
if frappe.db.get_value("Task", task.name, "status") in 'Pending Review':
|
||||||
|
if getdate(frappe.db.get_value("Task", task.name, "review_date")) < getdate(today()):
|
||||||
|
continue
|
||||||
frappe.get_doc("Task", task.name).update_status()
|
frappe.get_doc("Task", task.name).update_status()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -1422,6 +1422,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
'item_code': item.item_code,
|
'item_code': item.item_code,
|
||||||
'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(),
|
'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (doc.is_return) {
|
||||||
|
filters["is_return"] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.warehouse) filters["warehouse"] = item.warehouse;
|
if (item.warehouse) filters["warehouse"] = item.warehouse;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ def validate_gstin_check_digit(gstin, label='GSTIN'):
|
|||||||
total += digit
|
total += digit
|
||||||
factor = 2 if factor == 1 else 1
|
factor = 2 if factor == 1 else 1
|
||||||
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
|
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
|
||||||
frappe.throw(_("Invalid {0}! The check digit validation has failed. " +
|
frappe.throw(_("""Invalid {0}! The check digit validation has failed.
|
||||||
"Please ensure you've typed the {0} correctly.".format(label)))
|
Please ensure you've typed the {0} correctly.""".format(label)))
|
||||||
|
|
||||||
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
|
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
|
||||||
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
|
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
|
|
||||||
// delivery note
|
// delivery note
|
||||||
if(flt(doc.per_delivered, 6) < 100 && allow_delivery) {
|
if(flt(doc.per_delivered, 6) < 100 && allow_delivery) {
|
||||||
this.frm.add_custom_button(__('Delivery'),
|
this.frm.add_custom_button(__('Delivery Note'),
|
||||||
function() { me.make_delivery_note_based_on_delivery_date(); }, __("Make"));
|
function() { me.make_delivery_note_based_on_delivery_date(); }, __("Make"));
|
||||||
|
|
||||||
if(["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1){
|
if(["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1){
|
||||||
|
|||||||
@@ -645,12 +645,15 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
|
|
||||||
if source_parent.project:
|
if source_parent.project:
|
||||||
target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center")
|
target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center")
|
||||||
if not target.cost_center and target.item_code:
|
if target.item_code:
|
||||||
item = get_item_defaults(target.item_code, source_parent.company)
|
item = get_item_defaults(target.item_code, source_parent.company)
|
||||||
item_group = get_item_group_defaults(target.item_code, source_parent.company)
|
item_group = get_item_group_defaults(target.item_code, source_parent.company)
|
||||||
target.cost_center = item.get("selling_cost_center") \
|
cost_center = item.get("selling_cost_center") \
|
||||||
or item_group.get("selling_cost_center")
|
or item_group.get("selling_cost_center")
|
||||||
|
|
||||||
|
if cost_center:
|
||||||
|
target.cost_center = cost_center
|
||||||
|
|
||||||
doclist = get_mapped_doc("Sales Order", source_name, {
|
doclist = get_mapped_doc("Sales Order", source_name, {
|
||||||
"Sales Order": {
|
"Sales Order": {
|
||||||
"doctype": "Sales Invoice",
|
"doctype": "Sales Invoice",
|
||||||
|
|||||||
@@ -454,9 +454,8 @@ def get_applicable_shipping_rules(party=None, quotation=None):
|
|||||||
shipping_rules = get_shipping_rules(quotation)
|
shipping_rules = get_shipping_rules(quotation)
|
||||||
|
|
||||||
if shipping_rules:
|
if shipping_rules:
|
||||||
rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label")
|
|
||||||
# we need this in sorted order as per the position of the rule in the settings page
|
# we need this in sorted order as per the position of the rule in the settings page
|
||||||
return [[rule, rule_label_map.get(rule)] for rule in shipping_rules]
|
return [[rule, rule] for rule in shipping_rules]
|
||||||
|
|
||||||
def get_shipping_rules(quotation=None, cart_settings=None):
|
def get_shipping_rules(quotation=None, cart_settings=None):
|
||||||
if not quotation:
|
if not quotation:
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ frappe.listview_settings['Delivery Note'] = {
|
|||||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||||
} else if (doc.status === "Closed") {
|
} else if (doc.status === "Closed") {
|
||||||
return [__("Closed"), "green", "status,=,Closed"];
|
return [__("Closed"), "green", "status,=,Closed"];
|
||||||
} else if (doc.grand_total !== 0 && flt(doc.per_billed, 2) < 100) {
|
} else if (flt(doc.per_billed, 2) < 100) {
|
||||||
return [__("To Bill"), "orange", "per_billed,<,100"];
|
return [__("To Bill"), "orange", "per_billed,<,100"];
|
||||||
} else if (doc.grand_total === 0 || flt(doc.per_billed, 2) == 100) {
|
} else if (flt(doc.per_billed, 2) == 100) {
|
||||||
return [__("Completed"), "green", "per_billed,=,100"];
|
return [__("Completed"), "green", "per_billed,=,100"];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from __future__ import unicode_literals
|
|||||||
import frappe, json
|
import frappe, json
|
||||||
from frappe.utils import cstr, flt
|
from frappe.utils import cstr, flt
|
||||||
from erpnext.stock.get_item_details import get_item_details
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class PackedItem(Document):
|
class PackedItem(Document):
|
||||||
@@ -31,8 +30,11 @@ def get_bin_qty(item, warehouse):
|
|||||||
return det and det[0] or frappe._dict()
|
return det and det[0] or frappe._dict()
|
||||||
|
|
||||||
def update_packing_list_item(doc, packing_item_code, qty, main_item_row, description):
|
def update_packing_list_item(doc, packing_item_code, qty, main_item_row, description):
|
||||||
|
if doc.amended_from:
|
||||||
|
old_packed_items_map = get_old_packed_item_details(doc.packed_items)
|
||||||
|
else:
|
||||||
|
old_packed_items_map = False
|
||||||
item = get_packing_item_details(packing_item_code, doc.company)
|
item = get_packing_item_details(packing_item_code, doc.company)
|
||||||
|
|
||||||
# check if exists
|
# check if exists
|
||||||
exists = 0
|
exists = 0
|
||||||
for d in doc.get("packed_items"):
|
for d in doc.get("packed_items"):
|
||||||
@@ -52,11 +54,10 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip
|
|||||||
pi.uom = item.stock_uom
|
pi.uom = item.stock_uom
|
||||||
pi.qty = flt(qty)
|
pi.qty = flt(qty)
|
||||||
pi.description = description
|
pi.description = description
|
||||||
if not pi.warehouse:
|
if not pi.warehouse and not doc.amended_from:
|
||||||
pi.warehouse = (main_item_row.warehouse if ((doc.get('is_pos')
|
pi.warehouse = (main_item_row.warehouse if ((doc.get('is_pos')
|
||||||
or not item.default_warehouse) and main_item_row.warehouse) else item.default_warehouse)
|
or not item.default_warehouse) and main_item_row.warehouse) else item.default_warehouse)
|
||||||
|
if not pi.batch_no and not doc.amended_from:
|
||||||
if not pi.batch_no:
|
|
||||||
pi.batch_no = cstr(main_item_row.get("batch_no"))
|
pi.batch_no = cstr(main_item_row.get("batch_no"))
|
||||||
if not pi.target_warehouse:
|
if not pi.target_warehouse:
|
||||||
pi.target_warehouse = main_item_row.get("target_warehouse")
|
pi.target_warehouse = main_item_row.get("target_warehouse")
|
||||||
@@ -64,9 +65,13 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip
|
|||||||
pi.actual_qty = flt(bin.get("actual_qty"))
|
pi.actual_qty = flt(bin.get("actual_qty"))
|
||||||
pi.projected_qty = flt(bin.get("projected_qty"))
|
pi.projected_qty = flt(bin.get("projected_qty"))
|
||||||
|
|
||||||
|
if old_packed_items_map:
|
||||||
|
pi.batch_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].batch_no
|
||||||
|
pi.serial_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].serial_no
|
||||||
|
pi.warehouse = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].warehouse
|
||||||
|
|
||||||
def make_packing_list(doc):
|
def make_packing_list(doc):
|
||||||
"""make packing list for Product Bundle item"""
|
"""make packing list for Product Bundle item"""
|
||||||
|
|
||||||
if doc.get("_action") and doc._action == "update_after_submit": return
|
if doc.get("_action") and doc._action == "update_after_submit": return
|
||||||
|
|
||||||
parent_items = []
|
parent_items = []
|
||||||
@@ -113,3 +118,9 @@ def get_items_from_product_bundle(args):
|
|||||||
|
|
||||||
def on_doctype_update():
|
def on_doctype_update():
|
||||||
frappe.db.add_index("Packed Item", ["item_code", "warehouse"])
|
frappe.db.add_index("Packed Item", ["item_code", "warehouse"])
|
||||||
|
|
||||||
|
def get_old_packed_item_details(old_packed_items):
|
||||||
|
old_packed_items_map = {}
|
||||||
|
for items in old_packed_items:
|
||||||
|
old_packed_items_map.setdefault((items.item_code ,items.parent_item), []).append(items.as_dict())
|
||||||
|
return old_packed_items_map
|
||||||
@@ -227,7 +227,9 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if not d.expense_account:
|
if not d.expense_account:
|
||||||
frappe.throw(_("Please enter Difference Account"))
|
frappe.throw(_("Please enter <b>Difference Account</b> or set default <b>Stock Adjustment Account</b> for company {0}")
|
||||||
|
.format(frappe.bold(self.company)))
|
||||||
|
|
||||||
elif self.is_opening == "Yes" and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss":
|
elif self.is_opening == "Yes" and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss":
|
||||||
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"), OpeningEntryAccountError)
|
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"), OpeningEntryAccountError)
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,26 @@ def execute(filters=None):
|
|||||||
if opening_row:
|
if opening_row:
|
||||||
data.append(opening_row)
|
data.append(opening_row)
|
||||||
|
|
||||||
|
actual_qty = stock_value = 0
|
||||||
|
|
||||||
for sle in sl_entries:
|
for sle in sl_entries:
|
||||||
item_detail = item_details[sle.item_code]
|
item_detail = item_details[sle.item_code]
|
||||||
|
|
||||||
sle.update(item_detail)
|
sle.update(item_detail)
|
||||||
|
|
||||||
|
if filters.get("batch_no"):
|
||||||
|
actual_qty += sle.actual_qty
|
||||||
|
stock_value += sle.stock_value_difference
|
||||||
|
|
||||||
|
if sle.voucher_type == 'Stock Reconciliation':
|
||||||
|
actual_qty = sle.qty_after_transaction
|
||||||
|
stock_value = sle.stock_value
|
||||||
|
|
||||||
|
sle.update({
|
||||||
|
"qty_after_transaction": actual_qty,
|
||||||
|
"stock_value": stock_value
|
||||||
|
})
|
||||||
|
|
||||||
data.append(sle)
|
data.append(sle)
|
||||||
|
|
||||||
if include_uom:
|
if include_uom:
|
||||||
@@ -67,7 +83,7 @@ def get_stock_ledger_entries(filters, items):
|
|||||||
|
|
||||||
return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date,
|
return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date,
|
||||||
item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate,
|
item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate,
|
||||||
stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project
|
stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project, stock_value_difference
|
||||||
from `tabStock Ledger Entry` sle
|
from `tabStock Ledger Entry` sle
|
||||||
where company = %(company)s and
|
where company = %(company)s and
|
||||||
posting_date between %(from_date)s and %(to_date)s
|
posting_date between %(from_date)s and %(to_date)s
|
||||||
|
|||||||
Reference in New Issue
Block a user