mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-07 23:31:20 +00:00
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
This commit is contained in:
@@ -175,6 +175,61 @@ class TestQuotation(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertTrue(quotation.payment_schedule)
|
self.assertTrue(quotation.payment_schedule)
|
||||||
|
|
||||||
|
def test_terms_attachments_are_copied_to_quotation(self):
|
||||||
|
terms = make_terms_and_conditions(copy_attachments_to_transaction=True)
|
||||||
|
first_attachment = make_file_attachment(
|
||||||
|
"Terms and Conditions",
|
||||||
|
terms.name,
|
||||||
|
content="First terms attachment",
|
||||||
|
)
|
||||||
|
|
||||||
|
quotation = make_quotation(do_not_save=1)
|
||||||
|
quotation.tc_name = terms.name
|
||||||
|
quotation.insert()
|
||||||
|
|
||||||
|
self.assertEqual(get_attachment_urls("Quotation", quotation.name), {first_attachment.file_url})
|
||||||
|
|
||||||
|
second_attachment = make_file_attachment(
|
||||||
|
"Terms and Conditions",
|
||||||
|
terms.name,
|
||||||
|
content="Second terms attachment",
|
||||||
|
)
|
||||||
|
quotation.valid_till = add_days(getdate(quotation.valid_till), 1)
|
||||||
|
quotation.save()
|
||||||
|
|
||||||
|
quotation_attachments = get_attachment_urls("Quotation", quotation.name)
|
||||||
|
self.assertEqual(quotation_attachments, {first_attachment.file_url})
|
||||||
|
self.assertNotIn(second_attachment.file_url, quotation_attachments)
|
||||||
|
|
||||||
|
new_terms = make_terms_and_conditions(copy_attachments_to_transaction=True)
|
||||||
|
new_terms_attachment = make_file_attachment(
|
||||||
|
"Terms and Conditions",
|
||||||
|
new_terms.name,
|
||||||
|
content="Attachment from updated terms",
|
||||||
|
)
|
||||||
|
quotation.tc_name = new_terms.name
|
||||||
|
quotation.valid_till = add_days(getdate(quotation.valid_till), 1)
|
||||||
|
quotation.save()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_attachment_urls("Quotation", quotation.name),
|
||||||
|
{first_attachment.file_url, new_terms_attachment.file_url},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_terms_attachments_are_not_copied_when_disabled(self):
|
||||||
|
terms = make_terms_and_conditions(copy_attachments_to_transaction=False)
|
||||||
|
make_file_attachment(
|
||||||
|
"Terms and Conditions",
|
||||||
|
terms.name,
|
||||||
|
content="Terms attachment should stay on the template",
|
||||||
|
)
|
||||||
|
|
||||||
|
quotation = make_quotation(do_not_save=1)
|
||||||
|
quotation.tc_name = terms.name
|
||||||
|
quotation.insert()
|
||||||
|
|
||||||
|
self.assertFalse(get_attachment_urls("Quotation", quotation.name))
|
||||||
|
|
||||||
@change_settings(
|
@change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"automatically_fetch_payment_terms": 1},
|
{"automatically_fetch_payment_terms": 1},
|
||||||
@@ -1142,6 +1197,42 @@ def get_quotation_dict(party_name=None, item_code=None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def make_terms_and_conditions(copy_attachments_to_transaction=False):
|
||||||
|
return frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Terms and Conditions",
|
||||||
|
"title": f"_Test Terms and Conditions {frappe.generate_hash(length=8)}",
|
||||||
|
"selling": 1,
|
||||||
|
"terms": "Test terms",
|
||||||
|
"copy_attachments_to_transaction": 1 if copy_attachments_to_transaction else 0,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def make_file_attachment(doctype, docname, content):
|
||||||
|
return frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "File",
|
||||||
|
"file_name": f"terms-attachment-{frappe.generate_hash(length=8)}.txt",
|
||||||
|
"attached_to_doctype": doctype,
|
||||||
|
"attached_to_name": docname,
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def get_attachment_urls(doctype, docname):
|
||||||
|
return {
|
||||||
|
file.file_url
|
||||||
|
for file in frappe.get_all(
|
||||||
|
"File",
|
||||||
|
filters={"attached_to_doctype": doctype, "attached_to_name": docname},
|
||||||
|
fields=["file_url"],
|
||||||
|
)
|
||||||
|
if file.file_url
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def make_quotation(**args):
|
def make_quotation(**args):
|
||||||
qo = frappe.new_doc("Quotation")
|
qo = frappe.new_doc("Quotation")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
"title",
|
||||||
"disabled",
|
"disabled",
|
||||||
|
"column_break_ofhb",
|
||||||
|
"copy_attachments_to_transaction",
|
||||||
"applicable_modules_section",
|
"applicable_modules_section",
|
||||||
"selling",
|
"selling",
|
||||||
"buying",
|
"buying",
|
||||||
@@ -72,12 +74,22 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_7",
|
"fieldname": "section_break_7",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_ofhb",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "copy_attachments_to_transaction",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Copy Attachments to Transaction"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-legal",
|
"icon": "icon-legal",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-01-30 12:47:52.325531",
|
"modified": "2026-04-29 22:51:49.285298",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Terms and Conditions",
|
"name": "Terms and Conditions",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class TermsandConditions(Document):
|
|||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
buying: DF.Check
|
buying: DF.Check
|
||||||
|
copy_attachments_to_transaction: DF.Check
|
||||||
disabled: DF.Check
|
disabled: DF.Check
|
||||||
selling: DF.Check
|
selling: DF.Check
|
||||||
terms: DF.TextEditor | None
|
terms: DF.TextEditor | None
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ class UOMMustBeIntegerError(frappe.ValidationError):
|
|||||||
|
|
||||||
|
|
||||||
class TransactionBase(StatusUpdater):
|
class TransactionBase(StatusUpdater):
|
||||||
|
def on_change(self):
|
||||||
|
# `on_change` also fires for `db_set()`, so only run during an actual insert/save.
|
||||||
|
is_real_save = self.flags.in_insert or (self.doctype, self.name) in frappe.flags.currently_saving
|
||||||
|
if not is_real_save:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.copy_terms_and_conditions_attachments()
|
||||||
|
|
||||||
def validate_posting_time(self):
|
def validate_posting_time(self):
|
||||||
# set Edit Posting Date and Time to 1 while data import
|
# set Edit Posting Date and Time to 1 while data import
|
||||||
if frappe.flags.in_import and self.posting_date:
|
if frappe.flags.in_import and self.posting_date:
|
||||||
@@ -33,6 +41,56 @@ class TransactionBase(StatusUpdater):
|
|||||||
def validate_uom_is_integer(self, uom_field, qty_fields, child_dt=None):
|
def validate_uom_is_integer(self, uom_field, qty_fields, child_dt=None):
|
||||||
validate_uom_is_integer(self, uom_field, qty_fields, child_dt)
|
validate_uom_is_integer(self, uom_field, qty_fields, child_dt)
|
||||||
|
|
||||||
|
def copy_terms_and_conditions_attachments(self):
|
||||||
|
if (
|
||||||
|
not self.name
|
||||||
|
or not self.meta.has_field("tc_name")
|
||||||
|
or not self.tc_name
|
||||||
|
or not self.has_value_changed("tc_name")
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
copy_attachments_to_transaction = frappe.db.get_value(
|
||||||
|
"Terms and Conditions", self.tc_name, "copy_attachments_to_transaction"
|
||||||
|
)
|
||||||
|
if not cint(copy_attachments_to_transaction):
|
||||||
|
return
|
||||||
|
|
||||||
|
source_attachments = frappe.get_all(
|
||||||
|
"File",
|
||||||
|
filters={
|
||||||
|
"attached_to_doctype": "Terms and Conditions",
|
||||||
|
"attached_to_name": self.tc_name,
|
||||||
|
},
|
||||||
|
fields=["name", "file_url"],
|
||||||
|
)
|
||||||
|
if not source_attachments:
|
||||||
|
return
|
||||||
|
|
||||||
|
existing_file_urls = {
|
||||||
|
attachment.file_url
|
||||||
|
for attachment in frappe.get_all(
|
||||||
|
"File",
|
||||||
|
filters={
|
||||||
|
"attached_to_doctype": self.doctype,
|
||||||
|
"attached_to_name": self.name,
|
||||||
|
},
|
||||||
|
fields=["file_url"],
|
||||||
|
)
|
||||||
|
if attachment.file_url
|
||||||
|
}
|
||||||
|
|
||||||
|
for source_attachment in source_attachments:
|
||||||
|
if not source_attachment.file_url or source_attachment.file_url in existing_file_urls:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Reuse the existing file metadata so the same on-disk blob is shared.
|
||||||
|
new_attachment = frappe.get_doc("File", source_attachment.name).create_attachment_copy(
|
||||||
|
attached_to_doctype=self.doctype,
|
||||||
|
attached_to_name=self.name,
|
||||||
|
)
|
||||||
|
existing_file_urls.add(new_attachment.file_url)
|
||||||
|
|
||||||
def validate_with_previous_doc(self, ref):
|
def validate_with_previous_doc(self, ref):
|
||||||
self.exclude_fields = ["conversion_factor", "uom"] if self.get("is_return") else []
|
self.exclude_fields = ["conversion_factor", "uom"] if self.get("is_return") else []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user