Merge branch 'develop' into feat-bom-process-loss-fp

This commit is contained in:
18alantom 2021-08-24 20:15:51 +05:30
commit aff9aee29c
1287 changed files with 2281 additions and 4987 deletions

View File

@ -13,3 +13,6 @@
# This commit just changes spaces to tabs for indentation in some files # This commit just changes spaces to tabs for indentation in some files
5f473611bd6ed57703716244a054d3fb5ba9cd23 5f473611bd6ed57703716244a054d3fb5ba9cd23
# Whitespace fix throughout codebase
4551d7d6029b6f587f6c99d4f8df5519241c6a86

View File

@ -6,4 +6,4 @@
"scss/at-rule-no-unknown": true, "scss/at-rule-no-unknown": true,
"no-descending-specificity": null "no-descending-specificity": null
} }
} }

View File

@ -19,4 +19,4 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = {
reqd: 1 reqd: 1
}, },
] ]
}; };

View File

@ -450,5 +450,3 @@ def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
return debit_account return debit_account
else: else:
return credit_account return credit_account

View File

@ -60,4 +60,4 @@ frappe.ui.form.on('Accounting Dimension Detail', {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];
row.reference_document = frm.doc.document_type; row.reference_document = frm.doc.document_type;
} }
}); });

View File

@ -113,5 +113,3 @@ def disable_dimension():
dimension2 = frappe.get_doc("Accounting Dimension", "Location") dimension2 = frappe.get_doc("Accounting Dimension", "Location")
dimension2.disabled = 1 dimension2.disabled = 1
dimension2.save() dimension2.save()

View File

@ -79,4 +79,4 @@ frappe.ui.form.on('Allowed Dimension', {
row.accounting_dimension = frm.doc.accounting_dimension; row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions"); frm.refresh_field("dimensions");
} }
}); });

View File

@ -56,4 +56,4 @@ class AccountingPeriod(Document):
self.append('closed_documents', { self.append('closed_documents', {
"document_type": doctype_for_closing.document_type, "document_type": doctype_for_closing.document_type,
"closed": doctype_for_closing.closed "closed": doctype_for_closing.closed
}) })

View File

@ -48,4 +48,4 @@ frappe.tour['Accounts Settings'] = [
title: "Unlink Advance Payment on Cancellation of Order", title: "Unlink Advance Payment on Cancellation of Order",
description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.") description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.")
} }
]; ];

View File

@ -37,7 +37,7 @@ class AccountsSettings(Document):
def toggle_discount_accounting_fields(self): def toggle_discount_accounting_fields(self):
enable_discount_accounting = cint(self.enable_discount_accounting) enable_discount_accounting = cint(self.enable_discount_accounting)
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]: for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
if enable_discount_accounting: if enable_discount_accounting:
@ -52,4 +52,4 @@ class AccountsSettings(Document):
else: else:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False) make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False) make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)

View File

@ -5,4 +5,4 @@ frappe.ui.form.on('Accounts Settings', {
frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods"); frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods");
frm.set_df_property("credit_controller", "label", "Credit Manager"); frm.set_df_property("credit_controller", "label", "Credit Manager");
} }
}); });

View File

@ -120,4 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
plaid_success(token, response) { plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' }); frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
} }
}; };

View File

@ -13,4 +13,4 @@ class Bank(Document):
load_address_and_contact(self) load_address_and_contact(self)
def on_trash(self): def on_trash(self):
delete_contact_and_address('Bank', self.name) delete_contact_and_address('Bank', self.name)

View File

@ -26,4 +26,4 @@ def get_data():
'items': ['Journal Entry'] 'items': ['Journal Entry']
} }
] ]
} }

View File

@ -8,7 +8,7 @@ frappe.ui.form.on("Bank Clearance", {
onload: function(frm) { onload: function(frm) {
let default_bank_account = frappe.defaults.get_user_default("Company")? let default_bank_account = frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: ""; locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("account", default_bank_account); frm.set_value("account", default_bank_account);

View File

@ -6,4 +6,4 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class BankClearanceDetail(Document): class BankClearanceDetail(Document):
pass pass

View File

@ -25,6 +25,6 @@ class BankGuarantee(Document):
def get_vouchar_detials(column_list, doctype, docname): def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list) column_list = json.loads(column_list)
for col in column_list: for col in column_list:
sanitize_searchfield(col) sanitize_searchfield(col)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0] .format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]

View File

@ -105,4 +105,3 @@ def unclear_reference_payment(doctype, docname):
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None) frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
return doc.payment_entry return doc.payment_entry

View File

@ -10,4 +10,4 @@ frappe.listview_settings['Bank Transaction'] = {
return [__("Reconciled"), "green", "unallocated_amount,=,0"]; return [__("Reconciled"), "green", "unallocated_amount,=,0"];
} }
} }
}; };

View File

@ -77,4 +77,4 @@ def get_bank_mapping(bank_account):
mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping} mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping}
return mapping return mapping

View File

@ -6,4 +6,4 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CFormInvoiceDetail(Document): class CFormInvoiceDetail(Document):
pass pass

View File

@ -18,5 +18,3 @@ class CashFlowMapping(Document):
frappe._('You can only select a maximum of one option from the list of check boxes.'), frappe._('You can only select a maximum of one option from the list of check boxes.'),
title='Error' title='Error'
) )

View File

