Merge branch 'develop' into common-party-acc

This commit is contained in:
Saqib 2021-08-24 17:27:43 +05:30 committed by GitHub
commit 1db812ab9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 441 additions and 212 deletions

View File

@ -9,19 +9,8 @@ import frappe
import unittest
class TestFinanceBook(unittest.TestCase):
def create_finance_book(self):
if not frappe.db.exists("Finance Book", "_Test Finance Book"):
finance_book = frappe.get_doc({
"doctype": "Finance Book",
"finance_book_name": "_Test Finance Book"
}).insert()
else:
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
return finance_book
def test_finance_book(self):
finance_book = self.create_finance_book()
finance_book = create_finance_book()
# create jv entry
jv = make_journal_entry("_Test Bank - _TC",
@ -41,3 +30,14 @@ class TestFinanceBook(unittest.TestCase):
for gl_entry in gl_entries:
self.assertEqual(gl_entry.finance_book, finance_book.name)
def create_finance_book():
if not frappe.db.exists("Finance Book", "_Test Finance Book"):
finance_book = frappe.get_doc({
"doctype": "Finance Book",
"finance_book_name": "_Test Finance Book"
}).insert()
else:
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
return finance_book

View File

@ -533,8 +533,8 @@ frappe.ui.form.on('Payment Entry', {
source_exchange_rate: function(frm) {
if (frm.doc.paid_amount) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
if(!frm.set_paid_amount_based_on_received_amount &&
(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) {
// target exchange rate should always be same as source if both account currencies is same
if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}

View File

@ -55,14 +55,17 @@ class PaymentEntry(AccountsController):
self.validate_mandatory()
self.validate_reference_documents()
self.set_tax_withholding()
self.apply_taxes()
self.set_amounts()
self.validate_amounts()
self.apply_taxes()
self.set_amounts_after_tax()
self.clear_unallocated_reference_document_rows()
self.validate_payment_against_negative_invoice()
self.validate_transaction_reference()
self.set_title()
self.set_remarks()
self.validate_duplicate_entry()
self.validate_payment_type_with_outstanding()
self.validate_allocated_amount()
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked()
@ -118,6 +121,11 @@ class PaymentEntry(AccountsController):
if not self.get(field):
self.set(field, bank_data.account)
def validate_payment_type_with_outstanding(self):
total_outstanding = sum(d.allocated_amount for d in self.get('references'))
if total_outstanding < 0 and self.party_type == 'Customer' and self.payment_type == 'Receive':
frappe.throw(_("Cannot receive from customer against negative outstanding"), title=_("Incorrect Payment Type"))
def validate_allocated_amount(self):
for d in self.get("references"):
if (flt(d.allocated_amount))> 0:
@ -236,7 +244,9 @@ class PaymentEntry(AccountsController):
self.company_currency, self.posting_date)
def set_target_exchange_rate(self, ref_doc=None):
if self.paid_to and not self.target_exchange_rate:
if self.paid_from_account_currency == self.paid_to_account_currency:
self.target_exchange_rate = self.source_exchange_rate
elif self.paid_to and not self.target_exchange_rate:
if ref_doc:
if self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate")
@ -468,13 +478,22 @@ class PaymentEntry(AccountsController):
def set_amounts(self):
self.set_received_amount()
self.set_amounts_in_company_currency()
self.set_amounts_after_tax()
self.set_total_allocated_amount()
self.set_unallocated_amount()
self.set_difference_amount()
def validate_amounts(self):
self.validate_received_amount()
def validate_received_amount(self):
if self.paid_from_account_currency == self.paid_to_account_currency:
if self.paid_amount != self.received_amount:
frappe.throw(_("Received Amount cannot be greater than Paid Amount"))
def set_received_amount(self):
self.base_received_amount = self.base_paid_amount
if self.paid_from_account_currency == self.paid_to_account_currency:
self.received_amount = self.paid_amount
def set_amounts_after_tax(self):
applicable_tax = 0

View File

@ -107,7 +107,7 @@ class TestPaymentEntry(unittest.TestCase):
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.target_exchange_rate = 50
pe.source_exchange_rate = 50
pe.insert()
pe.submit()
@ -154,7 +154,7 @@ class TestPaymentEntry(unittest.TestCase):
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.target_exchange_rate = 50
pe.source_exchange_rate = 50
pe.insert()
pe.submit()
@ -491,7 +491,7 @@ class TestPaymentEntry(unittest.TestCase):
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.target_exchange_rate = 55
pe.source_exchange_rate = 55
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",

View File

@ -50,9 +50,13 @@ class PeriodClosingVoucher(AccountsController):
.format(pce[0][0], self.posting_date))
def make_gl_entries(self):
gl_entries = self.get_gl_entries()
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)
def get_gl_entries(self):
gl_entries = []
net_pl_balance = 0
pl_accounts = self.get_pl_balances()
for acc in pl_accounts:
@ -60,6 +64,7 @@ class PeriodClosingVoucher(AccountsController):
gl_entries.append(self.get_gl_dict({
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
@ -67,35 +72,13 @@ class PeriodClosingVoucher(AccountsController):
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
}, item=acc))
net_pl_balance += flt(acc.bal_in_company_currency)
if gl_entries:
gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts)
gl_entries += gle_for_net_pl_bal
if net_pl_balance:
if self.cost_center_wise_pnl:
costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts)
gl_entries += costcenter_wise_gl_entries
else:
gl_entry = self.get_pnl_gl_entry(net_pl_balance)
gl_entries.append(gl_entry)
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)
def get_pnl_gl_entry(self, net_pl_balance):
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entry = self.get_gl_dict({
"account": self.closing_account_head,
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"cost_center": cost_center
})
self.update_default_dimensions(gl_entry)
return gl_entry
def get_costcenter_wise_pnl_gl_entries(self, pl_accounts):
return gl_entries
def get_pnl_gl_entry(self, pl_accounts):
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries = []
@ -104,6 +87,7 @@ class PeriodClosingVoucher(AccountsController):
gl_entry = self.get_gl_dict({
"account": self.closing_account_head,
"cost_center": acc.cost_center or company_cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
@ -130,7 +114,7 @@ class PeriodClosingVoucher(AccountsController):
def get_pl_balances(self):
"""Get balance for dimension-wise pl accounts"""
dimension_fields = ['t1.cost_center']
dimension_fields = ['t1.cost_center', 't1.finance_book']
self.accounting_dimensions = get_accounting_dimensions()
for dimension in self.accounting_dimensions:

View File

@ -8,6 +8,7 @@ import frappe
from frappe.utils import flt, today
from erpnext.accounts.utils import get_fiscal_year, now
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestPeriodClosingVoucher(unittest.TestCase):
@ -118,6 +119,58 @@ class TestPeriodClosingVoucher(unittest.TestCase):
self.assertTrue(pcv_gle, expected_gle)
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
create_sales_invoice(
company=company,
income_account="Sales - TPC",
expense_account="Cost of Goods Sold - TPC",
cost_center=cost_center,
rate=400,
debit_to="Debtors - TPC"
)
jv = make_journal_entry(
account1="Cash - TPC",
account2="Sales - TPC",
amount=400,
cost_center=cost_center,
posting_date=now()
)
jv.company = company
jv.finance_book = create_finance_book().name
jv.save()
jv.submit()
pcv = frappe.get_doc({
"transaction_date": today(),
"posting_date": today(),
"fiscal_year": get_fiscal_year(today())[0],
"company": company,
"closing_account_head": surplus_account,
"remarks": "Test",
"doctype": "Period Closing Voucher"
})
pcv.insert()
pcv.submit()
expected_gle = (
(surplus_account, 0.0, 400.0, ''),
(surplus_account, 0.0, 400.0, jv.finance_book),
('Sales - TPC', 400.0, 0.0, ''),
('Sales - TPC', 400.0, 0.0, jv.finance_book)
)
pcv_gle = frappe.db.sql("""
select account, debit, credit, finance_book from `tabGL Entry` where voucher_no=%s
""", (pcv.name))
self.assertTrue(pcv_gle, expected_gle)
def make_period_closing_voucher(self):
pcv = frappe.get_doc({
"doctype": "Period Closing Voucher",

View File

@ -85,9 +85,15 @@ class TestPOSClosingEntry(unittest.TestCase):
pcv_doc.load_from_db()
pcv_doc.cancel()
si_doc.load_from_db()
cancelled_invoice = frappe.db.get_value(
'POS Invoice Merge Log', {'pos_closing_entry': pcv_doc.name},
'consolidated_invoice'
)
docstatus = frappe.db.get_value("Sales Invoice", cancelled_invoice, 'docstatus')
self.assertEqual(docstatus, 2)
pos_inv1.load_from_db()
self.assertEqual(si_doc.docstatus, 2)
self.assertEqual(pos_inv1.status, 'Paid')

View File

@ -16,7 +16,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
onload(doc) {
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
this.frm.refresh_fields();
@ -111,16 +111,12 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
}
write_off_outstanding_amount_automatically() {
if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
if (cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
// this will make outstanding amount 0
this.frm.set_value("write_off_amount",
flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
);
this.frm.toggle_enable("write_off_amount", false);
} else {
this.frm.toggle_enable("write_off_amount", true);
}
this.calculate_outstanding_amount(false);

View File

@ -99,6 +99,7 @@
"loyalty_redemption_account",
"loyalty_redemption_cost_center",
"section_break_49",
"coupon_code",
"apply_discount_on",
"base_discount_amount",
"column_break_51",
@ -1183,7 +1184,8 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
"print_hide": 1
"print_hide": 1,
"read_only_depends_on": "eval: doc.write_off_outstanding_amount_automatically"
},
{
"fieldname": "base_write_off_amount",
@ -1549,12 +1551,20 @@
"no_copy": 1,
"options": "Sales Invoice",
"read_only": 1
},
{
"depends_on": "coupon_code",
"fieldname": "coupon_code",
"fieldtype": "Link",
"label": "Coupon Code",
"options": "Coupon Code",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2021-08-17 20:13:44.255437",
"modified": "2021-08-18 16:13:52.080543",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@ -44,6 +44,9 @@ class POSInvoice(SalesInvoice):
self.validate_pos()
self.validate_payment_amount()
self.validate_loyalty_transaction()
if self.coupon_code:
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
validate_coupon_code(self.coupon_code)
def on_submit(self):
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
@ -58,6 +61,10 @@ class POSInvoice(SalesInvoice):
self.check_phone_payments()
self.set_status(update=True)
if self.coupon_code:
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
update_coupon_code_count(self.coupon_code,'used')
def before_cancel(self):
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
pos_closing_entry = frappe.get_all(
@ -84,6 +91,10 @@ class POSInvoice(SalesInvoice):
against_psi_doc.delete_loyalty_point_entry()
against_psi_doc.make_loyalty_point_entry()
if self.coupon_code:
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
update_coupon_code_count(self.coupon_code,'cancelled')
def check_phone_payments(self):
for pay in self.payments:
if pay.type == "Phone" and pay.amount >= 0:
@ -127,7 +138,7 @@ class POSInvoice(SalesInvoice):
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
def validate_stock_availablility(self):
if self.is_return:
if self.is_return or self.docstatus != 1:
return
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')

View File

@ -198,12 +198,19 @@ def apply_pricing_rule(args, doc=None):
set_serial_nos_based_on_fifo = frappe.db.get_single_value("Stock Settings",
"automatically_set_serial_nos_based_on_fifo")
item_code_list = tuple(item.get('item_code') for item in item_list)
query_items = frappe.get_all('Item', fields=['item_code','has_serial_no'], filters=[['item_code','in',item_code_list]],as_list=1)
serialized_items = dict()
for item_code, val in query_items:
serialized_items.setdefault(item_code, val)
for item in item_list:
args_copy = copy.deepcopy(args)
args_copy.update(item)
data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc)
out.append(data)
if not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
if serialized_items.get(item.get('item_code')) and not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
out[0].update(get_serial_no_for_item(args_copy))
return out

View File

@ -324,16 +324,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
}
write_off_outstanding_amount_automatically() {
if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
if (cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
// this will make outstanding amount 0
this.frm.set_value("write_off_amount",
flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
);
this.frm.toggle_enable("write_off_amount", false);
} else {
this.frm.toggle_enable("write_off_amount", true);
}
this.calculate_outstanding_amount(false);
@ -787,8 +783,6 @@ frappe.ui.form.on('Sales Invoice', {
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically));
frm.refresh_fields();
},

View File

@ -1444,7 +1444,8 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
"print_hide": 1
"print_hide": 1,
"read_only_depends_on": "eval:doc.write_off_outstanding_amount_automatically"
},
{
"fieldname": "base_write_off_amount",
@ -2014,7 +2015,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2021-08-17 20:16:12.737743",
"modified": "2021-08-18 16:07:45.122570",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -26,6 +26,7 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
from erpnext.stock.utils import get_incoming_rate
from erpnext.accounts.utils import PaymentEntryUnlinkError
class TestSalesInvoice(unittest.TestCase):
def make(self):
@ -136,7 +137,7 @@ class TestSalesInvoice(unittest.TestCase):
pe.paid_to_account_currency = si.currency
pe.source_exchange_rate = 1
pe.target_exchange_rate = 1
pe.paid_amount = si.grand_total
pe.paid_amount = si.outstanding_amount
pe.insert()
pe.submit()
@ -145,6 +146,42 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
def test_payment_entry_unlink_against_standalone_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si1 = create_sales_invoice(rate=1000)
si2 = create_sales_invoice(rate=300)
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC")
pe.append('references', {
'reference_doctype': 'Sales Invoice',
'reference_name': si2.name,
'total_amount': si2.grand_total,
'outstanding_amount': si2.outstanding_amount,
'allocated_amount': si2.outstanding_amount
})
pe.append('references', {
'reference_doctype': 'Sales Invoice',
'reference_name': si3.name,
'total_amount': si3.grand_total,
'outstanding_amount': si3.outstanding_amount,
'allocated_amount': si3.outstanding_amount
})
pe.reference_no = 'Test001'
pe.reference_date = nowdate()
pe.save()
pe.submit()
si2.load_from_db()
si2.cancel()
si1.load_from_db()
self.assertRaises(PaymentEntryUnlinkError, si1.cancel)
def test_sales_invoice_calculation_export_currency(self):
si = frappe.copy_doc(test_records[2])
si.currency = "USD"
@ -2014,7 +2051,7 @@ class TestSalesInvoice(unittest.TestCase):
data = get_ewb_data("Sales Invoice", [si.name])
self.assertEqual(data['version'], '1.0.1118')
self.assertEqual(data['version'], '1.0.0421')
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')
self.assertEqual(data['billLists'][0]['fromTrdName'], '_Test Company')
self.assertEqual(data['billLists'][0]['toTrdName'], '_Test Customer')

View File

@ -101,7 +101,7 @@ def merge_similar_entries(gl_map, precision=None):
def check_if_in_list(gle, gl_map, dimensions=None):
account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher',
'cost_center', 'against_voucher_type', 'party_type', 'project']
'cost_center', 'against_voucher_type', 'party_type', 'project', 'finance_book']
if dimensions:
account_head_fieldnames = account_head_fieldnames + dimensions

View File

@ -535,6 +535,8 @@ class ReceivablePayableReport(object):
if getdate(entry_date) > getdate(self.filters.report_date):
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
row.total_due = row.range1 + row.range2 + row.range3 + row.range4 + row.range5
def get_ageing_data(self, entry_date, row):
# [0-30, 30-60, 60-90, 90-120, 120-above]
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0

View File

@ -82,6 +82,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"range3": 0.0,
"range4": 0.0,
"range5": 0.0,
"total_due": 0.0,
"sales_person": []
}))
@ -135,3 +136,6 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
self.add_column(label=label, fieldname='range' + str(i+1))
# Add column for total due amount
self.add_column(label="Total Amount Due", fieldname='total_due')

