Merge branch 'develop' into validate-repost-ledger-settings-for-editable-invoices
This commit is contained in:
commit
5b3eba7bee
@ -1022,6 +1022,7 @@ class PaymentEntry(AccountsController):
|
||||
self.add_bank_gl_entries(gl_entries)
|
||||
self.add_deductions_gl_entries(gl_entries)
|
||||
self.add_tax_gl_entries(gl_entries)
|
||||
add_regional_gl_entries(gl_entries, self)
|
||||
return gl_entries
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
@ -1054,9 +1055,9 @@ class PaymentEntry(AccountsController):
|
||||
item=self,
|
||||
)
|
||||
|
||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||
|
||||
for d in self.get("references"):
|
||||
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
|
||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||
cost_center = self.cost_center
|
||||
if d.reference_doctype == "Sales Invoice" and not cost_center:
|
||||
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
|
||||
@ -1072,11 +1073,9 @@ class PaymentEntry(AccountsController):
|
||||
against_voucher_type = d.reference_doctype
|
||||
against_voucher = d.reference_name
|
||||
|
||||
reverse_dr_or_cr, standalone_note = 0, 0
|
||||
reverse_dr_or_cr = 0
|
||||
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
is_return, return_against = frappe.db.get_value(
|
||||
d.reference_doctype, d.reference_name, ["is_return", "return_against"]
|
||||
)
|
||||
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
|
||||
payable_party_types = get_party_types_from_account_type("Payable")
|
||||
receivable_party_types = get_party_types_from_account_type("Receivable")
|
||||
if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"):
|
||||
@ -1086,7 +1085,7 @@ class PaymentEntry(AccountsController):
|
||||
):
|
||||
reverse_dr_or_cr = 1
|
||||
|
||||
if is_return and not return_against and not reverse_dr_or_cr:
|
||||
if is_return and not reverse_dr_or_cr:
|
||||
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
||||
|
||||
gle.update(
|
||||
@ -1100,6 +1099,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
gl_entries.append(gle)
|
||||
|
||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||
if self.unallocated_amount:
|
||||
exchange_rate = self.get_exchange_rate()
|
||||
base_unallocated_amount = self.unallocated_amount * exchange_rate
|
||||
@ -2622,3 +2622,8 @@ def make_payment_order(source_name, target_doc=None):
|
||||
)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def add_regional_gl_entries(gl_entries, doc):
|
||||
return
|
||||
|
@ -1290,6 +1290,9 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||
|
||||
def test_receive_payment_from_payable_party_type(self):
|
||||
"""
|
||||
Checks GL entries generated while receiving payments from a Payable Party Type.
|
||||
"""
|
||||
pe = create_payment_entry(
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
@ -1301,8 +1304,57 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
)
|
||||
self.voucher_no = pe.name
|
||||
self.expected_gle = [
|
||||
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||
{"account": "Creditors - _TC", "debit": 0.0, "credit": 1000.0},
|
||||
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||
]
|
||||
self.check_gl_entries()
|
||||
|
||||
def test_payment_against_partial_return_invoice(self):
|
||||
"""
|
||||
Checks GL entries generated for partial return invoice payments.
|
||||
"""
|
||||
si = create_sales_invoice(qty=10, rate=10, customer="_Test Customer")
|
||||
credit_note = create_sales_invoice(
|
||||
qty=-4, rate=10, customer="_Test Customer", is_return=1, return_against=si.name
|
||||
)
|
||||
pe = create_payment_entry(
|
||||
party_type="Customer",
|
||||
party="_Test Customer",
|
||||
payment_type="Receive",
|
||||
paid_from="Debtors - _TC",
|
||||
paid_to="_Test Cash - _TC",
|
||||
)
|
||||
pe.set(
|
||||
"references",
|
||||
[
|
||||
{
|
||||
"reference_doctype": "Sales Invoice",
|
||||
"reference_name": si.name,
|
||||
"due_date": si.get("due_date"),
|
||||
"total_amount": si.grand_total,
|
||||
"outstanding_amount": si.outstanding_amount,
|
||||
"allocated_amount": si.outstanding_amount,
|
||||
},
|
||||
{
|
||||
"reference_doctype": "Sales Invoice",
|
||||
"reference_name": credit_note.name,
|
||||
"due_date": credit_note.get("due_date"),
|
||||
"total_amount": credit_note.grand_total,
|
||||
"outstanding_amount": credit_note.outstanding_amount,
|
||||
"allocated_amount": credit_note.outstanding_amount,
|
||||
},
|
||||
],
|
||||
)
|
||||
pe.save()
|
||||
pe.submit()
|
||||
self.assertEqual(pe.total_allocated_amount, 60)
|
||||
self.assertEqual(pe.unallocated_amount, 940)
|
||||
self.voucher_no = pe.name
|
||||
self.expected_gle = [
|
||||
{"account": "Debtors - _TC", "debit": 40.0, "credit": 0.0},
|
||||
{"account": "Debtors - _TC", "debit": 0.0, "credit": 940.0},
|
||||
{"account": "Debtors - _TC", "debit": 0.0, "credit": 100.0},
|
||||
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||
]
|
||||
self.check_gl_entries()
|
||||
|
||||
@ -1316,7 +1368,7 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
gle.credit,
|
||||
)
|
||||
.where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0))
|
||||
.orderby(gle.account)
|
||||
.orderby(gle.account, gle.debit, gle.credit, order=frappe.qb.desc)
|
||||
).run(as_dict=True)
|
||||
for row in range(len(self.expected_gle)):
|
||||
for field in ["account", "debit", "credit"]:
|
||||
|
@ -186,6 +186,7 @@
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -833,7 +834,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-12 13:36:40.160468",
|
||||
"modified": "2023-11-14 18:33:22.585715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
|
@ -158,6 +158,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -917,7 +918,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-13 20:26:18.329983",
|
||||
"modified": "2023-11-14 18:33:48.547297",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -167,6 +167,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -901,7 +902,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-26 12:53:22.404057",
|
||||
"modified": "2023-11-14 18:34:10.479329",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
@ -911,4 +912,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
@ -149,6 +149,11 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"label": __("In Party Currency"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
|
@ -184,6 +184,16 @@ def get_columns(filters):
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
else:
|
||||
columns.append(
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
|
@ -53,6 +53,9 @@ GL_REPOSTING_CHUNK = 100
|
||||
def get_fiscal_year(
|
||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
|
||||
):
|
||||
if isinstance(boolean, str):
|
||||
boolean = frappe.json.loads(boolean)
|
||||
|
||||
fiscal_years = get_fiscal_years(
|
||||
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
|
||||
)
|
||||
|
@ -481,11 +481,10 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval.doc.asset_quantity",
|
||||
"fieldname": "asset_quantity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Asset Quantity",
|
||||
"read_only": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "depr_entry_posting_status",
|
||||
@ -572,7 +571,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-27 17:03:46.629617",
|
||||
"modified": "2023-11-15 17:40:17.315203",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
@ -1,30 +1,21 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Bulk Transaction Log', {
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
|
||||
frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
|
||||
query(frm, 1);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
frappe.ui.form.on("Bulk Transaction Log", {
|
||||
refresh(frm) {
|
||||
frm.add_custom_button(__('Succeeded Entries'), function() {
|
||||
frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Success"});
|
||||
}, __("View"));
|
||||
frm.add_custom_button(__('Failed Entries'), function() {
|
||||
frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"});
|
||||
}, __("View"));
|
||||
if (frm.doc.failed) {
|
||||
frm.add_custom_button(__('Retry Failed Transactions'), function() {
|
||||
frappe.call({
|
||||
method: "erpnext.utilities.bulk_transaction.retry",
|
||||
args: {date: frm.doc.date}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function query(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
||||
args: {
|
||||
log_date: frm.doc.log_date
|
||||
}
|
||||
}).then((r) => {
|
||||
if (r.message === "No Failed Records") {
|
||||
frappe.show_alert(__(r.message), 5);
|
||||
} else {
|
||||
frappe.show_alert(__("Retrying Failed Transactions"), 5);
|
||||
}
|
||||
});
|
||||
}
|
@ -1,31 +1,64 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2021-11-30 13:41:16.343827",
|
||||
"allow_copy": 1,
|
||||
"creation": "2023-11-09 20:14:45.139593",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"log_date",
|
||||
"logger_data"
|
||||
"date",
|
||||
"column_break_bsan",
|
||||
"log_entries",
|
||||
"section_break_mdmv",
|
||||
"succeeded",
|
||||
"column_break_qryp",
|
||||
"failed"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "log_date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Log Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "logger_data",
|
||||
"fieldtype": "Table",
|
||||
"label": "Logger Data",
|
||||
"options": "Bulk Transaction Log Detail"
|
||||
"fieldname": "log_entries",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Log Entries",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bsan",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_mdmv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "succeeded",
|
||||
"fieldtype": "Int",
|
||||
"label": "Succeeded",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qryp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "failed",
|
||||
"fieldtype": "Int",
|
||||
"label": "Failed",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"in_create": 1,
|
||||
"is_virtual": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-03 17:23:02.935325",
|
||||
"modified": "2023-11-11 04:52:49.347376",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Bulk Transaction",
|
||||
"name": "Bulk Transaction Log",
|
||||
@ -47,5 +80,5 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
"title_field": "date"
|
||||
}
|
@ -1,67 +1,112 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.utilities.bulk_transaction import task, update_logger
|
||||
from frappe.query_builder.functions import Count
|
||||
from frappe.utils import cint
|
||||
from pypika import Order
|
||||
|
||||
|
||||
class BulkTransactionLog(Document):
|
||||
pass
|
||||
def db_insert(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def load_from_db(self):
|
||||
log_detail = qb.DocType("Bulk Transaction Log Detail")
|
||||
|
||||
@frappe.whitelist()
|
||||
def retry_failing_transaction(log_date=None):
|
||||
if not log_date:
|
||||
log_date = str(date.today())
|
||||
btp = frappe.qb.DocType("Bulk Transaction Log Detail")
|
||||
data = (
|
||||
frappe.qb.from_(btp)
|
||||
.select(btp.transaction_name, btp.from_doctype, btp.to_doctype)
|
||||
.distinct()
|
||||
.where(btp.retried != 1)
|
||||
.where(btp.transaction_status == "Failed")
|
||||
.where(btp.date == log_date)
|
||||
).run(as_dict=True)
|
||||
has_records = frappe.db.sql(
|
||||
f"select exists (select * from `tabBulk Transaction Log Detail` where date = '{self.name}');"
|
||||
)[0][0]
|
||||
if not has_records:
|
||||
raise frappe.DoesNotExistError
|
||||
|
||||
if data:
|
||||
if len(data) > 10:
|
||||
frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
|
||||
else:
|
||||
job(data, log_date)
|
||||
else:
|
||||
return "No Failed Records"
|
||||
succeeded_logs = (
|
||||
qb.from_(log_detail)
|
||||
.select(Count(log_detail.date).as_("count"))
|
||||
.where((log_detail.date == self.name) & (log_detail.transaction_status == "Success"))
|
||||
.run()
|
||||
)[0][0] or 0
|
||||
failed_logs = (
|
||||
qb.from_(log_detail)
|
||||
.select(Count(log_detail.date).as_("count"))
|
||||
.where((log_detail.date == self.name) & (log_detail.transaction_status == "Failed"))
|
||||
.run()
|
||||
)[0][0] or 0
|
||||
total_logs = succeeded_logs + failed_logs
|
||||
transaction_log = frappe._dict(
|
||||
{
|
||||
"date": self.name,
|
||||
"count": total_logs,
|
||||
"succeeded": succeeded_logs,
|
||||
"failed": failed_logs,
|
||||
}
|
||||
)
|
||||
super(Document, self).__init__(serialize_transaction_log(transaction_log))
|
||||
|
||||
@staticmethod
|
||||
def get_list(args):
|
||||
filter_date = parse_list_filters(args)
|
||||
limit = cint(args.get("page_length")) or 20
|
||||
log_detail = qb.DocType("Bulk Transaction Log Detail")
|
||||
|
||||
def job(data, log_date):
|
||||
for d in data:
|
||||
failed = []
|
||||
try:
|
||||
frappe.db.savepoint("before_creation_of_record")
|
||||
task(d.transaction_name, d.from_doctype, d.to_doctype)
|
||||
except Exception as e:
|
||||
frappe.db.rollback(save_point="before_creation_of_record")
|
||||
failed.append(e)
|
||||
update_logger(
|
||||
d.transaction_name,
|
||||
e,
|
||||
d.from_doctype,
|
||||
d.to_doctype,
|
||||
status="Failed",
|
||||
log_date=log_date,
|
||||
restarted=1,
|
||||
dates_query = (
|
||||
qb.from_(log_detail)
|
||||
.select(log_detail.date)
|
||||
.distinct()
|
||||
.orderby(log_detail.date, order=Order.desc)
|
||||
.limit(limit)
|
||||
)
|
||||
if filter_date:
|
||||
dates_query = dates_query.where(log_detail.date == filter_date)
|
||||
dates = dates_query.run()
|
||||
|
||||
transaction_logs = []
|
||||
if dates:
|
||||
transaction_logs_query = (
|
||||
qb.from_(log_detail)
|
||||
.select(log_detail.date.as_("date"), Count(log_detail.date).as_("count"))
|
||||
.where(log_detail.date.isin(dates))
|
||||
.orderby(log_detail.date, order=Order.desc)
|
||||
.groupby(log_detail.date)
|
||||
.limit(limit)
|
||||
)
|
||||
transaction_logs = transaction_logs_query.run(as_dict=True)
|
||||
|
||||
if not failed:
|
||||
update_logger(
|
||||
d.transaction_name,
|
||||
None,
|
||||
d.from_doctype,
|
||||
d.to_doctype,
|
||||
status="Success",
|
||||
log_date=log_date,
|
||||
restarted=1,
|
||||
)
|
||||
return [serialize_transaction_log(x) for x in transaction_logs]
|
||||
|
||||
@staticmethod
|
||||
def get_count(args):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_stats(args):
|
||||
pass
|
||||
|
||||
def db_update(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
pass
|
||||
|
||||
|
||||
def serialize_transaction_log(data):
|
||||
return frappe._dict(
|
||||
name=data.date,
|
||||
date=data.date,
|
||||
log_entries=data.count,
|
||||
succeeded=data.succeeded,
|
||||
failed=data.failed,
|
||||
)
|
||||
|
||||
|
||||
def parse_list_filters(args):
|
||||
# parse date filter
|
||||
filter_date = None
|
||||
for fil in args.get("filters"):
|
||||
if isinstance(fil, list):
|
||||
for elem in fil:
|
||||
if elem == "date":
|
||||
filter_date = fil[3]
|
||||
return filter_date
|
||||
|
@ -1,79 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.utilities.bulk_transaction import transaction_processing
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBulkTransactionLog(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_company()
|
||||
create_customer()
|
||||
create_item()
|
||||
|
||||
def test_entry_in_log(self):
|
||||
so_name = create_so()
|
||||
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
|
||||
doc = frappe.get_doc("Bulk Transaction Log", str(date.today()))
|
||||
for d in doc.get("logger_data"):
|
||||
if d.transaction_name == so_name:
|
||||
self.assertEqual(d.transaction_name, so_name)
|
||||
self.assertEqual(d.transaction_status, "Success")
|
||||
self.assertEqual(d.from_doctype, "Sales Order")
|
||||
self.assertEqual(d.to_doctype, "Sales Invoice")
|
||||
self.assertEqual(d.retried, 0)
|
||||
|
||||
|
||||
def create_company():
|
||||
if not frappe.db.exists("Company", "_Test Company"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": "_Test Company",
|
||||
"country": "India",
|
||||
"default_currency": "INR",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_customer():
|
||||
if not frappe.db.exists("Customer", "Bulk Customer"):
|
||||
frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert()
|
||||
|
||||
|
||||
def create_item():
|
||||
if not frappe.db.exists("Item", "MK"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item",
|
||||
"item_code": "MK",
|
||||
"item_name": "Milk",
|
||||
"description": "Milk",
|
||||
"item_group": "Products",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_so(intent=None):
|
||||
so = frappe.new_doc("Sales Order")
|
||||
so.customer = "Bulk Customer"
|
||||
so.company = "_Test Company"
|
||||
so.transaction_date = date.today()
|
||||
|
||||
so.set_warehouse = "Finished Goods - _TC"
|
||||
so.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "MK",
|
||||
"delivery_date": date.today(),
|
||||
"qty": 10,
|
||||
"rate": 80,
|
||||
},
|
||||
)
|
||||
so.insert()
|
||||
so.submit()
|
||||
return so.name
|
||||
class TestBulkTransactionLog(FrappeTestCase):
|
||||
pass
|
||||
|
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Bulk Transaction Log Detail", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
@ -6,12 +6,12 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"from_doctype",
|
||||
"transaction_name",
|
||||
"date",
|
||||
"time",
|
||||
"transaction_status",
|
||||
"error_description",
|
||||
"from_doctype",
|
||||
"to_doctype",
|
||||
"retried"
|
||||
],
|
||||
@ -20,8 +20,11 @@
|
||||
"fieldname": "transaction_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Name",
|
||||
"options": "from_doctype"
|
||||
"options": "from_doctype",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_status",
|
||||
@ -39,9 +42,11 @@
|
||||
{
|
||||
"fieldname": "from_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "From Doctype",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_doctype",
|
||||
@ -54,8 +59,10 @@
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date ",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time",
|
||||
@ -66,19 +73,33 @@
|
||||
{
|
||||
"fieldname": "retried",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Retried",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-03 19:57:31.650359",
|
||||
"modified": "2023-11-10 11:44:10.758342",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Bulk Transaction",
|
||||
"name": "Bulk Transaction Log Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
|
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBulkTransactionLogDetail(FrappeTestCase):
|
||||
pass
|
@ -189,6 +189,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -916,7 +917,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-06 11:00:53.596417",
|
||||
"modified": "2023-11-14 18:34:27.267382",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
@ -87,6 +87,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -260,13 +261,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-24 17:26:46.276934",
|
||||
"modified": "2023-11-14 18:34:48.327224",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -133,6 +133,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -559,13 +560,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-19 12:36:26.913211",
|
||||
"modified": "2023-11-14 18:35:03.435817",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -626,6 +626,18 @@ class SubcontractingController(StockController):
|
||||
(row.item_code, row.get(self.subcontract_data.order_field))
|
||||
] -= row.qty
|
||||
|
||||
def __set_rate_for_serial_and_batch_bundle(self):
|
||||
if self.doctype != "Subcontracting Receipt":
|
||||
return
|
||||
|
||||
for row in self.get(self.raw_material_table):
|
||||
if not row.get("serial_and_batch_bundle"):
|
||||
continue
|
||||
|
||||
row.rate = frappe.get_cached_value(
|
||||
"Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"
|
||||
)
|
||||
|
||||
def __modify_serial_and_batch_bundle(self):
|
||||
if self.is_new():
|
||||
return
|
||||
@ -681,6 +693,7 @@ class SubcontractingController(StockController):
|
||||
self.__remove_changed_rows()
|
||||
self.__set_supplied_items()
|
||||
self.__modify_serial_and_batch_bundle()
|
||||
self.__set_rate_for_serial_and_batch_bundle()
|
||||
|
||||
def __validate_batch_no(self, row, key):
|
||||
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
||||
|
@ -103,6 +103,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -165,7 +166,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-30 16:39:09.775720",
|
||||
"modified": "2023-11-14 18:35:30.887278",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity Item",
|
||||
@ -173,5 +174,6 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -15,7 +15,7 @@ frappe.ui.form.on("BOM Creator", {
|
||||
|| frappe.bom_configurator.bom_configurator !== frm.doc.name)) {
|
||||
frm.trigger("build_tree");
|
||||
}
|
||||
} else {
|
||||
} else if (!frm.doc.items?.length ) {
|
||||
let $parent = $(frm.fields_dict["bom_creator"].wrapper);
|
||||
$parent.empty();
|
||||
frm.trigger("make_new_entry");
|
||||
|
@ -6,7 +6,7 @@ from collections import OrderedDict
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_item_rate
|
||||
|
||||
@ -91,11 +91,19 @@ class BOMCreator(Document):
|
||||
parent_reference = {row.idx: row.name for row in self.items}
|
||||
|
||||
for row in self.items:
|
||||
if row.fg_reference_id:
|
||||
ref_id = ""
|
||||
|
||||
if row.parent_row_no:
|
||||
ref_id = parent_reference.get(cint(row.parent_row_no))
|
||||
|
||||
# Check whether the reference id of the FG Item has correct or not
|
||||
if row.fg_reference_id and row.fg_reference_id == ref_id:
|
||||
continue
|
||||
|
||||
if row.parent_row_no:
|
||||
row.fg_reference_id = parent_reference.get(row.parent_row_no)
|
||||
row.fg_reference_id = ref_id
|
||||
elif row.fg_item == self.item_code:
|
||||
row.fg_reference_id = self.name
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_boms(self):
|
||||
|
@ -215,7 +215,6 @@
|
||||
"fieldname": "parent_row_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Parent Row No",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@ -231,7 +230,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-07 11:52:30.492233",
|
||||
"modified": "2023-11-16 13:34:06.321061",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Creator Item",
|
||||
|
@ -85,6 +85,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -169,7 +170,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-27 13:42:23.305455",
|
||||
"modified": "2023-11-14 18:35:40.856895",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Explosion Item",
|
||||
|
@ -111,6 +111,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -289,7 +290,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-07-28 10:20:51.559010",
|
||||
"modified": "2023-11-14 18:35:51.378513",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Item",
|
||||
|
@ -139,7 +139,6 @@ function get_filters() {
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
|
||||
},
|
||||
@ -148,7 +147,6 @@ function get_filters() {
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
|
||||
},
|
||||
@ -197,5 +195,13 @@ function get_filters() {
|
||||
}
|
||||
]
|
||||
|
||||
// Dynamically set 'default' values for fiscal year filters
|
||||
let fy_filters = filters.filter(x=>{return ["from_fiscal_year", "to_fiscal_year"].includes(x.fieldname);})
|
||||
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, true);
|
||||
if (fiscal_year) {
|
||||
let fy = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, false);
|
||||
fy_filters.forEach(x=>{x.default = fy;})
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
@ -404,7 +404,7 @@ $.extend(erpnext.utils, {
|
||||
});
|
||||
},
|
||||
|
||||
get_fiscal_year: function(date, with_dates=false) {
|
||||
get_fiscal_year: function(date, with_dates=false, boolean=false) {
|
||||
if(!date) {
|
||||
date = frappe.datetime.get_today();
|
||||
}
|
||||
@ -413,7 +413,8 @@ $.extend(erpnext.utils, {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.utils.get_fiscal_year",
|
||||
args: {
|
||||
date: date
|
||||
date: date,
|
||||
boolean: boolean
|
||||
},
|
||||
async: false,
|
||||
callback: function(r) {
|
||||
|
@ -135,6 +135,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -666,7 +667,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-26 13:42:11.410294",
|
||||
"modified": "2023-11-14 18:24:24.619832",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation Item",
|
||||
@ -676,4 +677,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
@ -767,8 +767,11 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
|
||||
if target.company_address:
|
||||
target.update(get_fetch_values("Delivery Note", "company_address", target.company_address))
|
||||
|
||||
# set target items names to ensure proper linking with packed_items
|
||||
target.set_new_name()
|
||||
# if invoked in bulk creation, validations are ignored and thus this method is nerver invoked
|
||||
if frappe.flags.bulk_transaction:
|
||||
# set target items names to ensure proper linking with packed_items
|
||||
target.set_new_name()
|
||||
|
||||
make_packing_list(target)
|
||||
|
||||
def condition(doc):
|
||||
|
@ -68,7 +68,6 @@
|
||||
"total_weight",
|
||||
"column_break_21",
|
||||
"weight_uom",
|
||||
"accounting_dimensions_section",
|
||||
"warehouse_and_reference",
|
||||
"warehouse",
|
||||
"target_warehouse",
|
||||
@ -177,6 +176,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -890,18 +890,12 @@
|
||||
"label": "Production Plan Qty",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-17 18:18:26.475259",
|
||||
"modified": "2023-11-14 18:37:12.787893",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
|
@ -103,15 +103,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Closing Stock Balance",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "include_uom",
|
||||
"fieldtype": "Link",
|
||||
@ -145,4 +136,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +168,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -893,7 +894,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-16 16:18:18.013379",
|
||||
"modified": "2023-11-14 18:37:38.638144",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
@ -110,6 +110,7 @@
|
||||
"width": "250px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image",
|
||||
@ -478,7 +479,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-27 15:53:41.444236",
|
||||
"modified": "2023-11-14 18:37:59.599115",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request Item",
|
||||
|
@ -192,6 +192,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -1090,7 +1091,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-30 17:32:24.560337",
|
||||
"modified": "2023-11-14 18:38:15.251994",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
@ -1604,6 +1604,9 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]:
|
||||
)
|
||||
|
||||
for key, val in kwargs.items():
|
||||
if not val:
|
||||
continue
|
||||
|
||||
if key in ["get_subcontracted_item"]:
|
||||
continue
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"is_adjustment_entry",
|
||||
"auto_created_serial_and_batch_bundle",
|
||||
"column_break_6",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
@ -340,6 +341,13 @@
|
||||
"fieldname": "is_adjustment_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Adjustment Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "serial_and_batch_bundle",
|
||||
"fieldname": "auto_created_serial_and_batch_bundle",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Created Serial and Batch Bundle"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@ -348,7 +356,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-23 18:07:42.063615",
|
||||
"modified": "2023-11-14 16:47:39.791967",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Ledger Entry",
|
||||
|
@ -129,7 +129,9 @@ class SerialBatchBundle:
|
||||
frappe.throw(_(error_msg))
|
||||
|
||||
def set_serial_and_batch_bundle(self, sn_doc):
|
||||
self.sle.db_set("serial_and_batch_bundle", sn_doc.name)
|
||||
self.sle.db_set(
|
||||
{"serial_and_batch_bundle": sn_doc.name, "auto_created_serial_and_batch_bundle": 1}
|
||||
)
|
||||
|
||||
if sn_doc.is_rejected:
|
||||
frappe.db.set_value(
|
||||
@ -143,6 +145,12 @@ class SerialBatchBundle:
|
||||
@property
|
||||
def child_doctype(self):
|
||||
child_doctype = self.sle.voucher_type + " Item"
|
||||
|
||||
if (
|
||||
self.sle.voucher_type == "Subcontracting Receipt" and self.sle.dependant_sle_voucher_detail_no
|
||||
):
|
||||
child_doctype = "Subcontracting Receipt Supplied Item"
|
||||
|
||||
if self.sle.voucher_type == "Stock Entry":
|
||||
child_doctype = "Stock Entry Detail"
|
||||
|
||||
|
@ -766,7 +766,9 @@ class update_entries_after(object):
|
||||
sle.doctype = "Stock Ledger Entry"
|
||||
frappe.get_doc(sle).db_update()
|
||||
|
||||
if not self.args.get("sle_id"):
|
||||
if not self.args.get("sle_id") or (
|
||||
sle.serial_and_batch_bundle and sle.auto_created_serial_and_batch_bundle
|
||||
):
|
||||
self.update_outgoing_rate_on_transaction(sle)
|
||||
|
||||
def reset_actual_qty_for_stock_reco(self, sle):
|
||||
|
@ -112,6 +112,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -337,7 +338,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-01-20 23:25:45.363281",
|
||||
"modified": "2023-11-14 18:38:37.640677",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Order Item",
|
||||
|
@ -13,6 +13,16 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
||||
frm.trigger('set_queries');
|
||||
},
|
||||
|
||||
on_submit(frm) {
|
||||
frm.events.refresh_serial_batch_bundle_field(frm);
|
||||
},
|
||||
|
||||
refresh_serial_batch_bundle_field(frm) {
|
||||
frappe.route_hooks.after_submit = (frm_obj) => {
|
||||
frm_obj.reload_doc();
|
||||
}
|
||||
},
|
||||
|
||||
refresh: (frm) => {
|
||||
if (frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(__('Stock Ledger'), () => {
|
||||
|
@ -148,6 +148,8 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
if (
|
||||
frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on")
|
||||
== "BOM"
|
||||
and self.supplied_items
|
||||
and not any(item.serial_and_batch_bundle for item in self.supplied_items)
|
||||
):
|
||||
self.supplied_items = []
|
||||
|
||||
|
@ -6,7 +6,7 @@ import copy
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, cint, cstr, flt, today
|
||||
from frappe.utils import add_days, cint, cstr, flt, nowtime, today
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
@ -26,6 +26,10 @@ from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
@ -507,6 +511,162 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
||||
self.assertNotEqual(scr.supplied_items[0].rate, prev_cost)
|
||||
self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate)
|
||||
|
||||
def test_subcontracting_receipt_for_batch_raw_materials_without_material_transfer(self):
|
||||
set_backflush_based_on("BOM")
|
||||
|
||||
fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
|
||||
rm_item1 = make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BNGS-.####",
|
||||
}
|
||||
).name
|
||||
|
||||
bom = make_bom(item=fg_item, raw_materials=[rm_item1])
|
||||
|
||||
rm_batch_no = None
|
||||
for row in bom.items:
|
||||
se = make_stock_entry(
|
||||
item_code=row.item_code,
|
||||
qty=1,
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
rate=300,
|
||||
)
|
||||
|
||||
se.reload()
|
||||
rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 1",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"fg_item": fg_item,
|
||||
"fg_item_qty": 1,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
scr = make_subcontracting_receipt(sco.name)
|
||||
scr.save()
|
||||
scr.reload()
|
||||
|
||||
bundle_doc = make_serial_batch_bundle(
|
||||
{
|
||||
"item_code": scr.supplied_items[0].rm_item_code,
|
||||
"warehouse": "_Test Warehouse 1 - _TC",
|
||||
"voucher_type": "Subcontracting Receipt",
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
"qty": -1,
|
||||
"batches": frappe._dict({rm_batch_no: 1}),
|
||||
"type_of_transaction": "Outward",
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
|
||||
scr.supplied_items[0].serial_and_batch_bundle = bundle_doc.name
|
||||
scr.submit()
|
||||
scr.reload()
|
||||
|
||||
batch_no = get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle)
|
||||
self.assertEqual(batch_no, rm_batch_no)
|
||||
self.assertEqual(scr.items[0].rm_cost_per_qty, 300)
|
||||
self.assertEqual(scr.items[0].service_cost_per_qty, 100)
|
||||
|
||||
def test_subcontracting_receipt_valuation_with_auto_created_serial_batch_bundle(self):
|
||||
set_backflush_based_on("BOM")
|
||||
|
||||
fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
|
||||
rm_item1 = make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BNGS-.####",
|
||||
}
|
||||
).name
|
||||
|
||||
rm_item2 = make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"has_serial_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BNGS-.####",
|
||||
"serial_no_series": "BNSS-.####",
|
||||
}
|
||||
).name
|
||||
|
||||
rm_item3 = make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "BSSSS-.####",
|
||||
}
|
||||
).name
|
||||
|
||||
bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3])
|
||||
|
||||
rm_batch_no = None
|
||||
for row in bom.items:
|
||||
make_stock_entry(
|
||||
item_code=row.item_code,
|
||||
qty=1,
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
rate=300,
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=row.item_code,
|
||||
qty=1,
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
rate=400,
|
||||
)
|
||||
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 1",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"fg_item": fg_item,
|
||||
"fg_item_qty": 1,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1
|
||||
)
|
||||
scr = make_subcontracting_receipt(sco.name)
|
||||
scr.save()
|
||||
for row in scr.supplied_items:
|
||||
self.assertNotEqual(row.rate, 300.00)
|
||||
self.assertFalse(row.serial_and_batch_bundle)
|
||||
|
||||
scr.submit()
|
||||
scr.reload()
|
||||
|
||||
for row in scr.supplied_items:
|
||||
self.assertEqual(row.rate, 300.00)
|
||||
self.assertTrue(row.serial_and_batch_bundle)
|
||||
auto_created_serial_batch = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_no": scr.name, "voucher_detail_no": row.name},
|
||||
"auto_created_serial_and_batch_bundle",
|
||||
)
|
||||
|
||||
self.assertTrue(auto_created_serial_batch)
|
||||
|
||||
self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
|
||||
self.assertEqual(scr.items[0].service_cost_per_qty, 100)
|
||||
frappe.db.set_single_value(
|
||||
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
|
||||
)
|
||||
|
||||
def test_subcontracting_receipt_raw_material_rate(self):
|
||||
# Step - 1: Set Backflush Based On as "BOM"
|
||||
set_backflush_based_on("BOM")
|
||||
|
@ -109,6 +109,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@ -521,7 +522,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-03 17:04:21.214316",
|
||||
"modified": "2023-11-14 18:38:26.459669",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Item",
|
||||
|
@ -3,6 +3,7 @@ from datetime import date, datetime
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import get_link_to_form, today
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -28,6 +29,48 @@ def transaction_processing(data, from_doctype, to_doctype):
|
||||
job(deserialized_data, from_doctype, to_doctype)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def retry(date: str | None):
|
||||
if date:
|
||||
failed_docs = frappe.db.get_all(
|
||||
"Bulk Transaction Log Detail",
|
||||
filters={"date": date, "transaction_status": "Failed", "retried": 0},
|
||||
fields=["name", "transaction_name", "from_doctype", "to_doctype"],
|
||||
)
|
||||
if not failed_docs:
|
||||
frappe.msgprint(_("There are no Failed transactions"))
|
||||
else:
|
||||
job = frappe.enqueue(
|
||||
retry_failed_transactions,
|
||||
failed_docs=failed_docs,
|
||||
)
|
||||
frappe.msgprint(
|
||||
_("Job: {0} has been triggered for processing failed transactions").format(
|
||||
get_link_to_form("RQ Job", job.id)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def retry_failed_transactions(failed_docs: list | None):
|
||||
if failed_docs:
|
||||
for log in failed_docs:
|
||||
try:
|
||||
frappe.db.savepoint("before_creation_state")
|
||||
task(log.transaction_name, log.from_doctype, log.to_doctype)
|
||||
except Exception as e:
|
||||
frappe.db.rollback(save_point="before_creation_state")
|
||||
update_log(log.name, "Failed", 1, str(frappe.get_traceback()))
|
||||
else:
|
||||
update_log(log.name, "Success", 1)
|
||||
|
||||
|
||||
def update_log(log_name, status, retried, err=None):
|
||||
frappe.db.set_value("Bulk Transaction Log Detail", log_name, "transaction_status", status)
|
||||
frappe.db.set_value("Bulk Transaction Log Detail", log_name, "retried", retried)
|
||||
if err:
|
||||
frappe.db.set_value("Bulk Transaction Log Detail", log_name, "error_description", err)
|
||||
|
||||
|
||||
def job(deserialized_data, from_doctype, to_doctype):
|
||||
fail_count = 0
|
||||
for d in deserialized_data:
|
||||
@ -38,7 +81,7 @@ def job(deserialized_data, from_doctype, to_doctype):
|
||||
except Exception as e:
|
||||
frappe.db.rollback(save_point="before_creation_state")
|
||||
fail_count += 1
|
||||
update_logger(
|
||||
create_log(
|
||||
doc_name,
|
||||
str(frappe.get_traceback()),
|
||||
from_doctype,
|
||||
@ -47,7 +90,7 @@ def job(deserialized_data, from_doctype, to_doctype):
|
||||
log_date=str(date.today()),
|
||||
)
|
||||
else:
|
||||
update_logger(
|
||||
create_log(
|
||||
doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today())
|
||||
)
|
||||
|
||||
@ -98,6 +141,7 @@ def task(doc_name, from_doctype, to_doctype):
|
||||
},
|
||||
"Purchase Receipt": {"Purchase Invoice": purchase_receipt.make_purchase_invoice},
|
||||
}
|
||||
frappe.flags.bulk_transaction = True
|
||||
if to_doctype in ["Payment Entry"]:
|
||||
obj = mapper[from_doctype][to_doctype](from_doctype, doc_name)
|
||||
else:
|
||||
@ -106,47 +150,21 @@ def task(doc_name, from_doctype, to_doctype):
|
||||
obj.flags.ignore_validate = True
|
||||
obj.set_title_field()
|
||||
obj.insert(ignore_mandatory=True)
|
||||
del frappe.flags.bulk_transaction
|
||||
|
||||
|
||||
def check_logger_doc_exists(log_date):
|
||||
return frappe.db.exists("Bulk Transaction Log", log_date)
|
||||
|
||||
|
||||
def get_logger_doc(log_date):
|
||||
return frappe.get_doc("Bulk Transaction Log", log_date)
|
||||
|
||||
|
||||
def create_logger_doc():
|
||||
log_doc = frappe.new_doc("Bulk Transaction Log")
|
||||
log_doc.set_new_name(set_name=str(date.today()))
|
||||
log_doc.log_date = date.today()
|
||||
|
||||
return log_doc
|
||||
|
||||
|
||||
def append_data_to_logger(log_doc, doc_name, error, from_doctype, to_doctype, status, restarted):
|
||||
row = log_doc.append("logger_data", {})
|
||||
row.transaction_name = doc_name
|
||||
row.date = date.today()
|
||||
def create_log(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0):
|
||||
transaction_log = frappe.new_doc("Bulk Transaction Log Detail")
|
||||
transaction_log.transaction_name = doc_name
|
||||
transaction_log.date = today()
|
||||
now = datetime.now()
|
||||
row.time = now.strftime("%H:%M:%S")
|
||||
row.transaction_status = status
|
||||
row.error_description = str(error)
|
||||
row.from_doctype = from_doctype
|
||||
row.to_doctype = to_doctype
|
||||
row.retried = restarted
|
||||
|
||||
|
||||
def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0):
|
||||
if not check_logger_doc_exists(log_date):
|
||||
log_doc = create_logger_doc()
|
||||
append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted)
|
||||
log_doc.insert()
|
||||
else:
|
||||
log_doc = get_logger_doc(log_date)
|
||||
if record_exists(log_doc, doc_name, status):
|
||||
append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted)
|
||||
log_doc.save()
|
||||
transaction_log.time = now.strftime("%H:%M:%S")
|
||||
transaction_log.transaction_status = status
|
||||
transaction_log.error_description = str(e)
|
||||
transaction_log.from_doctype = from_doctype
|
||||
transaction_log.to_doctype = to_doctype
|
||||
transaction_log.retried = restarted
|
||||
transaction_log.save()
|
||||
|
||||
|
||||
def show_job_status(fail_count, deserialized_data_count, to_doctype):
|
||||
@ -176,23 +194,3 @@ def show_job_status(fail_count, deserialized_data_count, to_doctype):
|
||||
title="Failed",
|
||||
indicator="red",
|
||||
)
|
||||
|
||||
|
||||
def record_exists(log_doc, doc_name, status):
|
||||
record = mark_retrired_transaction(log_doc, doc_name)
|
||||
if record and status == "Failed":
|
||||
return False
|
||||
elif record and status == "Success":
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def mark_retrired_transaction(log_doc, doc_name):
|
||||
record = 0
|
||||
for d in log_doc.get("logger_data"):
|
||||
if d.transaction_name == doc_name and d.transaction_status == "Failed":
|
||||
frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1)
|
||||
record = record + 1
|
||||
|
||||
return record
|
||||
|
Loading…
x
Reference in New Issue
Block a user