Merge branch 'develop' of github.com:aerele/erpnext into update_actual_qty

This commit is contained in:
Bhavan23
2024-10-08 16:23:25 +05:30
544 changed files with 207737 additions and 194594 deletions

View File

@@ -5,8 +5,8 @@
import json
import frappe
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.tests.utils import make_test_records
from frappe.utils import flt
from erpnext.accounts.party import get_due_date
@@ -23,7 +23,16 @@ test_dependencies = ["Payment Term", "Payment Terms Template"]
test_records = frappe.get_test_records("Customer")
class TestCustomer(FrappeTestCase):
class UnitTestCustomer(UnitTestCase):
"""
Unit tests for Customer.
Use this class for testing individual functions and methods.
"""
pass
class TestCustomer(IntegrationTestCase):
def setUp(self):
if not frappe.get_value("Item", "_Test Item"):
make_test_records("Item")

View File

@@ -1,10 +1,11 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
from frappe.tests import IntegrationTestCase
# test_records = frappe.get_test_records('Installation Note')
class TestInstallationNote(unittest.TestCase):
class TestInstallationNote(IntegrationTestCase):
pass

View File

@@ -2,7 +2,7 @@
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.tests import IntegrationTestCase, UnitTestCase
from erpnext.controllers.queries import item_query
@@ -18,7 +18,16 @@ def create_party_specific_item(**args):
psi.insert()
class TestPartySpecificItem(FrappeTestCase):
class UnitTestPartySpecificItem(UnitTestCase):
"""
Unit tests for PartySpecificItem.
Use this class for testing individual functions and methods.
"""
pass
class TestPartySpecificItem(IntegrationTestCase):
def setUp(self):
self.customer = frappe.get_last_doc("Customer")
self.supplier = frappe.get_last_doc("Supplier")

View File

@@ -71,7 +71,7 @@ frappe.ui.form.on("Quotation", {
frm.trigger("set_label");
frm.trigger("toggle_reqd_lead_customer");
frm.trigger("set_dynamic_field_label");
frm.set_value("party_name", "");
// frm.set_value("party_name", ""); // removed to set party_name from url for crm integration
frm.set_value("customer_name", "");
},

View File

@@ -124,8 +124,10 @@
"customer_group",
"territory",
"column_break_108",
"campaign",
"source",
"utm_source",
"utm_campaign",
"utm_medium",
"utm_content",
"column_break4",
"opportunity",
"supplier_quotation",
@@ -853,24 +855,6 @@
"fieldtype": "Button",
"label": "Update Auto Repeat Reference"
},
{
"fieldname": "campaign",
"fieldtype": "Link",
"label": "Campaign",
"oldfieldname": "campaign",
"oldfieldtype": "Link",
"options": "Campaign",
"print_hide": 1
},
{
"fieldname": "source",
"fieldtype": "Link",
"label": "Source",
"oldfieldname": "source",
"oldfieldtype": "Select",
"options": "Lead Source",
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.status=='Lost'",
@@ -1068,13 +1052,44 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"fieldname": "utm_campaign",
"fieldtype": "Link",
"label": "Campaign",
"oldfieldname": "campaign",
"oldfieldtype": "Link",
"options": "UTM Campaign",
"print_hide": 1
},
{
"fieldname": "utm_source",
"fieldtype": "Link",
"label": "Source",
"oldfieldname": "source",
"oldfieldtype": "Select",
"options": "UTM Source",
"print_hide": 1
},
{
"fieldname": "utm_medium",
"print_hide": 1,
"fieldtype": "Link",
"label": "Medium",
"options": "UTM Medium"
},
{
"fieldname": "utm_content",
"print_hide": 1,
"fieldtype": "Data",
"label": "Content"
}
],
"icon": "fa fa-shopping-cart",
"idx": 82,
"is_submittable": 1,
"links": [],
"modified": "2024-04-20 01:15:19.171383",
"modified": "2024-06-28 10:32:47.638342",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
@@ -1172,4 +1187,4 @@
"states": [],
"timeline_field": "party_name",
"title_field": "title"
}
}

View File

