mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 04:39:11 +00:00
Merge pull request #42449 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -267,6 +267,7 @@ class PaymentReconciliation(Document):
|
|||||||
conditions.append(doc.docstatus == 1)
|
conditions.append(doc.docstatus == 1)
|
||||||
conditions.append(doc[frappe.scrub(self.party_type)] == self.party)
|
conditions.append(doc[frappe.scrub(self.party_type)] == self.party)
|
||||||
conditions.append(doc.is_return == 1)
|
conditions.append(doc.is_return == 1)
|
||||||
|
conditions.append(doc.outstanding_amount != 0)
|
||||||
|
|
||||||
if self.payment_name:
|
if self.payment_name:
|
||||||
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
||||||
|
|||||||
@@ -1805,6 +1805,46 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(pl_entries, expected_ple)
|
self.assertEqual(pl_entries, expected_ple)
|
||||||
|
|
||||||
|
def test_cr_note_payment_limit_filter(self):
|
||||||
|
transaction_date = nowdate()
|
||||||
|
amount = 100
|
||||||
|
|
||||||
|
for _ in range(6):
|
||||||
|
self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
|
||||||
|
cr_note = self.create_sales_invoice(
|
||||||
|
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
|
||||||
|
)
|
||||||
|
cr_note.is_return = 1
|
||||||
|
cr_note = cr_note.save().submit()
|
||||||
|
|
||||||
|
pr = self.create_payment_reconciliation()
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
self.assertEqual(len(pr.invoices), 6)
|
||||||
|
self.assertEqual(len(pr.payments), 6)
|
||||||
|
invoices = [x.as_dict() for x in pr.get("invoices")]
|
||||||
|
payments = [x.as_dict() for x in pr.get("payments")]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
self.assertEqual(pr.get("invoices"), [])
|
||||||
|
self.assertEqual(pr.get("payments"), [])
|
||||||
|
|
||||||
|
self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
|
||||||
|
cr_note = self.create_sales_invoice(
|
||||||
|
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
|
||||||
|
)
|
||||||
|
cr_note.is_return = 1
|
||||||
|
cr_note = cr_note.save().submit()
|
||||||
|
|
||||||
|
# Limit should not affect in fetching the unallocated cr_note
|
||||||
|
pr.invoice_limit = 5
|
||||||
|
pr.payment_limit = 5
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
self.assertEqual(len(pr.invoices), 1)
|
||||||
|
self.assertEqual(len(pr.payments), 1)
|
||||||
|
|
||||||
|
|
||||||
def make_customer(customer_name, currency=None):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
|||||||
@@ -144,6 +144,7 @@
|
|||||||
"fieldname": "grand_total",
|
"fieldname": "grand_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
|
"non_negative": 1,
|
||||||
"options": "currency"
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -395,7 +396,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-27 09:51:42.277638",
|
"modified": "2024-06-20 13:54:55.245774",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
@@ -433,4 +434,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_inv
|
|||||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
from erpnext.selling.page.point_of_sale.point_of_sale import get_items
|
from erpnext.selling.page.point_of_sale.point_of_sale import get_items
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||||
|
get_batch_from_bundle,
|
||||||
|
)
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
|
||||||
@@ -179,6 +183,94 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
accounting_dimension_department.save()
|
accounting_dimension_department.save()
|
||||||
disable_dimension()
|
disable_dimension()
|
||||||
|
|
||||||
|
def test_merging_into_sales_invoice_for_batched_item(self):
|
||||||
|
frappe.flags.print_message = False
|
||||||
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
|
||||||
|
init_user_and_profile,
|
||||||
|
)
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||||
|
consolidate_pos_invoices,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
item_doc = make_item(
|
||||||
|
"_Test Item With Batch FOR POS Merge Test",
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "BATCH-PM-POS-MERGE-.####",
|
||||||
|
"create_new_batch": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
item_code = item_doc.name
|
||||||
|
se = make_stock_entry(
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
basic_rate=100,
|
||||||
|
use_serial_batch_fields=0,
|
||||||
|
)
|
||||||
|
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
|
|
||||||
|
pos_inv = create_pos_invoice(
|
||||||
|
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
|
||||||
|
)
|
||||||
|
pos_inv2 = create_pos_invoice(
|
||||||
|
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
||||||
|
self.assertEqual(batch_qty, 10)
|
||||||
|
|
||||||
|
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||||
|
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||||
|
|
||||||
|
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||||
|
pcv_doc.submit()
|
||||||
|
|
||||||
|
piv_merge = frappe.db.get_value("POS Invoice Merge Log", {"pos_closing_entry": pcv_doc.name}, "name")
|
||||||
|
|
||||||
|
self.assertTrue(piv_merge)
|
||||||
|
piv_merge_doc = frappe.get_doc("POS Invoice Merge Log", piv_merge)
|
||||||
|
self.assertTrue(piv_merge_doc.pos_invoices[0].pos_invoice)
|
||||||
|
self.assertTrue(piv_merge_doc.pos_invoices[1].pos_invoice)
|
||||||
|
|
||||||
|
pos_inv.load_from_db()
|
||||||
|
self.assertTrue(pos_inv.consolidated_invoice)
|
||||||
|
pos_inv2.load_from_db()
|
||||||
|
self.assertTrue(pos_inv2.consolidated_invoice)
|
||||||
|
|
||||||
|
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
||||||
|
self.assertEqual(batch_qty, 0.0)
|
||||||
|
|
||||||
|
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||||
|
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||||
|
|
||||||
|
frappe.flags.print_message = True
|
||||||
|
|
||||||
|
pcv_doc.reload()
|
||||||
|
pcv_doc.cancel()
|
||||||
|
|
||||||
|
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
||||||
|
self.assertEqual(batch_qty, 10)
|
||||||
|
|
||||||
|
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||||
|
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||||
|
|
||||||
|
pos_inv.reload()
|
||||||
|
pos_inv2.reload()
|
||||||
|
|
||||||
|
pos_inv.cancel()
|
||||||
|
pos_inv2.cancel()
|
||||||
|
|
||||||
|
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||||
|
self.assertEqual(batch_qty_with_pos, 10.0)
|
||||||
|
|
||||||
|
|
||||||
def init_user_and_profile(**args):
|
def init_user_and_profile(**args):
|
||||||
user = "test@example.com"
|
user = "test@example.com"
|
||||||
|
|||||||
@@ -229,7 +229,9 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.check_phone_payments()
|
self.check_phone_payments()
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
||||||
self.make_bundle_for_sales_purchase_return()
|
self.make_bundle_for_sales_purchase_return()
|
||||||
self.submit_serial_batch_bundle()
|
for table_name in ["items", "packed_items"]:
|
||||||
|
self.make_bundle_using_old_serial_batch_fields(table_name)
|
||||||
|
self.submit_serial_batch_bundle(table_name)
|
||||||
|
|
||||||
if self.coupon_code:
|
if self.coupon_code:
|
||||||
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
|
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
|
||||||
@@ -283,10 +285,11 @@ class POSInvoice(SalesInvoice):
|
|||||||
{"is_cancelled": 1, "voucher_no": ""},
|
{"is_cancelled": 1, "voucher_no": ""},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle).cancel()
|
||||||
row.db_set("serial_and_batch_bundle", None)
|
row.db_set("serial_and_batch_bundle", None)
|
||||||
|
|
||||||
def submit_serial_batch_bundle(self):
|
def submit_serial_batch_bundle(self, table_name):
|
||||||
for item in self.items:
|
for item in self.get(table_name):
|
||||||
if item.serial_and_batch_bundle:
|
if item.serial_and_batch_bundle:
|
||||||
doc = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
|
doc = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
|
||||||
|
|
||||||
@@ -355,10 +358,16 @@ class POSInvoice(SalesInvoice):
|
|||||||
error_msg = []
|
error_msg = []
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
error_msg = ""
|
error_msg = ""
|
||||||
if d.get("has_serial_no") and not d.serial_and_batch_bundle:
|
if d.get("has_serial_no") and (
|
||||||
|
(not d.use_serial_batch_fields and not d.serial_and_batch_bundle)
|
||||||
|
or (d.use_serial_batch_fields and not d.serial_no)
|
||||||
|
):
|
||||||
error_msg = f"Row #{d.idx}: Please select Serial No. for item {bold(d.item_code)}"
|
error_msg = f"Row #{d.idx}: Please select Serial No. for item {bold(d.item_code)}"
|
||||||
|
|
||||||
elif d.get("has_batch_no") and not d.serial_and_batch_bundle:
|
elif d.get("has_batch_no") and (
|
||||||
|
(not d.use_serial_batch_fields and not d.serial_and_batch_bundle)
|
||||||
|
or (d.use_serial_batch_fields and not d.batch_no)
|
||||||
|
):
|
||||||
error_msg = f"Row #{d.idx}: Please select Batch No. for item {bold(d.item_code)}"
|
error_msg = f"Row #{d.idx}: Please select Batch No. for item {bold(d.item_code)}"
|
||||||
|
|
||||||
if error_msg:
|
if error_msg:
|
||||||
|
|||||||
@@ -780,8 +780,6 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
pos_inv1.submit()
|
pos_inv1.submit()
|
||||||
pos_inv1.reload()
|
pos_inv1.reload()
|
||||||
|
|
||||||
self.assertFalse(pos_inv1.items[0].serial_and_batch_bundle)
|
|
||||||
|
|
||||||
batches = get_auto_batch_nos(
|
batches = get_auto_batch_nos(
|
||||||
frappe._dict({"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"})
|
frappe._dict({"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"})
|
||||||
)
|
)
|
||||||
@@ -957,7 +955,7 @@ def create_pos_invoice(**args):
|
|||||||
pos_inv.set_missing_values()
|
pos_inv.set_missing_values()
|
||||||
|
|
||||||
bundle_id = None
|
bundle_id = None
|
||||||
if args.get("batch_no") or args.get("serial_no"):
|
if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
|
||||||
type_of_transaction = args.type_of_transaction or "Outward"
|
type_of_transaction = args.type_of_transaction or "Outward"
|
||||||
|
|
||||||
if pos_inv.is_return:
|
if pos_inv.is_return:
|
||||||
@@ -998,6 +996,9 @@ def create_pos_invoice(**args):
|
|||||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"serial_and_batch_bundle": bundle_id,
|
"serial_and_batch_bundle": bundle_id,
|
||||||
|
"use_serial_batch_fields": args.use_serial_batch_fields,
|
||||||
|
"serial_no": args.serial_no if args.use_serial_batch_fields else None,
|
||||||
|
"batch_no": args.batch_no if args.use_serial_batch_fields else None,
|
||||||
}
|
}
|
||||||
# append in pos invoice items without item_code by checking flag without_item_code
|
# append in pos invoice items without item_code by checking flag without_item_code
|
||||||
if args.without_item_code:
|
if args.without_item_code:
|
||||||
@@ -1023,6 +1024,8 @@ def create_pos_invoice(**args):
|
|||||||
pos_inv.insert()
|
pos_inv.insert()
|
||||||
if not args.do_not_submit:
|
if not args.do_not_submit:
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
|
if args.use_serial_batch_fields:
|
||||||
|
pos_inv.reload()
|
||||||
else:
|
else:
|
||||||
pos_inv.payment_schedule = []
|
pos_inv.payment_schedule = []
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -634,7 +634,6 @@
|
|||||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||||
"fieldname": "batch_no",
|
"fieldname": "batch_no",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 1,
|
|
||||||
"label": "Batch No",
|
"label": "Batch No",
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
@@ -655,7 +654,6 @@
|
|||||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||||
"fieldname": "serial_no",
|
"fieldname": "serial_no",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"hidden": 1,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Serial No",
|
"label": "Serial No",
|
||||||
"oldfieldname": "serial_no",
|
"oldfieldname": "serial_no",
|
||||||
@@ -827,7 +825,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.serial_and_batch_bundle",
|
||||||
"fieldname": "serial_and_batch_bundle",
|
"fieldname": "serial_and_batch_bundle",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Serial and Batch Bundle",
|
"label": "Serial and Batch Bundle",
|
||||||
@@ -853,7 +851,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-02-25 15:50:17.140269",
|
"modified": "2024-05-07 15:56:53.343317",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice Item",
|
"name": "POS Invoice Item",
|
||||||
@@ -863,4 +861,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
|
|
||||||
self.update_pos_invoices(pos_invoice_docs)
|
self.update_pos_invoices(pos_invoice_docs)
|
||||||
|
self.serial_and_batch_bundle_reference_for_pos_invoice()
|
||||||
self.cancel_linked_invoices()
|
self.cancel_linked_invoices()
|
||||||
|
|
||||||
def process_merging_into_sales_invoice(self, data):
|
def process_merging_into_sales_invoice(self, data):
|
||||||
@@ -191,6 +192,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
for i in items:
|
for i in items:
|
||||||
if (
|
if (
|
||||||
i.item_code == item.item_code
|
i.item_code == item.item_code
|
||||||
|
and not i.serial_and_batch_bundle
|
||||||
and not i.serial_no
|
and not i.serial_no
|
||||||
and not i.batch_no
|
and not i.batch_no
|
||||||
and i.uom == item.uom
|
and i.uom == item.uom
|
||||||
@@ -312,6 +314,12 @@ class POSInvoiceMergeLog(Document):
|
|||||||
doc.set_status(update=True)
|
doc.set_status(update=True)
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
|
def serial_and_batch_bundle_reference_for_pos_invoice(self):
|
||||||
|
for d in self.pos_invoices:
|
||||||
|
pos_invoice = frappe.get_doc("POS Invoice", d.pos_invoice)
|
||||||
|
for table_name in ["items", "packed_items"]:
|
||||||
|
pos_invoice.set_serial_and_batch_bundle(table_name)
|
||||||
|
|
||||||
def cancel_linked_invoices(self):
|
def cancel_linked_invoices(self):
|
||||||
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
|
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
|
||||||
if not si_name:
|
if not si_name:
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
|
|
||||||
const payment_is_overdue = doc.payment_schedule
|
const payment_is_overdue = doc.payment_schedule
|
||||||
.map((row) => Date.parse(row.due_date) < Date.now())
|
.map((row) => Date.parse(row.due_date) < Date.now())
|
||||||
.reduce((prev, current) => prev || current);
|
.reduce((prev, current) => prev || current, false);
|
||||||
|
|
||||||
if (payment_is_overdue) {
|
if (payment_is_overdue) {
|
||||||
this.frm.add_custom_button(
|
this.frm.add_custom_button(
|
||||||
|
|||||||
@@ -1213,6 +1213,8 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_precision_loss_gl_entry(gl_entries)
|
self.make_precision_loss_gl_entry(gl_entries)
|
||||||
self.make_discount_gl_entries(gl_entries)
|
self.make_discount_gl_entries(gl_entries)
|
||||||
|
|
||||||
|
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||||
|
|
||||||
# merge gl entries before adding pos entries
|
# merge gl entries before adding pos entries
|
||||||
gl_entries = merge_similar_entries(gl_entries)
|
gl_entries = merge_similar_entries(gl_entries)
|
||||||
|
|
||||||
@@ -2226,6 +2228,11 @@ def make_inter_company_purchase_invoice(source_name, target_doc=None):
|
|||||||
return make_inter_company_transaction("Sales Invoice", source_name, target_doc)
|
return make_inter_company_transaction("Sales Invoice", source_name, target_doc)
|
||||||
|
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def make_regional_gl_entries(gl_entries, doc):
|
||||||
|
return gl_entries
|
||||||
|
|
||||||
|
|
||||||
def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||||
source_doc = frappe.get_doc(doctype, source_name)
|
source_doc = frappe.get_doc(doctype, source_name)
|
||||||
|
|||||||
@@ -288,13 +288,13 @@ class ReceivablePayableReport:
|
|||||||
|
|
||||||
must_consider = False
|
must_consider = False
|
||||||
if self.filters.get("for_revaluation_journals"):
|
if self.filters.get("for_revaluation_journals"):
|
||||||
if (abs(row.outstanding) > 0.0 / 10**self.currency_precision) or (
|
if (abs(row.outstanding) >= 0.0 / 10**self.currency_precision) or (
|
||||||
abs(row.outstanding_in_account_currency) > 0.0 / 10**self.currency_precision
|
abs(row.outstanding_in_account_currency) >= 0.0 / 10**self.currency_precision
|
||||||
):
|
):
|
||||||
must_consider = True
|
must_consider = True
|
||||||
else:
|
else:
|
||||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
if (abs(row.outstanding) >= 1.0 / 10**self.currency_precision) and (
|
||||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
(abs(row.outstanding_in_account_currency) >= 1.0 / 10**self.currency_precision)
|
||||||
or (row.voucher_no in self.err_journals)
|
or (row.voucher_no in self.err_journals)
|
||||||
):
|
):
|
||||||
must_consider = True
|
must_consider = True
|
||||||
|
|||||||
@@ -955,3 +955,32 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount]
|
expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_accounts_receivable_output_for_minor_outstanding(self):
|
||||||
|
"""
|
||||||
|
AR/AP should report miniscule outstanding of 0.01. Or else there will be slight difference with General Ledger/Trial Balance
|
||||||
|
"""
|
||||||
|
filters = {
|
||||||
|
"company": self.company,
|
||||||
|
"report_date": today(),
|
||||||
|
"range1": 30,
|
||||||
|
"range2": 60,
|
||||||
|
"range3": 90,
|
||||||
|
"range4": 120,
|
||||||
|
}
|
||||||
|
|
||||||
|
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||||
|
si = self.create_sales_invoice(no_payment_schedule=True)
|
||||||
|
|
||||||
|
pe = get_payment_entry("Sales Invoice", si.name, bank_account=self.cash, party_amount=99.99)
|
||||||
|
pe.paid_from = self.debit_to
|
||||||
|
pe.save().submit()
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
expected_data_after_payment = [100, 100, 99.99, 0.01]
|
||||||
|
self.assertEqual(len(report[1]), 1)
|
||||||
|
row = report[1][0]
|
||||||
|
self.assertEqual(
|
||||||
|
expected_data_after_payment,
|
||||||
|
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import getdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
from erpnext.accounts.report.item_wise_purchase_register.item_wise_purchase_register import execute
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TestItemWisePurchaseRegister(AccountsTestMixin, FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.create_company()
|
||||||
|
self.create_supplier()
|
||||||
|
self.create_item()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def create_purchase_invoice(self, do_not_submit=False):
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
item=self.item,
|
||||||
|
company=self.company,
|
||||||
|
supplier=self.supplier,
|
||||||
|
is_return=False,
|
||||||
|
update_stock=False,
|
||||||
|
do_not_save=1,
|
||||||
|
rate=100,
|
||||||
|
price_list_rate=100,
|
||||||
|
qty=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = pi.save()
|
||||||
|
if not do_not_submit:
|
||||||
|
pi = pi.submit()
|
||||||
|
return pi
|
||||||
|
|
||||||
|
def test_basic_report_output(self):
|
||||||
|
pi = self.create_purchase_invoice()
|
||||||
|
|
||||||
|
filters = frappe._dict({"from_date": today(), "to_date": today(), "company": self.company})
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
self.assertEqual(len(report[1]), 1)
|
||||||
|
|
||||||
|
expected_result = {
|
||||||
|
"item_code": pi.items[0].item_code,
|
||||||
|
"invoice": pi.name,
|
||||||
|
"posting_date": getdate(),
|
||||||
|
"supplier": pi.supplier,
|
||||||
|
"credit_to": pi.credit_to,
|
||||||
|
"company": self.company,
|
||||||
|
"expense_account": pi.items[0].expense_account,
|
||||||
|
"stock_qty": 1.0,
|
||||||
|
"stock_uom": pi.items[0].stock_uom,
|
||||||
|
"rate": 100.0,
|
||||||
|
"amount": 100.0,
|
||||||
|
"total_tax": 0,
|
||||||
|
"total": 100.0,
|
||||||
|
"currency": "INR",
|
||||||
|
}
|
||||||
|
|
||||||
|
report_output = {k: v for k, v in report[1][0].items() if k in expected_result}
|
||||||
|
self.assertDictEqual(report_output, expected_result)
|
||||||
@@ -775,11 +775,8 @@ frappe.ui.form.on("Asset Finance Book", {
|
|||||||
|
|
||||||
depreciation_start_date: function (frm, cdt, cdn) {
|
depreciation_start_date: function (frm, cdt, cdn) {
|
||||||
const book = locals[cdt][cdn];
|
const book = locals[cdt][cdn];
|
||||||
if (
|
if (frm.doc.available_for_use_date && book.depreciation_start_date < frm.doc.available_for_use_date) {
|
||||||
frm.doc.available_for_use_date &&
|
frappe.msgprint(__("Depreciation Posting Date cannot be before Available-for-use Date"));
|
||||||
book.depreciation_start_date == frm.doc.available_for_use_date
|
|
||||||
) {
|
|
||||||
frappe.msgprint(__("Depreciation Posting Date should not be equal to Available for Use Date."));
|
|
||||||
book.depreciation_start_date = "";
|
book.depreciation_start_date = "";
|
||||||
frm.refresh_field("finance_books");
|
frm.refresh_field("finance_books");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,10 +267,10 @@ class Asset(AccountsController):
|
|||||||
frappe.throw(_("Available for use date is required"))
|
frappe.throw(_("Available for use date is required"))
|
||||||
|
|
||||||
for d in self.finance_books:
|
for d in self.finance_books:
|
||||||
if d.depreciation_start_date == self.available_for_use_date:
|
if getdate(d.depreciation_start_date) < getdate(self.available_for_use_date):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row #{}: Depreciation Posting Date should not be equal to Available for Use Date."
|
"Depreciation Row {0}: Depreciation Posting Date cannot be before Available-for-use Date"
|
||||||
).format(d.idx),
|
).format(d.idx),
|
||||||
title=_("Incorrect Date"),
|
title=_("Incorrect Date"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,15 @@
|
|||||||
|
|
||||||
frappe.ui.form.on("Asset Maintenance", {
|
frappe.ui.form.on("Asset Maintenance", {
|
||||||
setup: (frm) => {
|
setup: (frm) => {
|
||||||
|
frm.set_query("asset_name", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
docstatus: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("assign_to", "asset_maintenance_tasks", function (doc) {
|
frm.set_query("assign_to", "asset_maintenance_tasks", function (doc) {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.assets.doctype.asset_maintenance.asset_maintenance.get_team_members",
|
query: "erpnext.assets.doctype.asset_maintenance.asset_maintenance.get_team_members",
|
||||||
|
|||||||
@@ -174,7 +174,7 @@
|
|||||||
"fieldname": "supplier_type",
|
"fieldname": "supplier_type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Supplier Type",
|
"label": "Supplier Type",
|
||||||
"options": "Company\nIndividual\nProprietorship\nPartnership",
|
"options": "Company\nIndividual\nPartnership",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Supplier(TransactionBase):
|
|||||||
supplier_name: DF.Data
|
supplier_name: DF.Data
|
||||||
supplier_primary_address: DF.Link | None
|
supplier_primary_address: DF.Link | None
|
||||||
supplier_primary_contact: DF.Link | None
|
supplier_primary_contact: DF.Link | None
|
||||||
supplier_type: DF.Literal["Company", "Individual", "Proprietorship", "Partnership"]
|
supplier_type: DF.Literal["Company", "Individual", "Partnership"]
|
||||||
tax_category: DF.Link | None
|
tax_category: DF.Link | None
|
||||||
tax_id: DF.Data | None
|
tax_id: DF.Data | None
|
||||||
tax_withholding_category: DF.Link | None
|
tax_withholding_category: DF.Link | None
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ def get_data(filters):
|
|||||||
frappe.qb.from_(po)
|
frappe.qb.from_(po)
|
||||||
.from_(po_item)
|
.from_(po_item)
|
||||||
.left_join(pi_item)
|
.left_join(pi_item)
|
||||||
.on(pi_item.po_detail == po_item.name)
|
.on(pi_item.po_detail == po_item.name & pi_item.docstatus == 1)
|
||||||
.left_join(pi)
|
.left_join(pi)
|
||||||
.on(pi.name == pi_item.parent)
|
.on(pi.name == pi_item.parent & pi.docstatus == 1)
|
||||||
.select(
|
.select(
|
||||||
po.transaction_date.as_("date"),
|
po.transaction_date.as_("date"),
|
||||||
po_item.schedule_date.as_("required_date"),
|
po_item.schedule_date.as_("required_date"),
|
||||||
@@ -72,7 +72,6 @@ def get_data(filters):
|
|||||||
po_item.name,
|
po_item.name,
|
||||||
)
|
)
|
||||||
.where((po_item.parent == po.name) & (po.status.notin(("Stopped", "Closed"))) & (po.docstatus == 1))
|
.where((po_item.parent == po.name) & (po.status.notin(("Stopped", "Closed"))) & (po.docstatus == 1))
|
||||||
.where(pi.docstatus == 1)
|
|
||||||
.groupby(po_item.name)
|
.groupby(po_item.name)
|
||||||
.orderby(po.transaction_date)
|
.orderby(po.transaction_date)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -254,15 +254,16 @@ erpnext.patches.v13_0.reset_corrupt_defaults
|
|||||||
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
|
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
|
||||||
erpnext.patches.v15_0.delete_taxjar_doctypes
|
erpnext.patches.v15_0.delete_taxjar_doctypes
|
||||||
erpnext.patches.v15_0.delete_ecommerce_doctypes
|
erpnext.patches.v15_0.delete_ecommerce_doctypes
|
||||||
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
|
|
||||||
erpnext.patches.v14_0.update_reference_due_date_in_journal_entry
|
erpnext.patches.v14_0.update_reference_due_date_in_journal_entry
|
||||||
erpnext.patches.v15_0.saudi_depreciation_warning
|
erpnext.patches.v15_0.saudi_depreciation_warning
|
||||||
erpnext.patches.v15_0.delete_saudi_doctypes
|
erpnext.patches.v15_0.delete_saudi_doctypes
|
||||||
erpnext.patches.v14_0.show_loan_management_deprecation_warning
|
erpnext.patches.v14_0.show_loan_management_deprecation_warning
|
||||||
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
||||||
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
|
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
|
||||||
|
erpnext.patches.v14_0.update_proprietorship_to_individual
|
||||||
|
|
||||||
[post_model_sync]
|
[post_model_sync]
|
||||||
|
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
|
||||||
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
||||||
erpnext.patches.v14_0.update_posting_datetime_and_dropped_indexes #22-02-2024
|
erpnext.patches.v14_0.update_posting_datetime_and_dropped_indexes #22-02-2024
|
||||||
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for doctype in ["Customer", "Supplier"]:
|
||||||
|
field = doctype.lower() + "_type"
|
||||||
|
frappe.db.set_value(doctype, {field: "Proprietorship"}, field, "Individual")
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"label": "Customer Type",
|
"label": "Customer Type",
|
||||||
"oldfieldname": "customer_type",
|
"oldfieldname": "customer_type",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Company\nIndividual\nProprietorship\nPartnership",
|
"options": "Company\nIndividual\nPartnership",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class Customer(TransactionBase):
|
|||||||
customer_pos_id: DF.Data | None
|
customer_pos_id: DF.Data | None
|
||||||
customer_primary_address: DF.Link | None
|
customer_primary_address: DF.Link | None
|
||||||
customer_primary_contact: DF.Link | None
|
customer_primary_contact: DF.Link | None
|
||||||
customer_type: DF.Literal["Company", "Individual", "Proprietorship", "Partnership"]
|
customer_type: DF.Literal["Company", "Individual", "Partnership"]
|
||||||
default_bank_account: DF.Link | None
|
default_bank_account: DF.Link | None
|
||||||
default_commission_rate: DF.Float
|
default_commission_rate: DF.Float
|
||||||
default_currency: DF.Link | None
|
default_currency: DF.Link | None
|
||||||
|
|||||||
@@ -583,6 +583,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
new_item["serial_no"] = serial_no;
|
new_item["serial_no"] = serial_no;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new_item["use_serial_batch_fields"] = 1;
|
||||||
if (field === "serial_no") new_item["qty"] = value.split(`\n`).length || 0;
|
if (field === "serial_no") new_item["qty"] = value.split(`\n`).length || 0;
|
||||||
|
|
||||||
item_row = this.frm.add_child("items", new_item);
|
item_row = this.frm.add_child("items", new_item);
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
|
|
||||||
const serialized = item_row.has_serial_no;
|
const serialized = item_row.has_serial_no;
|
||||||
const batched = item_row.has_batch_no;
|
const batched = item_row.has_batch_no;
|
||||||
const no_bundle_selected = !item_row.serial_and_batch_bundle;
|
const no_bundle_selected =
|
||||||
|
!item_row.serial_and_batch_bundle && !item_row.serial_no && !item_row.batch_no;
|
||||||
|
|
||||||
if ((serialized && no_bundle_selected) || (batched && no_bundle_selected)) {
|
if ((serialized && no_bundle_selected) || (batched && no_bundle_selected)) {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
@@ -403,6 +404,7 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
frappe.model.set_value(item_row.doctype, item_row.name, {
|
frappe.model.set_value(item_row.doctype, item_row.name, {
|
||||||
serial_and_batch_bundle: r.name,
|
serial_and_batch_bundle: r.name,
|
||||||
qty: Math.abs(r.total_qty),
|
qty: Math.abs(r.total_qty),
|
||||||
|
use_serial_batch_fields: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -242,6 +242,9 @@ class QualityInspection(Document):
|
|||||||
# numeric readings
|
# numeric readings
|
||||||
for i in range(1, 11):
|
for i in range(1, 11):
|
||||||
field = "reading_" + str(i)
|
field = "reading_" + str(i)
|
||||||
|
if reading.get(field) is None:
|
||||||
|
continue
|
||||||
|
|
||||||
data[field] = parse_float(reading.get(field))
|
data[field] = parse_float(reading.get(field))
|
||||||
data["mean"] = self.calculate_mean(reading)
|
data["mean"] = self.calculate_mean(reading)
|
||||||
|
|
||||||
|
|||||||
@@ -935,6 +935,9 @@ class SerialandBatchBundle(Document):
|
|||||||
self.validate_voucher_no_docstatus()
|
self.validate_voucher_no_docstatus()
|
||||||
|
|
||||||
def validate_voucher_no_docstatus(self):
|
def validate_voucher_no_docstatus(self):
|
||||||
|
if self.voucher_type == "POS Invoice":
|
||||||
|
return
|
||||||
|
|
||||||
if frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1:
|
if frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1:
|
||||||
msg = f"""The {self.voucher_type} {bold(self.voucher_no)}
|
msg = f"""The {self.voucher_type} {bold(self.voucher_no)}
|
||||||
is in submitted state, please cancel it first"""
|
is in submitted state, please cancel it first"""
|
||||||
@@ -1098,7 +1101,9 @@ def create_serial_nos(item_code, serial_nos):
|
|||||||
|
|
||||||
|
|
||||||
def make_serial_nos(item_code, serial_nos):
|
def make_serial_nos(item_code, serial_nos):
|
||||||
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
|
item = frappe.get_cached_value(
|
||||||
|
"Item", item_code, ["description", "item_code", "item_name", "warranty_period"], as_dict=1
|
||||||
|
)
|
||||||
|
|
||||||
serial_nos = [d.get("serial_no") for d in serial_nos if d.get("serial_no")]
|
serial_nos = [d.get("serial_no") for d in serial_nos if d.get("serial_no")]
|
||||||
existing_serial_nos = frappe.get_all("Serial No", filters={"name": ("in", serial_nos)})
|
existing_serial_nos = frappe.get_all("Serial No", filters={"name": ("in", serial_nos)})
|
||||||
@@ -1123,6 +1128,7 @@ def make_serial_nos(item_code, serial_nos):
|
|||||||
item.item_code,
|
item.item_code,
|
||||||
item.item_name,
|
item.item_name,
|
||||||
item.description,
|
item.description,
|
||||||
|
item.warranty_period or 0,
|
||||||
"Inactive",
|
"Inactive",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1137,6 +1143,7 @@ def make_serial_nos(item_code, serial_nos):
|
|||||||
"item_code",
|
"item_code",
|
||||||
"item_name",
|
"item_name",
|
||||||
"description",
|
"description",
|
||||||
|
"warranty_period",
|
||||||
"status",
|
"status",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1718,6 +1725,7 @@ def get_reserved_batches_for_pos(kwargs) -> dict:
|
|||||||
"`tabPOS Invoice Item`.warehouse",
|
"`tabPOS Invoice Item`.warehouse",
|
||||||
"`tabPOS Invoice Item`.name as child_docname",
|
"`tabPOS Invoice Item`.name as child_docname",
|
||||||
"`tabPOS Invoice`.name as parent_docname",
|
"`tabPOS Invoice`.name as parent_docname",
|
||||||
|
"`tabPOS Invoice Item`.use_serial_batch_fields",
|
||||||
"`tabPOS Invoice Item`.serial_and_batch_bundle",
|
"`tabPOS Invoice Item`.serial_and_batch_bundle",
|
||||||
],
|
],
|
||||||
filters=[
|
filters=[
|
||||||
@@ -1731,7 +1739,7 @@ def get_reserved_batches_for_pos(kwargs) -> dict:
|
|||||||
ids = [
|
ids = [
|
||||||
pos_invoice.serial_and_batch_bundle
|
pos_invoice.serial_and_batch_bundle
|
||||||
for pos_invoice in pos_invoices
|
for pos_invoice in pos_invoices
|
||||||
if pos_invoice.serial_and_batch_bundle
|
if pos_invoice.serial_and_batch_bundle and not pos_invoice.use_serial_batch_fields
|
||||||
]
|
]
|
||||||
|
|
||||||
if ids:
|
if ids:
|
||||||
|
|||||||
@@ -2354,17 +2354,22 @@ class StockEntry(StockController):
|
|||||||
return [d.item_code for d in job_card_items]
|
return [d.item_code for d in job_card_items]
|
||||||
|
|
||||||
def add_to_stock_entry_detail(self, item_dict, bom_no=None):
|
def add_to_stock_entry_detail(self, item_dict, bom_no=None):
|
||||||
|
precision = frappe.get_precision("Stock Entry Detail", "qty")
|
||||||
for d in item_dict:
|
for d in item_dict:
|
||||||
item_row = item_dict[d]
|
item_row = item_dict[d]
|
||||||
stock_uom = item_row.get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")
|
|
||||||
|
child_qty = flt(item_row["qty"], precision)
|
||||||
|
if not self.is_return and child_qty <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
se_child = self.append("items")
|
se_child = self.append("items")
|
||||||
|
stock_uom = item_row.get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")
|
||||||
se_child.s_warehouse = item_row.get("from_warehouse")
|
se_child.s_warehouse = item_row.get("from_warehouse")
|
||||||
se_child.t_warehouse = item_row.get("to_warehouse")
|
se_child.t_warehouse = item_row.get("to_warehouse")
|
||||||
se_child.item_code = item_row.get("item_code") or cstr(d)
|
se_child.item_code = item_row.get("item_code") or cstr(d)
|
||||||
se_child.uom = item_row["uom"] if item_row.get("uom") else stock_uom
|
se_child.uom = item_row["uom"] if item_row.get("uom") else stock_uom
|
||||||
se_child.stock_uom = stock_uom
|
se_child.stock_uom = stock_uom
|
||||||
se_child.qty = flt(item_row["qty"], se_child.precision("qty"))
|
se_child.qty = child_qty
|
||||||
se_child.allow_alternative_item = item_row.get("allow_alternative_item", 0)
|
se_child.allow_alternative_item = item_row.get("allow_alternative_item", 0)
|
||||||
se_child.subcontracted_item = item_row.get("main_item_code")
|
se_child.subcontracted_item = item_row.get("main_item_code")
|
||||||
se_child.cost_center = item_row.get("cost_center") or get_default_cost_center(
|
se_child.cost_center = item_row.get("cost_center") or get_default_cost_center(
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ def get_data(filters):
|
|||||||
{
|
{
|
||||||
"serial_no": bundle_data.get("serial_no"),
|
"serial_no": bundle_data.get("serial_no"),
|
||||||
"valuation_rate": bundle_data.get("valuation_rate"),
|
"valuation_rate": bundle_data.get("valuation_rate"),
|
||||||
|
"qty": args.qty,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -246,6 +246,9 @@ class SerialBatchBundle:
|
|||||||
frappe.throw(_(msg))
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
def delink_serial_and_batch_bundle(self):
|
def delink_serial_and_batch_bundle(self):
|
||||||
|
if self.is_pos_transaction():
|
||||||
|
return
|
||||||
|
|
||||||
update_values = {
|
update_values = {
|
||||||
"serial_and_batch_bundle": "",
|
"serial_and_batch_bundle": "",
|
||||||
}
|
}
|
||||||
@@ -295,8 +298,22 @@ class SerialBatchBundle:
|
|||||||
self.cancel_serial_and_batch_bundle()
|
self.cancel_serial_and_batch_bundle()
|
||||||
|
|
||||||
def cancel_serial_and_batch_bundle(self):
|
def cancel_serial_and_batch_bundle(self):
|
||||||
|
if self.is_pos_transaction():
|
||||||
|
return
|
||||||
|
|
||||||
frappe.get_cached_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle).cancel()
|
frappe.get_cached_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle).cancel()
|
||||||
|
|
||||||
|
def is_pos_transaction(self):
|
||||||
|
if (
|
||||||
|
self.sle.voucher_type == "Sales Invoice"
|
||||||
|
and self.sle.serial_and_batch_bundle
|
||||||
|
and frappe.get_cached_value(
|
||||||
|
"Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "voucher_type"
|
||||||
|
)
|
||||||
|
== "POS Invoice"
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
def submit_serial_and_batch_bundle(self):
|
def submit_serial_and_batch_bundle(self):
|
||||||
doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle)
|
doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle)
|
||||||
self.validate_actual_qty(doc)
|
self.validate_actual_qty(doc)
|
||||||
|
|||||||
Reference in New Issue
Block a user