Merge branch 'develop' into separate-discount-account
This commit is contained in:
commit
6c86067d90
@ -5,7 +5,10 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, fmt_money, getdate, nowdate
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, getdate
|
||||
|
||||
import erpnext
|
||||
|
||||
form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
|
||||
|
||||
@ -76,6 +79,52 @@ class BankClearance(Document):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
|
||||
|
||||
loan_disbursements = (
|
||||
frappe.qb.from_(loan_disbursement)
|
||||
.select(
|
||||
ConstantColumn("Loan Disbursement").as_("payment_document"),
|
||||
loan_disbursement.name.as_("payment_entry"),
|
||||
loan_disbursement.disbursed_amount.as_("credit"),
|
||||
ConstantColumn(0).as_("debit"),
|
||||
loan_disbursement.reference_number.as_("cheque_number"),
|
||||
loan_disbursement.reference_date.as_("cheque_date"),
|
||||
loan_disbursement.disbursement_date.as_("posting_date"),
|
||||
loan_disbursement.applicant.as_("against_account"),
|
||||
)
|
||||
.where(loan_disbursement.docstatus == 1)
|
||||
.where(loan_disbursement.disbursement_date >= self.from_date)
|
||||
.where(loan_disbursement.disbursement_date <= self.to_date)
|
||||
.where(loan_disbursement.clearance_date.isnull())
|
||||
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
|
||||
.orderby(loan_disbursement.disbursement_date)
|
||||
.orderby(loan_disbursement.name, frappe.qb.desc)
|
||||
).run(as_dict=1)
|
||||
|
||||
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
||||
|
||||
loan_repayments = (
|
||||
frappe.qb.from_(loan_repayment)
|
||||
.select(
|
||||
ConstantColumn("Loan Repayment").as_("payment_document"),
|
||||
loan_repayment.name.as_("payment_entry"),
|
||||
loan_repayment.amount_paid.as_("debit"),
|
||||
ConstantColumn(0).as_("credit"),
|
||||
loan_repayment.reference_number.as_("cheque_number"),
|
||||
loan_repayment.reference_date.as_("cheque_date"),
|
||||
loan_repayment.applicant.as_("against_account"),
|
||||
loan_repayment.posting_date,
|
||||
)
|
||||
.where(loan_repayment.docstatus == 1)
|
||||
.where(loan_repayment.clearance_date.isnull())
|
||||
.where(loan_repayment.posting_date >= self.from_date)
|
||||
.where(loan_repayment.posting_date <= self.to_date)
|
||||
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
|
||||
.orderby(loan_repayment.posting_date)
|
||||
.orderby(loan_repayment.name, frappe.qb.desc)
|
||||
).run(as_dict=1)
|
||||
|
||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||
if self.include_pos_transactions:
|
||||
pos_sales_invoices = frappe.db.sql(
|
||||
@ -114,20 +163,29 @@ class BankClearance(Document):
|
||||
|
||||
entries = sorted(
|
||||
list(payment_entries)
|
||||
+ list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
|
||||
key=lambda k: k["posting_date"] or getdate(nowdate()),
|
||||
+ list(journal_entries)
|
||||
+ list(pos_sales_invoices)
|
||||
+ list(pos_purchase_invoices)
|
||||
+ list(loan_disbursements)
|
||||
+ list(loan_repayments),
|
||||
key=lambda k: getdate(k["posting_date"]),
|
||||
)
|
||||
|
||||
self.set("payment_entries", [])
|
||||
self.total_amount = 0.0
|
||||
default_currency = erpnext.get_default_currency()
|
||||
|
||||
for d in entries:
|
||||
row = self.append("payment_entries", {})
|
||||
|
||||
amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
|
||||
|
||||
if not d.get("account_currency"):
|
||||
d.account_currency = default_currency
|
||||
|
||||
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
|
||||
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
|
||||
d.posting_date = getdate(d.posting_date)
|
||||
|
||||
d.pop("credit")
|
||||
d.pop("debit")
|
||||
|
@ -1,9 +1,96 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_months, getdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.loan_management.doctype.loan.test_loan import (
|
||||
create_loan,
|
||||
create_loan_accounts,
|
||||
create_loan_type,
|
||||
create_repayment_entry,
|
||||
make_loan_disbursement_entry,
|
||||
)
|
||||
|
||||
|
||||
class TestBankClearance(unittest.TestCase):
|
||||
pass
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
make_bank_account()
|
||||
create_loan_accounts()
|
||||
create_loan_masters()
|
||||
add_transactions()
|
||||
|
||||
# Basic test case to test if bank clearance tool doesn't break
|
||||
# Detailed test can be added later
|
||||
def test_bank_clearance(self):
|
||||
bank_clearance = frappe.get_doc("Bank Clearance")
|
||||
bank_clearance.account = "_Test Bank Clearance - _TC"
|
||||
bank_clearance.from_date = add_months(getdate(), -1)
|
||||
bank_clearance.to_date = getdate()
|
||||
bank_clearance.get_payment_entries()
|
||||
self.assertEqual(len(bank_clearance.payment_entries), 3)
|
||||
|
||||
|
||||
def make_bank_account():
|
||||
if not frappe.db.get_value("Account", "_Test Bank Clearance - _TC"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_type": "Bank",
|
||||
"account_name": "_Test Bank Clearance",
|
||||
"company": "_Test Company",
|
||||
"parent_account": "Bank Accounts - _TC",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_loan_masters():
|
||||
create_loan_type(
|
||||
"Clearance Loan",
|
||||
2000000,
|
||||
13.5,
|
||||
25,
|
||||
0,
|
||||
5,
|
||||
"Cash",
|
||||
"_Test Bank Clearance - _TC",
|
||||
"_Test Bank Clearance - _TC",
|
||||
"Loan Account - _TC",
|
||||
"Interest Income Account - _TC",
|
||||
"Penalty Income Account - _TC",
|
||||
)
|
||||
|
||||
|
||||
def add_transactions():
|
||||
make_payment_entry()
|
||||
make_loan()
|
||||
|
||||
|
||||
def make_loan():
|
||||
loan = create_loan(
|
||||
"_Test Customer",
|
||||
"Clearance Loan",
|
||||
280000,
|
||||
"Repay Over Number of Periods",
|
||||
20,
|
||||
applicant_type="Customer",
|
||||
)
|
||||
loan.submit()
|
||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
|
||||
repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
|
||||
repayment_entry.save()
|
||||
repayment_entry.submit()
|
||||
|
||||
|
||||
def make_payment_entry():
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
|
||||
pe.reference_no = "Conrad Oct 18"
|
||||
pe.reference_date = "2018-10-24"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
@ -1,10 +1,17 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.test_runner import make_test_objects
|
||||
|
||||
from erpnext.accounts.party import get_party_shipping_address
|
||||
from erpnext.accounts.utils import get_future_stock_vouchers, get_voucherwise_gl_entries
|
||||
from erpnext.accounts.utils import (
|
||||
get_future_stock_vouchers,
|
||||
get_voucherwise_gl_entries,
|
||||
sort_stock_vouchers_by_posting_date,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
@ -47,6 +54,25 @@ class TestUtils(unittest.TestCase):
|
||||
msg="get_voucherwise_gl_entries not returning expected GLes",
|
||||
)
|
||||
|
||||
def test_stock_voucher_sorting(self):
|
||||
vouchers = []
|
||||
|
||||
item = make_item().name
|
||||
|
||||
stock_entry = {"item": item, "to_warehouse": "_Test Warehouse - _TC", "qty": 1, "rate": 10}
|
||||
|
||||
se1 = make_stock_entry(posting_date="2022-01-01", **stock_entry)
|
||||
se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
|
||||
se3 = make_stock_entry(posting_date="2022-03-01", **stock_entry)
|
||||
|
||||
for doc in (se1, se2, se3):
|
||||
vouchers.append((doc.doctype, doc.name))
|
||||
|
||||
vouchers.append(("Stock Entry", "Wat"))
|
||||
|
||||
sorted_vouchers = sort_stock_vouchers_by_posting_date(list(reversed(vouchers)))
|
||||
self.assertEqual(sorted_vouchers, vouchers)
|
||||
|
||||
|
||||
ADDRESS_RECORDS = [
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
from json import loads
|
||||
from typing import List, Tuple
|
||||
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
@ -1122,6 +1123,9 @@ def update_gl_entries_after(
|
||||
def repost_gle_for_stock_vouchers(
|
||||
stock_vouchers, posting_date, company=None, warehouse_account=None
|
||||
):
|
||||
if not stock_vouchers:
|
||||
return
|
||||
|
||||
def _delete_gl_entries(voucher_type, voucher_no):
|
||||
frappe.db.sql(
|
||||
"""delete from `tabGL Entry`
|
||||
@ -1129,6 +1133,8 @@ def repost_gle_for_stock_vouchers(
|
||||
(voucher_type, voucher_no),
|
||||
)
|
||||
|
||||
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
|
||||
|
||||
if not warehouse_account:
|
||||
warehouse_account = get_warehouse_account_map(company)
|
||||
|
||||
@ -1149,6 +1155,27 @@ def repost_gle_for_stock_vouchers(
|
||||
_delete_gl_entries(voucher_type, voucher_no)
|
||||
|
||||
|
||||
def sort_stock_vouchers_by_posting_date(
|
||||
stock_vouchers: List[Tuple[str, str]]
|
||||
) -> List[Tuple[str, str]]:
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
voucher_nos = [v[1] for v in stock_vouchers]
|
||||
|
||||
sles = (
|
||||
frappe.qb.from_(sle)
|
||||
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
|
||||
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
|
||||
.groupby(sle.voucher_type, sle.voucher_no)
|
||||
).run(as_dict=True)
|
||||
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
|
||||
|
||||
unknown_vouchers = set(stock_vouchers) - set(sorted_vouchers)
|
||||
if unknown_vouchers:
|
||||
sorted_vouchers.extend(unknown_vouchers)
|
||||
|
||||
return sorted_vouchers
|
||||
|
||||
|
||||
def get_future_stock_vouchers(
|
||||
posting_date, posting_time, for_warehouses=None, for_items=None, company=None
|
||||
):
|
||||
|
@ -62,6 +62,8 @@
|
||||
"holiday_list",
|
||||
"default_shift",
|
||||
"salary_information",
|
||||
"salary_currency",
|
||||
"ctc",
|
||||
"salary_mode",
|
||||
"payroll_cost_center",
|
||||
"column_break_52",
|
||||
@ -807,13 +809,25 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Shift Request Approver",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "salary_currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Salary Currency",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "ctc",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Cost to Company (CTC)",
|
||||
"options": "salary_currency"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2022-03-22 13:44:37.088519",
|
||||
"modified": "2022-04-22 16:21:55.811983",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
|
@ -1,397 +1,172 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "HR-EMP-PRO-.YYYY.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 18:33:59.476562",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"autoname": "HR-EMP-PRO-.YYYY.-.#####",
|
||||
"creation": "2018-04-13 18:33:59.476562",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"employee",
|
||||
"employee_name",
|
||||
"department",
|
||||
"salary_currency",
|
||||
"column_break_3",
|
||||
"promotion_date",
|
||||
"company",
|
||||
"details_section",
|
||||
"promotion_details",
|
||||
"salary_details_section",
|
||||
"current_ctc",
|
||||
"column_break_12",
|
||||
"revised_ctc",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Employee",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Employee",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Employee",
|
||||
"options": "Employee",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "employee.employee_name",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Employee Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fetch_from": "employee.employee_name",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Employee Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "employee.department",
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Department",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Department",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fetch_from": "employee.department",
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"label": "Department",
|
||||
"options": "Department",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "promotion_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Promotion Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "promotion_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Promotion Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "employee.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fetch_from": "employee.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Employee Promotion Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"description": "Set the properties that should be updated in the Employee master on promotion submission",
|
||||
"fieldname": "details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Employee Promotion Details"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "promotion_details",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Employee Promotion Detail",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Employee Property History",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "promotion_details",
|
||||
"fieldtype": "Table",
|
||||
"options": "Employee Property History",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Employee Promotion",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Employee Promotion",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "salary_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Salary Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.salary_currency",
|
||||
"fieldname": "salary_currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Salary Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.ctc",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "current_ctc",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Current CTC",
|
||||
"mandatory_depends_on": "revised_ctc",
|
||||
"options": "salary_currency"
|
||||
},
|
||||
{
|
||||
"depends_on": "current_ctc",
|
||||
"fieldname": "revised_ctc",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Revised CTC",
|
||||
"options": "salary_currency"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:40.284987",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Promotion",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-22 18:47:10.168744",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Promotion",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1
|
||||
}
|
@ -26,9 +26,17 @@ class EmployeePromotion(Document):
|
||||
employee = update_employee_work_history(
|
||||
employee, self.promotion_details, date=self.promotion_date
|
||||
)
|
||||
|
||||
if self.revised_ctc:
|
||||
employee.ctc = self.revised_ctc
|
||||
|
||||
employee.save()
|
||||
|
||||
def on_cancel(self):
|
||||
employee = frappe.get_doc("Employee", self.employee)
|
||||
employee = update_employee_work_history(employee, self.promotion_details, cancel=True)
|
||||
|
||||
if self.revised_ctc:
|
||||
employee.ctc = self.current_ctc
|
||||
|
||||
employee.save()
|
||||
|
@ -4,21 +4,22 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, getdate
|
||||
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee
|
||||
|
||||
|
||||
class TestEmployeePromotion(unittest.TestCase):
|
||||
class TestEmployeePromotion(FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.employee = make_employee("employee@promotions.com")
|
||||
frappe.db.sql("""delete from `tabEmployee Promotion`""")
|
||||
frappe.db.delete("Employee Promotion")
|
||||
|
||||
def test_submit_before_promotion_date(self):
|
||||
promotion_obj = frappe.get_doc(
|
||||
employee = make_employee("employee@promotions.com")
|
||||
promotion = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Employee Promotion",
|
||||
"employee": self.employee,
|
||||
"employee": employee,
|
||||
"promotion_details": [
|
||||
{
|
||||
"property": "Designation",
|
||||
@ -29,10 +30,68 @@ class TestEmployeePromotion(unittest.TestCase):
|
||||
],
|
||||
}
|
||||
)
|
||||
promotion_obj.promotion_date = add_days(getdate(), 1)
|
||||
promotion_obj.save()
|
||||
self.assertRaises(frappe.DocstatusTransitionError, promotion_obj.submit)
|
||||
promotion = frappe.get_doc("Employee Promotion", promotion_obj.name)
|
||||
promotion.promotion_date = add_days(getdate(), 1)
|
||||
self.assertRaises(frappe.DocstatusTransitionError, promotion.submit)
|
||||
|
||||
promotion.promotion_date = getdate()
|
||||
promotion.submit()
|
||||
self.assertEqual(promotion.docstatus, 1)
|
||||
|
||||
def test_employee_history(self):
|
||||
for grade in ["L1", "L2"]:
|
||||
frappe.get_doc({"doctype": "Employee Grade", "__newname": grade}).insert()
|
||||
|
||||
employee = make_employee(
|
||||
"test_employee_promotion@example.com",
|
||||
company="_Test Company",
|
||||
date_of_birth=getdate("30-09-1980"),
|
||||
date_of_joining=getdate("01-10-2021"),
|
||||
designation="Software Developer",
|
||||
grade="L1",
|
||||
salary_currency="INR",
|
||||
ctc="500000",
|
||||
)
|
||||
|
||||
promotion = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Employee Promotion",
|
||||
"employee": employee,
|
||||
"promotion_date": getdate(),
|
||||
"revised_ctc": "1000000",
|
||||
"promotion_details": [
|
||||
{
|
||||
"property": "Designation",
|
||||
"current": "Software Developer",
|
||||
"new": "Project Manager",
|
||||
"fieldname": "designation",
|
||||
},
|
||||
{"property": "Grade", "current": "L1", "new": "L2", "fieldname": "grade"},
|
||||
],
|
||||
}
|
||||
).submit()
|
||||
|
||||
# employee fields updated
|
||||
employee = frappe.get_doc("Employee", employee)
|
||||
self.assertEqual(employee.grade, "L2")
|
||||
self.assertEqual(employee.designation, "Project Manager")
|
||||
self.assertEqual(employee.ctc, 1000000)
|
||||
|
||||
# internal work history updated
|
||||
self.assertEqual(employee.internal_work_history[0].designation, "Software Developer")
|
||||
self.assertEqual(employee.internal_work_history[0].from_date, getdate("01-10-2021"))
|
||||
|
||||
self.assertEqual(employee.internal_work_history[1].designation, "Project Manager")
|
||||
self.assertEqual(employee.internal_work_history[1].from_date, getdate())
|
||||
|
||||
promotion.cancel()
|
||||
employee.reload()
|
||||
|
||||
# fields restored
|
||||
self.assertEqual(employee.grade, "L1")
|
||||
self.assertEqual(employee.designation, "Software Developer")
|
||||
self.assertEqual(employee.ctc, 500000)
|
||||
|
||||
# internal work history updated on cancellation
|
||||
self.assertEqual(len(employee.internal_work_history), 1)
|
||||
self.assertEqual(employee.internal_work_history[0].designation, "Software Developer")
|
||||
self.assertEqual(employee.internal_work_history[0].from_date, getdate("01-10-2021"))
|
||||
|
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Employee Transfer Property', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
});
|
@ -1,154 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 18:24:30.579965",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "property",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Property",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "current",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Current",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "new",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "New",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-13 18:25:54.889579",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Transfer Property",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EmployeeTransferProperty(Document):
|
||||
pass
|
@ -1,8 +0,0 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestEmployeeTransferProperty(unittest.TestCase):
|
||||
pass
|
@ -8,39 +8,65 @@ frappe.ui.form.on(cur_frm.doctype, {
|
||||
};
|
||||
});
|
||||
},
|
||||
onload: function(frm){
|
||||
if(frm.doc.__islocal){
|
||||
if(frm.doctype == "Employee Promotion"){
|
||||
frm.doc.promotion_details = [];
|
||||
}else if (frm.doctype == "Employee Transfer") {
|
||||
frm.doc.transfer_details = [];
|
||||
}
|
||||
}
|
||||
|
||||
onload: function(frm) {
|
||||
if (frm.doc.__islocal)
|
||||
frm.trigger("clear_property_table");
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
frm.add_fetch("employee", "company", "company");
|
||||
frm.trigger("clear_property_table");
|
||||
},
|
||||
|
||||
clear_property_table: function(frm) {
|
||||
let table = (frm.doctype == "Employee Promotion") ? "promotion_details" : "transfer_details";
|
||||
frm.clear_table(table);
|
||||
frm.refresh_field(table);
|
||||
|
||||
frm.fields_dict[table].grid.wrapper.find(".grid-add-row").hide();
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
var table;
|
||||
if(frm.doctype == "Employee Promotion"){
|
||||
let table;
|
||||
if (frm.doctype == "Employee Promotion") {
|
||||
table = "promotion_details";
|
||||
}else if (frm.doctype == "Employee Transfer") {
|
||||
} else if (frm.doctype == "Employee Transfer") {
|
||||
table = "transfer_details";
|
||||
}
|
||||
if(!table){return;}
|
||||
cur_frm.fields_dict[table].grid.wrapper.find('.grid-add-row').hide();
|
||||
cur_frm.fields_dict[table].grid.add_custom_button(__('Add Row'), () => {
|
||||
if(!frm.doc.employee){
|
||||
frappe.msgprint(__("Please select Employee"));
|
||||
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
frm.fields_dict[table].grid.wrapper.find(".grid-add-row").hide();
|
||||
frm.events.setup_employee_property_button(frm, table);
|
||||
},
|
||||
|
||||
setup_employee_property_button: function(frm, table) {
|
||||
frm.fields_dict[table].grid.add_custom_button(__("Add Employee Property"), () => {
|
||||
if (!frm.doc.employee) {
|
||||
frappe.msgprint(__("Please select Employee first."));
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: 'erpnext.hr.utils.get_employee_fields_label',
|
||||
callback: function(r) {
|
||||
if(r.message){
|
||||
show_dialog(frm, table, r.message);
|
||||
|
||||
const allowed_fields = [];
|
||||
const exclude_fields = ["naming_series", "employee", "first_name", "middle_name", "last_name", "marital_status", "ctc",
|
||||
"employee_name", "status", "image", "gender", "date_of_birth", "date_of_joining", "lft", "rgt", "old_parent"];
|
||||
|
||||
const exclude_field_types = ["HTML", "Section Break", "Column Break", "Button", "Read Only", "Tab Break", "Table"];
|
||||
|
||||
frappe.model.with_doctype("Employee", () => {
|
||||
const field_label_map = {};
|
||||
frappe.get_meta("Employee").fields.forEach(d => {
|
||||
field_label_map[d.fieldname] = __(d.label) + ` (${d.fieldname})`;
|
||||
if (!in_list(exclude_field_types, d.fieldtype) && !in_list(exclude_fields, d.fieldname)) {
|
||||
allowed_fields.push({
|
||||
label: field_label_map[d.fieldname],
|
||||
value: d.fieldname,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
show_dialog(frm, table, allowed_fields);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -50,21 +76,20 @@ var show_dialog = function(frm, table, field_labels) {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: "Update Property",
|
||||
fields: [
|
||||
{fieldname: "property", label: __('Select Property'), fieldtype:"Select", options: field_labels},
|
||||
{fieldname: "current", fieldtype: "Data", label:__('Current'), read_only: true},
|
||||
{fieldname: "field_html", fieldtype: "HTML"}
|
||||
{fieldname: "property", label: __("Select Property"), fieldtype: "Autocomplete", options: field_labels},
|
||||
{fieldname: "current", fieldtype: "Data", label: __("Current"), read_only: true},
|
||||
{fieldname: "new_value", fieldtype: "Data", label: __("New")}
|
||||
],
|
||||
primary_action_label: __('Add to Details'),
|
||||
primary_action_label: __("Add to Details"),
|
||||
primary_action: () => {
|
||||
d.get_primary_btn().attr('disabled', true);
|
||||
if(d.data) {
|
||||
var input = $('[data-fieldname="field_html"] input');
|
||||
d.data.new = input.val();
|
||||
$(input).remove();
|
||||
d.get_primary_btn().attr("disabled", true);
|
||||
if (d.data) {
|
||||
d.data.new = d.get_values().new_value;
|
||||
add_to_details(frm, d, table);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
d.fields_dict["property"].df.onchange = () => {
|
||||
let property = d.get_values().property;
|
||||
d.data.fieldname = property;
|
||||
@ -73,10 +98,10 @@ var show_dialog = function(frm, table, field_labels) {
|
||||
method: 'erpnext.hr.utils.get_employee_field_property',
|
||||
args: {employee: frm.doc.employee, fieldname: property},
|
||||
callback: function(r) {
|
||||
if(r.message){
|
||||
if (r.message) {
|
||||
d.data.current = r.message.value;
|
||||
d.data.property = r.message.label;
|
||||
d.fields_dict.field_html.$wrapper.html("");
|
||||
|
||||
d.set_value('current', r.message.value);
|
||||
render_dynamic_field(d, r.message.datatype, r.message.options, property);
|
||||
d.get_primary_btn().attr('disabled', false);
|
||||
@ -95,25 +120,26 @@ var render_dynamic_field = function(d, fieldtype, options, fieldname) {
|
||||
df: {
|
||||
"fieldtype": fieldtype,
|
||||
"fieldname": fieldname,
|
||||
"options": options || ''
|
||||
"options": options || '',
|
||||
"label": __("New")
|
||||
},
|
||||
parent: d.fields_dict.field_html.wrapper,
|
||||
parent: d.fields_dict.new_value.wrapper,
|
||||
only_input: false
|
||||
});
|
||||
dynamic_field.make_input();
|
||||
$(dynamic_field.label_area).text(__("New"));
|
||||
d.replace_field("new_value", dynamic_field.df);
|
||||
};
|
||||
|
||||
var add_to_details = function(frm, d, table) {
|
||||
let data = d.data;
|
||||
if(data.fieldname){
|
||||
if(validate_duplicate(frm, table, data.fieldname)){
|
||||
frappe.show_alert({message:__("Property already added"), indicator:'orange'});
|
||||
if (data.fieldname) {
|
||||
if (validate_duplicate(frm, table, data.fieldname)) {
|
||||
frappe.show_alert({message: __("Property already added"), indicator: "orange"});
|
||||
return false;
|
||||
}
|
||||
if(data.current == data.new){
|
||||
frappe.show_alert({message:__("Nothing to change"), indicator:'orange'});
|
||||
d.get_primary_btn().attr('disabled', false);
|
||||
if (data.current == data.new) {
|
||||
frappe.show_alert({message: __("Nothing to change"), indicator: "orange"});
|
||||
d.get_primary_btn().attr("disabled", false);
|
||||
return false;
|
||||
}
|
||||
frm.add_child(table, {
|
||||
@ -123,13 +149,16 @@ var add_to_details = function(frm, d, table) {
|
||||
new: data.new
|
||||
});
|
||||
frm.refresh_field(table);
|
||||
d.fields_dict.field_html.$wrapper.html("");
|
||||
|
||||
frm.fields_dict[table].grid.wrapper.find(".grid-add-row").hide();
|
||||
|
||||
d.fields_dict.new_value.$wrapper.html("");
|
||||
d.set_value("property", "");
|
||||
d.set_value('current', "");
|
||||
frappe.show_alert({message:__("Added to details"),indicator:'green'});
|
||||
d.set_value("current", "");
|
||||
frappe.show_alert({message: __("Added to details"), indicator: "green"});
|
||||
d.data = {};
|
||||
}else {
|
||||
frappe.show_alert({message:__("Value missing"),indicator:'red'});
|
||||
} else {
|
||||
frappe.show_alert({message: __("Value missing"), indicator: "red"});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -88,29 +88,6 @@ def delete_employee_work_history(details, employee, date):
|
||||
frappe.db.delete("Employee Internal Work History", filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_employee_fields_label():
|
||||
fields = []
|
||||
for df in frappe.get_meta("Employee").get("fields"):
|
||||
if df.fieldname in [
|
||||
"salutation",
|
||||
"user_id",
|
||||
"employee_number",
|
||||
"employment_type",
|
||||
"holiday_list",
|
||||
"branch",
|
||||
"department",
|
||||
"designation",
|
||||
"grade",
|
||||
"notice_number_of_days",
|
||||
"reports_to",
|
||||
"leave_policy",
|
||||
"company_email",
|
||||
]:
|
||||
fields.append({"value": df.fieldname, "label": df.label})
|
||||
return fields
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_employee_field_property(employee, fieldname):
|
||||
if employee and fieldname:
|
||||
|
@ -366,3 +366,4 @@ erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
|
||||
erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
|
||||
erpnext.patches.v13_0.copy_custom_field_filters_to_website_item
|
||||
erpnext.patches.v14_0.discount_accounting_separation
|
||||
erpnext.patches.v14_0.delete_employee_transfer_property_doctype
|
||||
|
@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.delete_doc("DocType", "Employee Transfer Property", ignore_missing=True)
|
@ -12,6 +12,7 @@ import traceback
|
||||
|
||||
import frappe
|
||||
import jwt
|
||||
import requests
|
||||
from frappe import _, bold
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
from frappe.integrations.utils import make_get_request, make_post_request
|
||||
@ -829,14 +830,25 @@ class GSPConnector:
|
||||
return self.e_invoice_settings.auth_token
|
||||
|
||||
def make_request(self, request_type, url, headers=None, data=None):
|
||||
if request_type == "post":
|
||||
res = make_post_request(url, headers=headers, data=data)
|
||||
else:
|
||||
res = make_get_request(url, headers=headers, data=data)
|
||||
try:
|
||||
if request_type == "post":
|
||||
res = make_post_request(url, headers=headers, data=data)
|
||||
else:
|
||||
res = make_get_request(url, headers=headers, data=data)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code in [401, 403] and not hasattr(self, "token_auto_refreshed"):
|
||||
self.auto_refresh_token()
|
||||
headers = self.get_headers()
|
||||
return self.make_request(request_type, url, headers, data)
|
||||
|
||||
self.log_request(url, headers, data, res)
|
||||
return res
|
||||
|
||||
def auto_refresh_token(self):
|
||||
self.fetch_auth_token()
|
||||
self.token_auto_refreshed = True
|
||||
|
||||
def log_request(self, url, headers, data, res):
|
||||
headers.update({"password": self.credentials.password})
|
||||
request_log = frappe.get_doc(
|
||||
|
@ -27,6 +27,7 @@ class Quotation(SellingController):
|
||||
self.set_status()
|
||||
self.validate_uom_is_integer("stock_uom", "qty")
|
||||
self.validate_valid_till()
|
||||
self.validate_shopping_cart_items()
|
||||
self.set_customer_name()
|
||||
if self.items:
|
||||
self.with_items = 1
|
||||
@ -49,6 +50,26 @@ class Quotation(SellingController):
|
||||
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
|
||||
frappe.throw(_("Valid till date cannot be before transaction date"))
|
||||
|
||||
def validate_shopping_cart_items(self):
|
||||
if self.order_type != "Shopping Cart":
|
||||
return
|
||||
|
||||
for item in self.items:
|
||||
has_web_item = frappe.db.exists("Website Item", {"item_code": item.item_code})
|
||||
|
||||
# If variant is unpublished but template is published: valid
|
||||
template = frappe.get_cached_value("Item", item.item_code, "variant_of")
|
||||
if template and not has_web_item:
|
||||
has_web_item = frappe.db.exists("Website Item", {"item_code": template})
|
||||
|
||||
if not has_web_item:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Item {1} must have a Website Item for Shopping Cart Quotations").format(
|
||||
item.idx, frappe.bold(item.item_code)
|
||||
),
|
||||
title=_("Unpublished Item"),
|
||||
)
|
||||
|
||||
def has_sales_order(self):
|
||||
return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})
|
||||
|
||||
|
@ -130,6 +130,15 @@ class TestQuotation(FrappeTestCase):
|
||||
quotation.submit()
|
||||
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
|
||||
|
||||
def test_shopping_cart_without_website_item(self):
|
||||
if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}):
|
||||
frappe.get_last_doc("Website Item", {"item_code": "_Test Item Home Desktop 100"}).delete()
|
||||
|
||||
quotation = frappe.copy_doc(test_records[0])
|
||||
quotation.order_type = "Shopping Cart"
|
||||
quotation.valid_till = getdate()
|
||||
self.assertRaises(frappe.ValidationError, quotation.validate)
|
||||
|
||||
def test_create_quotation_with_margin(self):
|
||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.functions import Coalesce, Sum
|
||||
from frappe.query_builder import Case, Order
|
||||
from frappe.query_builder.functions import Coalesce, CombineDatetime, Sum
|
||||
from frappe.utils import flt
|
||||
|
||||
|
||||
@ -121,24 +121,23 @@ def update_qty(bin_name, args):
|
||||
|
||||
bin_details = get_bin_details(bin_name)
|
||||
# actual qty is already updated by processing current voucher
|
||||
actual_qty = bin_details.actual_qty
|
||||
actual_qty = bin_details.actual_qty or 0.0
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
# actual qty is not up to date in case of backdated transaction
|
||||
if future_sle_exists(args):
|
||||
actual_qty = (
|
||||
frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
filters={
|
||||
"item_code": args.get("item_code"),
|
||||
"warehouse": args.get("warehouse"),
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
fieldname="qty_after_transaction",
|
||||
order_by="posting_date desc, posting_time desc, creation desc",
|
||||
)
|
||||
or 0.0
|
||||
last_sle_qty = (
|
||||
frappe.qb.from_(sle)
|
||||
.select(sle.qty_after_transaction)
|
||||
.where((sle.item_code == args.get("item_code")) & (sle.warehouse == args.get("warehouse")))
|
||||
.orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=Order.desc)
|
||||
.orderby(sle.creation, order=Order.desc)
|
||||
.run()
|
||||
)
|
||||
|
||||
if last_sle_qty:
|
||||
actual_qty = last_sle_qty[0][0]
|
||||
|
||||
ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty"))
|
||||
reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty"))
|
||||
indented_qty = flt(bin_details.indented_qty) + flt(args.get("indented_qty"))
|
||||
|
@ -23,6 +23,7 @@
|
||||
"error_section",
|
||||
"error_log",
|
||||
"items_to_be_repost",
|
||||
"affected_transactions",
|
||||
"distinct_item_and_warehouse",
|
||||
"current_index"
|
||||
],
|
||||
@ -172,12 +173,20 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "affected_transactions",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 1,
|
||||
"label": "Affected Transactions",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-30 07:22:48.520266",
|
||||
"modified": "2022-04-18 14:08:08.821602",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Repost Item Valuation",
|
||||
|
@ -6,11 +6,14 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime
|
||||
from frappe.utils.user import get_users_with_role
|
||||
from rq.timeouts import JobTimeoutException
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import update_gl_entries_after
|
||||
from erpnext.stock.stock_ledger import get_items_to_be_repost, repost_future_sle
|
||||
from erpnext.accounts.utils import get_future_stock_vouchers, repost_gle_for_stock_vouchers
|
||||
from erpnext.stock.stock_ledger import (
|
||||
get_affected_transactions,
|
||||
get_items_to_be_repost,
|
||||
repost_future_sle,
|
||||
)
|
||||
|
||||
|
||||
class RepostItemValuation(Document):
|
||||
@ -129,12 +132,12 @@ def repost(doc):
|
||||
|
||||
doc.set_status("Completed")
|
||||
|
||||
except (Exception, JobTimeoutException):
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(traceback)
|
||||
|
||||
message = frappe.message_log.pop()
|
||||
message = frappe.message_log.pop() if frappe.message_log else ""
|
||||
if traceback:
|
||||
message += "<br>" + "Traceback: <br>" + traceback
|
||||
frappe.db.set_value(doc.doctype, doc.name, "error_log", message)
|
||||
@ -170,6 +173,7 @@ def repost_sl_entries(doc):
|
||||
],
|
||||
allow_negative_stock=doc.allow_negative_stock,
|
||||
via_landed_cost_voucher=doc.via_landed_cost_voucher,
|
||||
doc=doc,
|
||||
)
|
||||
|
||||
|
||||
@ -177,27 +181,46 @@ def repost_gl_entries(doc):
|
||||
if not cint(erpnext.is_perpetual_inventory_enabled(doc.company)):
|
||||
return
|
||||
|
||||
# directly modified transactions
|
||||
directly_dependent_transactions = _get_directly_dependent_vouchers(doc)
|
||||
repost_affected_transaction = get_affected_transactions(doc)
|
||||
repost_gle_for_stock_vouchers(
|
||||
directly_dependent_transactions + list(repost_affected_transaction),
|
||||
doc.posting_date,
|
||||
doc.company,
|
||||
)
|
||||
|
||||
|
||||
def _get_directly_dependent_vouchers(doc):
|
||||
"""Get stock vouchers that are directly affected by reposting
|
||||
i.e. any one item-warehouse is present in the stock transaction"""
|
||||
|
||||
items = set()
|
||||
warehouses = set()
|
||||
|
||||
if doc.based_on == "Transaction":
|
||||
ref_doc = frappe.get_doc(doc.voucher_type, doc.voucher_no)
|
||||
doc_items, doc_warehouses = ref_doc.get_items_and_warehouses()
|
||||
items.update(doc_items)
|
||||
warehouses.update(doc_warehouses)
|
||||
|
||||
sles = get_items_to_be_repost(doc.voucher_type, doc.voucher_no)
|
||||
sle_items = [sle.item_code for sle in sles]
|
||||
sle_warehouse = [sle.warehouse for sle in sles]
|
||||
|
||||
items = list(set(doc_items).union(set(sle_items)))
|
||||
warehouses = list(set(doc_warehouses).union(set(sle_warehouse)))
|
||||
sle_items = {sle.item_code for sle in sles}
|
||||
sle_warehouses = {sle.warehouse for sle in sles}
|
||||
items.update(sle_items)
|
||||
warehouses.update(sle_warehouses)
|
||||
else:
|
||||
items = [doc.item_code]
|
||||
warehouses = [doc.warehouse]
|
||||
items.add(doc.item_code)
|
||||
warehouses.add(doc.warehouse)
|
||||
|
||||
update_gl_entries_after(
|
||||
doc.posting_date,
|
||||
doc.posting_time,
|
||||
for_warehouses=warehouses,
|
||||
for_items=items,
|
||||
affected_vouchers = get_future_stock_vouchers(
|
||||
posting_date=doc.posting_date,
|
||||
posting_time=doc.posting_time,
|
||||
for_warehouses=list(warehouses),
|
||||
for_items=list(items),
|
||||
company=doc.company,
|
||||
)
|
||||
return affected_vouchers
|
||||
|
||||
|
||||
def notify_error_to_stock_managers(doc, traceback):
|
||||
|
@ -186,3 +186,10 @@ class TestRepostItemValuation(FrappeTestCase):
|
||||
riv.db_set("status", "Skipped")
|
||||
riv.reload()
|
||||
riv.cancel() # it should cancel now
|
||||
|
||||
def test_queue_progress_serialization(self):
|
||||
# Make sure set/tuple -> list behaviour is retained.
|
||||
self.assertEqual(
|
||||
[["a", "b"], ["c", "d"]],
|
||||
sorted(frappe.parse_json(frappe.as_json(set([("a", "b"), ("c", "d")])))),
|
||||
)
|
||||
|
@ -777,11 +777,11 @@ def auto_fetch_serial_number(
|
||||
exclude_sr_nos = get_serial_nos(clean_serial_no_string("\n".join(exclude_sr_nos)))
|
||||
|
||||
if batch_nos:
|
||||
batch_nos = safe_json_loads(batch_nos)
|
||||
if isinstance(batch_nos, list):
|
||||
filters.batch_no = batch_nos
|
||||
batch_nos_list = safe_json_loads(batch_nos)
|
||||
if isinstance(batch_nos_list, list):
|
||||
filters.batch_no = batch_nos_list
|
||||
else:
|
||||
filters.batch_no = [str(batch_nos)]
|
||||
filters.batch_no = [batch_nos]
|
||||
|
||||
if posting_date:
|
||||
filters.expiry_date = posting_date
|
||||
|
@ -2,14 +2,15 @@
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
from operator import itemgetter
|
||||
from uuid import uuid4
|
||||
|
||||
import frappe
|
||||
from frappe.core.page.permission_manager.permission_manager import reset
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.query_builder.functions import CombineDatetime
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, today
|
||||
from frappe.utils.data import add_to_date
|
||||
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
@ -1067,6 +1068,121 @@ class TestStockLedgerEntry(FrappeTestCase):
|
||||
receipt2 = make_stock_entry(item_code=item, target=warehouse, qty=15, rate=15)
|
||||
self.assertSLEs(receipt2, [{"stock_queue": [[5, 15]], "stock_value_difference": 175}])
|
||||
|
||||
def test_dependent_gl_entry_reposting(self):
|
||||
def _get_stock_credit(doc):
|
||||
return frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"voucher_no": doc.name,
|
||||
"voucher_type": doc.doctype,
|
||||
"is_cancelled": 0,
|
||||
"account": "Stock In Hand - TCP1",
|
||||
},
|
||||
"sum(credit)",
|
||||
)
|
||||
|
||||
def _day(days):
|
||||
return add_to_date(date=today(), days=days)
|
||||
|
||||
item = make_item().name
|
||||
A = "Stores - TCP1"
|
||||
B = "Work In Progress - TCP1"
|
||||
C = "Finished Goods - TCP1"
|
||||
|
||||
make_stock_entry(item_code=item, to_warehouse=A, qty=5, rate=10, posting_date=_day(0))
|
||||
make_stock_entry(item_code=item, from_warehouse=A, to_warehouse=B, qty=5, posting_date=_day(1))
|
||||
depdendent_consumption = make_stock_entry(
|
||||
item_code=item, from_warehouse=B, qty=5, posting_date=_day(2)
|
||||
)
|
||||
self.assertEqual(50, _get_stock_credit(depdendent_consumption))
|
||||
|
||||
# backdated receipt - should trigger GL repost of all previous stock entries
|
||||
bd_receipt = make_stock_entry(
|
||||
item_code=item, to_warehouse=A, qty=5, rate=20, posting_date=_day(-1)
|
||||
)
|
||||
self.assertEqual(100, _get_stock_credit(depdendent_consumption))
|
||||
|
||||
# cancelling receipt should reset it back
|
||||
bd_receipt.cancel()
|
||||
self.assertEqual(50, _get_stock_credit(depdendent_consumption))
|
||||
|
||||
bd_receipt2 = make_stock_entry(
|
||||
item_code=item, to_warehouse=A, qty=2, rate=20, posting_date=_day(-2)
|
||||
)
|
||||
# total as per FIFO -> 2 * 20 + 3 * 10 = 70
|
||||
self.assertEqual(70, _get_stock_credit(depdendent_consumption))
|
||||
|
||||
# transfer WIP material to final destination and consume it all
|
||||
depdendent_consumption.cancel()
|
||||
make_stock_entry(item_code=item, from_warehouse=B, to_warehouse=C, qty=5, posting_date=_day(3))
|
||||
final_consumption = make_stock_entry(
|
||||
item_code=item, from_warehouse=C, qty=5, posting_date=_day(4)
|
||||
)
|
||||
# exact amount gets consumed
|
||||
self.assertEqual(70, _get_stock_credit(final_consumption))
|
||||
|
||||
# cancel original backdated receipt - should repost A -> B -> C
|
||||
bd_receipt2.cancel()
|
||||
# original amount
|
||||
self.assertEqual(50, _get_stock_credit(final_consumption))
|
||||
|
||||
def test_tie_breaking(self):
|
||||
frappe.flags.dont_execute_stock_reposts = True
|
||||
self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
|
||||
|
||||
item = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
posting_date = "2022-01-01"
|
||||
posting_time = "00:00:01"
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
def ordered_qty_after_transaction():
|
||||
return (
|
||||
frappe.qb.from_(sle)
|
||||
.select("qty_after_transaction")
|
||||
.where((sle.item_code == item) & (sle.warehouse == warehouse) & (sle.is_cancelled == 0))
|
||||
.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
|
||||
.orderby(sle.creation)
|
||||
).run(pluck=True)
|
||||
|
||||
first = make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse=warehouse,
|
||||
qty=10,
|
||||
posting_time=posting_time,
|
||||
posting_date=posting_date,
|
||||
do_not_submit=True,
|
||||
)
|
||||
second = make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse=warehouse,
|
||||
qty=1,
|
||||
posting_date=posting_date,
|
||||
posting_time=posting_time,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
first.submit()
|
||||
second.submit()
|
||||
|
||||
self.assertEqual([10, 11], ordered_qty_after_transaction())
|
||||
|
||||
first.cancel()
|
||||
self.assertEqual([1], ordered_qty_after_transaction())
|
||||
|
||||
backdated = make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse=warehouse,
|
||||
qty=1,
|
||||
posting_date="2021-01-01",
|
||||
posting_time=posting_time,
|
||||
)
|
||||
self.assertEqual([1, 2], ordered_qty_after_transaction())
|
||||
|
||||
backdated.cancel()
|
||||
self.assertEqual([1], ordered_qty_after_transaction())
|
||||
|
||||
|
||||
def create_repack_entry(**args):
|
||||
args = frappe._dict(args)
|
||||
|
@ -10,7 +10,7 @@ from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, cstr, flt, nowdate, nowtime, random_string
|
||||
|
||||
from erpnext.accounts.utils import get_stock_and_account_balance
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
|
||||
@ -31,6 +31,7 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
|
||||
def tearDown(self):
|
||||
frappe.flags.dont_execute_stock_reposts = None
|
||||
frappe.local.future_sle = {}
|
||||
|
||||
def test_reco_for_fifo(self):
|
||||
self._test_reco_sle_gle("FIFO")
|
||||
@ -311,9 +312,8 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
SR4 | Reco | 0 | 6 (posting date: today-1) [backdated]
|
||||
PR3 | PR | 1 | 7 (posting date: today) # can't post future PR
|
||||
"""
|
||||
item_code = "Backdated-Reco-Item"
|
||||
item_code = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
create_item(item_code)
|
||||
|
||||
pr1 = make_purchase_receipt(
|
||||
item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3)
|
||||
@ -395,9 +395,8 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.stock_ledger import NegativeStockError
|
||||
|
||||
item_code = "Backdated-Reco-Item"
|
||||
item_code = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
create_item(item_code)
|
||||
|
||||
pr1 = make_purchase_receipt(
|
||||
item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -2)
|
||||
@ -444,9 +443,8 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.stock_ledger import NegativeStockError
|
||||
|
||||
item_code = "Backdated-Reco-Cancellation-Item"
|
||||
item_code = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
create_item(item_code)
|
||||
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=item_code,
|
||||
@ -487,9 +485,8 @@ class TestStockReconciliation(FrappeTestCase):
|
||||
frappe.flags.dont_execute_stock_reposts = True
|
||||
frappe.db.rollback()
|
||||
|
||||
item_code = "Backdated-Reco-Cancellation-Item"
|
||||
item_code = make_item().name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
create_item(item_code)
|
||||
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), 10)
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import copy
|
||||
import json
|
||||
from typing import Optional
|
||||
from typing import Optional, Set, Tuple
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@ -214,6 +214,7 @@ def repost_future_sle(
|
||||
args = get_items_to_be_repost(voucher_type, voucher_no, doc)
|
||||
|
||||
distinct_item_warehouses = get_distinct_item_warehouse(args, doc)
|
||||
affected_transactions = get_affected_transactions(doc)
|
||||
|
||||
i = get_current_index(doc) or 0
|
||||
while i < len(args):
|
||||
@ -231,6 +232,7 @@ def repost_future_sle(
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
affected_transactions.update(obj.affected_transactions)
|
||||
|
||||
distinct_item_warehouses[
|
||||
(args[i].get("item_code"), args[i].get("warehouse"))
|
||||
@ -250,10 +252,14 @@ def repost_future_sle(
|
||||
i += 1
|
||||
|
||||
if doc and i % 2 == 0:
|
||||
update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
|
||||
update_args_in_repost_item_valuation(
|
||||
doc, i, args, distinct_item_warehouses, affected_transactions
|
||||
)
|
||||
|
||||
if doc and args:
|
||||
update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
|
||||
update_args_in_repost_item_valuation(
|
||||
doc, i, args, distinct_item_warehouses, affected_transactions
|
||||
)
|
||||
|
||||
|
||||
def validate_item_warehouse(args):
|
||||
@ -263,20 +269,22 @@ def validate_item_warehouse(args):
|
||||
frappe.throw(_(validation_msg))
|
||||
|
||||
|
||||
def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses):
|
||||
frappe.db.set_value(
|
||||
doc.doctype,
|
||||
doc.name,
|
||||
def update_args_in_repost_item_valuation(
|
||||
doc, index, args, distinct_item_warehouses, affected_transactions
|
||||
):
|
||||
doc.db_set(
|
||||
{
|
||||
"items_to_be_repost": json.dumps(args, default=str),
|
||||
"distinct_item_and_warehouse": json.dumps(
|
||||
{str(k): v for k, v in distinct_item_warehouses.items()}, default=str
|
||||
),
|
||||
"current_index": index,
|
||||
},
|
||||
"affected_transactions": frappe.as_json(affected_transactions),
|
||||
}
|
||||
)
|
||||
|
||||
frappe.db.commit()
|
||||
if not frappe.flags.in_test:
|
||||
frappe.db.commit()
|
||||
|
||||
frappe.publish_realtime(
|
||||
"item_reposting_progress",
|
||||
@ -313,6 +321,14 @@ def get_distinct_item_warehouse(args=None, doc=None):
|
||||
return distinct_item_warehouses
|
||||
|
||||
|
||||
def get_affected_transactions(doc) -> Set[Tuple[str, str]]:
|
||||
if not doc.affected_transactions:
|
||||
return set()
|
||||
|
||||
transactions = frappe.parse_json(doc.affected_transactions)
|
||||
return {tuple(transaction) for transaction in transactions}
|
||||
|
||||
|
||||
def get_current_index(doc=None):
|
||||
if doc and doc.current_index:
|
||||
return doc.current_index
|
||||
@ -360,6 +376,7 @@ class update_entries_after(object):
|
||||
|
||||
self.new_items_found = False
|
||||
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
|
||||
self.affected_transactions: Set[Tuple[str, str]] = set()
|
||||
|
||||
self.data = frappe._dict()
|
||||
self.initialize_previous_data(self.args)
|
||||
@ -518,6 +535,7 @@ class update_entries_after(object):
|
||||
|
||||
# previous sle data for this warehouse
|
||||
self.wh_data = self.data[sle.warehouse]
|
||||
self.affected_transactions.add((sle.voucher_type, sle.voucher_no))
|
||||
|
||||
if (sle.serial_no and not self.via_landed_cost_voucher) or not cint(self.allow_negative_stock):
|
||||
# validate negative stock for serialized items, fifo valuation
|
||||
|
@ -9224,7 +9224,7 @@ Customer/Lead Name,Name des Kunden / Lead,
|
||||
Unmarked Days,Nicht markierte Tage,
|
||||
Jan,Jan.,
|
||||
Feb,Feb.,
|
||||
Mar,Beschädigen,
|
||||
Mar,Mrz.,
|
||||
Apr,Apr.,
|
||||
Aug,Aug.,
|
||||
Sep,Sep.,
|
||||
|
Can't render this file because it is too large.
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user