Merge branch 'develop' into jc-excess-transfer-config
This commit is contained in:
commit
1a58b788e5
@ -94,7 +94,7 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
unlink_ref_doc_from_payment_entries(self)
|
unlink_ref_doc_from_payment_entries(self)
|
||||||
unlink_ref_doc_from_salary_slip(self.name)
|
unlink_ref_doc_from_salary_slip(self.name)
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||||
self.make_gl_entries(1)
|
self.make_gl_entries(1)
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
|
@ -95,7 +95,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||||
self.make_gl_entries(cancel=1)
|
self.make_gl_entries(cancel=1)
|
||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Payment Ledger Entry', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
@ -0,0 +1,180 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "format:PLE-{YY}-{MM}-{######}",
|
||||||
|
"creation": "2022-05-09 19:35:03.334361",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"posting_date",
|
||||||
|
"company",
|
||||||
|
"account_type",
|
||||||
|
"account",
|
||||||
|
"party_type",
|
||||||
|
"party",
|
||||||
|
"due_date",
|
||||||
|
"cost_center",
|
||||||
|
"finance_book",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_no",
|
||||||
|
"against_voucher_type",
|
||||||
|
"against_voucher_no",
|
||||||
|
"amount",
|
||||||
|
"account_currency",
|
||||||
|
"amount_in_account_currency",
|
||||||
|
"delinked"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Posting Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Account Type",
|
||||||
|
"options": "Receivable\nPayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Party Type",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Party",
|
||||||
|
"options": "party_type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Voucher Type",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Voucher No",
|
||||||
|
"options": "voucher_type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Against Voucher Type",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Against Voucher No",
|
||||||
|
"options": "against_voucher_type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account_currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount_in_account_currency",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount in Account Currency",
|
||||||
|
"options": "account_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "delinked",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "DeLinked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "due_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Due Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "finance_book",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Finance Book",
|
||||||
|
"options": "Finance Book"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"in_create": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2022-05-19 18:04:44.609115",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Payment Ledger Entry",
|
||||||
|
"naming_rule": "Expression",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts Manager",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Auditor",
|
||||||
|
"share": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search_fields": "voucher_no, against_voucher_no",
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentLedgerEntry(Document):
|
||||||
|
def validate_account(self):
|
||||||
|
valid_account = frappe.db.get_list(
|
||||||
|
"Account",
|
||||||
|
"name",
|
||||||
|
filters={"name": self.account, "account_type": self.account_type, "company": self.company},
|
||||||
|
ignore_permissions=True,
|
||||||
|
)
|
||||||
|
if not valid_account:
|
||||||
|
frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.validate_account()
|
@ -0,0 +1,408 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaymentLedgerEntry(FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.ple = qb.DocType("Payment Ledger Entry")
|
||||||
|
self.create_company()
|
||||||
|
self.create_item()
|
||||||
|
self.create_customer()
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def create_company(self):
|
||||||
|
company_name = "_Test Payment Ledger"
|
||||||
|
company = None
|
||||||
|
if frappe.db.exists("Company", company_name):
|
||||||
|
company = frappe.get_doc("Company", company_name)
|
||||||
|
else:
|
||||||
|
company = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Company",
|
||||||
|
"company_name": company_name,
|
||||||
|
"country": "India",
|
||||||
|
"default_currency": "INR",
|
||||||
|
"create_chart_of_accounts_based_on": "Standard Template",
|
||||||
|
"chart_of_accounts": "Standard",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
company = company.save()
|
||||||
|
|
||||||
|
self.company = company.name
|
||||||
|
self.cost_center = company.cost_center
|
||||||
|
self.warehouse = "All Warehouses - _PL"
|
||||||
|
self.income_account = "Sales - _PL"
|
||||||
|
self.expense_account = "Cost of Goods Sold - _PL"
|
||||||
|
self.debit_to = "Debtors - _PL"
|
||||||
|
self.creditors = "Creditors - _PL"
|
||||||
|
|
||||||
|
# create bank account
|
||||||
|
if frappe.db.exists("Account", "HDFC - _PL"):
|
||||||
|
self.bank = "HDFC - _PL"
|
||||||
|
else:
|
||||||
|
bank_acc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": "HDFC",
|
||||||
|
"parent_account": "Bank Accounts - _PL",
|
||||||
|
"company": self.company,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bank_acc.save()
|
||||||
|
self.bank = bank_acc.name
|
||||||
|
|
||||||
|
def create_item(self):
|
||||||
|
item_name = "_Test PL Item"
|
||||||
|
item = create_item(
|
||||||
|
item_code=item_name, is_stock_item=0, company=self.company, warehouse=self.warehouse
|
||||||
|
)
|
||||||
|
self.item = item if isinstance(item, str) else item.item_code
|
||||||
|
|
||||||
|
def create_customer(self):
|
||||||
|
name = "_Test PL Customer"
|
||||||
|
if frappe.db.exists("Customer", name):
|
||||||
|
self.customer = name
|
||||||
|
else:
|
||||||
|
customer = frappe.new_doc("Customer")
|
||||||
|
customer.customer_name = name
|
||||||
|
customer.type = "Individual"
|
||||||
|
customer.save()
|
||||||
|
self.customer = customer.name
|
||||||
|
|
||||||
|
def create_sales_invoice(
|
||||||
|
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Helper function to populate default values in sales invoice
|
||||||
|
"""
|
||||||
|
sinv = create_sales_invoice(
|
||||||
|
qty=qty,
|
||||||
|
rate=rate,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.customer,
|
||||||
|
item_code=self.item,
|
||||||
|
item_name=self.item,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
update_stock=0,
|
||||||
|
currency="INR",
|
||||||
|
is_pos=0,
|
||||||
|
is_return=0,
|
||||||
|
return_against=None,
|
||||||
|
income_account=self.income_account,
|
||||||
|
expense_account=self.expense_account,
|
||||||
|
do_not_save=do_not_save,
|
||||||
|
do_not_submit=do_not_submit,
|
||||||
|
)
|
||||||
|
return sinv
|
||||||
|
|
||||||
|
def create_payment_entry(self, amount=100, posting_date=nowdate()):
|
||||||
|
"""
|
||||||
|
Helper function to populate default values in payment entry
|
||||||
|
"""
|
||||||
|
payment = create_payment_entry(
|
||||||
|
company=self.company,
|
||||||
|
payment_type="Receive",
|
||||||
|
party_type="Customer",
|
||||||
|
party=self.customer,
|
||||||
|
paid_from=self.debit_to,
|
||||||
|
paid_to=self.bank,
|
||||||
|
paid_amount=amount,
|
||||||
|
)
|
||||||
|
payment.posting_date = posting_date
|
||||||
|
return payment
|
||||||
|
|
||||||
|
def clear_old_entries(self):
|
||||||
|
doctype_list = [
|
||||||
|
"GL Entry",
|
||||||
|
"Payment Ledger Entry",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Payment Entry",
|
||||||
|
"Journal Entry",
|
||||||
|
]
|
||||||
|
for doctype in doctype_list:
|
||||||
|
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||||
|
|
||||||
|
def create_journal_entry(
|
||||||
|
self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
|
||||||
|
):
|
||||||
|
je = frappe.new_doc("Journal Entry")
|
||||||
|
je.posting_date = posting_date or nowdate()
|
||||||
|
je.company = self.company
|
||||||
|
je.user_remark = "test"
|
||||||
|
if not cost_center:
|
||||||
|
cost_center = self.cost_center
|
||||||
|
je.set(
|
||||||
|
"accounts",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account": acc1,
|
||||||
|
"cost_center": cost_center,
|
||||||
|
"debit_in_account_currency": amount if amount > 0 else 0,
|
||||||
|
"credit_in_account_currency": abs(amount) if amount < 0 else 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": acc2,
|
||||||
|
"cost_center": cost_center,
|
||||||
|
"credit_in_account_currency": amount if amount > 0 else 0,
|
||||||
|
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return je
|
||||||
|
|
||||||
|
def test_payment_against_invoice(self):
|
||||||
|
transaction_date = nowdate()
|
||||||
|
amount = 100
|
||||||
|
ple = self.ple
|
||||||
|
|
||||||
|
# full payment using PE
|
||||||
|
si1 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
|
||||||
|
pe1 = get_payment_entry(si1.doctype, si1.name).save().submit()
|
||||||
|
|
||||||
|
pl_entries = (
|
||||||
|
qb.from_(ple)
|
||||||
|
.select(
|
||||||
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
|
ple.against_voucher_type,
|
||||||
|
ple.against_voucher_no,
|
||||||
|
ple.amount,
|
||||||
|
ple.delinked,
|
||||||
|
)
|
||||||
|
.where((ple.against_voucher_type == si1.doctype) & (ple.against_voucher_no == si1.name))
|
||||||
|
.orderby(ple.creation)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_values = [
|
||||||
|
{
|
||||||
|
"voucher_type": si1.doctype,
|
||||||
|
"voucher_no": si1.name,
|
||||||
|
"against_voucher_type": si1.doctype,
|
||||||
|
"against_voucher_no": si1.name,
|
||||||
|
"amount": amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"voucher_type": pe1.doctype,
|
||||||
|
"voucher_no": pe1.name,
|
||||||
|
"against_voucher_type": si1.doctype,
|
||||||
|
"against_voucher_no": si1.name,
|
||||||
|
"amount": -amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
self.assertEqual(pl_entries[0], expected_values[0])
|
||||||
|
self.assertEqual(pl_entries[1], expected_values[1])
|
||||||
|
|
||||||
|
def test_partial_payment_against_invoice(self):
|
||||||
|
ple = self.ple
|
||||||
|
transaction_date = nowdate()
|
||||||
|
amount = 100
|
||||||
|
|
||||||
|
# partial payment of invoice using PE
|
||||||
|
si2 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
|
||||||
|
pe2 = get_payment_entry(si2.doctype, si2.name)
|
||||||
|
pe2.get("references")[0].allocated_amount = 50
|
||||||
|
pe2.get("references")[0].outstanding_amount = 50
|
||||||
|
pe2 = pe2.save().submit()
|
||||||
|
|
||||||
|
pl_entries = (
|
||||||
|
qb.from_(ple)
|
||||||
|
.select(
|
||||||
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
|
ple.against_voucher_type,
|
||||||
|
ple.against_voucher_no,
|
||||||
|
ple.amount,
|
||||||
|
ple.delinked,
|
||||||
|
)
|
||||||
|
.where((ple.against_voucher_type == si2.doctype) & (ple.against_voucher_no == si2.name))
|
||||||
|
.orderby(ple.creation)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_values = [
|
||||||
|
{
|
||||||
|
"voucher_type": si2.doctype,
|
||||||
|
"voucher_no": si2.name,
|
||||||
|
"against_voucher_type": si2.doctype,
|
||||||
|
"against_voucher_no": si2.name,
|
||||||
|
"amount": amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"voucher_type": pe2.doctype,
|
||||||
|
"voucher_no": pe2.name,
|
||||||
|
"against_voucher_type": si2.doctype,
|
||||||
|
"against_voucher_no": si2.name,
|
||||||
|
"amount": -50,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
self.assertEqual(pl_entries[0], expected_values[0])
|
||||||
|
self.assertEqual(pl_entries[1], expected_values[1])
|
||||||
|
|
||||||
|
def test_cr_note_against_invoice(self):
|
||||||
|
ple = self.ple
|
||||||
|
transaction_date = nowdate()
|
||||||
|
amount = 100
|
||||||
|
|
||||||
|
# reconcile against return invoice
|
||||||
|
si3 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
|
||||||
|
cr_note1 = self.create_sales_invoice(
|
||||||
|
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
|
||||||
|
)
|
||||||
|
cr_note1.is_return = 1
|
||||||
|
cr_note1.return_against = si3.name
|
||||||
|
cr_note1 = cr_note1.save().submit()
|
||||||
|
|
||||||
|
pl_entries = (
|
||||||
|
qb.from_(ple)
|
||||||
|
.select(
|
||||||
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
|
ple.against_voucher_type,
|
||||||
|
ple.against_voucher_no,
|
||||||
|
ple.amount,
|
||||||
|
ple.delinked,
|
||||||
|
)
|
||||||
|
.where((ple.against_voucher_type == si3.doctype) & (ple.against_voucher_no == si3.name))
|
||||||
|
.orderby(ple.creation)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_values = [
|
||||||
|
{
|
||||||
|
"voucher_type": si3.doctype,
|
||||||
|
"voucher_no": si3.name,
|
||||||
|
"against_voucher_type": si3.doctype,
|
||||||
|
"against_voucher_no": si3.name,
|
||||||
|
"amount": amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"voucher_type": cr_note1.doctype,
|
||||||
|
"voucher_no": cr_note1.name,
|
||||||
|
"against_voucher_type": si3.doctype,
|
||||||
|
"against_voucher_no": si3.name,
|
||||||
|
"amount": -amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
self.assertEqual(pl_entries[0], expected_values[0])
|
||||||
|
self.assertEqual(pl_entries[1], expected_values[1])
|
||||||
|
|
||||||
|
def test_je_against_inv_and_note(self):
|
||||||
|
ple = self.ple
|
||||||
|
transaction_date = nowdate()
|
||||||
|
amount = 100
|
||||||
|
|
||||||
|
# reconcile against return invoice using JE
|
||||||
|
si4 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
|
||||||
|
cr_note2 = self.create_sales_invoice(
|
||||||
|
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
|
||||||
|
)
|
||||||
|
cr_note2.is_return = 1
|
||||||
|
cr_note2 = cr_note2.save().submit()
|
||||||
|
je1 = self.create_journal_entry(
|
||||||
|
self.debit_to, self.debit_to, amount, posting_date=transaction_date
|
||||||
|
)
|
||||||
|
je1.get("accounts")[0].party_type = je1.get("accounts")[1].party_type = "Customer"
|
||||||
|
je1.get("accounts")[0].party = je1.get("accounts")[1].party = self.customer
|
||||||
|
je1.get("accounts")[0].reference_type = cr_note2.doctype
|
||||||
|
je1.get("accounts")[0].reference_name = cr_note2.name
|
||||||
|
je1.get("accounts")[1].reference_type = si4.doctype
|
||||||
|
je1.get("accounts")[1].reference_name = si4.name
|
||||||
|
je1 = je1.save().submit()
|
||||||
|
|
||||||
|
pl_entries_for_invoice = (
|
||||||
|
qb.from_(ple)
|
||||||
|
.select(
|
||||||
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
|
ple.against_voucher_type,
|
||||||
|
ple.against_voucher_no,
|
||||||
|
ple.amount,
|
||||||
|
ple.delinked,
|
||||||
|
)
|
||||||
|
.where((ple.against_voucher_type == si4.doctype) & (ple.against_voucher_no == si4.name))
|
||||||
|
.orderby(ple.creation)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_values = [
|
||||||
|
{
|
||||||
|
"voucher_type": si4.doctype,
|
||||||
|
"voucher_no": si4.name,
|
||||||
|
"against_voucher_type": si4.doctype,
|
||||||
|
"against_voucher_no": si4.name,
|
||||||
|
"amount": amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"voucher_type": je1.doctype,
|
||||||
|
"voucher_no": je1.name,
|
||||||
|
"against_voucher_type": si4.doctype,
|
||||||
|
"against_voucher_no": si4.name,
|
||||||
|
"amount": -amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
self.assertEqual(pl_entries_for_invoice[0], expected_values[0])
|
||||||
|
self.assertEqual(pl_entries_for_invoice[1], expected_values[1])
|
||||||
|
|
||||||
|
pl_entries_for_crnote = (
|
||||||
|
qb.from_(ple)
|
||||||
|
.select(
|
||||||
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
|
ple.against_voucher_type,
|
||||||
|
ple.against_voucher_no,
|
||||||
|
ple.amount,
|
||||||
|
ple.delinked,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(ple.against_voucher_type == cr_note2.doctype) & (ple.against_voucher_no == cr_note2.name)
|
||||||
|
)
|
||||||
|
.orderby(ple.creation)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_values = [
|
||||||
|
{
|
||||||
|
"voucher_type": cr_note2.doctype,
|
||||||
|
"voucher_no": cr_note2.name,
|
||||||
|
"against_voucher_type": cr_note2.doctype,
|
||||||
|
"against_voucher_no": cr_note2.name,
|
||||||
|
"amount": -amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"voucher_type": je1.doctype,
|
||||||
|
"voucher_no": je1.name,
|
||||||
|
"against_voucher_type": cr_note2.doctype,
|
||||||
|
"against_voucher_no": cr_note2.name,
|
||||||
|
"amount": amount,
|
||||||
|
"delinked": 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
self.assertEqual(pl_entries_for_crnote[0], expected_values[0])
|
||||||
|
self.assertEqual(pl_entries_for_crnote[1], expected_values[1])
|
@ -96,6 +96,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
self.ignore_linked_doctypes = "Payment Ledger Entry"
|
||||||
# run on cancel method of selling controller
|
# run on cancel method of selling controller
|
||||||
super(SalesInvoice, self).on_cancel()
|
super(SalesInvoice, self).on_cancel()
|
||||||
if not self.is_return and self.loyalty_program:
|
if not self.is_return and self.loyalty_program:
|
||||||
|
@ -1418,7 +1418,12 @@ class PurchaseInvoice(BuyingController):
|
|||||||
frappe.db.set(self, "status", "Cancelled")
|
frappe.db.set(self, "status", "Cancelled")
|
||||||
|
|
||||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
self.ignore_linked_doctypes = (
|
||||||
|
"GL Entry",
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
"Repost Item Valuation",
|
||||||
|
"Payment Ledger Entry",
|
||||||
|
)
|
||||||
self.update_advance_tax_references(cancel=1)
|
self.update_advance_tax_references(cancel=1)
|
||||||
|
|
||||||
def update_project(self):
|
def update_project(self):
|
||||||
|
@ -396,7 +396,12 @@ class SalesInvoice(SellingController):
|
|||||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
|
|
||||||
self.unlink_sales_invoice_from_timesheets()
|
self.unlink_sales_invoice_from_timesheets()
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
self.ignore_linked_doctypes = (
|
||||||
|
"GL Entry",
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
"Repost Item Valuation",
|
||||||
|
"Payment Ledger Entry",
|
||||||
|
)
|
||||||
|
|
||||||
def update_status_updater_args(self):
|
def update_status_updater_args(self):
|
||||||
if cint(self.update_stock):
|
if cint(self.update_stock):
|
||||||
|
@ -14,6 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||||
|
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||||
|
|
||||||
|
|
||||||
class ClosedAccountingPeriod(frappe.ValidationError):
|
class ClosedAccountingPeriod(frappe.ValidationError):
|
||||||
@ -34,6 +35,7 @@ def make_gl_entries(
|
|||||||
validate_disabled_accounts(gl_map)
|
validate_disabled_accounts(gl_map)
|
||||||
gl_map = process_gl_map(gl_map, merge_entries)
|
gl_map = process_gl_map(gl_map, merge_entries)
|
||||||
if gl_map and len(gl_map) > 1:
|
if gl_map and len(gl_map) > 1:
|
||||||
|
create_payment_ledger_entry(gl_map)
|
||||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||||
# Post GL Map proccess there may no be any GL Entries
|
# Post GL Map proccess there may no be any GL Entries
|
||||||
elif gl_map:
|
elif gl_map:
|
||||||
@ -479,6 +481,7 @@ def make_reverse_gl_entries(
|
|||||||
).run(as_dict=1)
|
).run(as_dict=1)
|
||||||
|
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
|
create_payment_ledger_entry(gl_entries, cancel=1)
|
||||||
validate_accounting_period(gl_entries)
|
validate_accounting_period(gl_entries)
|
||||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||||
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
||||||
|
@ -7,7 +7,7 @@ from typing import List, Tuple
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe import _, throw
|
from frappe import _, qb, throw
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
|
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ import erpnext
|
|||||||
|
|
||||||
# imported to enable erpnext.accounts.utils.get_account_currency
|
# imported to enable erpnext.accounts.utils.get_account_currency
|
||||||
from erpnext.accounts.doctype.account.account import get_account_currency # noqa
|
from erpnext.accounts.doctype.account.account import get_account_currency # noqa
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||||
from erpnext.stock import get_warehouse_account_map
|
from erpnext.stock import get_warehouse_account_map
|
||||||
from erpnext.stock.utils import get_stock_value_on
|
from erpnext.stock.utils import get_stock_value_on
|
||||||
|
|
||||||
@ -1345,3 +1346,102 @@ def check_and_delete_linked_reports(report):
|
|||||||
if icons:
|
if icons:
|
||||||
for icon in icons:
|
for icon in icons:
|
||||||
frappe.delete_doc("Desktop Icon", icon)
|
frappe.delete_doc("Desktop Icon", icon)
|
||||||
|
|
||||||
|
|
||||||
|
def create_payment_ledger_entry(gl_entries, cancel=0):
|
||||||
|
if gl_entries:
|
||||||
|
ple = None
|
||||||
|
|
||||||
|
# companies
|
||||||
|
account = qb.DocType("Account")
|
||||||
|
companies = list(set([x.company for x in gl_entries]))
|
||||||
|
|
||||||
|
# receivable/payable account
|
||||||
|
accounts_with_types = (
|
||||||
|
qb.from_(account)
|
||||||
|
.select(account.name, account.account_type)
|
||||||
|
.where(
|
||||||
|
(account.account_type.isin(["Receivable", "Payable"]) & (account.company.isin(companies)))
|
||||||
|
)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
receivable_or_payable_accounts = [y.name for y in accounts_with_types]
|
||||||
|
|
||||||
|
def get_account_type(account):
|
||||||
|
for entry in accounts_with_types:
|
||||||
|
if entry.name == account:
|
||||||
|
return entry.account_type
|
||||||
|
|
||||||
|
dr_or_cr = 0
|
||||||
|
account_type = None
|
||||||
|
for gle in gl_entries:
|
||||||
|
if gle.account in receivable_or_payable_accounts:
|
||||||
|
account_type = get_account_type(gle.account)
|
||||||
|
if account_type == "Receivable":
|
||||||
|
dr_or_cr = gle.debit - gle.credit
|
||||||
|
dr_or_cr_account_currency = gle.debit_in_account_currency - gle.credit_in_account_currency
|
||||||
|
elif account_type == "Payable":
|
||||||
|
dr_or_cr = gle.credit - gle.debit
|
||||||
|
dr_or_cr_account_currency = gle.credit_in_account_currency - gle.debit_in_account_currency
|
||||||
|
|
||||||
|
if cancel:
|
||||||
|
dr_or_cr *= -1
|
||||||
|
dr_or_cr_account_currency *= -1
|
||||||
|
|
||||||
|
ple = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Payment Ledger Entry",
|
||||||
|
"posting_date": gle.posting_date,
|
||||||
|
"company": gle.company,
|
||||||
|
"account_type": account_type,
|
||||||
|
"account": gle.account,
|
||||||
|
"party_type": gle.party_type,
|
||||||
|
"party": gle.party,
|
||||||
|
"cost_center": gle.cost_center,
|
||||||
|
"finance_book": gle.finance_book,
|
||||||
|
"due_date": gle.due_date,
|
||||||
|
"voucher_type": gle.voucher_type,
|
||||||
|
"voucher_no": gle.voucher_no,
|
||||||
|
"against_voucher_type": gle.against_voucher_type
|
||||||
|
if gle.against_voucher_type
|
||||||
|
else gle.voucher_type,
|
||||||
|
"against_voucher_no": gle.against_voucher if gle.against_voucher else gle.voucher_no,
|
||||||
|
"currency": gle.currency,
|
||||||
|
"amount": dr_or_cr,
|
||||||
|
"amount_in_account_currency": dr_or_cr_account_currency,
|
||||||
|
"delinked": True if cancel else False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dimensions_and_defaults = get_dimensions()
|
||||||
|
if dimensions_and_defaults:
|
||||||
|
for dimension in dimensions_and_defaults[0]:
|
||||||
|
ple.set(dimension.fieldname, gle.get(dimension.fieldname))
|
||||||
|
|
||||||
|
if cancel:
|
||||||
|
delink_original_entry(ple)
|
||||||
|
ple.flags.ignore_permissions = 1
|
||||||
|
ple.submit()
|
||||||
|
|
||||||
|
|
||||||
|
def delink_original_entry(pl_entry):
|
||||||
|
if pl_entry:
|
||||||
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
|
query = (
|
||||||
|
qb.update(ple)
|
||||||
|
.set(ple.delinked, True)
|
||||||
|
.set(ple.modified, now())
|
||||||
|
.set(ple.modified_by, frappe.session.user)
|
||||||
|
.where(
|
||||||
|
(ple.company == pl_entry.company)
|
||||||
|
& (ple.account_type == pl_entry.account_type)
|
||||||
|
& (ple.account == pl_entry.account)
|
||||||
|
& (ple.party_type == pl_entry.party_type)
|
||||||
|
& (ple.party == pl_entry.party)
|
||||||
|
& (ple.voucher_type == pl_entry.voucher_type)
|
||||||
|
& (ple.voucher_no == pl_entry.voucher_no)
|
||||||
|
& (ple.against_voucher_type == pl_entry.against_voucher_type)
|
||||||
|
& (ple.against_voucher_no == pl_entry.against_voucher_no)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
query.run()
|
||||||
|
@ -323,6 +323,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
|
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
self.ignore_linked_doctypes = "Payment Ledger Entry"
|
||||||
super(PurchaseOrder, self).on_cancel()
|
super(PurchaseOrder, self).on_cancel()
|
||||||
|
|
||||||
if self.is_against_so():
|
if self.is_against_so():
|
||||||
|
@ -487,6 +487,7 @@ communication_doctypes = ["Customer", "Supplier"]
|
|||||||
|
|
||||||
accounting_dimension_doctypes = [
|
accounting_dimension_doctypes = [
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
|
"Payment Ledger Entry",
|
||||||
"Sales Invoice",
|
"Sales Invoice",
|
||||||
"Purchase Invoice",
|
"Purchase Invoice",
|
||||||
"Payment Entry",
|
"Payment Entry",
|
||||||
|
@ -105,7 +105,7 @@ class ExpenseClaim(AccountsController):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_task_and_project()
|
self.update_task_and_project()
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||||
if self.payable_account:
|
if self.payable_account:
|
||||||
self.make_gl_entries(cancel=True)
|
self.make_gl_entries(cancel=True)
|
||||||
|
|
||||||
|
@ -499,15 +499,11 @@ cur_frm.cscript.qty = function(doc) {
|
|||||||
|
|
||||||
cur_frm.cscript.rate = function(doc, cdt, cdn) {
|
cur_frm.cscript.rate = function(doc, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
var scrap_items = false;
|
const is_scrap_item = cdt == "BOM Scrap Item";
|
||||||
|
|
||||||
if(cdt == 'BOM Scrap Item') {
|
|
||||||
scrap_items = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (d.bom_no) {
|
if (d.bom_no) {
|
||||||
frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item."));
|
frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item."));
|
||||||
get_bom_material_detail(doc, cdt, cdn, scrap_items);
|
get_bom_material_detail(doc, cdt, cdn, is_scrap_item);
|
||||||
} else {
|
} else {
|
||||||
erpnext.bom.calculate_rm_cost(doc);
|
erpnext.bom.calculate_rm_cost(doc);
|
||||||
erpnext.bom.calculate_scrap_materials_cost(doc);
|
erpnext.bom.calculate_scrap_materials_cost(doc);
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
"amount",
|
"amount",
|
||||||
"base_amount",
|
"base_amount",
|
||||||
"section_break_18",
|
"section_break_18",
|
||||||
"scrap",
|
|
||||||
"qty_consumed_per_unit",
|
"qty_consumed_per_unit",
|
||||||
"section_break_27",
|
"section_break_27",
|
||||||
"has_variants",
|
"has_variants",
|
||||||
@ -223,15 +222,6 @@
|
|||||||
"fieldname": "section_break_18",
|
"fieldname": "section_break_18",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "scrap",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"label": "Scrap %",
|
|
||||||
"oldfieldname": "scrap",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"print_hide": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "qty_consumed_per_unit",
|
"fieldname": "qty_consumed_per_unit",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
@ -298,7 +288,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-01-24 16:57:57.020232",
|
"modified": "2022-05-19 02:32:43.785470",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Item",
|
"name": "BOM Item",
|
||||||
|
@ -897,6 +897,7 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
|
|||||||
target.set("time_logs", [])
|
target.set("time_logs", [])
|
||||||
target.set("employee", [])
|
target.set("employee", [])
|
||||||
target.set("items", [])
|
target.set("items", [])
|
||||||
|
target.set("sub_operations", [])
|
||||||
target.set_sub_operations()
|
target.set_sub_operations()
|
||||||
target.get_required_items()
|
target.get_required_items()
|
||||||
target.validate_time_logs()
|
target.validate_time_logs()
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import random_string
|
from frappe.utils import random_string
|
||||||
|
from frappe.utils.data import add_to_date, now
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.job_card.job_card import (
|
from erpnext.manufacturing.doctype.job_card.job_card import (
|
||||||
JobCardOverTransferError,
|
JobCardOverTransferError,
|
||||||
OperationMismatchError,
|
OperationMismatchError,
|
||||||
OverlapError,
|
OverlapError,
|
||||||
|
make_corrective_job_card,
|
||||||
)
|
)
|
||||||
from erpnext.manufacturing.doctype.job_card.job_card import (
|
from erpnext.manufacturing.doctype.job_card.job_card import (
|
||||||
make_stock_entry as make_stock_entry_from_jc,
|
make_stock_entry as make_stock_entry_from_jc,
|
||||||
)
|
)
|
||||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||||
|
from erpnext.manufacturing.doctype.work_order.work_order import WorkOrder
|
||||||
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
|
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
|
||||||
@ -21,35 +27,36 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
|||||||
class TestJobCard(FrappeTestCase):
|
class TestJobCard(FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
make_bom_for_jc_tests()
|
make_bom_for_jc_tests()
|
||||||
|
self.transfer_material_against: Literal["Work Order", "Job Card"] = "Work Order"
|
||||||
|
self.source_warehouse = None
|
||||||
|
self._work_order = None
|
||||||
|
|
||||||
transfer_material_against, source_warehouse = None, None
|
@property
|
||||||
|
def work_order(self) -> WorkOrder:
|
||||||
|
"""Work Order lazily created for tests."""
|
||||||
|
if not self._work_order:
|
||||||
|
self._work_order = make_wo_order_test_record(
|
||||||
|
item="_Test FG Item 2",
|
||||||
|
qty=2,
|
||||||
|
transfer_material_against=self.transfer_material_against,
|
||||||
|
source_warehouse=self.source_warehouse,
|
||||||
|
)
|
||||||
|
return self._work_order
|
||||||
|
|
||||||
tests_that_skip_setup = ("test_job_card_material_transfer_correctness",)
|
def generate_required_stock(self, work_order: WorkOrder) -> None:
|
||||||
tests_that_transfer_against_jc = (
|
"""Create twice the stock for all required items in work order."""
|
||||||
"test_job_card_multiple_materials_transfer",
|
for item in work_order.required_items:
|
||||||
"test_job_card_excess_material_transfer",
|
make_stock_entry(
|
||||||
"test_job_card_partial_material_transfer",
|
item_code=item.item_code,
|
||||||
"test_job_card_excess_material_transfer_block",
|
target=item.source_warehouse or self.source_warehouse,
|
||||||
)
|
qty=item.required_qty * 2,
|
||||||
|
basic_rate=100,
|
||||||
if self._testMethodName in tests_that_skip_setup:
|
)
|
||||||
return
|
|
||||||
|
|
||||||
if self._testMethodName in tests_that_transfer_against_jc:
|
|
||||||
transfer_material_against = "Job Card"
|
|
||||||
source_warehouse = "Stores - _TC"
|
|
||||||
|
|
||||||
self.work_order = make_wo_order_test_record(
|
|
||||||
item="_Test FG Item 2",
|
|
||||||
qty=2,
|
|
||||||
transfer_material_against=transfer_material_against,
|
|
||||||
source_warehouse=source_warehouse,
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
def test_job_card(self):
|
def test_job_card_operations(self):
|
||||||
|
|
||||||
job_cards = frappe.get_all(
|
job_cards = frappe.get_all(
|
||||||
"Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"]
|
"Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"]
|
||||||
@ -63,9 +70,6 @@ class TestJobCard(FrappeTestCase):
|
|||||||
doc.operation_id = "Test Data"
|
doc.operation_id = "Test Data"
|
||||||
self.assertRaises(OperationMismatchError, doc.save)
|
self.assertRaises(OperationMismatchError, doc.save)
|
||||||
|
|
||||||
for d in job_cards:
|
|
||||||
frappe.delete_doc("Job Card", d.name)
|
|
||||||
|
|
||||||
def test_job_card_with_different_work_station(self):
|
def test_job_card_with_different_work_station(self):
|
||||||
job_cards = frappe.get_all(
|
job_cards = frappe.get_all(
|
||||||
"Job Card",
|
"Job Card",
|
||||||
@ -101,19 +105,11 @@ class TestJobCard(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(completed_qty, job_card.for_quantity)
|
self.assertEqual(completed_qty, job_card.for_quantity)
|
||||||
|
|
||||||
doc.cancel()
|
|
||||||
|
|
||||||
for d in job_cards:
|
|
||||||
frappe.delete_doc("Job Card", d.name)
|
|
||||||
|
|
||||||
def test_job_card_overlap(self):
|
def test_job_card_overlap(self):
|
||||||
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
|
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
|
||||||
|
|
||||||
jc1_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
|
||||||
jc2_name = frappe.db.get_value("Job Card", {"work_order": wo2.name})
|
jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name})
|
||||||
|
|
||||||
jc1 = frappe.get_doc("Job Card", jc1_name)
|
|
||||||
jc2 = frappe.get_doc("Job Card", jc2_name)
|
|
||||||
|
|
||||||
employee = "_T-Employee-00001" # from test records
|
employee = "_T-Employee-00001" # from test records
|
||||||
|
|
||||||
@ -142,10 +138,10 @@ class TestJobCard(FrappeTestCase):
|
|||||||
|
|
||||||
def test_job_card_multiple_materials_transfer(self):
|
def test_job_card_multiple_materials_transfer(self):
|
||||||
"Test transferring RMs separately against Job Card with multiple RMs."
|
"Test transferring RMs separately against Job Card with multiple RMs."
|
||||||
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100)
|
self.transfer_material_against = "Job Card"
|
||||||
make_stock_entry(
|
self.source_warehouse = "Stores - _TC"
|
||||||
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=6, basic_rate=100
|
|
||||||
)
|
self.generate_required_stock(self.work_order)
|
||||||
|
|
||||||
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
||||||
job_card = frappe.get_doc("Job Card", job_card_name)
|
job_card = frappe.get_doc("Job Card", job_card_name)
|
||||||
@ -173,22 +169,21 @@ class TestJobCard(FrappeTestCase):
|
|||||||
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1})
|
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1})
|
||||||
def test_job_card_excess_material_transfer(self):
|
def test_job_card_excess_material_transfer(self):
|
||||||
"Test transferring more than required RM against Job Card."
|
"Test transferring more than required RM against Job Card."
|
||||||
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
|
self.transfer_material_against = "Job Card"
|
||||||
make_stock_entry(
|
self.source_warehouse = "Stores - _TC"
|
||||||
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
|
|
||||||
)
|
|
||||||
|
|
||||||
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
self.generate_required_stock(self.work_order)
|
||||||
job_card = frappe.get_doc("Job Card", job_card_name)
|
|
||||||
|
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
|
||||||
self.assertEqual(job_card.status, "Open")
|
self.assertEqual(job_card.status, "Open")
|
||||||
|
|
||||||
# fully transfer both RMs
|
# fully transfer both RMs
|
||||||
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
|
transfer_entry_1 = make_stock_entry_from_jc(job_card.name)
|
||||||
transfer_entry_1.insert()
|
transfer_entry_1.insert()
|
||||||
transfer_entry_1.submit()
|
transfer_entry_1.submit()
|
||||||
|
|
||||||
# transfer extra qty of both RM due to previously damaged RM
|
# transfer extra qty of both RM due to previously damaged RM
|
||||||
transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
|
transfer_entry_2 = make_stock_entry_from_jc(job_card.name)
|
||||||
# deliberately change 'For Quantity'
|
# deliberately change 'For Quantity'
|
||||||
transfer_entry_2.fg_completed_qty = 1
|
transfer_entry_2.fg_completed_qty = 1
|
||||||
transfer_entry_2.items[0].qty = 5
|
transfer_entry_2.items[0].qty = 5
|
||||||
@ -201,7 +196,7 @@ class TestJobCard(FrappeTestCase):
|
|||||||
|
|
||||||
# Check if 'For Quantity' is negative
|
# Check if 'For Quantity' is negative
|
||||||
# as 'transferred_qty' > Qty to Manufacture
|
# as 'transferred_qty' > Qty to Manufacture
|
||||||
transfer_entry_3 = make_stock_entry_from_jc(job_card_name)
|
transfer_entry_3 = make_stock_entry_from_jc(job_card.name)
|
||||||
self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
|
self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
|
||||||
|
|
||||||
job_card.append(
|
job_card.append(
|
||||||
@ -216,10 +211,11 @@ class TestJobCard(FrappeTestCase):
|
|||||||
|
|
||||||
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0})
|
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0})
|
||||||
def test_job_card_excess_material_transfer_block(self):
|
def test_job_card_excess_material_transfer_block(self):
|
||||||
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
|
|
||||||
make_stock_entry(
|
self.transfer_material_against = "Job Card"
|
||||||
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
|
self.source_warehouse = "Stores - _TC"
|
||||||
)
|
|
||||||
|
self.generate_required_stock(self.work_order)
|
||||||
|
|
||||||
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
||||||
|
|
||||||
@ -239,17 +235,15 @@ class TestJobCard(FrappeTestCase):
|
|||||||
|
|
||||||
def test_job_card_partial_material_transfer(self):
|
def test_job_card_partial_material_transfer(self):
|
||||||
"Test partial material transfer against Job Card"
|
"Test partial material transfer against Job Card"
|
||||||
|
self.transfer_material_against = "Job Card"
|
||||||
|
self.source_warehouse = "Stores - _TC"
|
||||||
|
|
||||||
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
|
self.generate_required_stock(self.work_order)
|
||||||
make_stock_entry(
|
|
||||||
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
|
|
||||||
)
|
|
||||||
|
|
||||||
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
|
||||||
job_card = frappe.get_doc("Job Card", job_card_name)
|
|
||||||
|
|
||||||
# partially transfer
|
# partially transfer
|
||||||
transfer_entry = make_stock_entry_from_jc(job_card_name)
|
transfer_entry = make_stock_entry_from_jc(job_card.name)
|
||||||
transfer_entry.fg_completed_qty = 1
|
transfer_entry.fg_completed_qty = 1
|
||||||
transfer_entry.get_items()
|
transfer_entry.get_items()
|
||||||
transfer_entry.insert()
|
transfer_entry.insert()
|
||||||
@ -261,7 +255,7 @@ class TestJobCard(FrappeTestCase):
|
|||||||
self.assertEqual(transfer_entry.items[1].qty, 3)
|
self.assertEqual(transfer_entry.items[1].qty, 3)
|
||||||
|
|
||||||
# transfer remaining
|
# transfer remaining
|
||||||
transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
|
transfer_entry_2 = make_stock_entry_from_jc(job_card.name)
|
||||||
|
|
||||||
self.assertEqual(transfer_entry_2.fg_completed_qty, 1)
|
self.assertEqual(transfer_entry_2.fg_completed_qty, 1)
|
||||||
self.assertEqual(transfer_entry_2.items[0].qty, 5)
|
self.assertEqual(transfer_entry_2.items[0].qty, 5)
|
||||||
@ -306,7 +300,49 @@ class TestJobCard(FrappeTestCase):
|
|||||||
self.assertEqual(transfer_entry.items[0].item_code, "_Test Item")
|
self.assertEqual(transfer_entry.items[0].item_code, "_Test Item")
|
||||||
self.assertEqual(transfer_entry.items[0].qty, 2)
|
self.assertEqual(transfer_entry.items[0].qty, 2)
|
||||||
|
|
||||||
# rollback via tearDown method
|
@change_settings(
|
||||||
|
"Manufacturing Settings", {"add_corrective_operation_cost_in_finished_good_valuation": 1}
|
||||||
|
)
|
||||||
|
def test_corrective_costing(self):
|
||||||
|
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
|
||||||
|
|
||||||
|
job_card.append(
|
||||||
|
"time_logs",
|
||||||
|
{"from_time": now(), "to_time": add_to_date(now(), hours=1), "completed_qty": 2},
|
||||||
|
)
|
||||||
|
job_card.submit()
|
||||||
|
|
||||||
|
self.work_order.reload()
|
||||||
|
original_cost = self.work_order.total_operating_cost
|
||||||
|
|
||||||
|
# Create a corrective operation against it
|
||||||
|
corrective_action = frappe.get_doc(
|
||||||
|
doctype="Operation", is_corrective_operation=1, name=frappe.generate_hash()
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
corrective_job_card = make_corrective_job_card(
|
||||||
|
job_card.name, operation=corrective_action.name, for_operation=job_card.operation
|
||||||
|
)
|
||||||
|
corrective_job_card.hour_rate = 100
|
||||||
|
corrective_job_card.insert()
|
||||||
|
corrective_job_card.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": add_to_date(now(), hours=2),
|
||||||
|
"to_time": add_to_date(now(), hours=2, minutes=30),
|
||||||
|
"completed_qty": 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
corrective_job_card.submit()
|
||||||
|
|
||||||
|
self.work_order.reload()
|
||||||
|
cost_after_correction = self.work_order.total_operating_cost
|
||||||
|
self.assertGreater(cost_after_correction, original_cost)
|
||||||
|
|
||||||
|
corrective_job_card.cancel()
|
||||||
|
self.work_order.reload()
|
||||||
|
cost_after_cancel = self.work_order.total_operating_cost
|
||||||
|
self.assertEqual(cost_after_cancel, original_cost)
|
||||||
|
|
||||||
|
|
||||||
def create_bom_with_multiple_operations():
|
def create_bom_with_multiple_operations():
|
||||||
|
@ -21,7 +21,7 @@ def get_exploded_items(bom, data, indent=0, qty=1):
|
|||||||
exploded_items = frappe.get_all(
|
exploded_items = frappe.get_all(
|
||||||
"BOM Item",
|
"BOM Item",
|
||||||
filters={"parent": bom},
|
filters={"parent": bom},
|
||||||
fields=["qty", "bom_no", "qty", "scrap", "item_code", "item_name", "description", "uom"],
|
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"],
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in exploded_items:
|
for item in exploded_items:
|
||||||
@ -37,7 +37,6 @@ def get_exploded_items(bom, data, indent=0, qty=1):
|
|||||||
"qty": item.qty * qty,
|
"qty": item.qty * qty,
|
||||||
"uom": item.uom,
|
"uom": item.uom,
|
||||||
"description": item.description,
|
"description": item.description,
|
||||||
"scrap": item.scrap,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if item.bom_no:
|
if item.bom_no:
|
||||||
@ -64,5 +63,4 @@ def get_columns():
|
|||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"width": 150,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"label": _("Scrap"), "fieldtype": "data", "fieldname": "scrap", "width": 100},
|
|
||||||
]
|
]
|
||||||
|
38
erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
Normal file
38
erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
|
get_dimensions,
|
||||||
|
make_dimension_in_accounting_doctypes,
|
||||||
|
)
|
||||||
|
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||||
|
|
||||||
|
|
||||||
|
def create_accounting_dimension_fields():
|
||||||
|
dimensions_and_defaults = get_dimensions()
|
||||||
|
if dimensions_and_defaults:
|
||||||
|
for dimension in dimensions_and_defaults[0]:
|
||||||
|
make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
# create accounting dimension fields in Payment Ledger
|
||||||
|
create_accounting_dimension_fields()
|
||||||
|
|
||||||
|
gl = qb.DocType("GL Entry")
|
||||||
|
accounts = frappe.db.get_list(
|
||||||
|
"Account", "name", filters={"account_type": ["in", ["Receivable", "Payable"]]}, as_list=True
|
||||||
|
)
|
||||||
|
gl_entries = []
|
||||||
|
if accounts:
|
||||||
|
# get all gl entries on receivable/payable accounts
|
||||||
|
gl_entries = (
|
||||||
|
qb.from_(gl)
|
||||||
|
.select("*")
|
||||||
|
.where(gl.account.isin(accounts))
|
||||||
|
.where(gl.is_cancelled == 0)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
if gl_entries:
|
||||||
|
# create payment ledger entries for the accounts receivable/payable
|
||||||
|
create_payment_ledger_entry(gl_entries, 0)
|
@ -149,58 +149,27 @@ erpnext.setup_einvoice_actions = (doctype) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
|
if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
|
||||||
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 action = () => {
|
const action = () => {
|
||||||
const d = new frappe.ui.Dialog({
|
let message = __('Cancellation of e-way bill is currently not supported.') + ' ';
|
||||||
title: __('Cancel E-Way Bill'),
|
message += '<br><br>';
|
||||||
fields: fields,
|
message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
|
||||||
primary_action: function() {
|
|
||||||
const data = d.get_values();
|
const dialog = frappe.msgprint({
|
||||||
frappe.call({
|
title: __('Update E-Way Bill Cancelled Status?'),
|
||||||
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
|
message: message,
|
||||||
args: {
|
indicator: 'orange',
|
||||||
doctype,
|
primary_action: {
|
||||||
docname: name,
|
action: function() {
|
||||||
eway_bill: ewaybill,
|
frappe.call({
|
||||||
reason: data.reason.split('-')[0],
|
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
|
||||||
remark: data.remark
|
args: { doctype, docname: name },
|
||||||
},
|
freeze: true,
|
||||||
freeze: true,
|
callback: () => frm.reload_doc() && dialog.hide()
|
||||||
callback: () => {
|
});
|
||||||
frappe.show_alert({
|
}
|
||||||
message: __('E-Way Bill Cancelled successfully'),
|
|
||||||
indicator: 'green'
|
|
||||||
}, 7);
|
|
||||||
frm.reload_doc();
|
|
||||||
d.hide();
|
|
||||||
},
|
|
||||||
error: () => {
|
|
||||||
frappe.show_alert({
|
|
||||||
message: __('E-Way Bill was not Cancelled'),
|
|
||||||
indicator: 'red'
|
|
||||||
}, 7);
|
|
||||||
d.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
primary_action_label: __('Submit')
|
primary_action_label: __('Yes')
|
||||||
});
|
});
|
||||||
d.show();
|
|
||||||
};
|
};
|
||||||
add_custom_button(__("Cancel E-Way Bill"), action);
|
add_custom_button(__("Cancel E-Way Bill"), action);
|
||||||
}
|
}
|
||||||
|
@ -649,6 +649,8 @@ def make_einvoice(invoice):
|
|||||||
try:
|
try:
|
||||||
einvoice = safe_json_load(einvoice)
|
einvoice = safe_json_load(einvoice)
|
||||||
einvoice = santize_einvoice_fields(einvoice)
|
einvoice = santize_einvoice_fields(einvoice)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
show_link_to_error_log(invoice, einvoice)
|
show_link_to_error_log(invoice, einvoice)
|
||||||
|
|
||||||
@ -765,7 +767,9 @@ def safe_json_load(json_string):
|
|||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Error in input data. Please check for any special characters near following input: <br> {}"
|
"Error in input data. Please check for any special characters near following input: <br> {}"
|
||||||
).format(snippet)
|
).format(snippet),
|
||||||
|
title=_("Invalid JSON"),
|
||||||
|
exc=e,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -797,7 +801,8 @@ class GSPConnector:
|
|||||||
self.irn_details_url = self.base_url + "/enriched/ei/api/invoice/irn"
|
self.irn_details_url = self.base_url + "/enriched/ei/api/invoice/irn"
|
||||||
self.generate_irn_url = self.base_url + "/enriched/ei/api/invoice"
|
self.generate_irn_url = self.base_url + "/enriched/ei/api/invoice"
|
||||||
self.gstin_details_url = self.base_url + "/enriched/ei/api/master/gstin"
|
self.gstin_details_url = self.base_url + "/enriched/ei/api/master/gstin"
|
||||||
self.cancel_ewaybill_url = self.base_url + "/enriched/ei/api/ewayapi"
|
# cancel_ewaybill_url will only work if user have bought ewb api from adaequare.
|
||||||
|
self.cancel_ewaybill_url = self.base_url + "/enriched/ewb/ewayapi?action=CANEWB"
|
||||||
self.generate_ewaybill_url = self.base_url + "/enriched/ei/api/ewaybill"
|
self.generate_ewaybill_url = self.base_url + "/enriched/ei/api/ewaybill"
|
||||||
self.get_qrcode_url = self.base_url + "/enriched/ei/others/qr/image"
|
self.get_qrcode_url = self.base_url + "/enriched/ei/others/qr/image"
|
||||||
|
|
||||||
@ -1185,6 +1190,7 @@ class GSPConnector:
|
|||||||
headers = self.get_headers()
|
headers = self.get_headers()
|
||||||
data = json.dumps({"ewbNo": eway_bill, "cancelRsnCode": reason, "cancelRmrk": remark}, indent=4)
|
data = json.dumps({"ewbNo": eway_bill, "cancelRsnCode": reason, "cancelRmrk": remark}, indent=4)
|
||||||
headers["username"] = headers["user_name"]
|
headers["username"] = headers["user_name"]
|
||||||
|
del headers["user_name"]
|
||||||
try:
|
try:
|
||||||
res = self.make_request("post", self.cancel_ewaybill_url, headers, data)
|
res = self.make_request("post", self.cancel_ewaybill_url, headers, data)
|
||||||
if res.get("success"):
|
if res.get("success"):
|
||||||
@ -1358,9 +1364,13 @@ def generate_eway_bill(doctype, docname, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
|
def cancel_eway_bill(doctype, docname):
|
||||||
gsp_connector = GSPConnector(doctype, docname)
|
# NOTE: cancel_eway_bill api is disabled by Adequare.
|
||||||
gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
# gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
||||||
|
|
||||||
|
frappe.db.set_value(doctype, docname, "ewaybill", "")
|
||||||
|
frappe.db.set_value(doctype, docname, "eway_bill_cancelled", 1)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -232,7 +232,7 @@ class SalesOrder(SellingController):
|
|||||||
update_coupon_code_count(self.coupon_code, "used")
|
update_coupon_code_count(self.coupon_code, "used")
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||||
super(SalesOrder, self).on_cancel()
|
super(SalesOrder, self).on_cancel()
|
||||||
|
|
||||||
# Cannot cancel closed SO
|
# Cannot cancel closed SO
|
||||||
|
@ -187,8 +187,9 @@ def get_so_with_invoices(filters):
|
|||||||
.on(soi.parent == so.name)
|
.on(soi.parent == so.name)
|
||||||
.join(ps)
|
.join(ps)
|
||||||
.on(ps.parent == so.name)
|
.on(ps.parent == so.name)
|
||||||
|
.select(so.name)
|
||||||
|
.distinct()
|
||||||
.select(
|
.select(
|
||||||
so.name,
|
|
||||||
so.customer,
|
so.customer,
|
||||||
so.transaction_date.as_("submitted"),
|
so.transaction_date.as_("submitted"),
|
||||||
ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
|
ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
|
||||||
|
@ -298,19 +298,17 @@ class StockEntry(StockController):
|
|||||||
for_update=True,
|
for_update=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
for f in (
|
reset_fields = ("stock_uom", "item_name")
|
||||||
"uom",
|
for field in reset_fields:
|
||||||
"stock_uom",
|
item.set(field, item_details.get(field))
|
||||||
"description",
|
|
||||||
"item_name",
|
update_fields = ("uom", "description", "expense_account", "cost_center", "conversion_factor")
|
||||||
"expense_account",
|
|
||||||
"cost_center",
|
for field in update_fields:
|
||||||
"conversion_factor",
|
if not item.get(field):
|
||||||
):
|
item.set(field, item_details.get(field))
|
||||||
if f == "stock_uom" or not item.get(f):
|
if field == "conversion_factor" and item.uom == item_details.get("stock_uom"):
|
||||||
item.set(f, item_details.get(f))
|
item.set(field, item_details.get(field))
|
||||||
if f == "conversion_factor" and item.uom == item_details.get("stock_uom"):
|
|
||||||
item.set(f, item_details.get(f))
|
|
||||||
|
|
||||||
if not item.transfer_qty and item.qty:
|
if not item.transfer_qty and item.qty:
|
||||||
item.transfer_qty = flt(
|
item.transfer_qty = flt(
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.permissions import add_user_permission, remove_user_permission
|
from frappe.permissions import add_user_permission, remove_user_permission
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
@ -12,6 +10,7 @@ from frappe.utils import flt, nowdate, nowtime
|
|||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
from erpnext.stock.doctype.item.test_item import (
|
from erpnext.stock.doctype.item.test_item import (
|
||||||
create_item,
|
create_item,
|
||||||
|
make_item,
|
||||||
make_item_variant,
|
make_item_variant,
|
||||||
set_item_variant_settings,
|
set_item_variant_settings,
|
||||||
)
|
)
|
||||||
@ -1443,6 +1442,21 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
self.assertEqual(mapped_se.items[0].basic_rate, 100)
|
self.assertEqual(mapped_se.items[0].basic_rate, 100)
|
||||||
self.assertEqual(mapped_se.items[0].basic_amount, 200)
|
self.assertEqual(mapped_se.items[0].basic_amount, 200)
|
||||||
|
|
||||||
|
def test_stock_entry_item_details(self):
|
||||||
|
item = make_item()
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=item.name, qty=1, to_warehouse="_Test Warehouse - _TC", do_not_submit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(se.items[0].item_name, item.item_name)
|
||||||
|
se.items[0].item_name = "wat"
|
||||||
|
se.items[0].stock_uom = "Kg"
|
||||||
|
se.save()
|
||||||
|
|
||||||
|
self.assertEqual(se.items[0].item_name, item.item_name)
|
||||||
|
self.assertEqual(se.items[0].stock_uom, item.stock_uom)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user