View File

@ -210,10 +210,10 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
company_currency = get_company_currency(filters)
if filters.filter_based_on == 'Fiscal Year':
start_date = fiscal_year.year_start_date
start_date = fiscal_year.year_start_date if filters.report != 'Balance Sheet' else None
end_date = fiscal_year.year_end_date
else:
start_date = filters.period_start_date
start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
end_date = filters.period_end_date
gl_entries_by_account = {}

View File

@ -19,6 +19,7 @@ from erpnext.stock import get_warehouse_account_map
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
class FiscalYearError(frappe.ValidationError): pass
class PaymentEntryUnlinkError(frappe.ValidationError): pass
@frappe.whitelist()
def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False):
@ -555,10 +556,16 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
for pe in linked_pe:
pe_doc = frappe.get_doc("Payment Entry", pe)
pe_doc.set_total_allocated_amount()
pe_doc.set_unallocated_amount()
pe_doc.clear_unallocated_reference_document_rows()
try:
pe_doc = frappe.get_doc("Payment Entry", pe)
pe_doc.set_amounts()
pe_doc.clear_unallocated_reference_document_rows()
pe_doc.validate_payment_type_with_outstanding()
except Exception as e:
msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
msg += '<br>'
msg += _("Please cancel payment entry manually first")
frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s