@@ -46,7 +46,6 @@ class Quotation(SellingController):
base_rounding_adjustment: DF.Currency
base_total: DF.Currency
base_total_taxes_and_charges: DF.Currency
campaign: DF.Link | None
company: DF.Link
company_address: DF.Link | None
company_address_display: DF.TextEditor | None
@@ -96,7 +95,6 @@ class Quotation(SellingController):
shipping_address: DF.TextEditor | None
shipping_address_name: DF.Link | None
shipping_rule: DF.Link | None
source: DF.Link | None
status: DF.Literal[
"Draft", "Open", "Replied", "Partially Ordered", "Ordered", "Lost", "Cancelled", "Expired"
]
@@ -113,6 +111,10 @@ class Quotation(SellingController):
total_qty: DF.Float
total_taxes_and_charges: DF.Currency
transaction_date: DF.Date
utm_campaign: DF.Link | None
utm_content: DF.Data | None
utm_medium: DF.Link | None
utm_source: DF.Link | None
valid_till: DF.Date | None
# end: auto-generated types
@@ -220,6 +222,10 @@ class Quotation(SellingController):
"Lead", self.party_name, ["lead_name", "company_name"]
)
self.customer_name = company_name or lead_name
elif self.party_name and self.quotation_to == "Prospect":
self.customer_name = self.party_name
elif self.party_name and self.quotation_to == "CRM Deal":
self.customer_name = frappe.db.get_value("CRM Deal", self.party_name, "organization")
def update_opportunity(self, status):
for opportunity in set(d.prevdoc_docname for d in self.get("items")):

View File

