mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-04 06:00:51 +00:00
Compare commits
99 Commits
orch_v13
...
add-einvoi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a16347f325 | ||
|
|
19b9875ba8 | ||
|
|
43aa670e90 | ||
|
|
4f76ed1c68 | ||
|
|
b93d13feef | ||
|
|
e1a32cc620 | ||
|
|
550f5f280c | ||
|
|
fbbfeb8563 | ||
|
|
418c131331 | ||
|
|
a8bf17e560 | ||
|
|
9961037f71 | ||
|
|
84ee1b86af | ||
|
|
afe86d83aa | ||
|
|
76e4bb44f1 | ||
|
|
c1bc1040b8 | ||
|
|
1d23c9a9fd | ||
|
|
5a211813d3 | ||
|
|
601b1e3821 | ||
|
|
a91483899b | ||
|
|
2f6d55d8d2 | ||
|
|
4a10454d89 | ||
|
|
12b15347bc | ||
|
|
9b63a1a2e9 | ||
|
|
8888957952 | ||
|
|
a9660bf026 | ||
|
|
b877b0d3a2 | ||
|
|
e4082093b8 | ||
|
|
9dc0edfb8a | ||
|
|
e13f05ce19 | ||
|
|
310e7c522c | ||
|
|
e32e0bc8fa | ||
|
|
0c24ba0118 | ||
|
|
60fa87751a | ||
|
|
50bc7785f7 | ||
|
|
d2aea0cd2e | ||
|
|
e9006582fb | ||
|
|
3483e0e2a5 | ||
|
|
65b047c94c | ||
|
|
ada3ce21c0 | ||
|
|
cb89dba5ab | ||
|
|
d73237e896 | ||
|
|
8e0d393cd4 | ||
|
|
292ff1bd6b | ||
|
|
804cf40f69 | ||
|
|
85f48dd6bd | ||
|
|
fa0a137a38 | ||
|
|
5fc8d20e96 | ||
|
|
7ddcfc00fc | ||
|
|
3f02059085 | ||
|
|
71b21086b1 | ||
|
|
c2817bed0b | ||
|
|
dc972718d5 | ||
|
|
7afb19f965 | ||
|
|
0ca137808b | ||
|
|
99e0106193 | ||
|
|
447485ae90 | ||
|
|
98bcb7255d | ||
|
|
2df24ec459 | ||
|
|
3a2f08fbda | ||
|
|
7c9b535514 | ||
|
|
4f0c2909ab | ||
|
|
1741501ea8 | ||
|
|
ea032893d3 | ||
|
|
7601a016b8 | ||
|
|
b5376ce5cb | ||
|
|
b8d97b82b8 | ||
|
|
56c4c3186b | ||
|
|
015a221d8d | ||
|
|
eec770a4a8 | ||
|
|
44f5ccc9dc | ||
|
|
0ae2a4f1c4 | ||
|
|
5fdaddad86 | ||
|
|
03ccddc1cc | ||
|
|
e00ebcd9e5 | ||
|
|
7e7122b668 | ||
|
|
b1c70af4db | ||
|
|
ff3bfb060a | ||
|
|
63e087d31e | ||
|
|
b62a397397 | ||
|
|
48efcc82b6 | ||
|
|
4383980bcc | ||
|
|
aa0552d788 | ||
|
|
3b5889ef85 | ||
|
|
8238b89907 | ||
|
|
6ae2f90683 | ||
|
|
cb7aef505d | ||
|
|
4a8c42d62b | ||
|
|
c67bdcf3c6 | ||
|
|
7a9e7b66ac | ||
|
|
151b9d4d7b | ||
|
|
b966f10711 | ||
|
|
0b48f13873 | ||
|
|
b531a38efe | ||
|
|
dd26ef96e0 | ||
|
|
227ce5f8a2 | ||
|
|
acd64ba7c1 | ||
|
|
6f43133c04 | ||
|
|
f72602ebf3 | ||
|
|
002ae8ae13 |
2
.github/workflows/server-tests.yml
vendored
2
.github/workflows/server-tests.yml
vendored
@@ -93,7 +93,7 @@ jobs:
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
|
||||
- name: Run Tests
|
||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 2 --build-number ${{ matrix.container }}'
|
||||
env:
|
||||
TYPE: server
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
|
||||
@@ -495,6 +495,67 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos.submit)
|
||||
|
||||
def test_value_error_on_serial_no_validation(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item(
|
||||
company="_Test Company",
|
||||
target_warehouse="Stores - _TC",
|
||||
cost_center="Main - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
serial_nos = se.get("items")[0].serial_no
|
||||
|
||||
# make a pos invoice
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
debit_to="Debtors - _TC",
|
||||
account_for_change_amount="Cash - _TC",
|
||||
warehouse="Stores - _TC",
|
||||
income_account="Sales - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
pos.set("payments", [])
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
pos = pos.save().submit()
|
||||
|
||||
# make a return
|
||||
pos_return = make_sales_return(pos.name)
|
||||
pos_return.paid_amount = pos_return.grand_total
|
||||
pos_return.save()
|
||||
pos_return.submit()
|
||||
|
||||
# set docstatus to 2 for pos to trigger this issue
|
||||
frappe.db.set_value("POS Invoice", pos.name, "docstatus", 2)
|
||||
|
||||
pos2 = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
debit_to="Debtors - _TC",
|
||||
account_for_change_amount="Cash - _TC",
|
||||
warehouse="Stores - _TC",
|
||||
income_account="Sales - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].has_serial_no = 1
|
||||
pos2.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
# Value error should not be triggered on validation
|
||||
pos2.save()
|
||||
|
||||
def test_loyalty_points(self):
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
get_loyalty_program_details_with_points,
|
||||
|
||||
@@ -269,6 +269,18 @@ def get_serial_no_for_item(args):
|
||||
return item_details
|
||||
|
||||
|
||||
def update_pricing_rule_uom(pricing_rule, args):
|
||||
child_doc = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}.get(
|
||||
pricing_rule.apply_on
|
||||
)
|
||||
|
||||
apply_on_field = frappe.scrub(pricing_rule.apply_on)
|
||||
|
||||
for row in pricing_rule.get(child_doc):
|
||||
if row.get(apply_on_field) == args.get(apply_on_field):
|
||||
pricing_rule.uom = row.uom
|
||||
|
||||
|
||||
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (
|
||||
get_applied_pricing_rules,
|
||||
@@ -325,7 +337,8 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
|
||||
if isinstance(pricing_rule, string_types):
|
||||
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
|
||||
update_pricing_rule_uom(pricing_rule, args)
|
||||
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or []
|
||||
|
||||
if pricing_rule.get("suggestion"):
|
||||
continue
|
||||
@@ -439,12 +452,15 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
if pricing_rule.currency == args.currency:
|
||||
pricing_rule_rate = pricing_rule.rate
|
||||
|
||||
# TODO https://github.com/frappe/erpnext/pull/23636 solve this in some other way.
|
||||
if pricing_rule_rate:
|
||||
is_blank_uom = pricing_rule.get("uom") != args.get("uom")
|
||||
# Override already set price list rate (from item price)
|
||||
# if pricing_rule_rate > 0
|
||||
item_details.update(
|
||||
{
|
||||
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
|
||||
"price_list_rate": pricing_rule_rate
|
||||
* (args.get("conversion_factor", 1) if is_blank_uom else 1),
|
||||
}
|
||||
)
|
||||
item_details.update({"discount_percentage": 0.0})
|
||||
|
||||
@@ -597,6 +597,121 @@ class TestPricingRule(unittest.TestCase):
|
||||
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
||||
item.delete()
|
||||
|
||||
def test_item_price_with_blank_uom_pricing_rule(self):
|
||||
properties = {
|
||||
"item_code": "Item Blank UOM",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item Blank UOM", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item Blank UOM Rule",
|
||||
"apply_on": "Item Code",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "Item Blank UOM",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"rate_or_discount": "Rate",
|
||||
"rate": 101,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
do_not_save=True, item_code="Item Blank UOM", uom="Box", conversion_factor=10
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
|
||||
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
|
||||
self.assertEqual(si.items[0].price_list_rate, 1010)
|
||||
self.assertEqual(si.items[0].rate, 1010)
|
||||
|
||||
si.delete()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True, item_code="Item Blank UOM", uom="Nos")
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
|
||||
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
|
||||
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||
self.assertEqual(si.items[0].rate, 101)
|
||||
|
||||
si.delete()
|
||||
rule.delete()
|
||||
frappe.get_doc("Item Price", {"item_code": "Item Blank UOM"}).delete()
|
||||
|
||||
item.delete()
|
||||
|
||||
def test_item_price_with_selling_uom_pricing_rule(self):
|
||||
properties = {
|
||||
"item_code": "Item UOM other than Stock",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item UOM other than Stock", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item UOM other than Stock Rule",
|
||||
"apply_on": "Item Code",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "Item UOM other than Stock",
|
||||
"uom": "Box",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"rate_or_discount": "Rate",
|
||||
"rate": 101,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
do_not_save=True, item_code="Item UOM other than Stock", uom="Box", conversion_factor=10
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is Box so apply pricing_rule only on Box UOM.
|
||||
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
|
||||
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||
self.assertEqual(si.items[0].rate, 101)
|
||||
|
||||
si.delete()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True, item_code="Item UOM other than Stock", uom="Nos")
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
|
||||
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
|
||||
self.assertEqual(si.items[0].price_list_rate, 100)
|
||||
self.assertEqual(si.items[0].rate, 100)
|
||||
|
||||
si.delete()
|
||||
rule.delete()
|
||||
frappe.get_doc("Item Price", {"item_code": "Item UOM other than Stock"}).delete()
|
||||
|
||||
item.delete()
|
||||
|
||||
def test_pricing_rule_for_different_currency(self):
|
||||
make_item("Test Sanitizer Item")
|
||||
|
||||
|
||||
@@ -111,6 +111,12 @@ def _get_pricing_rules(apply_on, args, values):
|
||||
)
|
||||
|
||||
if apply_on_field == "item_code":
|
||||
if args.get("uom", None):
|
||||
item_conditions += (
|
||||
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
|
||||
child_doc=child_doc, item_uom=args.get("uom")
|
||||
)
|
||||
)
|
||||
if "variant_of" not in args:
|
||||
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
this._super();
|
||||
|
||||
// Ignore linked advances
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry'];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice'];
|
||||
|
||||
if(!this.frm.doc.__islocal) {
|
||||
// show credit_to in print format
|
||||
@@ -569,6 +569,10 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
|
||||
if (frm.is_new()) {
|
||||
frm.clear_table("tax_withheld_vouchers");
|
||||
}
|
||||
},
|
||||
|
||||
is_subcontracted: function(frm) {
|
||||
|
||||
@@ -67,6 +67,9 @@ class PurchaseInvoice(BuyingController):
|
||||
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
self.set_onload("supplier_tds", supplier_tds)
|
||||
|
||||
if self.is_new():
|
||||
self.set("tax_withheld_vouchers", [])
|
||||
|
||||
def before_save(self):
|
||||
if not self.on_hold:
|
||||
self.release_date = ""
|
||||
@@ -695,6 +698,10 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
credit_amount = item.base_net_amount
|
||||
if self.is_internal_supplier and item.valuation_rate:
|
||||
credit_amount = flt(item.valuation_rate * item.stock_qty)
|
||||
|
||||
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@@ -704,7 +711,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
|
||||
},
|
||||
warehouse_account[item.from_warehouse]["account_currency"],
|
||||
item=item,
|
||||
@@ -1364,7 +1371,14 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.db.set(self, "status", "Cancelled")
|
||||
|
||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
|
||||
self.ignore_linked_doctypes = (
|
||||
"GL Entry",
|
||||
"Stock Ledger Entry",
|
||||
"Repost Item Valuation",
|
||||
"Tax Withheld Vouchers",
|
||||
)
|
||||
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
def update_project(self):
|
||||
|
||||
@@ -706,6 +706,7 @@
|
||||
"label": "Valuation Rate",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"precision": "6",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -871,7 +872,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-15 17:04:07.191013",
|
||||
"modified": "2022-10-12 03:37:29.032732",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -2187,6 +2187,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
update_address(
|
||||
target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
|
||||
)
|
||||
update_address(
|
||||
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
|
||||
)
|
||||
|
||||
if currency:
|
||||
target_doc.currency = currency
|
||||
|
||||
@@ -31,10 +31,20 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import (
|
||||
get_qty_after_transaction,
|
||||
make_stock_entry,
|
||||
)
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
|
||||
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
|
||||
|
||||
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
|
||||
create_internal_parties()
|
||||
setup_accounts()
|
||||
|
||||
def make(self):
|
||||
w = frappe.copy_doc(test_records[0])
|
||||
w.is_pos = 0
|
||||
@@ -906,7 +916,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
pos_return.insert()
|
||||
pos_return.submit()
|
||||
|
||||
self.assertEqual(pos_return.get("payments")[0].amount, -1000)
|
||||
self.assertEqual(pos_return.get("payments")[0].amount, -500)
|
||||
self.assertEqual(pos_return.get("payments")[1].amount, -500)
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
make_pos_profile(
|
||||
@@ -1687,7 +1698,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate * 25) / 100 + price_list_rate))
|
||||
|
||||
def test_outstanding_amount_after_advance_jv_cancelation(self):
|
||||
def test_outstanding_amount_after_advance_jv_cancellation(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
)
|
||||
@@ -1731,7 +1742,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")),
|
||||
)
|
||||
|
||||
def test_outstanding_amount_after_advance_payment_entry_cancelation(self):
|
||||
def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
|
||||
pe = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Payment Entry",
|
||||
@@ -2369,29 +2380,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
acc_settings.save()
|
||||
|
||||
def test_inter_company_transaction(self):
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer",
|
||||
represents_company="_Test Company 1",
|
||||
allowed_to_interact_with="Wind Power LLC",
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
|
||||
supplier = frappe.get_doc(
|
||||
{
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
"supplier_name": "_Test Internal Supplier",
|
||||
"doctype": "Supplier",
|
||||
"is_internal_supplier": 1,
|
||||
"represents_company": "Wind Power LLC",
|
||||
}
|
||||
)
|
||||
|
||||
supplier.append("companies", {"company": "_Test Company 1"})
|
||||
|
||||
supplier.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
company="Wind Power LLC",
|
||||
customer="_Test Internal Customer",
|
||||
@@ -2451,34 +2439,9 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
se.cancel()
|
||||
|
||||
def test_internal_transfer_gl_entry(self):
|
||||
## Create internal transfer account
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
account = create_account(
|
||||
account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
|
||||
)
|
||||
|
||||
customer = create_internal_customer(
|
||||
"_Test Internal Customer 2",
|
||||
"_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
create_internal_supplier(
|
||||
"_Test Internal Supplier 2",
|
||||
"_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company with perpetual inventory",
|
||||
customer=customer,
|
||||
customer="_Test Internal Customer 2",
|
||||
debit_to="Debtors - TCP1",
|
||||
warehouse="Stores - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
@@ -2492,7 +2455,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.update_stock = 1
|
||||
si.items[0].target_warehouse = "Work In Progress - TCP1"
|
||||
|
||||
# Add stock to stores for succesful stock transfer
|
||||
# Add stock to stores for successful stock transfer
|
||||
make_stock_entry(
|
||||
target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=1, basic_rate=100
|
||||
)
|
||||
@@ -2830,6 +2793,77 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
|
||||
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
|
||||
|
||||
def test_internal_transfer_gl_precision_issues(self):
|
||||
# Make a stock queue of an item with two valuations
|
||||
|
||||
# Remove all existing stock for this
|
||||
if get_stock_balance("_Test Internal Transfer Item", "Stores - TCP1", "2022-04-10"):
|
||||
create_stock_reconciliation(
|
||||
item_code="_Test Internal Transfer Item",
|
||||
warehouse="Stores - TCP1",
|
||||
qty=0,
|
||||
rate=0,
|
||||
company="_Test Company with perpetual inventory",
|
||||
expense_account="Stock Adjustment - TCP1"
|
||||
if frappe.get_all("Stock Ledger Entry")
|
||||
else "Temporary Opening - TCP1",
|
||||
posting_date="2020-04-10",
|
||||
posting_time="14:00",
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code="_Test Internal Transfer Item",
|
||||
target="Stores - TCP1",
|
||||
qty=9000000,
|
||||
basic_rate=52.0,
|
||||
posting_date="2020-04-10",
|
||||
posting_time="14:00",
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code="_Test Internal Transfer Item",
|
||||
target="Stores - TCP1",
|
||||
qty=60000000,
|
||||
basic_rate=52.349777,
|
||||
posting_date="2020-04-10",
|
||||
posting_time="14:00",
|
||||
)
|
||||
|
||||
# Make an internal transfer Sales Invoice Stock in non stock uom to check
|
||||
# for rounding errors while converting to stock uom
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company with perpetual inventory",
|
||||
customer="_Test Internal Customer 2",
|
||||
item_code="_Test Internal Transfer Item",
|
||||
qty=5000000,
|
||||
uom="Box",
|
||||
debit_to="Debtors - TCP1",
|
||||
warehouse="Stores - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
currency="INR",
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
# Check GL Entries with precision
|
||||
si.update_stock = 1
|
||||
si.items[0].target_warehouse = "Work In Progress - TCP1"
|
||||
si.items[0].conversion_factor = 10
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
# Check if adjustment entry is created
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"GL Entry",
|
||||
{
|
||||
"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name,
|
||||
"remarks": "Rounding gain/loss Entry for Stock Transfer",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def test_item_tax_net_range(self):
|
||||
item = create_item("T Shirt")
|
||||
|
||||
@@ -3278,7 +3312,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
[deferred_account, 2022.47, 0.0, "2019-03-15"],
|
||||
]
|
||||
|
||||
gl_entries = gl_entries = frappe.db.sql(
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, debit, credit, posting_date
|
||||
from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
|
||||
@@ -3683,6 +3717,7 @@ def create_sales_invoice(**args):
|
||||
"description": args.description or "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"target_warehouse": args.target_warehouse,
|
||||
"qty": args.qty or 1,
|
||||
"uom": args.uom or "Nos",
|
||||
"stock_uom": args.uom or "Nos",
|
||||
@@ -3695,7 +3730,7 @@ def create_sales_invoice(**args):
|
||||
"discount_amount": args.discount_amount or 0,
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
"conversion_factor": 1,
|
||||
"conversion_factor": args.get("conversion_factor", 1),
|
||||
"incoming_rate": args.incoming_rate or 0,
|
||||
"batch_no": args.batch_no or None,
|
||||
},
|
||||
@@ -3809,6 +3844,34 @@ def get_taxes_and_charges():
|
||||
]
|
||||
|
||||
|
||||
def create_internal_parties():
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer",
|
||||
represents_company="_Test Company 1",
|
||||
allowed_to_interact_with="Wind Power LLC",
|
||||
)
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer 2",
|
||||
represents_company="_Test Company with perpetual inventory",
|
||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
create_internal_supplier(
|
||||
supplier_name="_Test Internal Supplier",
|
||||
represents_company="Wind Power LLC",
|
||||
allowed_to_interact_with="_Test Company 1",
|
||||
)
|
||||
|
||||
create_internal_supplier(
|
||||
supplier_name="_Test Internal Supplier 2",
|
||||
represents_company="_Test Company with perpetual inventory",
|
||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
|
||||
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
|
||||
if not frappe.db.exists("Supplier", supplier_name):
|
||||
supplier = frappe.get_doc(
|
||||
@@ -3831,6 +3894,19 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
|
||||
return supplier_name
|
||||
|
||||
|
||||
def setup_accounts():
|
||||
## Create internal transfer account
|
||||
account = create_account(
|
||||
account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
|
||||
)
|
||||
|
||||
|
||||
def add_taxes(doc):
|
||||
doc.append(
|
||||
"taxes",
|
||||
|
||||
@@ -812,6 +812,8 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Incoming Rate (Costing)",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"precision": "6",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -841,7 +843,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-08-26 12:06:31.205417",
|
||||
"modified": "2022-10-10 20:57:38.340026",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
<div class="col-xs-4"><label>Document No</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
|
||||
</div>
|
||||
<div class="row data-field">
|
||||
<div class="col-xs-4"><label>Document Date</label></div>
|
||||
<div class="col-xs-8 value">{{ einvoice.DocDtls.Dt }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4 column-break">
|
||||
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
|
||||
|
||||
@@ -835,10 +835,7 @@ def remove_return_pos_invoices(party_type, party, invoice_list):
|
||||
else:
|
||||
return invoice_list
|
||||
|
||||
# remove pos return invoices from invoice_list
|
||||
for idx, inv in enumerate(invoice_list, 0):
|
||||
if inv.voucher_no in return_pos:
|
||||
del invoice_list[idx]
|
||||
invoice_list = [x for x in invoice_list if x.voucher_no not in return_pos]
|
||||
|
||||
return invoice_list
|
||||
|
||||
|
||||
@@ -384,7 +384,11 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
set_values_from_purchase_doc: function(frm, doctype, purchase_doc) {
|
||||
frm.set_value('company', purchase_doc.company);
|
||||
frm.set_value('purchase_date', purchase_doc.posting_date);
|
||||
if (purchase_doc.bill_date) {
|
||||
frm.set_value('purchase_date', purchase_doc.bill_date);
|
||||
} else {
|
||||
frm.set_value('purchase_date', purchase_doc.posting_date);
|
||||
}
|
||||
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
|
||||
if (!item) {
|
||||
doctype_field = frappe.scrub(doctype)
|
||||
|
||||
@@ -43,6 +43,11 @@ frappe.ui.form.on("Purchase Order", {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
|
||||
// On cancel and amending a purchase order with advance payment, reset advance paid amount
|
||||
if (frm.is_new()) {
|
||||
frm.set_value("advance_paid", 0)
|
||||
}
|
||||
},
|
||||
|
||||
apply_tds: function(frm) {
|
||||
|
||||
@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
from erpnext.accounts.party import get_party_account, get_party_account_currency
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
@@ -533,6 +533,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
target.set_advances()
|
||||
|
||||
target.set_payment_schedule()
|
||||
target.credit_to = get_party_account("Supplier", source.supplier, source.company)
|
||||
|
||||
def update_item(obj, target, source_parent):
|
||||
target.amount = flt(obj.amount) - flt(obj.billed_amt)
|
||||
|
||||
@@ -152,6 +152,7 @@ class AccountsController(TransactionBase):
|
||||
self.validate_inter_company_reference()
|
||||
|
||||
self.disable_pricing_rule_on_internal_transfer()
|
||||
self.disable_tax_included_prices_for_internal_transfer()
|
||||
self.set_incoming_rate()
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
@@ -395,6 +396,20 @@ class AccountsController(TransactionBase):
|
||||
alert=1,
|
||||
)
|
||||
|
||||
def disable_tax_included_prices_for_internal_transfer(self):
|
||||
if self.is_internal_transfer():
|
||||
tax_updated = False
|
||||
for tax in self.get("taxes"):
|
||||
if tax.get("included_in_print_rate"):
|
||||
tax.included_in_print_rate = 0
|
||||
tax_updated = True
|
||||
|
||||
if tax_updated:
|
||||
frappe.msgprint(
|
||||
_("Disabled tax included prices since this {} is an internal transfer").format(self.doctype),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
def validate_due_date(self):
|
||||
if self.get("is_pos"):
|
||||
return
|
||||
|
||||
@@ -439,11 +439,17 @@ class SellingController(StockController):
|
||||
# For internal transfers use incoming rate as the valuation rate
|
||||
if self.is_internal_transfer():
|
||||
if d.doctype == "Packed Item":
|
||||
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
|
||||
incoming_rate = flt(
|
||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
||||
d.precision("incoming_rate"),
|
||||
)
|
||||
if d.incoming_rate != incoming_rate:
|
||||
d.incoming_rate = incoming_rate
|
||||
else:
|
||||
rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
|
||||
rate = flt(
|
||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
||||
d.precision("rate"),
|
||||
)
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
|
||||
@@ -139,13 +139,15 @@ class StockController(AccountsController):
|
||||
warehouse_with_no_account = []
|
||||
precision = self.get_debit_field_precision()
|
||||
for item_row in voucher_details:
|
||||
|
||||
sle_list = sle_map.get(item_row.name)
|
||||
sle_rounding_diff = 0.0
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
if warehouse_account.get(sle.warehouse):
|
||||
# from warehouse account
|
||||
|
||||
sle_rounding_diff += flt(sle.stock_value_difference)
|
||||
|
||||
self.check_expense_account(item_row)
|
||||
|
||||
# expense account/ target_warehouse / source_warehouse
|
||||
@@ -188,6 +190,46 @@ class StockController(AccountsController):
|
||||
elif sle.warehouse not in warehouse_with_no_account:
|
||||
warehouse_with_no_account.append(sle.warehouse)
|
||||
|
||||
if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer():
|
||||
warehouse_asset_account = ""
|
||||
if self.get("is_internal_customer"):
|
||||
warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"]
|
||||
elif self.get("is_internal_supplier"):
|
||||
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
|
||||
|
||||
expense_account = frappe.db.get_value("Company", self.company, "default_expense_account")
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": warehouse_asset_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get("project"),
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"debit": sle_rounding_diff,
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
warehouse_account[sle.warehouse]["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_asset_account,
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"credit": sle_rounding_diff,
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
for wh in warehouse_with_no_account:
|
||||
if frappe.db.get_value("Warehouse", wh, "company"):
|
||||
|
||||
@@ -890,24 +890,33 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
|
||||
|
||||
def set_total_amount_to_default_mop(self, total_amount_to_pay):
|
||||
default_mode_of_payment = frappe.db.get_value(
|
||||
"POS Payment Method",
|
||||
{"parent": self.doc.pos_profile, "default": 1},
|
||||
["mode_of_payment"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.payments = []
|
||||
self.doc.append(
|
||||
"payments",
|
||||
{
|
||||
"mode_of_payment": default_mode_of_payment.mode_of_payment,
|
||||
"amount": total_amount_to_pay,
|
||||
"default": 1,
|
||||
},
|
||||
total_paid_amount = 0
|
||||
for payment in self.doc.get("payments"):
|
||||
total_paid_amount += (
|
||||
payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount
|
||||
)
|
||||
|
||||
pending_amount = total_amount_to_pay - total_paid_amount
|
||||
|
||||
if pending_amount > 0:
|
||||
default_mode_of_payment = frappe.db.get_value(
|
||||
"POS Payment Method",
|
||||
{"parent": self.doc.pos_profile, "default": 1},
|
||||
["mode_of_payment"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.payments = []
|
||||
self.doc.append(
|
||||
"payments",
|
||||
{
|
||||
"mode_of_payment": default_mode_of_payment.mode_of_payment,
|
||||
"amount": pending_amount,
|
||||
"default": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_itemised_tax_breakup_html(doc):
|
||||
if not doc.taxes:
|
||||
|
||||
@@ -334,8 +334,6 @@ has_website_permission = {
|
||||
"Patient": "erpnext.healthcare.web_form.personal_details.personal_details.has_website_permission",
|
||||
}
|
||||
|
||||
dump_report_map = "erpnext.startup.report_data_map.data_map"
|
||||
|
||||
before_tests = "erpnext.setup.utils.before_tests"
|
||||
|
||||
standard_queries = {
|
||||
|
||||
@@ -61,6 +61,10 @@ frappe.ui.form.on('Loan', {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.repayment_schedule_type == "Pro-rated calendar months") {
|
||||
frm.set_df_property("repayment_start_date", "label", "Interest Calculation Start Date");
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1) {
|
||||
if (["Disbursed", "Partially Disbursed"].includes(frm.doc.status) && (!frm.doc.repay_from_salary)) {
|
||||
frm.add_custom_button(__('Request Loan Closure'), function() {
|
||||
@@ -103,6 +107,14 @@ frappe.ui.form.on('Loan', {
|
||||
frm.trigger("toggle_fields");
|
||||
},
|
||||
|
||||
repayment_schedule_type: function(frm) {
|
||||
if (frm.doc.repayment_schedule_type == "Pro-rated calendar months") {
|
||||
frm.set_df_property("repayment_start_date", "label", "Interest Calculation Start Date");
|
||||
} else {
|
||||
frm.set_df_property("repayment_start_date", "label", "Repayment Start Date");
|
||||
}
|
||||
},
|
||||
|
||||
loan_type: function(frm) {
|
||||
frm.toggle_reqd("repayment_method", frm.doc.is_term_loan);
|
||||
frm.toggle_display("repayment_method", frm.doc.is_term_loan);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"manually_update_paid_amount_in_salary_slip",
|
||||
"section_break_8",
|
||||
"loan_type",
|
||||
"repayment_schedule_type",
|
||||
"loan_amount",
|
||||
"rate_of_interest",
|
||||
"is_secured_loan",
|
||||
@@ -167,7 +168,8 @@
|
||||
"depends_on": "is_term_loan",
|
||||
"fieldname": "repayment_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Repayment Start Date"
|
||||
"label": "Repayment Start Date",
|
||||
"mandatory_depends_on": "is_term_loan"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
@@ -419,12 +421,20 @@
|
||||
"fieldname": "manually_update_paid_amount_in_salary_slip",
|
||||
"fieldtype": "Check",
|
||||
"label": "Manually Update Paid Amount in Salary Slip"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_term_loan",
|
||||
"fetch_from": "loan_type.repayment_schedule_type",
|
||||
"fieldname": "repayment_schedule_type",
|
||||
"fieldtype": "Data",
|
||||
"label": "Repayment Schedule Type",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-13 02:05:25.017190",
|
||||
"modified": "2022-11-01 10:36:47.902903",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan",
|
||||
|
||||
@@ -7,7 +7,16 @@ import math
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_months, flt, get_last_day, getdate, now_datetime, nowdate
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
date_diff,
|
||||
flt,
|
||||
get_last_day,
|
||||
getdate,
|
||||
now_datetime,
|
||||
nowdate,
|
||||
)
|
||||
from six import string_types
|
||||
|
||||
import erpnext
|
||||
@@ -115,30 +124,81 @@ class Loan(AccountsController):
|
||||
if not self.repayment_start_date:
|
||||
frappe.throw(_("Repayment Start Date is mandatory for term loans"))
|
||||
|
||||
schedule_type_details = frappe.db.get_value(
|
||||
"Loan Type", self.loan_type, ["repayment_schedule_type", "repayment_date_on"], as_dict=1
|
||||
)
|
||||
|
||||
self.repayment_schedule = []
|
||||
payment_date = self.repayment_start_date
|
||||
balance_amount = self.loan_amount
|
||||
while balance_amount > 0:
|
||||
interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12 * 100))
|
||||
principal_amount = self.monthly_repayment_amount - interest_amount
|
||||
balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
|
||||
if balance_amount < 0:
|
||||
principal_amount += balance_amount
|
||||
balance_amount = 0.0
|
||||
|
||||
total_payment = principal_amount + interest_amount
|
||||
self.append(
|
||||
"repayment_schedule",
|
||||
{
|
||||
"payment_date": payment_date,
|
||||
"principal_amount": principal_amount,
|
||||
"interest_amount": interest_amount,
|
||||
"total_payment": total_payment,
|
||||
"balance_loan_amount": balance_amount,
|
||||
},
|
||||
while balance_amount > 0:
|
||||
interest_amount, principal_amount, balance_amount, total_payment = self.get_amounts(
|
||||
payment_date,
|
||||
balance_amount,
|
||||
schedule_type_details.repayment_schedule_type,
|
||||
schedule_type_details.repayment_date_on,
|
||||
)
|
||||
next_payment_date = add_single_month(payment_date)
|
||||
payment_date = next_payment_date
|
||||
|
||||
if schedule_type_details.repayment_schedule_type == "Pro-rated calendar months":
|
||||
next_payment_date = get_last_day(payment_date)
|
||||
if schedule_type_details.repayment_date_on == "Start of the next month":
|
||||
next_payment_date = add_days(next_payment_date, 1)
|
||||
|
||||
payment_date = next_payment_date
|
||||
|
||||
self.add_repayment_schedule_row(
|
||||
payment_date, principal_amount, interest_amount, total_payment, balance_amount
|
||||
)
|
||||
|
||||
if (
|
||||
schedule_type_details.repayment_schedule_type == "Monthly as per repayment start date"
|
||||
or schedule_type_details.repayment_date_on == "End of the current month"
|
||||
):
|
||||
next_payment_date = add_single_month(payment_date)
|
||||
payment_date = next_payment_date
|
||||
|
||||
def get_amounts(self, payment_date, balance_amount, schedule_type, repayment_date_on):
|
||||
if schedule_type == "Monthly as per repayment start date":
|
||||
days = 1
|
||||
months = 12
|
||||
else:
|
||||
expected_payment_date = get_last_day(payment_date)
|
||||
if repayment_date_on == "Start of the next month":
|
||||
expected_payment_date = add_days(expected_payment_date, 1)
|
||||
|
||||
if expected_payment_date == payment_date:
|
||||
# using 30 days for calculating interest for all full months
|
||||
days = 30
|
||||
months = 365
|
||||
else:
|
||||
days = date_diff(get_last_day(payment_date), payment_date)
|
||||
months = 365
|
||||
|
||||
interest_amount = flt(balance_amount * flt(self.rate_of_interest) * days / (months * 100))
|
||||
principal_amount = self.monthly_repayment_amount - interest_amount
|
||||
balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
|
||||
if balance_amount < 0:
|
||||
principal_amount += balance_amount
|
||||
balance_amount = 0.0
|
||||
|
||||
total_payment = principal_amount + interest_amount
|
||||
|
||||
return interest_amount, principal_amount, balance_amount, total_payment
|
||||
|
||||
def add_repayment_schedule_row(
|
||||
self, payment_date, principal_amount, interest_amount, total_payment, balance_loan_amount
|
||||
):
|
||||
self.append(
|
||||
"repayment_schedule",
|
||||
{
|
||||
"payment_date": payment_date,
|
||||
"principal_amount": principal_amount,
|
||||
"interest_amount": interest_amount,
|
||||
"total_payment": total_payment,
|
||||
"balance_loan_amount": balance_loan_amount,
|
||||
},
|
||||
)
|
||||
|
||||
def set_repayment_period(self):
|
||||
if self.repayment_method == "Repay Fixed Amount per Period":
|
||||
|
||||
@@ -4,7 +4,16 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_days, add_months, add_to_date, date_diff, flt, get_datetime, nowdate
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
add_to_date,
|
||||
date_diff,
|
||||
flt,
|
||||
format_date,
|
||||
get_datetime,
|
||||
nowdate,
|
||||
)
|
||||
|
||||
from erpnext.loan_management.doctype.loan.loan import (
|
||||
make_loan_write_off,
|
||||
@@ -50,6 +59,51 @@ class TestLoan(unittest.TestCase):
|
||||
loan_account="Loan Account - _TC",
|
||||
interest_income_account="Interest Income Account - _TC",
|
||||
penalty_income_account="Penalty Income Account - _TC",
|
||||
repayment_schedule_type="Monthly as per repayment start date",
|
||||
)
|
||||
|
||||
create_loan_type(
|
||||
"Term Loan Type 1",
|
||||
12000,
|
||||
7.5,
|
||||
is_term_loan=1,
|
||||
mode_of_payment="Cash",
|
||||
disbursement_account="Disbursement Account - _TC",
|
||||
payment_account="Payment Account - _TC",
|
||||
loan_account="Loan Account - _TC",
|
||||
interest_income_account="Interest Income Account - _TC",
|
||||
penalty_income_account="Penalty Income Account - _TC",
|
||||
repayment_schedule_type="Monthly as per repayment start date",
|
||||
)
|
||||
|
||||
create_loan_type(
|
||||
"Term Loan Type 2",
|
||||
12000,
|
||||
7.5,
|
||||
is_term_loan=1,
|
||||
mode_of_payment="Cash",
|
||||
disbursement_account="Disbursement Account - _TC",
|
||||
payment_account="Payment Account - _TC",
|
||||
loan_account="Loan Account - _TC",
|
||||
interest_income_account="Interest Income Account - _TC",
|
||||
penalty_income_account="Penalty Income Account - _TC",
|
||||
repayment_schedule_type="Pro-rated calendar months",
|
||||
repayment_date_on="Start of the next month",
|
||||
)
|
||||
|
||||
create_loan_type(
|
||||
"Term Loan Type 3",
|
||||
12000,
|
||||
7.5,
|
||||
is_term_loan=1,
|
||||
mode_of_payment="Cash",
|
||||
disbursement_account="Disbursement Account - _TC",
|
||||
payment_account="Payment Account - _TC",
|
||||
loan_account="Loan Account - _TC",
|
||||
interest_income_account="Interest Income Account - _TC",
|
||||
penalty_income_account="Penalty Income Account - _TC",
|
||||
repayment_schedule_type="Pro-rated calendar months",
|
||||
repayment_date_on="End of the current month",
|
||||
)
|
||||
|
||||
create_loan_type(
|
||||
@@ -65,6 +119,7 @@ class TestLoan(unittest.TestCase):
|
||||
"Loan Account - _TC",
|
||||
"Interest Income Account - _TC",
|
||||
"Penalty Income Account - _TC",
|
||||
repayment_schedule_type="Monthly as per repayment start date",
|
||||
)
|
||||
|
||||
create_loan_type(
|
||||
@@ -912,6 +967,69 @@ class TestLoan(unittest.TestCase):
|
||||
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
|
||||
self.assertEqual(flt(amounts["pending_principal_amount"], 0), 0)
|
||||
|
||||
def test_term_loan_schedule_types(self):
|
||||
loan = create_loan(
|
||||
self.applicant1,
|
||||
"Term Loan Type 1",
|
||||
12000,
|
||||
"Repay Over Number of Periods",
|
||||
12,
|
||||
repayment_start_date="2022-10-17",
|
||||
)
|
||||
|
||||
# Check for first, second and last installment date
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "17-10-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "17-11-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "17-09-2023"
|
||||
)
|
||||
|
||||
loan.loan_type = "Term Loan Type 2"
|
||||
loan.save()
|
||||
|
||||
# Check for first, second and last installment date
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "01-11-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "01-12-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "01-10-2023"
|
||||
)
|
||||
|
||||
loan.loan_type = "Term Loan Type 3"
|
||||
loan.save()
|
||||
|
||||
# Check for first, second and last installment date
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023"
|
||||
)
|
||||
|
||||
loan.repayment_method = "Repay Fixed Amount per Period"
|
||||
loan.monthly_repayment_amount = 1042
|
||||
loan.save()
|
||||
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022"
|
||||
)
|
||||
self.assertEqual(
|
||||
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023"
|
||||
)
|
||||
|
||||
|
||||
def create_loan_scenario_for_penalty(doc):
|
||||
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
|
||||
@@ -1043,6 +1161,8 @@ def create_loan_type(
|
||||
penalty_income_account=None,
|
||||
repayment_method=None,
|
||||
repayment_periods=None,
|
||||
repayment_schedule_type=None,
|
||||
repayment_date_on=None,
|
||||
):
|
||||
|
||||
if not frappe.db.exists("Loan Type", loan_name):
|
||||
@@ -1052,6 +1172,7 @@ def create_loan_type(
|
||||
"company": "_Test Company",
|
||||
"loan_name": loan_name,
|
||||
"is_term_loan": is_term_loan,
|
||||
"repayment_schedule_type": "Monthly as per repayment start date",
|
||||
"maximum_loan_amount": maximum_loan_amount,
|
||||
"rate_of_interest": rate_of_interest,
|
||||
"penalty_interest_rate": penalty_interest_rate,
|
||||
@@ -1066,8 +1187,14 @@ def create_loan_type(
|
||||
"repayment_periods": repayment_periods,
|
||||
"write_off_amount": 100,
|
||||
}
|
||||
).insert()
|
||||
)
|
||||
|
||||
if loan_type.is_term_loan:
|
||||
loan_type.repayment_schedule_type = repayment_schedule_type
|
||||
if loan_type.repayment_schedule_type != "Monthly as per repayment start date":
|
||||
loan_type.repayment_date_on = repayment_date_on
|
||||
|
||||
loan_type.insert()
|
||||
loan_type.submit()
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"company",
|
||||
"is_term_loan",
|
||||
"disabled",
|
||||
"repayment_schedule_type",
|
||||
"repayment_date_on",
|
||||
"description",
|
||||
"account_details_section",
|
||||
"mode_of_payment",
|
||||
@@ -157,12 +159,30 @@
|
||||
"label": "Disbursement Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_term_loan",
|
||||
"description": "The schedule type that will be used for generating the term loan schedules (will affect the payment date and monthly repayment amount)",
|
||||
"fieldname": "repayment_schedule_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Repayment Schedule Type",
|
||||
"mandatory_depends_on": "is_term_loan",
|
||||
"options": "\nMonthly as per repayment start date\nPro-rated calendar months"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"",
|
||||
"description": "Select whether the repayment date should be the end of the current month or start of the upcoming month",
|
||||
"fieldname": "repayment_date_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Repayment Date On",
|
||||
"mandatory_depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"",
|
||||
"options": "\nStart of the next month\nEnd of the current month"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-25 16:23:57.009349",
|
||||
"modified": "2022-11-01 17:43:03.954201",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Type",
|
||||
|
||||
@@ -384,6 +384,7 @@ class BOM(WebsiteGenerator):
|
||||
if self.docstatus == 2:
|
||||
return
|
||||
|
||||
self.flags.cost_updated = False
|
||||
existing_bom_cost = self.total_cost
|
||||
|
||||
if self.docstatus == 1:
|
||||
@@ -406,7 +407,11 @@ class BOM(WebsiteGenerator):
|
||||
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
|
||||
|
||||
if not from_child_bom:
|
||||
frappe.msgprint(_("Cost Updated"), alert=True)
|
||||
msg = "Cost Updated"
|
||||
if not self.flags.cost_updated:
|
||||
msg = "No changes in cost found"
|
||||
|
||||
frappe.msgprint(_(msg), alert=True)
|
||||
|
||||
def update_parent_cost(self):
|
||||
if self.total_cost:
|
||||
@@ -592,11 +597,16 @@ class BOM(WebsiteGenerator):
|
||||
# not via doc event, table is not regenerated and needs updation
|
||||
self.calculate_exploded_cost()
|
||||
|
||||
old_cost = self.total_cost
|
||||
|
||||
self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
|
||||
self.base_total_cost = (
|
||||
self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
|
||||
)
|
||||
|
||||
if self.total_cost != old_cost:
|
||||
self.flags.cost_updated = True
|
||||
|
||||
def calculate_op_cost(self, update_hour_rate=False):
|
||||
"""Update workstation rate and calculates totals"""
|
||||
self.operating_cost = 0
|
||||
|
||||
@@ -583,6 +583,28 @@ class TestBOM(FrappeTestCase):
|
||||
bom.submit()
|
||||
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
|
||||
|
||||
def test_bom_cost_update_flag(self):
|
||||
rm_item = make_item(
|
||||
properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
|
||||
).name
|
||||
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
|
||||
bom = make_bom(item=fg_item, raw_materials=[rm_item])
|
||||
|
||||
create_stock_reconciliation(
|
||||
item_code=rm_item, warehouse="_Test Warehouse - _TC", qty=100, rate=600
|
||||
)
|
||||
|
||||
bom.load_from_db()
|
||||
bom.update_cost()
|
||||
self.assertTrue(bom.flags.cost_updated)
|
||||
|
||||
bom.load_from_db()
|
||||
bom.update_cost()
|
||||
self.assertFalse(bom.flags.cost_updated)
|
||||
|
||||
|
||||
def get_default_bom(item_code="_Test FG Item 2"):
|
||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||
|
||||
@@ -133,7 +133,7 @@ class JobCard(Document):
|
||||
(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
|
||||
)
|
||||
and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
|
||||
order by jctl.to_time desc limit 1""".format(
|
||||
order by jctl.to_time desc""".format(
|
||||
extra_cond, validate_overlap_for
|
||||
),
|
||||
{
|
||||
|
||||
@@ -133,6 +133,45 @@ class TestJobCard(FrappeTestCase):
|
||||
)
|
||||
self.assertRaises(OverlapError, jc2.save)
|
||||
|
||||
def test_job_card_overlap_with_capacity(self):
|
||||
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
|
||||
|
||||
workstation = make_workstation(workstation_name=random_string(5)).name
|
||||
frappe.db.set_value("Workstation", workstation, "production_capacity", 1)
|
||||
|
||||
jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
|
||||
jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name})
|
||||
|
||||
jc1.workstation = workstation
|
||||
jc1.append(
|
||||
"time_logs",
|
||||
{"from_time": "2021-01-01 00:00:00", "to_time": "2021-01-01 08:00:00", "completed_qty": 1},
|
||||
)
|
||||
jc1.save()
|
||||
|
||||
jc2.workstation = workstation
|
||||
|
||||
# add a new entry in same time slice
|
||||
jc2.append(
|
||||
"time_logs",
|
||||
{"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1},
|
||||
)
|
||||
self.assertRaises(OverlapError, jc2.save)
|
||||
|
||||
frappe.db.set_value("Workstation", workstation, "production_capacity", 2)
|
||||
jc2.load_from_db()
|
||||
|
||||
jc2.workstation = workstation
|
||||
|
||||
# add a new entry in same time slice
|
||||
jc2.append(
|
||||
"time_logs",
|
||||
{"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1},
|
||||
)
|
||||
|
||||
jc2.save()
|
||||
self.assertTrue(jc2.name)
|
||||
|
||||
def test_job_card_multiple_materials_transfer(self):
|
||||
"Test transferring RMs separately against Job Card with multiple RMs."
|
||||
self.transfer_material_against = "Job Card"
|
||||
|
||||
@@ -27,6 +27,7 @@ from six import iteritems
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_children, validate_bom_no
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||
|
||||
|
||||
@@ -651,13 +652,23 @@ class ProductionPlan(Document):
|
||||
else:
|
||||
material_request = material_request_map[key]
|
||||
|
||||
conversion_factor = 1.0
|
||||
if (
|
||||
material_request_type == "Purchase"
|
||||
and item_doc.purchase_uom
|
||||
and item_doc.purchase_uom != item_doc.stock_uom
|
||||
):
|
||||
conversion_factor = (
|
||||
get_conversion_factor(item_doc.name, item_doc.purchase_uom).get("conversion_factor") or 1.0
|
||||
)
|
||||
|
||||
# add item
|
||||
material_request.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item.item_code,
|
||||
"from_warehouse": item.from_warehouse,
|
||||
"qty": item.quantity,
|
||||
"qty": item.quantity / conversion_factor,
|
||||
"schedule_date": schedule_date,
|
||||
"warehouse": item.warehouse,
|
||||
"sales_order": item.sales_order,
|
||||
|
||||
@@ -732,6 +732,35 @@ class TestProductionPlan(FrappeTestCase):
|
||||
self.assertEqual(pln.status, "Completed")
|
||||
self.assertEqual(pln.po_items[0].produced_qty, 5)
|
||||
|
||||
def test_material_request_item_for_purchase_uom(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
|
||||
bom_item = make_item(
|
||||
properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
|
||||
).name
|
||||
|
||||
if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
|
||||
doc = frappe.get_doc("Item", bom_item)
|
||||
doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
|
||||
doc.save()
|
||||
|
||||
make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
|
||||
|
||||
pln = create_production_plan(
|
||||
item_code=fg_item, planned_qty=10, ignore_existing_ordered_qty=1, stock_uom="_Test UOM 1"
|
||||
)
|
||||
|
||||
pln.make_material_request()
|
||||
for row in frappe.get_all(
|
||||
"Material Request Item",
|
||||
filters={"production_plan": pln.name},
|
||||
fields=["item_code", "uom", "qty"],
|
||||
):
|
||||
self.assertEqual(row.item_code, bom_item)
|
||||
self.assertEqual(row.uom, "Nos")
|
||||
self.assertEqual(row.qty, 1)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -555,66 +555,69 @@ erpnext.work_order = {
|
||||
}
|
||||
}
|
||||
|
||||
if(!frm.doc.skip_transfer){
|
||||
if (frm.doc.status != 'Stopped') {
|
||||
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
||||
if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') {
|
||||
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
|
||||
frm.has_finish_btn = true;
|
||||
|
||||
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
||||
// Only show "Material Consumption" when required_qty > consumed_qty
|
||||
var counter = 0;
|
||||
var tbl = frm.doc.required_items || [];
|
||||
var tbl_lenght = tbl.length;
|
||||
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
||||
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
|
||||
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
if (counter > 0) {
|
||||
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
|
||||
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
|
||||
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
|
||||
});
|
||||
consumption_btn.addClass('btn-primary');
|
||||
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
||||
if (flt(doc.material_transferred_for_manufacturing) > 0 || frm.doc.skip_transfer) {
|
||||
// Only show "Material Consumption" when required_qty > consumed_qty
|
||||
var counter = 0;
|
||||
var tbl = frm.doc.required_items || [];
|
||||
var tbl_lenght = tbl.length;
|
||||
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
||||
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
|
||||
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
if (counter > 0) {
|
||||
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
|
||||
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
|
||||
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
|
||||
});
|
||||
consumption_btn.addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!frm.doc.skip_transfer){
|
||||
if (flt(doc.material_transferred_for_manufacturing) > 0) {
|
||||
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
|
||||
frm.has_finish_btn = true;
|
||||
|
||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
|
||||
if(doc.material_transferred_for_manufacturing>=doc.qty) {
|
||||
// all materials transferred for manufacturing, make this primary
|
||||
finish_btn.addClass('btn-primary');
|
||||
}
|
||||
} else {
|
||||
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
|
||||
let allowance_percentage = doc.overproduction_percentage_for_work_order;
|
||||
|
||||
if (allowance_percentage > 0) {
|
||||
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
|
||||
|
||||
if ((flt(doc.produced_qty) < allowed_qty)) {
|
||||
frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((flt(doc.produced_qty) < flt(doc.qty))) {
|
||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
|
||||
if(doc.material_transferred_for_manufacturing>=doc.qty) {
|
||||
// all materials transferred for manufacturing, make this primary
|
||||
finish_btn.addClass('btn-primary');
|
||||
}
|
||||
} else {
|
||||
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
|
||||
let allowance_percentage = doc.overproduction_percentage_for_work_order;
|
||||
|
||||
if (allowance_percentage > 0) {
|
||||
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
|
||||
|
||||
if ((flt(doc.produced_qty) < allowed_qty)) {
|
||||
frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
finish_btn.addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
|
||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
finish_btn.addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
calculate_cost: function(doc) {
|
||||
if (doc.operations){
|
||||
|
||||
@@ -374,3 +374,4 @@ erpnext.patches.v13_0.reset_corrupt_defaults
|
||||
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
|
||||
execute:frappe.db.set_value("Naming Series", "Naming Series", {"select_doc_for_series": "", "set_options": "", "prefix": "", "current_value": 0, "user_must_always_select": 0})
|
||||
erpnext.patches.v13_0.update_schedule_type_in_loans
|
||||
17
erpnext/patches/v13_0/update_schedule_type_in_loans.py
Normal file
17
erpnext/patches/v13_0/update_schedule_type_in_loans.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("loan_management", "doctype", "loan")
|
||||
frappe.reload_doc("loan_management", "doctype", "loan_type")
|
||||
|
||||
loan = frappe.qb.DocType("Loan")
|
||||
loan_type = frappe.qb.DocType("Loan Type")
|
||||
|
||||
frappe.qb.update(loan_type).set(
|
||||
loan_type.repayment_schedule_type, "Monthly as per repayment start date"
|
||||
).where(loan_type.is_term_loan == 1).run()
|
||||
|
||||
frappe.qb.update(loan).set(
|
||||
loan.repayment_schedule_type, "Monthly as per repayment start date"
|
||||
).where(loan.is_term_loan == 1).run()
|
||||
@@ -295,6 +295,7 @@ class TestPayrollEntry(FrappeTestCase):
|
||||
loan_account="Loan Account - _TC",
|
||||
interest_income_account="Interest Income Account - _TC",
|
||||
penalty_income_account="Penalty Income Account - _TC",
|
||||
repayment_schedule_type="Monthly as per repayment start date",
|
||||
)
|
||||
|
||||
loan = create_loan(
|
||||
|
||||
@@ -545,6 +545,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(!this.validate_company_and_party()) {
|
||||
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
|
||||
} else {
|
||||
item.pricing_rules = ''
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_item_details",
|
||||
child: item,
|
||||
@@ -1160,6 +1161,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
uom: function(doc, cdt, cdn) {
|
||||
var me = this;
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
item.pricing_rules = ''
|
||||
if(item.item_code && item.uom) {
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_conversion_factor",
|
||||
@@ -1236,6 +1238,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
|
||||
qty: function(doc, cdt, cdn) {
|
||||
let item = frappe.get_doc(cdt, cdn);
|
||||
item.pricing_rules = ''
|
||||
this.conversion_factor(doc, cdt, cdn, true);
|
||||
this.calculate_stock_uom_rate(doc, cdt, cdn);
|
||||
this.apply_pricing_rule(item, true);
|
||||
|
||||
@@ -242,20 +242,29 @@ erpnext.utils.set_taxes = function(frm, triggered_from_field) {
|
||||
});
|
||||
};
|
||||
|
||||
erpnext.utils.get_contact_details = function(frm) {
|
||||
erpnext.utils.get_contact_details = function (frm) {
|
||||
if (frm.updating_party_details) return;
|
||||
|
||||
if (frm.doc["contact_person"]) {
|
||||
frappe.call({
|
||||
method: "frappe.contacts.doctype.contact.contact.get_contact_details",
|
||||
args: {contact: frm.doc.contact_person },
|
||||
callback: function(r) {
|
||||
if (r.message)
|
||||
frm.set_value(r.message);
|
||||
}
|
||||
})
|
||||
args: { contact: frm.doc.contact_person },
|
||||
callback: function (r) {
|
||||
if (r.message) frm.set_value(r.message);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
frm.set_value({
|
||||
contact_person: "",
|
||||
contact_display: "",
|
||||
contact_email: "",
|
||||
contact_mobile: "",
|
||||
contact_phone: "",
|
||||
contact_designation: "",
|
||||
contact_department: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
|
||||
if (!value) {
|
||||
|
||||
@@ -47,6 +47,12 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
reverse_charge: function(frm) {
|
||||
if (frm.doc.reverse_charge == "Y") {
|
||||
frm.set_value('eligibility_for_itc', 'ITC on Reverse Charge');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -983,8 +983,6 @@ def validate_reverse_charge_transaction(doc, method):
|
||||
|
||||
frappe.throw(msg)
|
||||
|
||||
doc.eligibility_for_itc = "ITC on Reverse Charge"
|
||||
|
||||
|
||||
def update_itc_availed_fields(doc, method):
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
|
||||
@@ -83,11 +83,12 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
|
||||
}
|
||||
}
|
||||
|
||||
if(doc.docstatus == 1 && !(['Lost', 'Ordered']).includes(doc.status)) {
|
||||
if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
|
||||
cur_frm.add_custom_button(__('Sales Order'),
|
||||
cur_frm.cscript['Make Sales Order'], __('Create'));
|
||||
}
|
||||
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Order"),
|
||||
this.frm.cscript["Make Sales Order"],
|
||||
__("Create")
|
||||
);
|
||||
|
||||
if(doc.status!=="Ordered") {
|
||||
this.frm.add_custom_button(__('Set as Lost'), () => {
|
||||
|
||||
@@ -95,6 +95,11 @@ frappe.ui.form.on("Sales Order", {
|
||||
return query;
|
||||
});
|
||||
|
||||
// On cancel and amending a sales order with advance payment, reset advance paid amount
|
||||
if (frm.is_new()) {
|
||||
frm.set_value("advance_paid", 0)
|
||||
}
|
||||
|
||||
frm.ignore_doctypes_on_cancel_all = ['Purchase Order'];
|
||||
},
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
update_linked_doc,
|
||||
validate_inter_company_party,
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
||||
get_items_for_material_requests,
|
||||
@@ -797,6 +798,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
|
||||
if source.loyalty_points and source.order_type == "Shopping Cart":
|
||||
target.redeem_loyalty_points = 1
|
||||
|
||||
target.debit_to = get_party_account("Customer", source.customer, source.company)
|
||||
|
||||
def update_item(source, target, source_parent):
|
||||
target.amount = flt(source.amount) - flt(source.billed_amt)
|
||||
target.base_amount = target.amount * flt(source_parent.conversion_rate)
|
||||
|
||||
@@ -67,7 +67,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
{
|
||||
fieldtype: 'Link', label: __('POS Profile'),
|
||||
options: 'POS Profile', fieldname: 'pos_profile', reqd: 1,
|
||||
get_query: () => pos_profile_query,
|
||||
get_query: () => pos_profile_query(),
|
||||
onchange: () => fetch_pos_payment_methods()
|
||||
},
|
||||
{
|
||||
@@ -101,9 +101,11 @@ erpnext.PointOfSale.Controller = class {
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
dialog.show();
|
||||
const pos_profile_query = {
|
||||
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
|
||||
filters: { company: dialog.fields_dict.company.get_value() }
|
||||
const pos_profile_query = () => {
|
||||
return {
|
||||
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
|
||||
filters: { company: dialog.fields_dict.company.get_value() }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,71 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on('Brand', {
|
||||
setup: (frm) => {
|
||||
frm.fields_dict["brand_defaults"].grid.get_field("default_warehouse").get_query = function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: { company: row.company }
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict["brand_defaults"].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
'report_type': 'Profit and Loss',
|
||||
'company': row.company,
|
||||
"is_group": 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//--------- ONLOAD -------------
|
||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
frm.fields_dict["brand_defaults"].grid.get_field("buying_cost_center").get_query = function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
"is_group": 0,
|
||||
"company": row.company
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
frm.fields_dict["brand_defaults"].grid.get_field("expense_account").get_query = function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_expense_account",
|
||||
filters: { company: row.company }
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
||||
frm.fields_dict["brand_defaults"].grid.get_field("default_provisional_account").get_query = function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
"company": row.company,
|
||||
"root_type": ["in", ["Liability", "Asset"]],
|
||||
"is_group": 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
frm.fields_dict["brand_defaults"].grid.get_field("selling_cost_center").get_query = function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
"is_group": 0,
|
||||
"company": row.company
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict["brand_defaults"].grid.get_field("income_account").get_query = function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: { company: row.company }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,327 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
# mappings for table dumps
|
||||
# "remember to add indexes!"
|
||||
|
||||
data_map = {
|
||||
"Company": {"columns": ["name"], "conditions": ["docstatus < 2"]},
|
||||
"Fiscal Year": {
|
||||
"columns": ["name", "year_start_date", "year_end_date"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
},
|
||||
# Accounts
|
||||
"Account": {
|
||||
"columns": ["name", "parent_account", "lft", "rgt", "report_type", "company", "is_group"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "lft",
|
||||
"links": {
|
||||
"company": ["Company", "name"],
|
||||
},
|
||||
},
|
||||
"Cost Center": {
|
||||
"columns": ["name", "lft", "rgt"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "lft",
|
||||
},
|
||||
"GL Entry": {
|
||||
"columns": [
|
||||
"name",
|
||||
"account",
|
||||
"posting_date",
|
||||
"cost_center",
|
||||
"debit",
|
||||
"credit",
|
||||
"is_opening",
|
||||
"company",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"remarks",
|
||||
],
|
||||
"order_by": "posting_date, account",
|
||||
"links": {
|
||||
"account": ["Account", "name"],
|
||||
"company": ["Company", "name"],
|
||||
"cost_center": ["Cost Center", "name"],
|
||||
},
|
||||
},
|
||||
# Stock
|
||||
"Item": {
|
||||
"columns": [
|
||||
"name",
|
||||
"if(item_name=name, '', item_name) as item_name",
|
||||
"description",
|
||||
"item_group as parent_item_group",
|
||||
"stock_uom",
|
||||
"brand",
|
||||
"valuation_method",
|
||||
],
|
||||
# "conditions": ["docstatus < 2"],
|
||||
"order_by": "name",
|
||||
"links": {"parent_item_group": ["Item Group", "name"], "brand": ["Brand", "name"]},
|
||||
},
|
||||
"Item Group": {
|
||||
"columns": ["name", "parent_item_group"],
|
||||
# "conditions": ["docstatus < 2"],
|
||||
"order_by": "lft",
|
||||
},
|
||||
"Brand": {"columns": ["name"], "conditions": ["docstatus < 2"], "order_by": "name"},
|
||||
"Project": {"columns": ["name"], "conditions": ["docstatus < 2"], "order_by": "name"},
|
||||
"Warehouse": {"columns": ["name"], "conditions": ["docstatus < 2"], "order_by": "name"},
|
||||
"Stock Ledger Entry": {
|
||||
"columns": [
|
||||
"name",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"item_code",
|
||||
"warehouse",
|
||||
"actual_qty as qty",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"project",
|
||||
"incoming_rate as incoming_rate",
|
||||
"stock_uom",
|
||||
"serial_no",
|
||||
"qty_after_transaction",
|
||||
"valuation_rate",
|
||||
],
|
||||
"order_by": "posting_date, posting_time, creation",
|
||||
"links": {
|
||||
"item_code": ["Item", "name"],
|
||||
"warehouse": ["Warehouse", "name"],
|
||||
"project": ["Project", "name"],
|
||||
},
|
||||
"force_index": "posting_sort_index",
|
||||
},
|
||||
"Serial No": {
|
||||
"columns": ["name", "purchase_rate as incoming_rate"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "name",
|
||||
},
|
||||
"Stock Entry": {
|
||||
"columns": ["name", "purpose"],
|
||||
"conditions": ["docstatus=1"],
|
||||
"order_by": "posting_date, posting_time, name",
|
||||
},
|
||||
"Material Request Item": {
|
||||
"columns": ["item.name as name", "item_code", "warehouse", "(qty - ordered_qty) as qty"],
|
||||
"from": "`tabMaterial Request Item` item, `tabMaterial Request` main",
|
||||
"conditions": [
|
||||
"item.parent = main.name",
|
||||
"main.docstatus=1",
|
||||
"main.status != 'Stopped'",
|
||||
"ifnull(warehouse, '')!=''",
|
||||
"qty > ordered_qty",
|
||||
],
|
||||
"links": {"item_code": ["Item", "name"], "warehouse": ["Warehouse", "name"]},
|
||||
},
|
||||
"Purchase Order Item": {
|
||||
"columns": [
|
||||
"item.name as name",
|
||||
"item_code",
|
||||
"warehouse",
|
||||
"(qty - received_qty)*conversion_factor as qty",
|
||||
],
|
||||
"from": "`tabPurchase Order Item` item, `tabPurchase Order` main",
|
||||
"conditions": [
|
||||
"item.parent = main.name",
|
||||
"main.docstatus=1",
|
||||
"main.status != 'Stopped'",
|
||||
"ifnull(warehouse, '')!=''",
|
||||
"qty > received_qty",
|
||||
],
|
||||
"links": {"item_code": ["Item", "name"], "warehouse": ["Warehouse", "name"]},
|
||||
},
|
||||
"Sales Order Item": {
|
||||
"columns": [
|
||||
"item.name as name",
|
||||
"item_code",
|
||||
"(qty - delivered_qty)*conversion_factor as qty",
|
||||
"warehouse",
|
||||
],
|
||||
"from": "`tabSales Order Item` item, `tabSales Order` main",
|
||||
"conditions": [
|
||||
"item.parent = main.name",
|
||||
"main.docstatus=1",
|
||||
"main.status != 'Stopped'",
|
||||
"ifnull(warehouse, '')!=''",
|
||||
"qty > delivered_qty",
|
||||
],
|
||||
"links": {"item_code": ["Item", "name"], "warehouse": ["Warehouse", "name"]},
|
||||
},
|
||||
# Sales
|
||||
"Customer": {
|
||||
"columns": [
|
||||
"name",
|
||||
"if(customer_name=name, '', customer_name) as customer_name",
|
||||
"customer_group as parent_customer_group",
|
||||
"territory as parent_territory",
|
||||
],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "name",
|
||||
"links": {
|
||||
"parent_customer_group": ["Customer Group", "name"],
|
||||
"parent_territory": ["Territory", "name"],
|
||||
},
|
||||
},
|
||||
"Customer Group": {
|
||||
"columns": ["name", "parent_customer_group"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "lft",
|
||||
},
|
||||
"Territory": {
|
||||
"columns": ["name", "parent_territory"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "lft",
|
||||
},
|
||||
"Sales Invoice": {
|
||||
"columns": ["name", "customer", "posting_date", "company"],
|
||||
"conditions": ["docstatus=1"],
|
||||
"order_by": "posting_date",
|
||||
"links": {"customer": ["Customer", "name"], "company": ["Company", "name"]},
|
||||
},
|
||||
"Sales Invoice Item": {
|
||||
"columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"],
|
||||
"conditions": ["docstatus=1", "ifnull(parent, '')!=''"],
|
||||
"order_by": "parent",
|
||||
"links": {"parent": ["Sales Invoice", "name"], "item_code": ["Item", "name"]},
|
||||
},
|
||||
"Sales Order": {
|
||||
"columns": ["name", "customer", "transaction_date as posting_date", "company"],
|
||||
"conditions": ["docstatus=1"],
|
||||
"order_by": "transaction_date",
|
||||
"links": {"customer": ["Customer", "name"], "company": ["Company", "name"]},
|
||||
},
|
||||
"Sales Order Item[Sales Analytics]": {
|
||||
"columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"],
|
||||
"conditions": ["docstatus=1", "ifnull(parent, '')!=''"],
|
||||
"order_by": "parent",
|
||||
"links": {"parent": ["Sales Order", "name"], "item_code": ["Item", "name"]},
|
||||
},
|
||||
"Delivery Note": {
|
||||
"columns": ["name", "customer", "posting_date", "company"],
|
||||
"conditions": ["docstatus=1"],
|
||||
"order_by": "posting_date",
|
||||
"links": {"customer": ["Customer", "name"], "company": ["Company", "name"]},
|
||||
},
|
||||
"Delivery Note Item[Sales Analytics]": {
|
||||
"columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"],
|
||||
"conditions": ["docstatus=1", "ifnull(parent, '')!=''"],
|
||||
"order_by": "parent",
|
||||
"links": {"parent": ["Delivery Note", "name"], "item_code": ["Item", "name"]},
|
||||
},
|
||||
"Supplier": {
|
||||
"columns": [
|
||||
"name",
|
||||
"if(supplier_name=name, '', supplier_name) as supplier_name",
|
||||
"supplier_group as parent_supplier_group",
|
||||
],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "name",
|
||||
"links": {
|
||||
"parent_supplier_group": ["Supplier Group", "name"],
|
||||
},
|
||||
},
|
||||
"Supplier Group": {
|
||||
"columns": ["name", "parent_supplier_group"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "name",
|
||||
},
|
||||
"Purchase Invoice": {
|
||||
"columns": ["name", "supplier", "posting_date", "company"],
|
||||
"conditions": ["docstatus=1"],
|
||||
"order_by": "posting_date",
|
||||
"links": {"supplier": ["Supplier", "name"], "company": ["Company", "name"]},
|
||||
},
|
||||
"Purchase Invoice Item": {
|
||||
"columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"],
|
||||
"conditions": ["docstatus=1", "ifnull(parent, '')!=''"],
|
||||
"order_by": "parent",
|
||||
"links": {"parent": ["Purchase Invoice", "name"], "item_code": ["Item", "name"]},
|
||||
},
|
||||
"Purchase Order": {
|
||||
"columns": ["name", "supplier", "transaction_date as posting_date", "company"],
|
||||
"conditions": ["docstatus=1"],
|
||||
"order_by": "posting_date",
|
||||
"links": {"supplier": ["Supplier", "name"], "company": ["Company", "name"]},
|
||||
},
|
||||
"Purchase Order Item[Purchase Analytics]": {
|
||||
"columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"],
|
||||
"conditions": ["docstatus=1", "ifnull(parent, '')!=''"],
|
||||
"order_by": "parent",
|
||||
"links": {"parent": ["Purchase Order", "name"], "item_code": ["Item", "name"]},
|
||||
},
|
||||
"Purchase Receipt": {
|
||||
"columns": ["name", "supplier", "posting_date", "company"],
|
||||
"conditions": ["docstatus=1"],
|
||||
"order_by": "posting_date",
|
||||
"links": {"supplier": ["Supplier", "name"], "company": ["Company", "name"]},
|
||||
},
|
||||
"Purchase Receipt Item[Purchase Analytics]": {
|
||||
"columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"],
|
||||
"conditions": ["docstatus=1", "ifnull(parent, '')!=''"],
|
||||
"order_by": "parent",
|
||||
"links": {"parent": ["Purchase Receipt", "name"], "item_code": ["Item", "name"]},
|
||||
},
|
||||
# Support
|
||||
"Issue": {
|
||||
"columns": ["name", "status", "creation", "resolution_date", "first_responded_on"],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "creation",
|
||||
},
|
||||
# Manufacturing
|
||||
"Work Order": {
|
||||
"columns": [
|
||||
"name",
|
||||
"status",
|
||||
"creation",
|
||||
"planned_start_date",
|
||||
"planned_end_date",
|
||||
"status",
|
||||
"actual_start_date",
|
||||
"actual_end_date",
|
||||
"modified",
|
||||
],
|
||||
"conditions": ["docstatus = 1"],
|
||||
"order_by": "creation",
|
||||
},
|
||||
# Medical
|
||||
"Patient": {
|
||||
"columns": [
|
||||
"name",
|
||||
"creation",
|
||||
"owner",
|
||||
"if(patient_name=name, '', patient_name) as patient_name",
|
||||
],
|
||||
"conditions": ["docstatus < 2"],
|
||||
"order_by": "name",
|
||||
"links": {"owner": ["User", "name"]},
|
||||
},
|
||||
"Patient Appointment": {
|
||||
"columns": [
|
||||
"name",
|
||||
"appointment_type",
|
||||
"patient",
|
||||
"practitioner",
|
||||
"appointment_date",
|
||||
"department",
|
||||
"status",
|
||||
"company",
|
||||
],
|
||||
"order_by": "name",
|
||||
"links": {
|
||||
"practitioner": ["Healthcare Practitioner", "name"],
|
||||
"appointment_type": ["Appointment Type", "name"],
|
||||
},
|
||||
},
|
||||
"Healthcare Practitioner": {
|
||||
"columns": ["name", "department"],
|
||||
"order_by": "name",
|
||||
"links": {
|
||||
"department": ["Department", "name"],
|
||||
},
|
||||
},
|
||||
"Appointment Type": {"columns": ["name"], "order_by": "name"},
|
||||
"Medical Department": {"columns": ["name"], "order_by": "name"},
|
||||
}
|
||||
@@ -285,7 +285,7 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
|
||||
batches = get_batches(item_code, warehouse, qty, throw, serial_no)
|
||||
|
||||
for batch in batches:
|
||||
if cint(qty) <= cint(batch.qty):
|
||||
if flt(qty) <= flt(batch.qty):
|
||||
batch_no = batch.batch_id
|
||||
break
|
||||
|
||||
|
||||
@@ -832,6 +832,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
update_address(
|
||||
target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
|
||||
)
|
||||
update_address(
|
||||
target_doc, "billing_address", "billing_address_display", source_doc.customer_address
|
||||
)
|
||||
|
||||
update_taxes(
|
||||
target_doc,
|
||||
|
||||
@@ -1050,9 +1050,22 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
dn.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"description": "Tax 1",
|
||||
"rate": 14,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(dn.items[0].rate, 500) # haven't saved yet
|
||||
dn.save()
|
||||
self.assertEqual(dn.ignore_pricing_rule, 1)
|
||||
self.assertEqual(dn.taxes[0].included_in_print_rate, 0)
|
||||
|
||||
# rate should reset to incoming rate
|
||||
self.assertEqual(dn.items[0].rate, rate)
|
||||
@@ -1063,6 +1076,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
dn.save()
|
||||
|
||||
self.assertEqual(dn.items[0].rate, rate)
|
||||
self.assertEqual(dn.items[0].net_rate, rate)
|
||||
|
||||
def test_internal_transfer_precision_gle(self):
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
@@ -746,6 +746,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Incoming Rate",
|
||||
"no_copy": 1,
|
||||
"precision": "6",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -780,7 +781,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-02 12:09:39.610075",
|
||||
"modified": "2022-10-12 03:36:05.344847",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
||||
@@ -30,7 +30,7 @@ test_ignore = ["BOM"]
|
||||
test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"]
|
||||
|
||||
|
||||
def make_item(item_code=None, properties=None):
|
||||
def make_item(item_code=None, properties=None, uoms=None):
|
||||
if not item_code:
|
||||
item_code = frappe.generate_hash(length=16)
|
||||
|
||||
@@ -54,6 +54,11 @@ def make_item(item_code=None, properties=None):
|
||||
for item_default in [doc for doc in item.get("item_defaults") if not doc.default_warehouse]:
|
||||
item_default.default_warehouse = "_Test Warehouse - _TC"
|
||||
item_default.company = "_Test Company"
|
||||
|
||||
if uoms:
|
||||
for uom in uoms:
|
||||
item.append("uoms", uom)
|
||||
|
||||
item.insert()
|
||||
|
||||
return item
|
||||
|
||||
@@ -4,10 +4,13 @@ from frappe import _
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "material_request",
|
||||
"internal_links": {
|
||||
"Sales Order": ["items", "sales_order"],
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("Reference"),
|
||||
"items": ["Request for Quotation", "Supplier Quotation", "Purchase Order"],
|
||||
"items": ["Sales Order", "Request for Quotation", "Supplier Quotation", "Purchase Order"],
|
||||
},
|
||||
{"label": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]},
|
||||
{"label": _("Manufacturing"), "items": ["Work Order"]},
|
||||
|
||||
@@ -12,13 +12,17 @@ def get_data():
|
||||
"Purchase Receipt": "return_against",
|
||||
},
|
||||
"internal_links": {
|
||||
"Material Request": ["items", "material_request"],
|
||||
"Purchase Order": ["items", "purchase_order"],
|
||||
"Project": ["items", "project"],
|
||||
"Quality Inspection": ["items", "quality_inspection"],
|
||||
},
|
||||
"transactions": [
|
||||
{"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]},
|
||||
{"label": _("Reference"), "items": ["Purchase Order", "Quality Inspection", "Project"]},
|
||||
{
|
||||
"label": _("Reference"),
|
||||
"items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"],
|
||||
},
|
||||
{"label": _("Returns"), "items": ["Purchase Receipt"]},
|
||||
{"label": _("Subscription"), "items": ["Auto Repeat"]},
|
||||
],
|
||||
|
||||
@@ -1404,6 +1404,8 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
self.assertEqual(pr1.items[0].rate, 100)
|
||||
pr1.submit()
|
||||
|
||||
self.assertEqual(pr1.is_internal_supplier, 1)
|
||||
|
||||
# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name,
|
||||
@@ -1446,6 +1448,234 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
|
||||
self.assertEqual(query[0].value, 0)
|
||||
|
||||
def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_purchase_receipt(
|
||||
self,
|
||||
):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
customer = "_Test Internal Customer 2"
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
|
||||
to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
|
||||
item_doc = create_item("Test Internal Transfer Item")
|
||||
|
||||
target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
posting_date=add_days(today(), -1),
|
||||
warehouse=from_warehouse,
|
||||
qty=1,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
# Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction
|
||||
for i in range(1, 4):
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
posting_date=add_days(today(), -1 * i),
|
||||
warehouse=target_warehouse,
|
||||
qty=1,
|
||||
rate=320 * i,
|
||||
)
|
||||
|
||||
dn1 = create_delivery_note(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
customer=customer,
|
||||
cost_center="Main - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
qty=1,
|
||||
rate=500,
|
||||
warehouse=from_warehouse,
|
||||
target_warehouse=target_warehouse,
|
||||
)
|
||||
|
||||
self.assertEqual(dn1.items[0].rate, 100)
|
||||
|
||||
pr1 = make_inter_company_purchase_receipt(dn1.name)
|
||||
pr1.items[0].warehouse = to_warehouse
|
||||
self.assertEqual(pr1.items[0].rate, 100)
|
||||
pr1.submit()
|
||||
|
||||
stk_ledger = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": target_warehouse},
|
||||
["stock_value_difference", "outgoing_rate"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(abs(stk_ledger.stock_value_difference), 100)
|
||||
self.assertEqual(stk_ledger.outgoing_rate, 100)
|
||||
|
||||
# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
posting_date=add_days(today(), -2),
|
||||
warehouse=from_warehouse,
|
||||
qty=1,
|
||||
rate=200,
|
||||
)
|
||||
|
||||
dn_value = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse},
|
||||
"stock_value_difference",
|
||||
)
|
||||
|
||||
self.assertEqual(abs(dn_value), 200.00)
|
||||
|
||||
pr_value = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse},
|
||||
"stock_value_difference",
|
||||
)
|
||||
|
||||
self.assertEqual(abs(pr_value), 200.00)
|
||||
pr1.load_from_db()
|
||||
|
||||
self.assertEqual(pr1.items[0].valuation_rate, 200)
|
||||
self.assertEqual(pr1.items[0].rate, 100)
|
||||
|
||||
Gl = frappe.qb.DocType("GL Entry")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(Gl)
|
||||
.select(
|
||||
(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
|
||||
)
|
||||
.where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name))
|
||||
).run(as_dict=True)
|
||||
|
||||
self.assertEqual(query[0].value, 0)
|
||||
|
||||
def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_purchase_invoice(
|
||||
self,
|
||||
):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
||||
make_purchase_invoice as make_purchase_invoice_for_si,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
make_inter_company_purchase_invoice,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
customer = "_Test Internal Customer 2"
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
|
||||
to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
|
||||
item_doc = create_item("Test Internal Transfer Item")
|
||||
|
||||
target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
|
||||
|
||||
make_purchase_invoice_for_si(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
posting_date=add_days(today(), -1),
|
||||
warehouse=from_warehouse,
|
||||
qty=1,
|
||||
update_stock=1,
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
rate=100,
|
||||
)
|
||||
|
||||
# Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction
|
||||
for i in range(1, 4):
|
||||
make_purchase_invoice_for_si(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
posting_date=add_days(today(), -1 * i),
|
||||
warehouse=target_warehouse,
|
||||
update_stock=1,
|
||||
qty=1,
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
rate=320 * i,
|
||||
)
|
||||
|
||||
si1 = create_sales_invoice(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
customer=customer,
|
||||
cost_center="Main - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
qty=1,
|
||||
rate=500,
|
||||
update_stock=1,
|
||||
warehouse=from_warehouse,
|
||||
target_warehouse=target_warehouse,
|
||||
)
|
||||
|
||||
self.assertEqual(si1.items[0].rate, 100)
|
||||
|
||||
pi1 = make_inter_company_purchase_invoice(si1.name)
|
||||
pi1.items[0].warehouse = to_warehouse
|
||||
self.assertEqual(pi1.items[0].rate, 100)
|
||||
pi1.update_stock = 1
|
||||
pi1.save()
|
||||
pi1.submit()
|
||||
|
||||
stk_ledger = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": target_warehouse},
|
||||
["stock_value_difference", "outgoing_rate"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(abs(stk_ledger.stock_value_difference), 100)
|
||||
self.assertEqual(stk_ledger.outgoing_rate, 100)
|
||||
|
||||
# Backdated purchase receipt entry, the valuation rate should be updated for si1 and pi1
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name,
|
||||
company=company,
|
||||
posting_date=add_days(today(), -2),
|
||||
warehouse=from_warehouse,
|
||||
qty=1,
|
||||
rate=200,
|
||||
)
|
||||
|
||||
si_value = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": si1.doctype, "voucher_no": si1.name, "warehouse": target_warehouse},
|
||||
"stock_value_difference",
|
||||
)
|
||||
|
||||
self.assertEqual(abs(si_value), 200.00)
|
||||
|
||||
pi_value = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": to_warehouse},
|
||||
"stock_value_difference",
|
||||
)
|
||||
|
||||
self.assertEqual(abs(pi_value), 200.00)
|
||||
pi1.load_from_db()
|
||||
|
||||
self.assertEqual(pi1.items[0].valuation_rate, 200)
|
||||
self.assertEqual(pi1.items[0].rate, 100)
|
||||
|
||||
Gl = frappe.qb.DocType("GL Entry")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(Gl)
|
||||
.select(
|
||||
(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
|
||||
)
|
||||
.where((Gl.voucher_type == pi1.doctype) & (Gl.voucher_no == pi1.name))
|
||||
).run(as_dict=True)
|
||||
|
||||
self.assertEqual(query[0].value, 0)
|
||||
|
||||
def test_batch_expiry_for_purchase_receipt(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
|
||||
@@ -738,6 +738,7 @@
|
||||
"oldfieldname": "valuation_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"precision": "6",
|
||||
"print_hide": 1,
|
||||
"print_width": "80px",
|
||||
"read_only": 1,
|
||||
@@ -992,7 +993,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-07-28 19:27:54.880781",
|
||||
"modified": "2022-10-12 03:37:59.516609",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -864,16 +864,15 @@ def get_pos_reserved_serial_nos(filters):
|
||||
|
||||
pos_transacted_sr_nos = query.run(as_dict=True)
|
||||
|
||||
reserved_sr_nos = []
|
||||
returned_sr_nos = []
|
||||
reserved_sr_nos = set()
|
||||
returned_sr_nos = set()
|
||||
for d in pos_transacted_sr_nos:
|
||||
if d.is_return == 0:
|
||||
reserved_sr_nos += get_serial_nos(d.serial_no)
|
||||
[reserved_sr_nos.add(x) for x in get_serial_nos(d.serial_no)]
|
||||
elif d.is_return == 1:
|
||||
returned_sr_nos += get_serial_nos(d.serial_no)
|
||||
[returned_sr_nos.add(x) for x in get_serial_nos(d.serial_no)]
|
||||
|
||||
for sr_no in returned_sr_nos:
|
||||
reserved_sr_nos.remove(sr_no)
|
||||
reserved_sr_nos = list(reserved_sr_nos - returned_sr_nos)
|
||||
|
||||
return reserved_sr_nos
|
||||
|
||||
|
||||
@@ -902,13 +902,15 @@ def create_product_bundle_item(new_item_code, packed_items):
|
||||
item.save()
|
||||
|
||||
|
||||
def create_items():
|
||||
items = [
|
||||
"_Test Item for Reposting",
|
||||
"_Test Finished Item for Reposting",
|
||||
"_Test Subcontracted Item for Reposting",
|
||||
"_Test Bundled Item for Reposting",
|
||||
]
|
||||
def create_items(items=None, uoms=None):
|
||||
if not items:
|
||||
items = [
|
||||
"_Test Item for Reposting",
|
||||
"_Test Finished Item for Reposting",
|
||||
"_Test Subcontracted Item for Reposting",
|
||||
"_Test Bundled Item for Reposting",
|
||||
]
|
||||
|
||||
for d in items:
|
||||
properties = {"valuation_method": "FIFO"}
|
||||
if d == "_Test Bundled Item for Reposting":
|
||||
@@ -916,7 +918,7 @@ def create_items():
|
||||
elif d == "_Test Subcontracted Item for Reposting":
|
||||
properties.update({"is_sub_contracted_item": 1})
|
||||
|
||||
make_item(d, properties=properties)
|
||||
make_item(d, properties=properties, uoms=uoms)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
@@ -131,7 +131,9 @@ class StockReconciliation(StockController):
|
||||
key.append(row.get(field))
|
||||
|
||||
if key in item_warehouse_combinations:
|
||||
self.validation_messages.append(_get_msg(row_num, _("Duplicate entry")))
|
||||
self.validation_messages.append(
|
||||
_get_msg(row_num, _("Same item and warehouse combination already entered."))
|
||||
)
|
||||
else:
|
||||
item_warehouse_combinations.append(key)
|
||||
|
||||
|
||||
@@ -74,10 +74,21 @@ def get_conditions(filters):
|
||||
else:
|
||||
frappe.throw(_("'To Date' is required"))
|
||||
|
||||
for field in ["item_code", "warehouse", "batch_no", "company"]:
|
||||
for field in ["item_code", "batch_no", "company"]:
|
||||
if filters.get(field):
|
||||
conditions += " and {0} = {1}".format(field, frappe.db.escape(filters.get(field)))
|
||||
|
||||
if filters.get("warehouse"):
|
||||
warehouse_details = frappe.db.get_value(
|
||||
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
||||
)
|
||||
if warehouse_details:
|
||||
conditions += (
|
||||
" and exists (select name from `tabWarehouse` wh \
|
||||
where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"
|
||||
% (warehouse_details.lft, warehouse_details.rgt)
|
||||
)
|
||||
|
||||
return conditions
|
||||
|
||||
|
||||
@@ -87,7 +98,7 @@ def get_stock_ledger_entries(filters):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
|
||||
from `tabStock Ledger Entry`
|
||||
from `tabStock Ledger Entry` as sle
|
||||
where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s
|
||||
group by voucher_no, batch_no, item_code, warehouse
|
||||
order by item_code, warehouse"""
|
||||
|
||||
@@ -532,6 +532,14 @@ class update_entries_after(object):
|
||||
if not self.args.get("sle_id"):
|
||||
self.get_dynamic_incoming_outgoing_rate(sle)
|
||||
|
||||
if (
|
||||
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||
and sle.voucher_detail_no
|
||||
and sle.actual_qty < 0
|
||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||
):
|
||||
sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle)
|
||||
|
||||
if get_serial_nos(sle.serial_no):
|
||||
self.get_serialized_values(sle)
|
||||
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
|
||||
@@ -579,6 +587,7 @@ class update_entries_after(object):
|
||||
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
|
||||
sle.stock_value_difference = stock_value_difference
|
||||
sle.doctype = "Stock Ledger Entry"
|
||||
|
||||
frappe.get_doc(sle).db_update()
|
||||
|
||||
if not self.args.get("sle_id"):
|
||||
@@ -641,22 +650,7 @@ class update_entries_after(object):
|
||||
and sle.voucher_detail_no
|
||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||
):
|
||||
field = (
|
||||
"delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item"
|
||||
)
|
||||
doctype = (
|
||||
"Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item"
|
||||
)
|
||||
refernce_name = frappe.get_cached_value(
|
||||
sle.voucher_type + " Item", sle.voucher_detail_no, field
|
||||
)
|
||||
|
||||
if refernce_name:
|
||||
rate = frappe.get_cached_value(
|
||||
doctype,
|
||||
refernce_name,
|
||||
"incoming_rate",
|
||||
)
|
||||
rate = get_incoming_rate_for_inter_company_transfer(sle)
|
||||
else:
|
||||
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
||||
rate_field = "valuation_rate"
|
||||
@@ -731,14 +725,12 @@ class update_entries_after(object):
|
||||
|
||||
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
|
||||
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
|
||||
frappe.db.set_value(
|
||||
sle.voucher_type + " Item",
|
||||
sle.voucher_detail_no,
|
||||
{
|
||||
"base_net_rate": outgoing_rate,
|
||||
"valuation_rate": outgoing_rate,
|
||||
},
|
||||
)
|
||||
if sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and frappe.get_cached_value(
|
||||
sle.voucher_type, sle.voucher_no, "is_internal_supplier"
|
||||
):
|
||||
frappe.db.set_value(
|
||||
f"{sle.voucher_type} Item", sle.voucher_detail_no, "valuation_rate", sle.outgoing_rate
|
||||
)
|
||||
else:
|
||||
frappe.db.set_value(
|
||||
"Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
|
||||
@@ -1482,3 +1474,25 @@ def _round_off_if_near_zero(number: float, precision: int = 6) -> float:
|
||||
return 0.0
|
||||
|
||||
return flt(number)
|
||||
|
||||
|
||||
def get_incoming_rate_for_inter_company_transfer(sle) -> float:
|
||||
"""
|
||||
For inter company transfer, incoming rate is the average of the outgoing rate
|
||||
"""
|
||||
rate = 0.0
|
||||
|
||||
field = "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item"
|
||||
|
||||
doctype = "Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item"
|
||||
|
||||
reference_name = frappe.get_cached_value(sle.voucher_type + " Item", sle.voucher_detail_no, field)
|
||||
|
||||
if reference_name:
|
||||
rate = frappe.get_cached_value(
|
||||
doctype,
|
||||
reference_name,
|
||||
"incoming_rate",
|
||||
)
|
||||
|
||||
return rate
|
||||
|
||||
@@ -9865,3 +9865,35 @@ Leave Type Allocation,Zuordnung Abwesenheitsarten,
|
||||
From Lead,Aus Lead,
|
||||
From Opportunity,Aus Chance,
|
||||
Publish in Website,Auf Webseite veröffentlichen,
|
||||
Total Allocated Leave(s),Gesamte zugewiesene Urlaubstage,
|
||||
Expired Leave(s),Verfallene Urlaubstage,
|
||||
Used Leave(s),Verbrauchte Urlaubstage,
|
||||
Leave(s) Pending Approval,Urlaubstage zur Genehmigung ausstehend,
|
||||
Available Leave(s),Verfügbare Urlaubstage,
|
||||
Party Specific Item,Parteispezifischer Artikel,
|
||||
Active Customers,Aktive Kunden,
|
||||
Annual Sales,Jährlicher Umsatz,
|
||||
Total Outgoing Bills,Ausgangsrechnungen insgesamt,
|
||||
Total Incoming Bills,Eingangsrechnungen insgesamt,
|
||||
Total Incoming Payment,Zahlungseingang insgesamt,
|
||||
Total Outgoing Payment,Zahlungsausgang insgesamt,
|
||||
Incoming Bills (Purchase Invoice),Eingehende Rechnungen (Eingangsrechnung),
|
||||
Outgoing Bills (Sales Invoice),Ausgehende Rechnungen (Ausgangsrechnung),
|
||||
Accounts Receivable Ageing,Fälligkeit Forderungen,
|
||||
Accounts Payable Ageing,Fälligkeit Verbindlichkeiten,
|
||||
Budget Variance,Budgetabweichung,
|
||||
Based On Value,Basierend auf Wert,
|
||||
Restrict Items Based On,Artikel einschränken auf Basis von,
|
||||
Earnings & Deductions,Erträge & Abzüge,
|
||||
Is Process Loss,Ist Prozessverlust,
|
||||
Is Finished Item,Ist fertiger Artikel,
|
||||
Is Scrap Item,Ist Schrott,
|
||||
Issue a debit note with 0 qty against an existing Sales Invoice,Lastschrift mit Menge 0 gegen eine bestehende Ausgangsrechnung ausstellen,
|
||||
Show Remarks,Bemerkungen anzeigen,
|
||||
Website Item,Webseiten-Artikel,
|
||||
Update Property,Eigenschaft aktualisieren,
|
||||
Recurring Sales Invoice,Wiederkehrende Ausgangsrechnung,
|
||||
Total Asset,Aktiva,
|
||||
Total Liability,Verbindlichkeiten,
|
||||
Total Equity,Eigenkapital,
|
||||
Warehouse wise Stock Value,Warenwert nach Lager,
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -60,7 +60,7 @@ def update_youtube_data():
|
||||
"Video Settings", "Video Settings", ["enable_youtube_tracking", "frequency"]
|
||||
)
|
||||
|
||||
if not enable_youtube_tracking:
|
||||
if not frappe.utils.cint(enable_youtube_tracking):
|
||||
return
|
||||
|
||||
frequency = get_frequency(frequency)
|
||||
|
||||
Reference in New Issue
Block a user