View File

@ -1896,6 +1896,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
for d in data:
new_child_flag = False
if not d.get("item_code"):
# ignore empty rows
continue
if not d.get("docname"):
new_child_flag = True
check_doc_permissions(parent, 'create')

View File

@ -329,7 +329,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.po_detail = source_doc.po_detail
target_doc.pr_detail = source_doc.pr_detail
target_doc.purchase_invoice_item = source_doc.name
target_doc.price_list_rate = 0
elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
@ -360,7 +359,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
else:
target_doc.pos_invoice_item = source_doc.name
target_doc.price_list_rate = 0
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return

View File

@ -595,7 +595,8 @@ class calculate_taxes_and_totals(object):
self.doc.precision("outstanding_amount"))
if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
self.update_paid_amount_for_return(total_amount_to_pay)
self.set_total_amount_to_default_mop(total_amount_to_pay)
self.calculate_paid_amount()
def calculate_paid_amount(self):
@ -675,7 +676,7 @@ class calculate_taxes_and_totals(object):
def set_item_wise_tax_breakup(self):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def update_paid_amount_for_return(self, total_amount_to_pay):
def set_total_amount_to_default_mop(self, total_amount_to_pay):
default_mode_of_payment = frappe.db.get_value('POS Payment Method',
{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
@ -685,9 +686,7 @@ class calculate_taxes_and_totals(object):
'mode_of_payment': default_mode_of_payment.mode_of_payment,
'amount': total_amount_to_pay,
'default': 1
})
self.calculate_paid_amount()
})
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:

View File

@ -4,18 +4,11 @@
frappe.ui.form.on('Salary Component', {
setup: function(frm) {
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
let d = frappe.get_doc(cdt, cdn);
let root_type = "Liability";
if (frm.doc.type == "Deduction") {
root_type = "Expense";
}
var d = locals[cdt][cdn];
return {
filters: {
"is_group": 0,
"company": d.company,
"root_type": root_type
"company": d.company
}
};
});

View File

@ -754,8 +754,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
});
this.frm.refresh_fields();
this.calculate_paid_amount();
}
set_default_payment(total_amount_to_pay, update_paid_amount) {

View File

@ -2242,12 +2242,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
coupon_code() {
var me = this;
frappe.run_serially([
() => this.frm.doc.ignore_pricing_rule=1,
() => me.ignore_pricing_rule(),
() => this.frm.doc.ignore_pricing_rule=0,
() => me.apply_pricing_rule()
]);
if (this.frm.doc.coupon_code) {
frappe.run_serially([
() => this.frm.doc.ignore_pricing_rule=1,
() => me.ignore_pricing_rule(),
() => this.frm.doc.ignore_pricing_rule=0,
() => me.apply_pricing_rule()
]);
} else {
frappe.run_serially([
() => this.frm.doc.ignore_pricing_rule=1,
() => me.ignore_pricing_rule()
]);
}
}
};