@ -33,4 +33,4 @@ class CashierClosing(Document):
def validate_time(self): def validate_time(self):
if self.from_time >= self.time: if self.from_time >= self.time:
frappe.throw(_("From Time Should Be Less Than To Time")) frappe.throw(_("From Time Should Be Less Than To Time"))

View File

@ -10,10 +10,10 @@ frappe.ui.form.on('Cheque Print Template', {
function() { function() {
erpnext.cheque_print.view_cheque_print(frm); erpnext.cheque_print.view_cheque_print(frm);
}).addClass("btn-primary"); }).addClass("btn-primary");
$(frm.fields_dict.cheque_print_preview.wrapper).empty() $(frm.fields_dict.cheque_print_preview.wrapper).empty()
var template = '<div style="position: relative; overflow-x: scroll;">\ var template = '<div style="position: relative; overflow-x: scroll;">\
<div id="cheque_preview" style="width: {{ cheque_width }}cm; \ <div id="cheque_preview" style="width: {{ cheque_width }}cm; \
height: {{ cheque_height }}cm;\ height: {{ cheque_height }}cm;\
@ -47,9 +47,9 @@ frappe.ui.form.on('Cheque Print Template', {
position: absolute;"> Signatory Name </span>\ position: absolute;"> Signatory Name </span>\
</div>\ </div>\
</div>'; </div>';
$(frappe.render(template, frm.doc)).appendTo(frm.fields_dict.cheque_print_preview.wrapper) $(frappe.render(template, frm.doc)).appendTo(frm.fields_dict.cheque_print_preview.wrapper)
if (frm.doc.scanned_cheque) { if (frm.doc.scanned_cheque) {
$(frm.fields_dict.cheque_print_preview.wrapper).find("#cheque_preview").css('background-image', 'url(' + frm.doc.scanned_cheque + ')'); $(frm.fields_dict.cheque_print_preview.wrapper).find("#cheque_preview").css('background-image', 'url(' + frm.doc.scanned_cheque + ')');
} }

View File

@ -129,4 +129,4 @@ def get_name_with_number(new_account, account_number):
def check_if_distributed_cost_center_enabled(cost_center_list): def check_if_distributed_cost_center_enabled(cost_center_list):
value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1) value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
return next((True for x in value_list if x[0]), False) return next((True for x in value_list if x[0]), False)

View File

@ -12,4 +12,4 @@ def get_data():
'items': ['Budget Variance Report', 'General Ledger'] 'items': ['Budget Variance Report', 'General Ledger']
} }
] ]
} }

View File

@ -51,4 +51,4 @@ frappe.treeview_settings["Cost Center"] = {
} }
} }

View File

@ -62,6 +62,3 @@ def create_cost_center(**args):
cc.is_group = args.is_group or 0 cc.is_group = args.is_group or 0
cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC" cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC"
cc.insert() cc.insert()

View File

@ -17,7 +17,7 @@ class CouponCode(Document):
self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper() self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
elif self.coupon_type == "Gift Card": elif self.coupon_type == "Gift Card":
self.coupon_code = frappe.generate_hash()[:10].upper() self.coupon_code = frappe.generate_hash()[:10].upper()
def validate(self): def validate(self):
if self.coupon_type == "Gift Card": if self.coupon_type == "Gift Card":
self.maximum_use = 1 self.maximum_use = 1

View File

@ -124,6 +124,3 @@ class TestCouponCode(unittest.TestCase):
so.submit() so.submit()
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1) self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)

View File

@ -7,4 +7,4 @@ from __future__ import unicode_literals
from frappe.model.document import Document from frappe.model.document import Document
class DiscountedInvoice(Document): class DiscountedInvoice(Document):
pass pass

View File

@ -14,4 +14,4 @@ def get_data():
'items': ['Payment Entry', 'Journal Entry'] 'items': ['Payment Entry', 'Journal Entry']
} }
] ]
} }

View File

@ -143,4 +143,4 @@ def create_dunning_type_with_zero_interest_rate():
'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.' 'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.'
} }
) )
dunning_type.save() dunning_type.save()

View File

@ -31,7 +31,7 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
}, __('Create')); }, __('Create'));
} }
} }
}); });
} }
}, },
@ -128,4 +128,4 @@ var get_account_details = function(frm, cdt, cdn) {
frm.events.get_total_gain_loss(frm); frm.events.get_total_gain_loss(frm);
} }
}); });
}; };

View File

@ -44,7 +44,7 @@ class ExchangeRateRevaluation(Document):
if total_amt != total_debit: if total_amt != total_debit:
return True return True
return False return False
@frappe.whitelist() @frappe.whitelist()
@ -205,4 +205,4 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
"new_balance_in_base_currency": new_balance_in_base_currency "new_balance_in_base_currency": new_balance_in_base_currency
} }
return account_details return account_details

View File

@ -9,19 +9,8 @@ import frappe
import unittest import unittest
class TestFinanceBook(unittest.TestCase): 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): def test_finance_book(self):
finance_book = self.create_finance_book() finance_book = create_finance_book()
# create jv entry # create jv entry
jv = make_journal_entry("_Test Bank - _TC", jv = make_journal_entry("_Test Bank - _TC",
@ -41,3 +30,14 @@ class TestFinanceBook(unittest.TestCase):
for gl_entry in gl_entries: for gl_entry in gl_entries:
self.assertEqual(gl_entry.finance_book, finance_book.name) 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

@ -17,4 +17,4 @@ def get_data():
'items': ['Payment Entry', 'Journal Entry'] 'items': ['Payment Entry', 'Journal Entry']
} }
] ]
} }