@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.utils import add_days, add_months, flt, getdate, nowdate
from erpnext.controllers.accounts_controller import InvalidQtyError
@@ -10,7 +10,16 @@ from erpnext.controllers.accounts_controller import InvalidQtyError
test_dependencies = ["Product Bundle"]
class TestQuotation(FrappeTestCase):
class UnitTestQuotation(UnitTestCase):
"""
Unit tests for Quotation.
Use this class for testing individual functions and methods.
"""
pass
class TestQuotation(IntegrationTestCase):
def test_quotation_qty(self):
qo = make_quotation(qty=0, do_not_save=True)
with self.assertRaises(InvalidQtyError):
@@ -573,12 +582,50 @@ class TestQuotation(FrappeTestCase):
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 10,
"included_in_print_rate": 1,
},
)
quotation.submit()
self.assertEqual(quotation.net_total, 290)
self.assertEqual(quotation.grand_total, 319)
self.assertEqual(round(quotation.items[1].net_rate, 2), 136.36)
self.assertEqual(round(quotation.items[1].amount, 2), 150)
self.assertEqual(round(quotation.items[2].net_rate, 2), 163.64)
self.assertEqual(round(quotation.items[2].amount, 2), 180)
self.assertEqual(round(quotation.net_total, 2), 263.64)
self.assertEqual(round(quotation.total_taxes_and_charges, 2), 26.36)
self.assertEqual(quotation.grand_total, 290)
def test_amount_calculation_for_alternative_items(self):
"""Make sure that the amount is calculated correctly for alternative items when the qty is changed."""
from erpnext.stock.doctype.item.test_item import make_item
item_list = []
stock_items = {
"_Test Simple Item 1": 100,
"_Test Alt 1": 120,
}
for item, rate in stock_items.items():
make_item(item, {"is_stock_item": 0})
item_list.append(
{
"item_code": item,
"qty": 1,
"rate": rate,
"is_alternative": "Alt" in item,
}
)
quotation = make_quotation(item_list=item_list, do_not_submit=1)
self.assertEqual(quotation.items[1].amount, 120)
quotation.items[1].qty = 2
quotation.save()
self.assertEqual(quotation.items[1].amount, 240)
def test_alternative_items_sales_order_mapping_with_stock_items(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order

View File

@@ -34,6 +34,7 @@
"column_break_18",
"discount_percentage",
"discount_amount",
"distributed_discount_amount",
"base_rate_with_margin",
"section_break1",
"rate",
@@ -235,7 +236,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount || doc.distributed_discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -662,12 +663,18 @@
"label": "Has Alternative Item",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:31.183320",
"modified": "2024-06-02 06:21:09.508680",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",

View File

@@ -32,6 +32,7 @@ class QuotationItem(Document):
description: DF.TextEditor | None
discount_amount: DF.Currency
discount_percentage: DF.Percent
distributed_discount_amount: DF.Currency
gross_profit: DF.Currency
has_alternative_item: DF.Check
image: DF.Attach | None

View File

@@ -1249,7 +1249,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
],
},
],
primary_action_label: "Create Purchase Order",
primary_action_label: __("Create Purchase Order"),
primary_action(args) {
if (!args) return;

View File

@@ -159,10 +159,13 @@
"additional_info_section",
"is_internal_customer",
"represents_company",
"column_break_yvzv",
"utm_source",
"utm_campaign",
"utm_medium",
"utm_content",
"column_break_152",
"source",
"inter_company_order_reference",
"campaign",
"party_account_currency",
"connections_tab"
],
@@ -1165,28 +1168,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "source",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Source",
"oldfieldname": "source",
"oldfieldtype": "Select",
"options": "Lead Source",
"print_hide": 1
},
{
"fieldname": "campaign",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Campaign",
"oldfieldname": "campaign",
"oldfieldtype": "Link",
"options": "Campaign",
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "printing_details",
@@ -1656,13 +1637,52 @@
"no_copy": 1,
"options": "Not Requested\nRequested\nPartially Paid\nFully Paid",
"print_hide": 1
},
{
"fieldname": "column_break_yvzv",
"fieldtype": "Column Break"
},
{
"fieldname": "utm_medium",
"print_hide": 1,
"fieldtype": "Link",
"label": "Medium",
"options": "UTM Medium"
},
{
"fieldname": "utm_content",
"print_hide": 1,
"fieldtype": "Data",
"label": "Content"
},
{
"fieldname": "utm_source",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Source",
"oldfieldname": "source",
"oldfieldtype": "Select",
"options": "UTM Source",
"print_hide": 1
},
{
"fieldname": "utm_campaign",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Campaign",
"oldfieldname": "campaign",
"oldfieldtype": "Link",
"options": "UTM Campaign",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2024-05-27 18:51:54.905804",
"modified": "2024-06-28 10:36:23.824623",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
@@ -1740,4 +1760,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -81,7 +81,6 @@ class SalesOrder(SellingController):
base_total: DF.Currency
base_total_taxes_and_charges: DF.Currency
billing_status: DF.Literal["Not Billed", "Fully Billed", "Partly Billed", "Closed"]
campaign: DF.Link | None
commission_rate: DF.Float
company: DF.Link
company_address: DF.Link | None
@@ -152,7 +151,6 @@ class SalesOrder(SellingController):
shipping_address_name: DF.Link | None
shipping_rule: DF.Link | None
skip_delivery_note: DF.Check
source: DF.Link | None
status: DF.Literal[
"",
"Draft",
@@ -180,12 +178,18 @@ class SalesOrder(SellingController):
total_qty: DF.Float
total_taxes_and_charges: DF.Currency
transaction_date: DF.Date
utm_campaign: DF.Link | None
utm_content: DF.Data | None
utm_medium: DF.Link | None
utm_source: DF.Link | None
# end: auto-generated types
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def onload(self) -> None:
super().onload()
if frappe.db.get_single_value("Stock Settings", "enable_stock_reservation"):
if self.has_unreserved_stock():
self.set_onload("has_unreserved_stock", True)
@@ -1045,6 +1049,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
)
dn_item.qty = flt(sre.reserved_qty) * flt(dn_item.get("conversion_factor", 1))
dn_item.warehouse = sre.warehouse
if sre.reservation_based_on == "Serial and Batch" and (sre.has_serial_no or sre.has_batch_no):
dn_item.serial_and_batch_bundle = get_ssb_bundle_for_voucher(sre)
@@ -1093,6 +1098,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
# set the redeem loyalty points if provided via shopping cart
if source.loyalty_points and source.order_type == "Shopping Cart":
target.redeem_loyalty_points = 1
target.loyalty_points = source.loyalty_points
target.debit_to = get_party_account("Customer", source.customer, source.company)

View File

@@ -7,7 +7,7 @@ from unittest.mock import patch
import frappe
import frappe.permissions
from frappe.core.doctype.user_permission.test_user_permission import create_user
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.tests import IntegrationTestCase
from frappe.utils import add_days, flt, getdate, nowdate, today
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
@@ -33,7 +33,7 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@@ -53,6 +53,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.create_customer("_Test Customer Credit")
def tearDown(self):
frappe.db.rollback()
frappe.set_user("Administrator")
def test_sales_order_with_negative_rate(self):
@@ -1315,7 +1316,9 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertRaises(frappe.LinkExistsError, so_doc.cancel)
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
@IntegrationTestCase.change_settings(
"Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1}
)
def test_advance_paid_upon_payment_cancellation(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
@@ -1905,7 +1908,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertEqual(len(dn.packed_items), 1)
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2")
@change_settings("Selling Settings", {"editable_bundle_item_rates": 1})
@IntegrationTestCase.change_settings("Selling Settings", {"editable_bundle_item_rates": 1})
def test_expired_rate_for_packed_item(self):
bundle = "_Test Product Bundle 1"
packed_item = "_Packed Item 1"
@@ -2197,6 +2200,75 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertRaises(frappe.ValidationError, so1.update_status, "Draft")
@IntegrationTestCase.change_settings("Stock Settings", {"enable_stock_reservation": True})
def test_warehouse_mapping_based_on_stock_reservation(self):
self.create_company(company_name="Glass Ceiling", abbr="GC")
self.create_item("Lamy Safari 2", True, self.warehouse_stores, self.company, 2000)
self.create_customer()
self.clear_old_entries()
so = frappe.new_doc("Sales Order")
so.company = self.company
so.customer = self.customer
so.transaction_date = today()
so.append(
"items",
{
"item_code": self.item,
"qty": 10,
"rate": 2000,
"warehouse": self.warehouse_stores,
"delivery_date": today(),
},
)
so.submit()
# Create stock
se = frappe.get_doc(
{
"doctype": "Stock Entry",
"company": self.company,
"stock_entry_type": "Material Receipt",
"posting_date": today(),
"items": [
{"item_code": self.item, "t_warehouse": self.warehouse_stores, "qty": 5},
{"item_code": self.item, "t_warehouse": self.warehouse_finished_goods, "qty": 5},
],
}
)
se.submit()
# Reserve stock on 2 different warehouses
itm = so.items[0]
so.create_stock_reservation_entries(
[
{
"sales_order_item": itm.name,
"item_code": itm.item_code,
"warehouse": self.warehouse_stores,
"qty_to_reserve": 2,
}
]
)
so.create_stock_reservation_entries(
[
{
"sales_order_item": itm.name,
"item_code": itm.item_code,
"warehouse": self.warehouse_finished_goods,
"qty_to_reserve": 3,
}
]
)
# Delivery note should auto-select warehouse based on reservation
dn = make_delivery_note(so.name, kwargs={"for_reserved_stock": True})
self.assertEqual(2, len(dn.items))
self.assertEqual(dn.items[0].qty, 2)
self.assertEqual(dn.items[0].warehouse, self.warehouse_stores)
self.assertEqual(dn.items[1].qty, 3)
self.assertEqual(dn.items[1].warehouse, self.warehouse_finished_goods)
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")

View File

@@ -40,6 +40,7 @@
"column_break_19",
"discount_percentage",
"discount_amount",
"distributed_discount_amount",
"base_rate_with_margin",
"section_break_simple1",
"rate",
@@ -280,7 +281,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount || doc.distributed_discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -905,12 +906,18 @@
"label": "Is Stock Item",
"print_hide": 1,
"report_hide": 1
},
{
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:37.177978",
"modified": "2024-06-02 06:13:40.597947",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",

View File

@@ -38,6 +38,7 @@ class SalesOrderItem(Document):
description: DF.TextEditor | None
discount_amount: DF.Currency
discount_percentage: DF.Percent
distributed_discount_amount: DF.Currency
ensure_delivery_based_on_produced_serial_no: DF.Check
grant_commission: DF.Check
gross_profit: DF.Currency

View File

@@ -1,8 +1,9 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
from frappe.tests import IntegrationTestCase
class TestSalesPartnerType(unittest.TestCase):
class TestSalesPartnerType(IntegrationTestCase):
pass

View File

@@ -1,12 +1,12 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests import IntegrationTestCase
class TestSellingSettings(unittest.TestCase):
class TestSellingSettings(IntegrationTestCase):
def test_defaults_populated(self):
# Setup default values are not populated on migrate, this test checks
# if setup was completed correctly