View File

@ -563,7 +563,7 @@ erpnext.utils.update_child_items = function(opts) {
},
],
primary_action: function() {
const trans_items = this.get_values()["trans_items"];
const trans_items = this.get_values()["trans_items"].filter((item) => !!item.item_code);
frappe.call({
method: 'erpnext.controllers.accounts_controller.update_child_qty_rate',
freeze: true,

View File

@ -860,6 +860,8 @@
.invoice-fields {
overflow-y: scroll;
height: 100%;
padding-right: var(--padding-sm);
}
}

View File

@ -531,6 +531,7 @@ def make_custom_fields(update=True):
fieldtype='Link', options='Salary Component', insert_after='hra_section'),
dict(fieldname='hra_component', label='HRA Component',
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
dict(fieldname='arrear_component', label='Arrear Component',
fieldtype='Link', options='Salary Component', insert_after='hra_component'),
dict(fieldname='non_profit_section', label='Non Profit Settings',
@ -539,6 +540,7 @@ def make_custom_fields(update=True):
fieldtype='Data', insert_after='non_profit_section'),
dict(fieldname='with_effect_from', label='80G With Effect From',
fieldtype='Date', insert_after='company_80g_number'),
dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
dict(fieldname='pan_details', label='PAN Number',
fieldtype='Data', insert_after='with_effect_from')
],

View File

@ -475,7 +475,7 @@ def get_ewb_data(dt, dn):
ewaybills.append(data)
data = {
'version': '1.0.1118',
'version': '1.0.0421',
'billLists': ewaybills
}

View File

@ -6,9 +6,8 @@ import frappe
from frappe.utils import flt, cstr
from erpnext.controllers.taxes_and_totals import get_itemised_tax
from frappe import _
from frappe.core.doctype.file.file import remove_file
from frappe.utils.file_manager import remove_file
from six import string_types
from frappe.desk.form.load import get_attachments
from erpnext.regional.italy import state_codes

View File

@ -394,6 +394,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
}
_set_batch_number(doc) {
if (doc.batch_no) {
return
}
let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)};
if (doc.has_serial_no && doc.serial_no) {
args['serial_no'] = doc.serial_no

View File

@ -46,6 +46,43 @@ frappe.ui.form.on("Company", {
});
},
change_abbreviation(frm) {
var dialog = new frappe.ui.Dialog({
title: "Replace Abbr",
fields: [
{"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr",
"reqd": 1 },
{"fieldtype": "Button", "label": "Update", "fieldname": "update"},
]
});
dialog.fields_dict.update.$input.click(function() {
var args = dialog.get_values();
if (!args) return;
frappe.show_alert(__("Update in progress. It might take a while."));
return frappe.call({
method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr",
args: {
"company": frm.doc.name,
"old": frm.doc.abbr,
"new": args.new_abbr
},
callback: function(r) {
if (r.exc) {
frappe.msgprint(__("There were errors."));
return;
} else {
frm.set_value("abbr", args.new_abbr);
}
dialog.hide();
frm.refresh();
},
btn: this
});
});
dialog.show();
},
company_name: function(frm) {
if(frm.doc.__islocal) {
// add missing " " arg in split method
@ -127,6 +164,10 @@ frappe.ui.form.on("Company", {
}, __('Manage'));
}
}
frm.add_custom_button(__('Change Abbreviation'), () => {
frm.trigger('change_abbreviation');
}, __('Manage'));
}
erpnext.company.set_chart_of_accounts_options(frm.doc);
@ -204,43 +245,6 @@ erpnext.company.set_chart_of_accounts_options = function(doc) {
}
}
cur_frm.cscript.change_abbr = function() {
var dialog = new frappe.ui.Dialog({
title: "Replace Abbr",
fields: [
{"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr",
"reqd": 1 },
{"fieldtype": "Button", "label": "Update", "fieldname": "update"},
]
});
dialog.fields_dict.update.$input.click(function() {
var args = dialog.get_values();
if(!args) return;
frappe.show_alert(__("Update in progress. It might take a while."));
return frappe.call({
method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr",
args: {
"company": cur_frm.doc.name,
"old": cur_frm.doc.abbr,
"new": args.new_abbr
},
callback: function(r) {
if(r.exc) {
frappe.msgprint(__("There were errors."));
return;
} else {
cur_frm.set_value("abbr", args.new_abbr);
}
dialog.hide();
cur_frm.refresh();
},
btn: this
})
});
dialog.show();
}
erpnext.company.setup_queries = function(frm) {
$.each([
["default_bank_account", {"account_type": "Bank"}],

View File

@ -12,33 +12,48 @@
"details",
"company_name",
"abbr",
"change_abbr",
"default_currency",
"country",
"is_group",
"cb0",
"domain",
"parent_company",
"charts_section",
"default_currency",
"default_letter_head",
"default_holiday_list",
"default_finance_book",
"default_selling_terms",
"default_buying_terms",
"default_warehouse_for_sales_return",
"default_in_transit_warehouse",
"column_break_10",
"country",
"create_chart_of_accounts_based_on",
"chart_of_accounts",
"existing_company",
"tax_id",
"domain",
"date_of_establishment",
"parent_company",
"company_info",
"company_logo",
"date_of_incorporation",
"phone_no",
"email",
"company_description",
"column_break1",
"date_of_commencement",
"fax",
"website",
"address_html",
"section_break_28",
"create_chart_of_accounts_based_on",
"existing_company",
"column_break_26",
"chart_of_accounts",
"charts_section",
"sales_settings",
"monthly_sales_target",
"default_buying_terms",
"sales_monthly_history",
"column_break_goals",
"transactions_annual_history",
"monthly_sales_target",
"total_monthly_sales",
"column_break_goals",
"default_selling_terms",
"default_warehouse_for_sales_return",
"credit_limit",
"transactions_annual_history",
"hr_settings_section",
"default_holiday_list",
"default_expense_claim_payable_account",
"column_break_10",
"default_employee_advance_account",
"default_payroll_payable_account",
"default_settings",
"default_bank_account",
"default_cash_account",
@ -52,24 +67,20 @@
"column_break0",
"allow_account_creation_against_child_company",
"default_payable_account",
"default_employee_advance_account",
"default_expense_account",
"default_income_account",
"default_deferred_revenue_account",
"default_deferred_expense_account",
"default_payroll_payable_account",
"default_expense_claim_payable_account",
"default_discount_account",
"section_break_22",
"cost_center",
"column_break_26",
"credit_limit",
"payment_terms",
"cost_center",
"default_finance_book",
"auto_accounting_for_stock_settings",
"enable_perpetual_inventory",
"enable_perpetual_inventory_for_non_stock_items",
"default_inventory_account",
"stock_adjustment_account",
"default_in_transit_warehouse",
"column_break_32",
"stock_received_but_not_billed",
"service_received_but_not_billed",
@ -79,25 +90,14 @@
"depreciation_expense_account",
"series_for_depreciation_entry",
"expenses_included_in_asset_valuation",
"repair_and_maintenance_account",
"column_break_40",
"disposal_account",
"depreciation_cost_center",
"capital_work_in_progress_account",
"repair_and_maintenance_account",
"asset_received_but_not_billed",
"budget_detail",
"exception_budget_approver_role",
"company_info",
"company_logo",
"date_of_incorporation",
"address_html",
"date_of_commencement",
"phone_no",
"fax",
"email",
"website",
"column_break1",
"company_description",
"registration_info",
"registration_details",
"lft",
@ -127,12 +127,6 @@
"oldfieldtype": "Data",
"reqd": 1
},
{
"depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")",
"fieldname": "change_abbr",
"fieldtype": "Button",
"label": "Change Abbreviation"
},
{
"bold": 1,
"default": "0",
@ -176,10 +170,9 @@
"label": "Company Description"
},
{
"collapsible": 1,
"fieldname": "sales_settings",
"fieldtype": "Section Break",
"label": "Sales Settings"
"label": "Buying & Selling Settings"
},
{
"fieldname": "sales_monthly_history",
@ -442,10 +435,6 @@
"no_copy": 1,
"options": "Account"
},
{
"fieldname": "section_break_22",
"fieldtype": "Section Break"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "cost_center",
@ -455,10 +444,6 @@
"no_copy": 1,
"options": "Cost Center"
},
{
"fieldname": "column_break_26",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "credit_limit",
@ -589,10 +574,10 @@
},
{
"collapsible": 1,
"description": "For reference only.",
"depends_on": "eval: doc.docstatus == 0 && doc.__islocal != 1",
"fieldname": "company_info",
"fieldtype": "Section Break",
"label": "Company Info"
"label": "Address & Contact"
},
{
"fieldname": "date_of_incorporation",
@ -741,6 +726,20 @@
"fieldtype": "Link",
"label": "Repair and Maintenance Account",
"options": "Account"
},
{
"fieldname": "section_break_28",
"fieldtype": "Section Break",
"label": "Chart of Accounts"
},
{
"fieldname": "hr_settings_section",
"fieldtype": "Section Break",
"label": "HR & Payroll Settings"
},
{
"fieldname": "column_break_26",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-building",
@ -748,7 +747,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2021-05-12 16:51:08.187233",
"modified": "2021-07-12 11:27:06.353860",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@ -84,8 +84,6 @@
"oldfieldtype": "Section Break"
},
{
"allow_on_submit": 1,
"default": "{purpose}",
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
@ -630,7 +628,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-08-17 20:16:12.737743",
"modified": "2021-08-20 19:19:31.514846",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@ -58,6 +58,7 @@ class StockEntry(StockController):
self.validate_posting_time()
self.validate_purpose()
self.set_title()
self.validate_item()
self.validate_customer_provided_item()
self.validate_qty()
@ -1608,6 +1609,14 @@ class StockEntry(StockController):
return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
def set_title(self):
if frappe.flags.in_import and self.title:
# Allow updating title during data import/update
return
self.title = self.purpose
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, string_types):

View File

@ -278,6 +278,10 @@ def get_basic_details(args, item, overwrite_warehouse=True):
else:
args.uom = item.stock_uom
if (args.get("batch_no") and
item.name != frappe.get_cached_value('Batch', args.get("batch_no"), 'item')):
args['batch_no'] = ''
out = frappe._dict({
"item_code": item.name,
"item_name": item.item_name,

View File

@ -36,12 +36,26 @@ frappe.query_reports["Stock Analytics"] = {
options:"Brand",
default: "",
},
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1,
},
{
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
options:"Warehouse",
options: "Warehouse",
default: "",
get_query: function() {
const company = frappe.query_report.get_filter_value('company');
return {
filters: { 'company': company }
}
}
},
{
fieldname: "from_date",

View File

@ -1,14 +1,15 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import datetime
from __future__ import unicode_literals
import frappe
from frappe import _, scrub
from frappe.utils import getdate, flt
from frappe.utils import getdate, get_quarter_start, get_first_day_of_week
from frappe.utils import get_first_day as get_first_day_of_month
from erpnext.stock.report.stock_balance.stock_balance import (get_items, get_stock_ledger_entries, get_item_details)
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.utils import is_reposting_item_valuation_in_progress
from six import iteritems
def execute(filters=None):
is_reposting_item_valuation_in_progress()
@ -71,7 +72,8 @@ def get_columns(filters):
def get_period_date_ranges(filters):
from dateutil.relativedelta import relativedelta
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
from_date = round_down_to_nearest_frequency(filters.from_date, filters.range)
to_date = getdate(filters.to_date)
increment = {
"Monthly": 1,
@ -97,6 +99,31 @@ def get_period_date_ranges(filters):
return periodic_daterange
def round_down_to_nearest_frequency(date: str, frequency: str) -> datetime.datetime:
"""Rounds down the date to nearest frequency unit.
example:
>>> round_down_to_nearest_frequency("2021-02-21", "Monthly")
datetime.datetime(2021, 2, 1)
>>> round_down_to_nearest_frequency("2021-08-21", "Yearly")
datetime.datetime(2021, 1, 1)
"""
def _get_first_day_of_fiscal_year(date):
fiscal_year = get_fiscal_year(date)
return fiscal_year and fiscal_year[1] or date
round_down_function = {
"Monthly": get_first_day_of_month,
"Quarterly": get_quarter_start,
"Weekly": get_first_day_of_week,
"Yearly": _get_first_day_of_fiscal_year,
}.get(frequency, getdate)
return round_down_function(date)
def get_period(posting_date, filters):
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
@ -177,7 +204,7 @@ def get_data(filters):
periodic_data = get_periodic_data(sle, filters)
ranges = get_period_date_ranges(filters)
for dummy, item_data in iteritems(item_details):
for dummy, item_data in item_details.items():
row = {
"name": item_data.name,
"item_name": item_data.item_name,

View File

@ -0,0 +1,35 @@
import datetime
import unittest
from frappe import _dict
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges
class TestStockAnalyticsReport(unittest.TestCase):
def test_get_period_date_ranges(self):
filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06")
ranges = get_period_date_ranges(filters)
expected_ranges = [
[datetime.date(2020, 12, 1), datetime.date(2020, 12, 31)],
[datetime.date(2021, 1, 1), datetime.date(2021, 1, 31)],
[datetime.date(2021, 2, 1), datetime.date(2021, 2, 6)],
]
self.assertEqual(ranges, expected_ranges)
def test_get_period_date_ranges_yearly(self):
filters = _dict(range="Yearly", from_date="2021-01-28", to_date="2021-02-06")
ranges = get_period_date_ranges(filters)
first_date = get_fiscal_year("2021-01-28")[1]
expected_ranges = [
[first_date, datetime.date(2021, 2, 6)],
]
self.assertEqual(ranges, expected_ranges)

View File

@ -23,6 +23,7 @@ def execute(filters=None):
conversion_factors = []
if opening_row:
data.append(opening_row)
conversion_factors.append(0)
actual_qty = stock_value = 0