View File

@ -18,4 +18,4 @@ frappe.listview_settings['Invoice Discounting'] = {
return [__("Canceled"), "red", "status,=,Canceled"]; return [__("Canceled"), "red", "status,=,Canceled"];
} }
} }
}; };

View File

@ -14,4 +14,4 @@ frappe.ui.form.on("Journal Entry", {
}; };
}); });
} }
}); });

View File

@ -100,7 +100,7 @@ class TestJournalEntry(unittest.TestCase):
"debit_in_account_currency": 0 if diff > 0 else abs(diff), "debit_in_account_currency": 0 if diff > 0 else abs(diff),
"credit_in_account_currency": diff if diff > 0 else 0 "credit_in_account_currency": diff if diff > 0 else 0
}) })
jv.append("accounts", { jv.append("accounts", {
"account": "Stock Adjustment - TCP1", "account": "Stock Adjustment - TCP1",
"cost_center": "Main - TCP1", "cost_center": "Main - TCP1",

View File

@ -88,4 +88,4 @@ frappe.ui.form.on("Journal Entry Template", {
frappe.model.clear_table(frm.doc, "accounts"); frappe.model.clear_table(frm.doc, "accounts");
frm.refresh_field("accounts"); frm.refresh_field("accounts");
} }
}); });

View File

@ -14,4 +14,4 @@ frappe.ui.form.on('Mode of Payment', {
}; };
}); });
}, },
}); });

View File

@ -39,4 +39,3 @@ class ModeofPayment(Document):
message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \ message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \
Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode." Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode."
frappe.throw(_(message), title="Not Allowed") frappe.throw(_(message), title="Not Allowed")

View File

@ -55,4 +55,4 @@ def get_percentage(doc, start_date, period):
if d.month in months: if d.month in months:
percentage += d.percentage_allocation percentage += d.percentage_allocation
return percentage return percentage

View File

@ -20,4 +20,4 @@ def get_data():
'items': ['Budget'] 'items': ['Budget']
} }
] ]
} }

View File

@ -162,4 +162,4 @@ frappe.ui.form.on('Opening Invoice Creation Tool Item', {
invoices_add: (frm) => { invoices_add: (frm) => {
frm.trigger('update_invoice_table'); frm.trigger('update_invoice_table');
} }
}); });

View File

@ -241,4 +241,3 @@ def get_temporary_opening_account(company=None):
frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts")) frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts"))
return accounts[0].name return accounts[0].name

View File

@ -533,8 +533,8 @@ frappe.ui.form.on('Payment Entry', {
source_exchange_rate: function(frm) { source_exchange_rate: function(frm) {
if (frm.doc.paid_amount) { if (frm.doc.paid_amount) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate)); 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 && // target exchange rate should always be same as source if both account currencies is same
(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) { 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("target_exchange_rate", frm.doc.source_exchange_rate);
frm.set_value("base_received_amount", frm.doc.base_paid_amount); 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_mandatory()
self.validate_reference_documents() self.validate_reference_documents()
self.set_tax_withholding() self.set_tax_withholding()
self.apply_taxes()
self.set_amounts() self.set_amounts()
self.validate_amounts()
self.apply_taxes()
self.set_amounts_after_tax()
self.clear_unallocated_reference_document_rows() self.clear_unallocated_reference_document_rows()
self.validate_payment_against_negative_invoice() self.validate_payment_against_negative_invoice()
self.validate_transaction_reference() self.validate_transaction_reference()
self.set_title() self.set_title()
self.set_remarks() self.set_remarks()
self.validate_duplicate_entry() self.validate_duplicate_entry()
self.validate_payment_type_with_outstanding()
self.validate_allocated_amount() self.validate_allocated_amount()
self.validate_paid_invoices() self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked() self.ensure_supplier_is_not_blocked()
@ -118,6 +121,11 @@ class PaymentEntry(AccountsController):
if not self.get(field): if not self.get(field):
self.set(field, bank_data.account) 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): def validate_allocated_amount(self):
for d in self.get("references"): for d in self.get("references"):
if (flt(d.allocated_amount))> 0: if (flt(d.allocated_amount))> 0:
@ -185,7 +193,7 @@ class PaymentEntry(AccountsController):
for field, value in iteritems(ref_details): for field, value in iteritems(ref_details):
if d.exchange_gain_loss: if d.exchange_gain_loss:
# for cases where gain/loss is booked into invoice # for cases where gain/loss is booked into invoice
# exchange_gain_loss is calculated from invoice & populated # exchange_gain_loss is calculated from invoice & populated
# and row.exchange_rate is already set to payment entry's exchange rate # and row.exchange_rate is already set to payment entry's exchange rate
# refer -> `update_reference_in_payment_entry()` in utils.py # refer -> `update_reference_in_payment_entry()` in utils.py
continue continue
@ -236,7 +244,9 @@ class PaymentEntry(AccountsController):
self.company_currency, self.posting_date) self.company_currency, self.posting_date)
def set_target_exchange_rate(self, ref_doc=None): 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 ref_doc:
if self.paid_to_account_currency == ref_doc.currency: if self.paid_to_account_currency == ref_doc.currency:
self.target_exchange_rate = ref_doc.get("exchange_rate") self.target_exchange_rate = ref_doc.get("exchange_rate")
@ -417,7 +427,7 @@ class PaymentEntry(AccountsController):
net_total_for_tds = 0 net_total_for_tds = 0
if reference.reference_doctype == 'Purchase Order': if reference.reference_doctype == 'Purchase Order':
net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total')) net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
if net_total_for_tds: if net_total_for_tds:
net_total = net_total_for_tds net_total = net_total_for_tds
@ -468,13 +478,22 @@ class PaymentEntry(AccountsController):
def set_amounts(self): def set_amounts(self):
self.set_received_amount() self.set_received_amount()
self.set_amounts_in_company_currency() self.set_amounts_in_company_currency()
self.set_amounts_after_tax()
self.set_total_allocated_amount() self.set_total_allocated_amount()
self.set_unallocated_amount() self.set_unallocated_amount()
self.set_difference_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): def set_received_amount(self):
self.base_received_amount = self.base_paid_amount 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): def set_amounts_after_tax(self):
applicable_tax = 0 applicable_tax = 0
@ -529,7 +548,7 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Receive" \ if self.payment_type == "Receive" \
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
self.unallocated_amount = (self.received_amount + total_deductions - self.unallocated_amount = (self.base_received_amount + total_deductions -
self.base_total_allocated_amount) / self.source_exchange_rate self.base_total_allocated_amount) / self.source_exchange_rate
self.unallocated_amount -= included_taxes self.unallocated_amount -= included_taxes
elif self.payment_type == "Pay" \ elif self.payment_type == "Pay" \
@ -841,7 +860,7 @@ class PaymentEntry(AccountsController):
if account_details: if account_details:
row.update(account_details) row.update(account_details)
if not row.get('amount'): if not row.get('amount'):
# if no difference amount # if no difference amount
return return

