Merge pull request #38690 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
commit
0d8a52f63b
@ -1689,13 +1689,42 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list:
|
||||||
invoice_ref_based_on_payment_terms = {}
|
"""Split a list of invoices based on their payment terms."""
|
||||||
|
exc_rates = get_currency_data(outstanding_invoices, company)
|
||||||
|
|
||||||
|
outstanding_invoices_after_split = []
|
||||||
|
for entry in outstanding_invoices:
|
||||||
|
if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
|
if payment_term_template := frappe.db.get_value(
|
||||||
|
entry.voucher_type, entry.voucher_no, "payment_terms_template"
|
||||||
|
):
|
||||||
|
split_rows = get_split_invoice_rows(entry, payment_term_template, exc_rates)
|
||||||
|
if not split_rows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Splitting {0} {1} into {2} rows as per Payment Terms").format(
|
||||||
|
_(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows)
|
||||||
|
),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
outstanding_invoices_after_split += split_rows
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If not an invoice or no payment terms template, add as it is
|
||||||
|
outstanding_invoices_after_split.append(entry)
|
||||||
|
|
||||||
|
return outstanding_invoices_after_split
|
||||||
|
|
||||||
|
|
||||||
|
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict:
|
||||||
|
"""Get currency and conversion data for a list of invoices."""
|
||||||
|
exc_rates = frappe._dict()
|
||||||
company_currency = (
|
company_currency = (
|
||||||
frappe.db.get_value("Company", company, "default_currency") if company else None
|
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||||
)
|
)
|
||||||
exc_rates = frappe._dict()
|
|
||||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||||
for x in frappe.db.get_all(
|
for x in frappe.db.get_all(
|
||||||
@ -1710,72 +1739,54 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
|||||||
company_currency=company_currency,
|
company_currency=company_currency,
|
||||||
)
|
)
|
||||||
|
|
||||||
for idx, d in enumerate(outstanding_invoices):
|
return exc_rates
|
||||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
|
||||||
payment_term_template = frappe.db.get_value(
|
|
||||||
d.voucher_type, d.voucher_no, "payment_terms_template"
|
def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: dict) -> list:
|
||||||
|
"""Split invoice based on its payment schedule table."""
|
||||||
|
split_rows = []
|
||||||
|
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||||
|
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not allocate_payment_based_on_payment_terms:
|
||||||
|
return [invoice]
|
||||||
|
|
||||||
|
payment_schedule = frappe.get_all(
|
||||||
|
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
|
||||||
|
)
|
||||||
|
for payment_term in payment_schedule:
|
||||||
|
if not payment_term.outstanding > 0.1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
doc_details = exc_rates.get(payment_term.parent, None)
|
||||||
|
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||||
|
doc_details.party_account_currency != doc_details.company_currency
|
||||||
|
)
|
||||||
|
payment_term_outstanding = flt(payment_term.outstanding)
|
||||||
|
if not is_multi_currency_acc:
|
||||||
|
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||||
|
|
||||||
|
split_rows.append(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"due_date": invoice.due_date,
|
||||||
|
"currency": invoice.currency,
|
||||||
|
"voucher_no": invoice.voucher_no,
|
||||||
|
"voucher_type": invoice.voucher_type,
|
||||||
|
"posting_date": invoice.posting_date,
|
||||||
|
"invoice_amount": flt(invoice.invoice_amount),
|
||||||
|
"outstanding_amount": payment_term_outstanding
|
||||||
|
if payment_term_outstanding
|
||||||
|
else invoice.outstanding_amount,
|
||||||
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
|
"payment_amount": payment_term.payment_amount,
|
||||||
|
"payment_term": payment_term.payment_term,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if payment_term_template:
|
)
|
||||||
allocate_payment_based_on_payment_terms = frappe.get_cached_value(
|
|
||||||
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
|
||||||
)
|
|
||||||
if allocate_payment_based_on_payment_terms:
|
|
||||||
payment_schedule = frappe.get_all(
|
|
||||||
"Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"]
|
|
||||||
)
|
|
||||||
|
|
||||||
for payment_term in payment_schedule:
|
return split_rows
|
||||||
if payment_term.outstanding > 0.1:
|
|
||||||
doc_details = exc_rates.get(payment_term.parent, None)
|
|
||||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
|
||||||
doc_details.party_account_currency != doc_details.company_currency
|
|
||||||
)
|
|
||||||
payment_term_outstanding = flt(payment_term.outstanding)
|
|
||||||
if not is_multi_currency_acc:
|
|
||||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
|
||||||
|
|
||||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
|
||||||
invoice_ref_based_on_payment_terms[idx].append(
|
|
||||||
frappe._dict(
|
|
||||||
{
|
|
||||||
"due_date": d.due_date,
|
|
||||||
"currency": d.currency,
|
|
||||||
"voucher_no": d.voucher_no,
|
|
||||||
"voucher_type": d.voucher_type,
|
|
||||||
"posting_date": d.posting_date,
|
|
||||||
"invoice_amount": flt(d.invoice_amount),
|
|
||||||
"outstanding_amount": payment_term_outstanding
|
|
||||||
if payment_term_outstanding
|
|
||||||
else d.outstanding_amount,
|
|
||||||
"payment_term_outstanding": payment_term_outstanding,
|
|
||||||
"payment_amount": payment_term.payment_amount,
|
|
||||||
"payment_term": payment_term.payment_term,
|
|
||||||
"account": d.account,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split = []
|
|
||||||
if invoice_ref_based_on_payment_terms:
|
|
||||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
|
||||||
voucher_no = ref[0]["voucher_no"]
|
|
||||||
voucher_type = ref[0]["voucher_type"]
|
|
||||||
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Spliting {} {} into {} row(s) as per Payment Terms").format(
|
|
||||||
voucher_type, voucher_no, len(ref)
|
|
||||||
),
|
|
||||||
alert=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
|
|
||||||
|
|
||||||
existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices))
|
|
||||||
index = outstanding_invoices.index(existing_row[0])
|
|
||||||
outstanding_invoices.pop(index)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split += outstanding_invoices
|
|
||||||
return outstanding_invoices_after_split
|
|
||||||
|
|
||||||
|
|
||||||
def get_orders_to_be_billed(
|
def get_orders_to_be_billed(
|
||||||
|
@ -6,11 +6,12 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import add_days, flt, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||||
InvalidPaymentEntry,
|
InvalidPaymentEntry,
|
||||||
|
get_outstanding_reference_documents,
|
||||||
get_payment_entry,
|
get_payment_entry,
|
||||||
get_reference_details,
|
get_reference_details,
|
||||||
)
|
)
|
||||||
@ -1471,6 +1472,45 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
for field in ["account", "debit", "credit"]:
|
for field in ["account", "debit", "credit"]:
|
||||||
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
|
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
|
||||||
|
|
||||||
|
def test_outstanding_invoices_api(self):
|
||||||
|
"""
|
||||||
|
Test if `get_outstanding_reference_documents` fetches invoices in the right order.
|
||||||
|
"""
|
||||||
|
customer = create_customer("Max Mustermann", "INR")
|
||||||
|
create_payment_terms_template()
|
||||||
|
|
||||||
|
# SI has an earlier due date and SI2 has a later due date
|
||||||
|
si = create_sales_invoice(
|
||||||
|
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
|
||||||
|
)
|
||||||
|
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
|
||||||
|
si2.payment_terms_template = "Test Receivable Template"
|
||||||
|
si2.submit()
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"posting_date": nowdate(),
|
||||||
|
"company": "_Test Company",
|
||||||
|
"party_type": "Customer",
|
||||||
|
"payment_type": "Pay",
|
||||||
|
"party": customer,
|
||||||
|
"party_account": "Debtors - _TC",
|
||||||
|
}
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
"get_outstanding_invoices": True,
|
||||||
|
"from_posting_date": add_days(nowdate(), -4),
|
||||||
|
"to_posting_date": add_days(nowdate(), 2),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
references = get_outstanding_reference_documents(args)
|
||||||
|
|
||||||
|
self.assertEqual(len(references), 3)
|
||||||
|
self.assertEqual(references[0].voucher_no, si.name)
|
||||||
|
self.assertEqual(references[1].voucher_no, si2.name)
|
||||||
|
self.assertEqual(references[2].voucher_no, si2.name)
|
||||||
|
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||||
|
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
@ -1531,6 +1571,9 @@ def create_payment_terms_template():
|
|||||||
def create_payment_terms_template_with_discount(
|
def create_payment_terms_template_with_discount(
|
||||||
name=None, discount_type=None, discount=None, template_name=None
|
name=None, discount_type=None, discount=None, template_name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Create a Payment Terms Template with % or amount discount.
|
||||||
|
"""
|
||||||
create_payment_term(name or "30 Credit Days with 10% Discount")
|
create_payment_term(name or "30 Credit Days with 10% Discount")
|
||||||
template_name = template_name or "Test Discount Template"
|
template_name = template_name or "Test Discount Template"
|
||||||
|
|
||||||
|
@ -34,4 +34,6 @@ class PaymentReconciliationAllocation(Document):
|
|||||||
unreconciled_amount: DF.Currency
|
unreconciled_amount: DF.Currency
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
pass
|
||||||
|
@ -26,4 +26,6 @@ class PaymentReconciliationInvoice(Document):
|
|||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
pass
|
||||||
|
@ -30,4 +30,6 @@ class PaymentReconciliationPayment(Document):
|
|||||||
remark: DF.SmallText | None
|
remark: DF.SmallText | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
pass
|
||||||
|
@ -581,6 +581,8 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
if d.price_or_product_discount == "Price":
|
if d.price_or_product_discount == "Price":
|
||||||
if d.apply_discount_on:
|
if d.apply_discount_on:
|
||||||
doc.set("apply_discount_on", d.apply_discount_on)
|
doc.set("apply_discount_on", d.apply_discount_on)
|
||||||
|
# Variable to track whether the condition has been met
|
||||||
|
condition_met = False
|
||||||
|
|
||||||
for field in ["additional_discount_percentage", "discount_amount"]:
|
for field in ["additional_discount_percentage", "discount_amount"]:
|
||||||
pr_field = "discount_percentage" if field == "additional_discount_percentage" else field
|
pr_field = "discount_percentage" if field == "additional_discount_percentage" else field
|
||||||
@ -603,6 +605,11 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
if coupon_code_pricing_rule == d.name:
|
if coupon_code_pricing_rule == d.name:
|
||||||
# if selected coupon code is linked with pricing rule
|
# if selected coupon code is linked with pricing rule
|
||||||
doc.set(field, d.get(pr_field))
|
doc.set(field, d.get(pr_field))
|
||||||
|
|
||||||
|
# Set the condition_met variable to True and break out of the loop
|
||||||
|
condition_met = True
|
||||||
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# reset discount if not linked
|
# reset discount if not linked
|
||||||
doc.set(field, 0)
|
doc.set(field, 0)
|
||||||
@ -611,6 +618,10 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
doc.set(field, 0)
|
doc.set(field, 0)
|
||||||
|
|
||||||
doc.calculate_taxes_and_totals()
|
doc.calculate_taxes_and_totals()
|
||||||
|
|
||||||
|
# Break out of the main loop if the condition is met
|
||||||
|
if condition_met:
|
||||||
|
break
|
||||||
elif d.price_or_product_discount == "Product":
|
elif d.price_or_product_discount == "Product":
|
||||||
item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []})
|
item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []})
|
||||||
get_product_discount_rule(d, item_details, doc=doc)
|
get_product_discount_rule(d, item_details, doc=doc)
|
||||||
|
@ -126,7 +126,7 @@ class RepostAccountingLedger(Document):
|
|||||||
return rendered_page
|
return rendered_page
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if len(self.vouchers) > 1:
|
if len(self.vouchers) > 5:
|
||||||
job_name = "repost_accounting_ledger_" + self.name
|
job_name = "repost_accounting_ledger_" + self.name
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
||||||
@ -170,8 +170,6 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
doc.make_gl_entries(1)
|
doc.make_gl_entries(1)
|
||||||
doc.make_gl_entries()
|
doc.make_gl_entries()
|
||||||
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def get_allowed_types_from_settings():
|
def get_allowed_types_from_settings():
|
||||||
return [
|
return [
|
||||||
|
@ -20,18 +20,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_customer()
|
self.create_customer()
|
||||||
self.create_item()
|
self.create_item()
|
||||||
self.update_repost_settings()
|
update_repost_settings()
|
||||||
|
|
||||||
def teadDown(self):
|
def tearDown(self):
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
def update_repost_settings(self):
|
|
||||||
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
|
||||||
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
|
||||||
for x in allowed_types:
|
|
||||||
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
|
||||||
repost_settings.save()
|
|
||||||
|
|
||||||
def test_01_basic_functions(self):
|
def test_01_basic_functions(self):
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
item=self.item,
|
item=self.item,
|
||||||
@ -90,9 +83,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
# Submit repost document
|
# Submit repost document
|
||||||
ral.save().submit()
|
ral.save().submit()
|
||||||
|
|
||||||
# background jobs don't run on test cases. Manually triggering repost function.
|
|
||||||
start_repost(ral.name)
|
|
||||||
|
|
||||||
res = (
|
res = (
|
||||||
qb.from_(gl)
|
qb.from_(gl)
|
||||||
.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
|
.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
|
||||||
@ -177,26 +167,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
pe = get_payment_entry(si.doctype, si.name)
|
pe = get_payment_entry(si.doctype, si.name)
|
||||||
pe.save().submit()
|
pe.save().submit()
|
||||||
|
|
||||||
# without deletion flag set
|
|
||||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
|
||||||
ral.company = self.company
|
|
||||||
ral.delete_cancelled_entries = False
|
|
||||||
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
|
||||||
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
|
||||||
ral.save()
|
|
||||||
|
|
||||||
# assert preview data is generated
|
|
||||||
preview = ral.generate_preview()
|
|
||||||
self.assertIsNotNone(preview)
|
|
||||||
|
|
||||||
ral.save().submit()
|
|
||||||
|
|
||||||
# background jobs don't run on test cases. Manually triggering repost function.
|
|
||||||
start_repost(ral.name)
|
|
||||||
|
|
||||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
|
||||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
|
||||||
|
|
||||||
# with deletion flag set
|
# with deletion flag set
|
||||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||||
ral.company = self.company
|
ral.company = self.company
|
||||||
@ -205,6 +175,38 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
||||||
ral.save().submit()
|
ral.save().submit()
|
||||||
|
|
||||||
start_repost(ral.name)
|
|
||||||
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||||
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||||
|
|
||||||
|
def test_05_without_deletion_flag(self):
|
||||||
|
si = create_sales_invoice(
|
||||||
|
item=self.item,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.customer,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
pe = get_payment_entry(si.doctype, si.name)
|
||||||
|
pe.save().submit()
|
||||||
|
|
||||||
|
# without deletion flag set
|
||||||
|
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||||
|
ral.company = self.company
|
||||||
|
ral.delete_cancelled_entries = False
|
||||||
|
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
||||||
|
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
||||||
|
ral.save().submit()
|
||||||
|
|
||||||
|
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||||
|
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||||
|
|
||||||
|
|
||||||
|
def update_repost_settings():
|
||||||
|
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||||
|
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||||
|
for x in allowed_types:
|
||||||
|
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
||||||
|
repost_settings.save()
|
||||||
|
@ -2799,6 +2799,12 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||||
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
|
||||||
|
update_repost_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
update_repost_settings()
|
||||||
|
|
||||||
additional_discount_account = create_account(
|
additional_discount_account = create_account(
|
||||||
account_name="Discount Account",
|
account_name="Discount Account",
|
||||||
parent_account="Indirect Expenses - _TC",
|
parent_account="Indirect Expenses - _TC",
|
||||||
|
@ -8,7 +8,17 @@ import re
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
add_months,
|
||||||
|
cint,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
formatdate,
|
||||||
|
get_first_day,
|
||||||
|
getdate,
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
@ -43,6 +53,8 @@ def get_period_list(
|
|||||||
year_start_date = getdate(period_start_date)
|
year_start_date = getdate(period_start_date)
|
||||||
year_end_date = getdate(period_end_date)
|
year_end_date = getdate(period_end_date)
|
||||||
|
|
||||||
|
year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
|
||||||
|
|
||||||
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
|
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
|
||||||
|
|
||||||
period_list = []
|
period_list = []
|
||||||
|
@ -340,6 +340,10 @@ class AssetDepreciationSchedule(Document):
|
|||||||
n == 0
|
n == 0
|
||||||
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||||
and not self.opening_accumulated_depreciation
|
and not self.opening_accumulated_depreciation
|
||||||
|
and get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||||
|
asset_doc, value_after_depreciation, row, False
|
||||||
|
)
|
||||||
|
== row.rate_of_depreciation
|
||||||
):
|
):
|
||||||
from_date = add_days(
|
from_date = add_days(
|
||||||
asset_doc.available_for_use_date, -1
|
asset_doc.available_for_use_date, -1
|
||||||
@ -605,7 +609,9 @@ def get_depreciation_amount(
|
|||||||
|
|
||||||
|
|
||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row):
|
def get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||||
|
asset, depreciable_value, fb_row, show_msg=True
|
||||||
|
):
|
||||||
return fb_row.rate_of_depreciation
|
return fb_row.rate_of_depreciation
|
||||||
|
|
||||||
|
|
||||||
|
@ -292,6 +292,7 @@ class AccountsController(TransactionBase):
|
|||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
self._remove_references_in_repost_doctypes()
|
self._remove_references_in_repost_doctypes()
|
||||||
self._remove_references_in_unreconcile()
|
self._remove_references_in_unreconcile()
|
||||||
|
self.remove_serial_and_batch_bundle()
|
||||||
|
|
||||||
# delete sl and gl entries on deletion of transaction
|
# delete sl and gl entries on deletion of transaction
|
||||||
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
|
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
|
||||||
@ -307,6 +308,15 @@ class AccountsController(TransactionBase):
|
|||||||
(self.doctype, self.name),
|
(self.doctype, self.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def remove_serial_and_batch_bundle(self):
|
||||||
|
bundles = frappe.get_all(
|
||||||
|
"Serial and Batch Bundle",
|
||||||
|
filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)},
|
||||||
|
)
|
||||||
|
|
||||||
|
for bundle in bundles:
|
||||||
|
frappe.delete_doc("Serial and Batch Bundle", bundle.name)
|
||||||
|
|
||||||
def validate_deferred_income_expense_account(self):
|
def validate_deferred_income_expense_account(self):
|
||||||
field_map = {
|
field_map = {
|
||||||
"Sales Invoice": "deferred_revenue_account",
|
"Sales Invoice": "deferred_revenue_account",
|
||||||
|
@ -637,6 +637,7 @@ additional_timeline_content = {
|
|||||||
|
|
||||||
extend_bootinfo = [
|
extend_bootinfo = [
|
||||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
|
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
|
||||||
|
"erpnext.startup.boot.bootinfo",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,14 +36,14 @@ erpnext.buying = {
|
|||||||
|
|
||||||
// no idea where me is coming from
|
// no idea where me is coming from
|
||||||
if(this.frm.get_field('shipping_address')) {
|
if(this.frm.get_field('shipping_address')) {
|
||||||
this.frm.set_query("shipping_address", function() {
|
this.frm.set_query("shipping_address", () => {
|
||||||
if(me.frm.doc.customer) {
|
if(this.frm.doc.customer) {
|
||||||
return {
|
return {
|
||||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||||
filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer }
|
filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer }
|
||||||
};
|
};
|
||||||
} else
|
} else
|
||||||
return erpnext.queries.company_address_query(me.frm.doc)
|
return erpnext.queries.company_address_query(this.frm.doc)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,7 +471,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
item.pricing_rules = ''
|
item.pricing_rules = ''
|
||||||
return this.frm.call({
|
return this.frm.call({
|
||||||
method: "erpnext.stock.get_item_details.get_item_details",
|
method: "erpnext.stock.get_item_details.get_item_details",
|
||||||
child: item,
|
|
||||||
args: {
|
args: {
|
||||||
doc: me.frm.doc,
|
doc: me.frm.doc,
|
||||||
args: {
|
args: {
|
||||||
@ -520,6 +519,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
|
() => {
|
||||||
|
var child = locals[cdt][cdn];
|
||||||
|
var std_field_list = ["doctype"]
|
||||||
|
.concat(frappe.model.std_fields_list)
|
||||||
|
.concat(frappe.model.child_table_field_list);
|
||||||
|
|
||||||
|
for (var key in r.message) {
|
||||||
|
if (std_field_list.indexOf(key) === -1) {
|
||||||
|
if (key === "qty" && child[key]) continue;
|
||||||
|
child[key] = r.message[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
() => {
|
() => {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
me.add_taxes_from_item_tax_template(d.item_tax_rate);
|
me.add_taxes_from_item_tax_template(d.item_tax_rate);
|
||||||
|
@ -2,10 +2,16 @@ frappe.provide("erpnext.financial_statements");
|
|||||||
|
|
||||||
erpnext.financial_statements = {
|
erpnext.financial_statements = {
|
||||||
"filters": get_filters(),
|
"filters": get_filters(),
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter, filter) {
|
||||||
if (data && column.fieldname=="account") {
|
if (data && column.fieldname=="account") {
|
||||||
value = data.account_name || value;
|
value = data.account_name || value;
|
||||||
|
|
||||||
|
if (filter && filter?.text && filter?.type == "contains") {
|
||||||
|
if (!value.toLowerCase().includes(filter.text)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.account) {
|
if (data.account) {
|
||||||
column.link_onclick =
|
column.link_onclick =
|
||||||
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
||||||
|
@ -8,7 +8,7 @@ $.extend(erpnext, {
|
|||||||
if(!company && cur_frm)
|
if(!company && cur_frm)
|
||||||
company = cur_frm.doc.company;
|
company = cur_frm.doc.company;
|
||||||
if(company)
|
if(company)
|
||||||
return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency;
|
return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency;
|
||||||
else
|
else
|
||||||
return frappe.boot.sysdefaults.currency;
|
return frappe.boot.sysdefaults.currency;
|
||||||
},
|
},
|
||||||
|
@ -31,7 +31,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
secondary_action: () => this.edit_full_form(),
|
secondary_action: () => this.edit_full_form(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dialog.set_value("qty", this.item.qty).then(() => {
|
let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty;
|
||||||
|
this.dialog.set_value("qty", qty).then(() => {
|
||||||
if (this.item.serial_no) {
|
if (this.item.serial_no) {
|
||||||
this.dialog.set_value("scan_serial_no", this.item.serial_no);
|
this.dialog.set_value("scan_serial_no", this.item.serial_no);
|
||||||
frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
|
frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@ -114,10 +114,11 @@ def update_regional_tax_settings(country, company):
|
|||||||
frappe.scrub(country)
|
frappe.scrub(country)
|
||||||
)
|
)
|
||||||
frappe.get_attr(module_name)(country, company)
|
frappe.get_attr(module_name)(country, company)
|
||||||
except Exception as e:
|
except (ImportError, AttributeError):
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
# Log error and ignore if failed to setup regional tax settings
|
# Log error and ignore if failed to setup regional tax settings
|
||||||
frappe.log_error("Unable to setup regional tax settings")
|
frappe.log_error("Unable to setup regional tax settings")
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def make_taxes_and_charges_template(company_name, doctype, template):
|
def make_taxes_and_charges_template(company_name, doctype, template):
|
||||||
|
@ -75,3 +75,11 @@ def update_page_info(bootinfo):
|
|||||||
"Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"},
|
"Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bootinfo(bootinfo):
|
||||||
|
if bootinfo.get("user") and bootinfo["user"].get("name"):
|
||||||
|
bootinfo["user"]["employee"] = ""
|
||||||
|
employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name")
|
||||||
|
if employee:
|
||||||
|
bootinfo["user"]["employee"] = employee
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint
|
from frappe.utils.deprecations import deprecated
|
||||||
|
|
||||||
|
|
||||||
def get_leaderboards():
|
def get_leaderboards():
|
||||||
@ -54,12 +54,13 @@ def get_leaderboards():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_customers(date_range, company, field, limit=None):
|
def get_all_customers(date_range, company, field, limit=None):
|
||||||
|
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
if field == "outstanding_amount":
|
if field == "outstanding_amount":
|
||||||
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
if from_date and to_date:
|
||||||
if date_range:
|
filters.append(["posting_date", "between", [from_date, to_date]])
|
||||||
date_range = frappe.parse_json(date_range)
|
|
||||||
filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]])
|
return frappe.get_list(
|
||||||
return frappe.db.get_all(
|
|
||||||
"Sales Invoice",
|
"Sales Invoice",
|
||||||
fields=["customer as name", "sum(outstanding_amount) as value"],
|
fields=["customer as name", "sum(outstanding_amount) as value"],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
@ -69,26 +70,20 @@ def get_all_customers(date_range, company, field, limit=None):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if field == "total_sales_amount":
|
if field == "total_sales_amount":
|
||||||
select_field = "sum(so_item.base_net_amount)"
|
select_field = "base_net_total"
|
||||||
elif field == "total_qty_sold":
|
elif field == "total_qty_sold":
|
||||||
select_field = "sum(so_item.stock_qty)"
|
select_field = "total_qty"
|
||||||
|
|
||||||
date_condition = get_date_condition(date_range, "so.transaction_date")
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.get_list(
|
||||||
"""
|
"Sales Order",
|
||||||
select so.customer as name, {0} as value
|
fields=["customer as name", f"sum({select_field}) as value"],
|
||||||
FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item
|
filters=filters,
|
||||||
ON so.name = so_item.parent
|
group_by="customer",
|
||||||
where so.docstatus = 1 {1} and so.company = %s
|
order_by="value desc",
|
||||||
group by so.customer
|
limit=limit,
|
||||||
order by value DESC
|
|
||||||
limit %s
|
|
||||||
""".format(
|
|
||||||
select_field, date_condition
|
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -96,55 +91,58 @@ def get_all_customers(date_range, company, field, limit=None):
|
|||||||
def get_all_items(date_range, company, field, limit=None):
|
def get_all_items(date_range, company, field, limit=None):
|
||||||
if field in ("available_stock_qty", "available_stock_value"):
|
if field in ("available_stock_qty", "available_stock_value"):
|
||||||
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
|
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
|
||||||
return frappe.db.get_all(
|
results = frappe.db.get_all(
|
||||||
"Bin",
|
"Bin",
|
||||||
fields=["item_code as name", "{0} as value".format(select_field)],
|
fields=["item_code as name", "{0} as value".format(select_field)],
|
||||||
group_by="item_code",
|
group_by="item_code",
|
||||||
order_by="value desc",
|
order_by="value desc",
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name"))
|
||||||
|
return [item for item in results if item["name"] in readable_active_items]
|
||||||
else:
|
else:
|
||||||
if field == "total_sales_amount":
|
if field == "total_sales_amount":
|
||||||
select_field = "sum(order_item.base_net_amount)"
|
select_field = "base_net_amount"
|
||||||
select_doctype = "Sales Order"
|
select_doctype = "Sales Order"
|
||||||
elif field == "total_purchase_amount":
|
elif field == "total_purchase_amount":
|
||||||
select_field = "sum(order_item.base_net_amount)"
|
select_field = "base_net_amount"
|
||||||
select_doctype = "Purchase Order"
|
select_doctype = "Purchase Order"
|
||||||
elif field == "total_qty_sold":
|
elif field == "total_qty_sold":
|
||||||
select_field = "sum(order_item.stock_qty)"
|
select_field = "stock_qty"
|
||||||
select_doctype = "Sales Order"
|
select_doctype = "Sales Order"
|
||||||
elif field == "total_qty_purchased":
|
elif field == "total_qty_purchased":
|
||||||
select_field = "sum(order_item.stock_qty)"
|
select_field = "stock_qty"
|
||||||
select_doctype = "Purchase Order"
|
select_doctype = "Purchase Order"
|
||||||
|
|
||||||
date_condition = get_date_condition(date_range, "sales_order.transaction_date")
|
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
child_doctype = f"{select_doctype} Item"
|
||||||
"""
|
return frappe.get_list(
|
||||||
select order_item.item_code as name, {0} as value
|
select_doctype,
|
||||||
from `tab{1}` sales_order join `tab{1} Item` as order_item
|
fields=[
|
||||||
on sales_order.name = order_item.parent
|
f"`tab{child_doctype}`.item_code as name",
|
||||||
where sales_order.docstatus = 1
|
f"sum(`tab{child_doctype}`.{select_field}) as value",
|
||||||
and sales_order.company = %s {2}
|
],
|
||||||
group by order_item.item_code
|
filters=filters,
|
||||||
order by value desc
|
order_by="value desc",
|
||||||
limit %s
|
group_by=f"`tab{child_doctype}`.item_code",
|
||||||
""".format(
|
limit=limit,
|
||||||
select_field, select_doctype, date_condition
|
)
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_suppliers(date_range, company, field, limit=None):
|
def get_all_suppliers(date_range, company, field, limit=None):
|
||||||
|
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
|
|
||||||
if field == "outstanding_amount":
|
if field == "outstanding_amount":
|
||||||
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
if from_date and to_date:
|
||||||
if date_range:
|
filters.append(["posting_date", "between", [from_date, to_date]])
|
||||||
date_range = frappe.parse_json(date_range)
|
|
||||||
filters.append(["posting_date", "between", [date_range[0], date_range[1]]])
|
return frappe.get_list(
|
||||||
return frappe.db.get_all(
|
|
||||||
"Purchase Invoice",
|
"Purchase Invoice",
|
||||||
fields=["supplier as name", "sum(outstanding_amount) as value"],
|
fields=["supplier as name", "sum(outstanding_amount) as value"],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
@ -154,48 +152,40 @@ def get_all_suppliers(date_range, company, field, limit=None):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if field == "total_purchase_amount":
|
if field == "total_purchase_amount":
|
||||||
select_field = "sum(purchase_order_item.base_net_amount)"
|
select_field = "base_net_total"
|
||||||
elif field == "total_qty_purchased":
|
elif field == "total_qty_purchased":
|
||||||
select_field = "sum(purchase_order_item.stock_qty)"
|
select_field = "total_qty"
|
||||||
|
|
||||||
date_condition = get_date_condition(date_range, "purchase_order.modified")
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.get_list(
|
||||||
"""
|
"Purchase Order",
|
||||||
select purchase_order.supplier as name, {0} as value
|
fields=["supplier as name", f"sum({select_field}) as value"],
|
||||||
FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item`
|
filters=filters,
|
||||||
as purchase_order_item ON purchase_order.name = purchase_order_item.parent
|
group_by="supplier",
|
||||||
where
|
order_by="value desc",
|
||||||
purchase_order.docstatus = 1
|
limit=limit,
|
||||||
{1}
|
)
|
||||||
and purchase_order.company = %s
|
|
||||||
group by purchase_order.supplier
|
|
||||||
order by value DESC
|
|
||||||
limit %s""".format(
|
|
||||||
select_field, date_condition
|
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_sales_partner(date_range, company, field, limit=None):
|
def get_all_sales_partner(date_range, company, field, limit=None):
|
||||||
if field == "total_sales_amount":
|
if field == "total_sales_amount":
|
||||||
select_field = "sum(`base_net_total`)"
|
select_field = "base_net_total"
|
||||||
elif field == "total_commission":
|
elif field == "total_commission":
|
||||||
select_field = "sum(`total_commission`)"
|
select_field = "total_commission"
|
||||||
|
|
||||||
filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company}
|
filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]]
|
||||||
if date_range:
|
from_date, to_date = parse_date_range(date_range)
|
||||||
date_range = frappe.parse_json(date_range)
|
if from_date and to_date:
|
||||||
filters["transaction_date"] = ["between", [date_range[0], date_range[1]]]
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.get_list(
|
return frappe.get_list(
|
||||||
"Sales Order",
|
"Sales Order",
|
||||||
fields=[
|
fields=[
|
||||||
"`sales_partner` as name",
|
"sales_partner as name",
|
||||||
"{} as value".format(select_field),
|
f"sum({select_field}) as value",
|
||||||
],
|
],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
group_by="sales_partner",
|
group_by="sales_partner",
|
||||||
@ -206,27 +196,29 @@ def get_all_sales_partner(date_range, company, field, limit=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_sales_person(date_range, company, field=None, limit=0):
|
def get_all_sales_person(date_range, company, field=None, limit=0):
|
||||||
date_condition = get_date_condition(date_range, "sales_order.transaction_date")
|
filters = [
|
||||||
|
["docstatus", "=", "1"],
|
||||||
|
["company", "=", company],
|
||||||
|
["Sales Team", "sales_person", "is", "set"],
|
||||||
|
]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.get_list(
|
||||||
"""
|
"Sales Order",
|
||||||
select sales_team.sales_person as name, sum(sales_order.base_net_total) as value
|
fields=[
|
||||||
from `tabSales Order` as sales_order join `tabSales Team` as sales_team
|
"`tabSales Team`.sales_person as name",
|
||||||
on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order'
|
"sum(`tabSales Team`.allocated_amount) as value",
|
||||||
where sales_order.docstatus = 1
|
],
|
||||||
and sales_order.company = %s
|
filters=filters,
|
||||||
{date_condition}
|
group_by="`tabSales Team`.sales_person",
|
||||||
group by sales_team.sales_person
|
order_by="value desc",
|
||||||
order by value DESC
|
limit=limit,
|
||||||
limit %s
|
|
||||||
""".format(
|
|
||||||
date_condition=date_condition
|
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated
|
||||||
def get_date_condition(date_range, field):
|
def get_date_condition(date_range, field):
|
||||||
date_condition = ""
|
date_condition = ""
|
||||||
if date_range:
|
if date_range:
|
||||||
@ -236,3 +228,11 @@ def get_date_condition(date_range, field):
|
|||||||
field, frappe.db.escape(from_date), frappe.db.escape(to_date)
|
field, frappe.db.escape(from_date), frappe.db.escape(to_date)
|
||||||
)
|
)
|
||||||
return date_condition
|
return date_condition
|
||||||
|
|
||||||
|
|
||||||
|
def parse_date_range(date_range):
|
||||||
|
if date_range:
|
||||||
|
date_range = frappe.parse_json(date_range)
|
||||||
|
return date_range[0], date_range[1]
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
@ -121,7 +121,7 @@ frappe.ui.form.on('Serial and Batch Bundle', {
|
|||||||
frappe.throw(__("Please attach CSV file"));
|
frappe.throw(__("Please attach CSV file"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) {
|
if (frm.doc.has_serial_no && !prompt_data.csv_file && !prompt_data.serial_nos) {
|
||||||
frappe.throw(__("Please enter serial nos"));
|
frappe.throw(__("Please enter serial nos"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2022-09-29 14:56:38.338267",
|
"creation": "2023-08-11 17:22:12.907518",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -250,7 +250,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-28 12:56:03.072224",
|
"modified": "2023-12-07 17:56:55.528563",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Bundle",
|
"name": "Serial and Batch Bundle",
|
||||||
@ -270,6 +270,118 @@
|
|||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Purchase User",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Purchase Manager",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock User",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Delivery User",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Delivery Manager",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Manufacturing User",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Manufacturing Manager",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
@ -506,6 +506,22 @@ class SerialandBatchBundle(Document):
|
|||||||
serial_batches = {}
|
serial_batches = {}
|
||||||
|
|
||||||
for row in self.entries:
|
for row in self.entries:
|
||||||
|
if self.has_serial_no and not row.serial_no:
|
||||||
|
frappe.throw(
|
||||||
|
_("At row {0}: Serial No is mandatory for Item {1}").format(
|
||||||
|
bold(row.idx), bold(self.item_code)
|
||||||
|
),
|
||||||
|
title=_("Serial No is mandatory"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.has_batch_no and not row.batch_no:
|
||||||
|
frappe.throw(
|
||||||
|
_("At row {0}: Batch No is mandatory for Item {1}").format(
|
||||||
|
bold(row.idx), bold(self.item_code)
|
||||||
|
),
|
||||||
|
title=_("Batch No is mandatory"),
|
||||||
|
)
|
||||||
|
|
||||||
if row.serial_no:
|
if row.serial_no:
|
||||||
serial_nos.append(row.serial_no)
|
serial_nos.append(row.serial_no)
|
||||||
|
|
||||||
@ -688,6 +704,7 @@ class SerialandBatchBundle(Document):
|
|||||||
"item_code": self.item_code,
|
"item_code": self.item_code,
|
||||||
"warehouse": self.warehouse,
|
"warehouse": self.warehouse,
|
||||||
"batch_no": batches,
|
"batch_no": batches,
|
||||||
|
"consider_negative_batches": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -698,6 +715,9 @@ class SerialandBatchBundle(Document):
|
|||||||
available_batches = get_available_batches_qty(available_batches)
|
available_batches = get_available_batches_qty(available_batches)
|
||||||
for batch_no in batches:
|
for batch_no in batches:
|
||||||
if batch_no not in available_batches or available_batches[batch_no] < 0:
|
if batch_no not in available_batches or available_batches[batch_no] < 0:
|
||||||
|
if flt(available_batches.get(batch_no)) < 0:
|
||||||
|
self.validate_negative_batch(batch_no, available_batches[batch_no])
|
||||||
|
|
||||||
self.throw_error_message(
|
self.throw_error_message(
|
||||||
f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}"
|
f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}"
|
||||||
)
|
)
|
||||||
@ -789,6 +809,9 @@ def parse_csv_file_to_get_serial_batch(reader):
|
|||||||
if index == 0:
|
if index == 0:
|
||||||
has_serial_no = row[0] == "Serial No"
|
has_serial_no = row[0] == "Serial No"
|
||||||
has_batch_no = row[0] == "Batch No"
|
has_batch_no = row[0] == "Batch No"
|
||||||
|
if not has_batch_no:
|
||||||
|
has_batch_no = row[1] == "Batch No"
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not row[0]:
|
if not row[0]:
|
||||||
@ -805,6 +828,13 @@ def parse_csv_file_to_get_serial_batch(reader):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
batch_nos.append(
|
||||||
|
{
|
||||||
|
"batch_no": row[1],
|
||||||
|
"qty": row[2],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
serial_nos.append(_dict)
|
serial_nos.append(_dict)
|
||||||
elif has_batch_no:
|
elif has_batch_no:
|
||||||
batch_nos.append(
|
batch_nos.append(
|
||||||
@ -840,6 +870,9 @@ def make_serial_nos(item_code, serial_nos):
|
|||||||
serial_nos_details = []
|
serial_nos_details = []
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
|
if frappe.db.exists("Serial No", serial_no):
|
||||||
|
continue
|
||||||
|
|
||||||
serial_nos_details.append(
|
serial_nos_details.append(
|
||||||
(
|
(
|
||||||
serial_no,
|
serial_no,
|
||||||
@ -870,7 +903,7 @@ def make_serial_nos(item_code, serial_nos):
|
|||||||
|
|
||||||
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
|
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
|
||||||
|
|
||||||
frappe.msgprint(_("Serial Nos are created successfully"))
|
frappe.msgprint(_("Serial Nos are created successfully"), alert=True)
|
||||||
|
|
||||||
|
|
||||||
def make_batch_nos(item_code, batch_nos):
|
def make_batch_nos(item_code, batch_nos):
|
||||||
@ -881,6 +914,9 @@ def make_batch_nos(item_code, batch_nos):
|
|||||||
batch_nos_details = []
|
batch_nos_details = []
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
for batch_no in batch_nos:
|
for batch_no in batch_nos:
|
||||||
|
if frappe.db.exists("Batch", batch_no):
|
||||||
|
continue
|
||||||
|
|
||||||
batch_nos_details.append(
|
batch_nos_details.append(
|
||||||
(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
|
(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
|
||||||
)
|
)
|
||||||
@ -899,7 +935,7 @@ def make_batch_nos(item_code, batch_nos):
|
|||||||
|
|
||||||
frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))
|
frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))
|
||||||
|
|
||||||
frappe.msgprint(_("Batch Nos are created successfully"))
|
frappe.msgprint(_("Batch Nos are created successfully"), alert=True)
|
||||||
|
|
||||||
|
|
||||||
def parse_serial_nos(data):
|
def parse_serial_nos(data):
|
||||||
@ -1454,7 +1490,8 @@ def get_auto_batch_nos(kwargs):
|
|||||||
available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches
|
available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches
|
||||||
)
|
)
|
||||||
|
|
||||||
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
|
if not kwargs.consider_negative_batches:
|
||||||
|
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
|
||||||
|
|
||||||
if not qty:
|
if not qty:
|
||||||
return available_batches
|
return available_batches
|
||||||
|
@ -368,6 +368,58 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
|||||||
# Batch does not belong to serial no
|
# Batch does not belong to serial no
|
||||||
self.assertRaises(frappe.exceptions.ValidationError, doc.save)
|
self.assertRaises(frappe.exceptions.ValidationError, doc.save)
|
||||||
|
|
||||||
|
def test_auto_delete_draft_serial_and_batch_bundle(self):
|
||||||
|
serial_and_batch_code = "New Serial No Auto Delete 1"
|
||||||
|
make_item(
|
||||||
|
serial_and_batch_code,
|
||||||
|
{
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "TEST-SER-VALL-.#####",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ste = make_stock_entry(
|
||||||
|
item_code=serial_and_batch_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=1,
|
||||||
|
rate=500,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_no = "SN-TEST-AUTO-DEL"
|
||||||
|
if not frappe.db.exists("Serial No", serial_no):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Serial No",
|
||||||
|
"serial_no": serial_no,
|
||||||
|
"item_code": serial_and_batch_code,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
bundle_doc = make_serial_batch_bundle(
|
||||||
|
{
|
||||||
|
"item_code": serial_and_batch_code,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"posting_date": ste.posting_date,
|
||||||
|
"posting_time": ste.posting_time,
|
||||||
|
"qty": 1,
|
||||||
|
"serial_nos": [serial_no],
|
||||||
|
"type_of_transaction": "Inward",
|
||||||
|
"do_not_submit": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
bundle_doc.reload()
|
||||||
|
ste.items[0].serial_and_batch_bundle = bundle_doc.name
|
||||||
|
ste.save()
|
||||||
|
ste.reload()
|
||||||
|
|
||||||
|
ste.delete()
|
||||||
|
self.assertFalse(frappe.db.exists("Serial and Batch Bundle", bundle_doc.name))
|
||||||
|
|
||||||
|
|
||||||
def get_batch_from_bundle(bundle):
|
def get_batch_from_bundle(bundle):
|
||||||
from erpnext.stock.serial_batch_bundle import get_batch_nos
|
from erpnext.stock.serial_batch_bundle import get_batch_nos
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Serial No",
|
"label": "Serial No",
|
||||||
"mandatory_depends_on": "eval:parent.has_serial_no == 1",
|
|
||||||
"options": "Serial No",
|
"options": "Serial No",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@ -38,7 +37,6 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Batch No",
|
"label": "Batch No",
|
||||||
"mandatory_depends_on": "eval:parent.has_batch_no == 1",
|
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@ -122,7 +120,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-03 15:29:50.199075",
|
"modified": "2023-12-10 19:47:48.227772",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Entry",
|
"name": "Serial and Batch Entry",
|
||||||
|
@ -1737,6 +1737,45 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
self.assertFalse(doc.is_enqueue_action())
|
self.assertFalse(doc.is_enqueue_action())
|
||||||
frappe.flags.in_test = True
|
frappe.flags.in_test = True
|
||||||
|
|
||||||
|
def test_negative_batch(self):
|
||||||
|
item_code = "Test Negative Batch Item - 001"
|
||||||
|
make_item(
|
||||||
|
item_code,
|
||||||
|
{"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"},
|
||||||
|
)
|
||||||
|
|
||||||
|
se1 = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
qty=100,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
se1.reload()
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
se2 = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
purpose="Material Issue",
|
||||||
|
batch_no=batch_no,
|
||||||
|
qty=10,
|
||||||
|
source="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
se2.reload()
|
||||||
|
|
||||||
|
se3 = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
qty=100,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
se3.reload()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, se1.cancel)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -209,7 +209,7 @@ frappe.ui.form.on("Stock Reconciliation", {
|
|||||||
|
|
||||||
set_amount_quantity: function(doc, cdt, cdn) {
|
set_amount_quantity: function(doc, cdt, cdn) {
|
||||||
var d = frappe.model.get_doc(cdt, cdn);
|
var d = frappe.model.get_doc(cdt, cdn);
|
||||||
if (d.qty & d.valuation_rate) {
|
if (d.qty && d.valuation_rate) {
|
||||||
frappe.model.set_value(cdt, cdn, "amount", flt(d.qty) * flt(d.valuation_rate));
|
frappe.model.set_value(cdt, cdn, "amount", flt(d.qty) * flt(d.valuation_rate));
|
||||||
frappe.model.set_value(cdt, cdn, "quantity_difference", flt(d.qty) - flt(d.current_qty));
|
frappe.model.set_value(cdt, cdn, "quantity_difference", flt(d.qty) - flt(d.current_qty));
|
||||||
frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount));
|
frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount));
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle
|
||||||
from erpnext.stock.stock_ledger import get_stock_ledger_entries
|
from erpnext.stock.stock_ledger import get_stock_ledger_entries
|
||||||
|
|
||||||
|
|
||||||
@ -15,8 +18,8 @@ def execute(filters=None):
|
|||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
columns = [
|
columns = [
|
||||||
{"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"},
|
{"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 120},
|
||||||
{"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"},
|
{"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90},
|
||||||
{
|
{
|
||||||
"label": _("Voucher Type"),
|
"label": _("Voucher Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -29,7 +32,7 @@ def get_columns(filters):
|
|||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"fieldname": "voucher_no",
|
"fieldname": "voucher_no",
|
||||||
"options": "voucher_type",
|
"options": "voucher_type",
|
||||||
"width": 180,
|
"width": 230,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Company"),
|
"label": _("Company"),
|
||||||
@ -49,7 +52,7 @@ def get_columns(filters):
|
|||||||
"label": _("Status"),
|
"label": _("Status"),
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"width": 120,
|
"width": 90,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Serial No"),
|
"label": _("Serial No"),
|
||||||
@ -62,7 +65,7 @@ def get_columns(filters):
|
|||||||
"label": _("Valuation Rate"),
|
"label": _("Valuation Rate"),
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"fieldname": "valuation_rate",
|
"fieldname": "valuation_rate",
|
||||||
"width": 150,
|
"width": 130,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Qty"),
|
"label": _("Qty"),
|
||||||
@ -102,15 +105,29 @@ def get_data(filters):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}]
|
serial_nos = []
|
||||||
|
if row.serial_no:
|
||||||
|
parsed_serial_nos = get_serial_nos_from_sle(row.serial_no)
|
||||||
|
for serial_no in parsed_serial_nos:
|
||||||
|
if filters.get("serial_no") and filters.get("serial_no") != serial_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
serial_nos.append(
|
||||||
|
{
|
||||||
|
"serial_no": serial_no,
|
||||||
|
"valuation_rate": abs(row.stock_value_difference / row.actual_qty),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if row.serial_and_batch_bundle:
|
if row.serial_and_batch_bundle:
|
||||||
serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])
|
serial_nos.extend(bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []))
|
||||||
|
|
||||||
for index, bundle_data in enumerate(serial_nos):
|
for index, bundle_data in enumerate(serial_nos):
|
||||||
if index == 0:
|
if index == 0:
|
||||||
args.serial_no = bundle_data.get("serial_no")
|
new_args = copy.deepcopy(args)
|
||||||
args.valuation_rate = bundle_data.get("valuation_rate")
|
new_args.serial_no = bundle_data.get("serial_no")
|
||||||
data.append(args)
|
new_args.valuation_rate = bundle_data.get("valuation_rate")
|
||||||
|
data.append(new_args)
|
||||||
else:
|
else:
|
||||||
data.append(
|
data.append(
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ from frappe.model.mapper import get_mapped_doc
|
|||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
|
from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status
|
||||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||||
from erpnext.stock.stock_balance import update_bin_qty
|
from erpnext.stock.stock_balance import update_bin_qty
|
||||||
from erpnext.stock.utils import get_bin
|
from erpnext.stock.utils import get_bin
|
||||||
@ -308,6 +309,9 @@ class SubcontractingOrder(SubcontractingController):
|
|||||||
"Subcontracting Order", self.name, "status", status, update_modified=update_modified
|
"Subcontracting Order", self.name, "status", status, update_modified=update_modified
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if status == "Closed":
|
||||||
|
update_po_status("Closed", self.purchase_order)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_subcontracting_receipt(source_name, target_doc=None):
|
def make_subcontracting_receipt(source_name, target_doc=None):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user