View File

@ -11,4 +11,4 @@ frappe.listview_settings['Payment Entry'] = {
}; };
} }
} }
}; };

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 = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1" pe.reference_no = "1"
pe.reference_date = "2016-01-01" pe.reference_date = "2016-01-01"
pe.target_exchange_rate = 50 pe.source_exchange_rate = 50
pe.insert() pe.insert()
pe.submit() 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 = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1" pe.reference_no = "1"
pe.reference_date = "2016-01-01" pe.reference_date = "2016-01-01"
pe.target_exchange_rate = 50 pe.source_exchange_rate = 50
pe.insert() pe.insert()
pe.submit() pe.submit()
@ -295,6 +295,34 @@ class TestPaymentEntry(unittest.TestCase):
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 80) self.assertEqual(outstanding_amount, 80)
def test_payment_entry_against_si_usd_to_usd_with_deduction_in_base_currency (self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50, do_not_save=1)
si.plc_conversion_rate = 50
si.save()
si.submit()
pe = get_payment_entry("Sales Invoice", si.name, party_amount=20,
bank_account="_Test Bank USD - _TC", bank_amount=900)
pe.source_exchange_rate = 45.263
pe.target_exchange_rate = 45.263
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 94.80
})
pe.save()
self.assertEqual(flt(pe.difference_amount, 2), 0.0)
self.assertEqual(flt(pe.unallocated_amount, 2), 0.0)
def test_payment_entry_retrieves_last_exchange_rate(self): def test_payment_entry_retrieves_last_exchange_rate(self):
from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records
@ -463,7 +491,7 @@ class TestPaymentEntry(unittest.TestCase):
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC") pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1" pe.reference_no = "1"
pe.reference_date = "2016-01-01" pe.reference_date = "2016-01-01"
pe.target_exchange_rate = 55 pe.source_exchange_rate = 55
pe.append("deductions", { pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC", "account": "_Test Exchange Gain/Loss - _TC",

View File

@ -57,4 +57,4 @@ QUnit.test("test payment entry", function(assert) {
() => frappe.timeout(3), () => frappe.timeout(3),
() => done() () => done()
]); ]);
}); });

View File

@ -25,4 +25,4 @@ QUnit.test("test payment entry", function(assert) {
() => frappe.timeout(0.3), () => frappe.timeout(0.3),
() => done() () => done()
]); ]);
}); });

View File

@ -64,4 +64,4 @@ QUnit.test("test payment entry", function(assert) {
}, },
() => done() () => done()
]); ]);
}); });

View File

@ -9,19 +9,19 @@ from frappe.model.document import Document
class PaymentGatewayAccount(Document): class PaymentGatewayAccount(Document):
def autoname(self): def autoname(self):
self.name = self.payment_gateway + " - " + self.currency self.name = self.payment_gateway + " - " + self.currency
def validate(self): def validate(self):
self.currency = frappe.db.get_value("Account", self.payment_account, "account_currency") self.currency = frappe.db.get_value("Account", self.payment_account, "account_currency")
self.update_default_payment_gateway() self.update_default_payment_gateway()
self.set_as_default_if_not_set() self.set_as_default_if_not_set()
def update_default_payment_gateway(self): def update_default_payment_gateway(self):
if self.is_default: if self.is_default:
frappe.db.sql("""update `tabPayment Gateway Account` set is_default = 0 frappe.db.sql("""update `tabPayment Gateway Account` set is_default = 0
where is_default = 1 """) where is_default = 1 """)
def set_as_default_if_not_set(self): def set_as_default_if_not_set(self):
if not frappe.db.get_value("Payment Gateway Account", if not frappe.db.get_value("Payment Gateway Account",
{"is_default": 1, "name": ("!=", self.name)}, "name"): {"is_default": 1, "name": ("!=", self.name)}, "name"):
self.is_default = 1 self.is_default = 1

View File

@ -136,4 +136,4 @@ frappe.ui.form.on('Payment Order', {
dialog.show(); dialog.show();
}, },
}); });

View File

@ -9,4 +9,4 @@ def get_data():
'items': ['Payment Entry', 'Journal Entry'] 'items': ['Payment Entry', 'Journal Entry']
} }
] ]
} }

View File

@ -46,4 +46,4 @@ def create_payment_order_against_payment_entry(ref_doc, order_type):
doc = make_payment_order(ref_doc.name, payment_order) doc = make_payment_order(ref_doc.name, payment_order)
doc.save() doc.save()
doc.submit() doc.submit()
return doc return doc

View File

@ -307,4 +307,4 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
] ]
}) })
jv.flags.ignore_mandatory = True jv.flags.ignore_mandatory = True
jv.submit() jv.submit()

View File

@ -541,4 +541,4 @@ def make_payment_order(source_name, target_doc=None):
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist return doclist

View File

@ -138,4 +138,4 @@ class TestPaymentRequest(unittest.TestCase):
# Try to make Payment Request more than SO amount, should give validation # Try to make Payment Request more than SO amount, should give validation
pr2.grand_total = 900 pr2.grand_total = 900
self.assertRaises(frappe.ValidationError, pr2.save) self.assertRaises(frappe.ValidationError, pr2.save)

View File

@ -19,4 +19,4 @@ frappe.ui.form.on('Payment Term', {
frm.set_df_property("discount", "description", description); frm.set_df_property("discount", "description", description);
} }
} }
}); });

View File

@ -3,6 +3,6 @@
frappe.ui.form.on('Payment Terms Template', { frappe.ui.form.on('Payment Terms Template', {
setup: function(frm) { setup: function(frm) {
} }
}); });

View File

@ -30,4 +30,4 @@ def get_data():
'items': ['Customer Group', 'Supplier Group'] 'items': ['Customer Group', 'Supplier Group']
} }
] ]
} }

View File

@ -50,9 +50,13 @@ class PeriodClosingVoucher(AccountsController):
.format(pce[0][0], self.posting_date)) .format(pce[0][0], self.posting_date))
def make_gl_entries(self): 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 = [] gl_entries = []
net_pl_balance = 0
pl_accounts = self.get_pl_balances() pl_accounts = self.get_pl_balances()
for acc in pl_accounts: for acc in pl_accounts:
@ -60,6 +64,7 @@ class PeriodClosingVoucher(AccountsController):
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": acc.account, "account": acc.account,
"cost_center": acc.cost_center, "cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency, "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_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, "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 "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
}, item=acc)) }, 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: return gl_entries
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): def get_pnl_gl_entry(self, pl_accounts):
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):
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center") company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries = [] gl_entries = []
@ -104,6 +87,7 @@ class PeriodClosingVoucher(AccountsController):
gl_entry = self.get_gl_dict({ gl_entry = self.get_gl_dict({
"account": self.closing_account_head, "account": self.closing_account_head,
"cost_center": acc.cost_center or company_cost_center, "cost_center": acc.cost_center or company_cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency, "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_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, "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): def get_pl_balances(self):
"""Get balance for dimension-wise pl accounts""" """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() self.accounting_dimensions = get_accounting_dimensions()
for dimension in self.accounting_dimensions: for dimension in self.accounting_dimensions:

View File

@ -8,6 +8,7 @@ import frappe
from frappe.utils import flt, today from frappe.utils import flt, today
from erpnext.accounts.utils import get_fiscal_year, now 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.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 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestPeriodClosingVoucher(unittest.TestCase): class TestPeriodClosingVoucher(unittest.TestCase):
@ -118,6 +119,58 @@ class TestPeriodClosingVoucher(unittest.TestCase):
self.assertTrue(pcv_gle, expected_gle) 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): def make_period_closing_voucher(self):
pcv = frappe.get_doc({ pcv = frappe.get_doc({
"doctype": "Period Closing Voucher", "doctype": "Period Closing Voucher",
@ -139,7 +192,7 @@ def create_company():
'company_name': "Test PCV Company", 'company_name': "Test PCV Company",
'country': 'United States', 'country': 'United States',
'default_currency': 'USD' 'default_currency': 'USD'
}) })
company.insert(ignore_if_duplicate = True) company.insert(ignore_if_duplicate = True)
return company.name return company.name

View File

@ -20,9 +20,9 @@ frappe.ui.form.on('POS Closing Entry', {
frm.set_query("pos_opening_entry", function(doc) { frm.set_query("pos_opening_entry", function(doc) {
return { filters: { 'status': 'Open', 'docstatus': 1 } }; return { filters: { 'status': 'Open', 'docstatus': 1 } };
}); });
if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime()); if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
frappe.realtime.on('closing_process_complete', async function(data) { frappe.realtime.on('closing_process_complete', async function(data) {
await frm.reload_doc(); await frm.reload_doc();
if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) { if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) {
@ -43,7 +43,7 @@ frappe.ui.form.on('POS Closing Entry', {
const issue = '<a id="jump_to_error" style="text-decoration: underline;">issue</a>'; const issue = '<a id="jump_to_error" style="text-decoration: underline;">issue</a>';
frm.dashboard.set_headline( frm.dashboard.set_headline(
__('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue])); __('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue]));
$('#jump_to_error').on('click', (e) => { $('#jump_to_error').on('click', (e) => {
e.preventDefault(); e.preventDefault();
frappe.utils.scroll_to( frappe.utils.scroll_to(

View File

@ -85,9 +85,15 @@ class TestPOSClosingEntry(unittest.TestCase):
pcv_doc.load_from_db() pcv_doc.load_from_db()
pcv_doc.cancel() 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() pos_inv1.load_from_db()
self.assertEqual(si_doc.docstatus, 2)
self.assertEqual(pos_inv1.status, 'Paid') self.assertEqual(pos_inv1.status, 'Paid')

View File

@ -16,7 +16,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
onload(doc) { onload(doc) {
super.onload(); 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') { if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos"); this.frm.script_manager.trigger("is_pos");
this.frm.refresh_fields(); this.frm.refresh_fields();
@ -111,16 +111,12 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
} }
write_off_outstanding_amount_automatically() { 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"]); frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
// this will make outstanding amount 0 // this will make outstanding amount 0
this.frm.set_value("write_off_amount", 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")) 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); this.calculate_outstanding_amount(false);

View File

@ -99,6 +99,7 @@
"loyalty_redemption_account", "loyalty_redemption_account",
"loyalty_redemption_cost_center", "loyalty_redemption_cost_center",
"section_break_49", "section_break_49",
"coupon_code",
"apply_discount_on", "apply_discount_on",
"base_discount_amount", "base_discount_amount",
"column_break_51", "column_break_51",
@ -595,7 +596,8 @@
{ {
"fieldname": "scan_barcode", "fieldname": "scan_barcode",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Scan Barcode" "label": "Scan Barcode",
"options": "Barcode"
}, },
{ {
"allow_bulk_edit": 1, "allow_bulk_edit": 1,
@ -1182,7 +1184,8 @@
"label": "Write Off Amount", "label": "Write Off Amount",
"no_copy": 1, "no_copy": 1,
"options": "currency", "options": "currency",
"print_hide": 1 "print_hide": 1,
"read_only_depends_on": "eval: doc.write_off_outstanding_amount_automatically"
}, },
{ {
"fieldname": "base_write_off_amount", "fieldname": "base_write_off_amount",
@ -1548,12 +1551,20 @@
"no_copy": 1, "no_copy": 1,
"options": "Sales Invoice", "options": "Sales Invoice",
"read_only": 1 "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", "icon": "fa fa-file-text",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-07-29 13:37:20.636171", "modified": "2021-08-18 16:13:52.080543",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice", "name": "POS Invoice",

View File

@ -44,6 +44,9 @@ class POSInvoice(SalesInvoice):
self.validate_pos() self.validate_pos()
self.validate_payment_amount() self.validate_payment_amount()
self.validate_loyalty_transaction() 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): def on_submit(self):
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program # 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.check_phone_payments()
self.set_status(update=True) 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): def before_cancel(self):
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1: if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
pos_closing_entry = frappe.get_all( pos_closing_entry = frappe.get_all(
@ -84,6 +91,10 @@ class POSInvoice(SalesInvoice):
against_psi_doc.delete_loyalty_point_entry() against_psi_doc.delete_loyalty_point_entry()
against_psi_doc.make_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): def check_phone_payments(self):
for pay in self.payments: for pay in self.payments:
if pay.type == "Phone" and pay.amount >= 0: 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")) .format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
def validate_stock_availablility(self): def validate_stock_availablility(self):
if self.is_return: if self.is_return or self.docstatus != 1:
return return
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')

View File

@ -320,7 +320,8 @@ class TestPOSInvoice(unittest.TestCase):
pos2.get("items")[0].serial_no = serial_nos[0] pos2.get("items")[0].serial_no = serial_nos[0]
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
self.assertRaises(frappe.ValidationError, pos2.insert) pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
def test_delivered_serialized_item_transaction(self): def test_delivered_serialized_item_transaction(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
@ -348,7 +349,8 @@ class TestPOSInvoice(unittest.TestCase):
pos2.get("items")[0].serial_no = serial_nos[0] pos2.get("items")[0].serial_no = serial_nos[0]
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
self.assertRaises(frappe.ValidationError, pos2.insert) pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
def test_loyalty_points(self): def test_loyalty_points(self):
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records

View File

@ -5,10 +5,10 @@ frappe.ui.form.on('POS Invoice Merge Log', {
setup: function(frm) { setup: function(frm) {
frm.set_query("pos_invoice", "pos_invoices", doc => { frm.set_query("pos_invoice", "pos_invoices", doc => {
return{ return{
filters: { filters: {
'docstatus': 1, 'docstatus': 1,
'customer': doc.customer, 'customer': doc.customer,
'consolidated_invoice': '' 'consolidated_invoice': ''
} }
} }
}); });

View File

@ -354,4 +354,4 @@ def safe_load_json(message):
except Exception: except Exception:
json_message = message json_message = message
return json_message return json_message

View File

@ -147,4 +147,3 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.set_user("Administrator") frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`") frappe.db.sql("delete from `tabPOS Invoice`")

View File

@ -53,4 +53,4 @@ frappe.ui.form.on('POS Opening Entry', {
}); });
} }
} }
}); });

View File

@ -38,4 +38,4 @@ class POSOpeningEntry(StatusUpdater):
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account")) frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
def on_submit(self): def on_submit(self):
self.set_status(update=True) self.set_status(update=True)

View File

@ -21,8 +21,8 @@ def create_opening_entry(pos_profile, user):
balance_details.append(frappe._dict({ balance_details.append(frappe._dict({
'mode_of_payment': d.mode_of_payment 'mode_of_payment': d.mode_of_payment
})) }))
entry.set("balance_details", balance_details) entry.set("balance_details", balance_details)
entry.submit() entry.submit()
return entry.as_dict() return entry.as_dict()

View File

@ -8,4 +8,4 @@ from frappe.model.document import Document
class POSSettings(Document): class POSSettings(Document):
def validate(self): def validate(self):
pass pass

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", set_serial_nos_based_on_fifo = frappe.db.get_single_value("Stock Settings",
"automatically_set_serial_nos_based_on_fifo") "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: for item in item_list:
args_copy = copy.deepcopy(args) args_copy = copy.deepcopy(args)
args_copy.update(item) args_copy.update(item)
data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc) data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc)
out.append(data) 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)) out[0].update(get_serial_no_for_item(args_copy))
return out return out

View File

@ -615,4 +615,4 @@ def delete_existing_pricing_rules():
for doctype in ["Pricing Rule", "Pricing Rule Item Code", for doctype in ["Pricing Rule", "Pricing Rule Item Code",
"Pricing Rule Item Group", "Pricing Rule Brand"]: "Pricing Rule Item Group", "Pricing Rule Brand"]:
frappe.db.sql("delete from `tab{0}`".format(doctype)) frappe.db.sql("delete from `tab{0}`".format(doctype))

View File

@ -26,4 +26,3 @@ QUnit.test("test pricing rule", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -31,4 +31,4 @@ class ProcessDeferredAccounting(Document):
'against_voucher': self.name 'against_voucher': self.name
}) })
make_reverse_gl_entries(gl_entries=gl_entries) make_reverse_gl_entries(gl_entries=gl_entries)

View File

@ -45,4 +45,4 @@ class TestProcessDeferredAccounting(unittest.TestCase):
["Sales - _TC", 0.0, 33.85, "2019-01-31"] ["Sales - _TC", 0.0, 33.85, "2019-01-31"]
] ]
check_gl_entries(self, si.name, expected_gle, "2019-01-10") check_gl_entries(self, si.name, expected_gle, "2019-01-10")

View File

@ -284,4 +284,4 @@ def send_auto_email():
selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1}) selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1})
for entry in selected: for entry in selected:
send_emails(entry.name, from_scheduler=True) send_emails(entry.name, from_scheduler=True)
return True return True

View File

@ -48,4 +48,4 @@ frappe.ui.form.on('Promotional Scheme', {
frm.doc.apply_on === key ? 1 : 0); frm.doc.apply_on === key ? 1 : 0);
} }
} }
}); });

View File

@ -9,4 +9,4 @@ def get_data():
'items': ['Pricing Rule'] 'items': ['Pricing Rule']
} }
] ]
} }

View File

@ -11,25 +11,25 @@ class TestPromotionalScheme(unittest.TestCase):
ps = make_promotional_scheme() ps = make_promotional_scheme()
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"], price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"],
filters = {'promotional_scheme': ps.name}) filters = {'promotional_scheme': ps.name})
self.assertTrue(len(price_rules),1) self.assertTrue(len(price_rules),1)
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1) price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
self.assertTrue(price_doc_details.customer, '_Test Customer') self.assertTrue(price_doc_details.customer, '_Test Customer')
self.assertTrue(price_doc_details.min_qty, 4) self.assertTrue(price_doc_details.min_qty, 4)
self.assertTrue(price_doc_details.discount_percentage, 20) self.assertTrue(price_doc_details.discount_percentage, 20)
ps.price_discount_slabs[0].min_qty = 6 ps.price_discount_slabs[0].min_qty = 6
ps.append('customer', { ps.append('customer', {
'customer': "_Test Customer 2"}) 'customer': "_Test Customer 2"})
ps.save() ps.save()
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"], price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
filters = {'promotional_scheme': ps.name}) filters = {'promotional_scheme': ps.name})
self.assertTrue(len(price_rules), 2) self.assertTrue(len(price_rules), 2)
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[1].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1) price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[1].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
self.assertTrue(price_doc_details.customer, '_Test Customer 2') self.assertTrue(price_doc_details.customer, '_Test Customer 2')
self.assertTrue(price_doc_details.min_qty, 6) self.assertTrue(price_doc_details.min_qty, 6)
self.assertTrue(price_doc_details.discount_percentage, 20) self.assertTrue(price_doc_details.discount_percentage, 20)
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1) price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
self.assertTrue(price_doc_details.customer, '_Test Customer') self.assertTrue(price_doc_details.customer, '_Test Customer')
self.assertTrue(price_doc_details.min_qty, 6) self.assertTrue(price_doc_details.min_qty, 6)
@ -38,7 +38,7 @@ class TestPromotionalScheme(unittest.TestCase):
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"], price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
filters = {'promotional_scheme': ps.name}) filters = {'promotional_scheme': ps.name})
self.assertEqual(price_rules, []) self.assertEqual(price_rules, [])
def make_promotional_scheme(): def make_promotional_scheme():
ps = frappe.new_doc('Promotional Scheme') ps = frappe.new_doc('Promotional Scheme')
ps.name = '_Test Scheme' ps.name = '_Test Scheme'
@ -57,4 +57,4 @@ def make_promotional_scheme():
}) })
ps.save() ps.save()
return ps return ps

View File

@ -106,7 +106,6 @@
"depends_on": "eval:doc.rate_or_discount==\"Rate\"", "depends_on": "eval:doc.rate_or_discount==\"Rate\"",
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate" "label": "Rate"
}, },
{ {
@ -170,7 +169,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-03-07 11:56:23.424137", "modified": "2021-08-19 15:49:29.598727",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Promotional Scheme Price Discount", "name": "Promotional Scheme Price Discount",

View File

@ -591,4 +591,4 @@ frappe.ui.form.on("Purchase Invoice", {
company: function(frm) { company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
}, },
}) })

View File

@ -668,8 +668,7 @@
"fieldname": "scan_barcode", "fieldname": "scan_barcode",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Scan Barcode", "label": "Scan Barcode",
"show_days": 1, "options": "Barcode"
"show_seconds": 1
}, },
{ {
"allow_bulk_edit": 1, "allow_bulk_edit": 1,
@ -1715,7 +1714,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-08-07 17:53:14.351439", "modified": "2021-08-17 20:16:12.737743",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -34,4 +34,4 @@ def get_data():
'items': ['Auto Repeat'] 'items': ['Auto Repeat']
}, },
] ]
} }

View File

@ -26,4 +26,4 @@ frappe.listview_settings['Purchase Invoice'] = {
return [__("Paid"), "green", "outstanding_amount,=,0"]; return [__("Paid"), "green", "outstanding_amount,=,0"];
} }
} }
}; };

View File

@ -72,4 +72,3 @@ QUnit.test("test purchase invoice", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class PurchaseInvoiceAdvance(Document): class PurchaseInvoiceAdvance(Document):
pass pass

View File

@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class PurchaseTaxesandCharges(Document): class PurchaseTaxesandCharges(Document):
pass pass

View File

@ -19,4 +19,4 @@ def get_data():
'items': ['Supplier Quotation', 'Tax Rule'] 'items': ['Supplier Quotation', 'Tax Rule']
} }
] ]
} }

View File

@ -26,4 +26,3 @@ QUnit.test("test sales taxes and charges template", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -1,8 +1,6 @@
{% include "erpnext/regional/india/taxes.js" %} {% include "erpnext/regional/india/taxes.js" %}
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
erpnext.setup_auto_gst_taxation('Sales Invoice'); erpnext.setup_auto_gst_taxation('Sales Invoice');
erpnext.setup_einvoice_actions('Sales Invoice')
frappe.ui.form.on("Sales Invoice", { frappe.ui.form.on("Sales Invoice", {
setup: function(frm) { setup: function(frm) {

View File

@ -36,139 +36,4 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
}; };
list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false); list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
};
const generate_irns = () => {
const docnames = list_view.get_checked_items(true);
if (docnames && docnames.length) {
frappe.call({
method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
args: { docnames },
freeze: true,
freeze_message: __('Generating E-Invoices...')
});
} else {
frappe.msgprint({
message: __('Please select at least one sales invoice to generate IRN'),
title: __('No Invoice Selected'),
indicator: 'red'
});
}
};
const cancel_irns = () => {
const docnames = list_view.get_checked_items(true);
const fields = [
{
"label": "Reason",
"fieldname": "reason",
"fieldtype": "Select",
"reqd": 1,
"default": "1-Duplicate",
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
},
{
"label": "Remark",
"fieldname": "remark",
"fieldtype": "Data",
"reqd": 1
}
];
const d = new frappe.ui.Dialog({
title: __("Cancel IRN"),
fields: fields,
primary_action: function() {
const data = d.get_values();
frappe.call({
method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
args: {
doctype: list_view.doctype,
docnames,
reason: data.reason.split('-')[0],
remark: data.remark
},
freeze: true,
freeze_message: __('Cancelling E-Invoices...'),
});
d.hide();
},
primary_action_label: __('Submit')
});
d.show();
};
let einvoicing_enabled = false;
frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
einvoicing_enabled = enabled;
});
list_view.$result.on("change", "input[type=checkbox]", () => {
if (einvoicing_enabled) {
const docnames = list_view.get_checked_items(true);
// show/hide e-invoicing actions when no sales invoices are checked
if (docnames && docnames.length) {
// prevent adding actions twice if e-invoicing action group already exists
if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
}
} else {
list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
}
}
});
frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
const { failures, user, invoices } = data;
if (invoices.length != failures.length) {
frappe.msgprint({
message: __('{0} e-invoices generated successfully', [invoices.length]),
title: __('Bulk E-Invoice Generation Complete'),
indicator: 'orange'
});
}
if (failures && failures.length && user == frappe.session.user) {
let message = `
Failed to generate IRNs for following ${failures.length} sales invoices:
<ul style="padding-left: 20px; padding-top: 5px;">
${failures.map(d => `<li>${d.docname}</li>`).join('')}
</ul>
`;
frappe.msgprint({
message: message,
title: __('Bulk E-Invoice Generation Complete'),
indicator: 'orange'
});
}
});
frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
const { failures, user, invoices } = data;
if (invoices.length != failures.length) {
frappe.msgprint({
message: __('{0} e-invoices cancelled successfully', [invoices.length]),
title: __('Bulk E-Invoice Cancellation Complete'),
indicator: 'orange'
});
}
if (failures && failures.length && user == frappe.session.user) {
let message = `
Failed to cancel IRNs for following ${failures.length} sales invoices:
<ul style="padding-left: 20px; padding-top: 5px;">
${failures.map(d => `<li>${d.docname}</li>`).join('')}
</ul>
`;
frappe.msgprint({
message: message,
title: __('Bulk E-Invoice Cancellation Complete'),
indicator: 'orange'
});
}
});
};

Some files were not shown because too many files have changed in this diff Show More