mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-15 19:28:50 +00:00
Compare commits
128 Commits
v13.39.4
...
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 | ||
|
|
558dc57b94 | ||
|
|
cd942d36a8 | ||
|
|
3257533ee3 | ||
|
|
2763d06307 | ||
|
|
a0772ea8d7 | ||
|
|
d67b44fcec | ||
|
|
257a2a3d71 | ||
|
|
e6abbd1c83 | ||
|
|
41599cf29f | ||
|
|
997990c69f | ||
|
|
2bf76f64bd | ||
|
|
e1c41b9195 | ||
|
|
32a9575f07 | ||
|
|
e031e095dd | ||
|
|
39ce3756c8 | ||
|
|
f2515d9c9c | ||
|
|
27fe9c9179 | ||
|
|
503183cb0b | ||
|
|
f1ea5de022 | ||
|
|
baa4fec611 | ||
|
|
476175b307 | ||
|
|
6dabfbedf1 | ||
|
|
68c592377b | ||
|
|
f24f71f387 | ||
|
|
552c5951bd | ||
|
|
308c400c6a | ||
|
|
a9546dd01f | ||
|
|
cb4fbd5432 | ||
|
|
6f8d62088e |
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 }}
|
||||
|
||||
@@ -4,7 +4,7 @@ import frappe
|
||||
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = "13.39.4"
|
||||
__version__ = "13.36.1"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -173,8 +173,8 @@ frappe.ui.form.on("Journal Entry", {
|
||||
var update_jv_details = function(doc, r) {
|
||||
$.each(r, function(i, d) {
|
||||
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
|
||||
row.account = d.account;
|
||||
row.balance = d.balance;
|
||||
frappe.model.set_value(row.doctype, row.name, "account", d.account)
|
||||
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
|
||||
});
|
||||
refresh_field("accounts");
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1428,6 +1428,7 @@
|
||||
"fieldname": "tax_withheld_vouchers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withheld Vouchers",
|
||||
"no_copy": 1,
|
||||
"options": "Tax Withheld Vouchers",
|
||||
"read_only": 1
|
||||
}
|
||||
@@ -1436,7 +1437,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-13 23:39:54.525037",
|
||||
"modified": "2022-10-07 14:19:14.214157",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
@@ -1500,4 +1501,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -240,6 +240,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
)
|
||||
else:
|
||||
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
|
||||
|
||||
# once tds is deducted, not need to add vouchers in the invoice
|
||||
voucher_wise_amount = {}
|
||||
else:
|
||||
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)
|
||||
|
||||
@@ -419,7 +422,10 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
):
|
||||
# Get net total again as TDS is calculated on net total
|
||||
# Grand is used to just check for threshold breach
|
||||
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0
|
||||
net_total = 0
|
||||
if vouchers:
|
||||
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)")
|
||||
|
||||
net_total += inv.net_total
|
||||
supp_credit_amt = net_total - cumulative_threshold
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -323,6 +323,7 @@ class PurchaseOrder(BuyingController):
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry")
|
||||
super(PurchaseOrder, self).on_cancel()
|
||||
|
||||
if self.is_against_so():
|
||||
@@ -532,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:
|
||||
|
||||
@@ -11,9 +11,12 @@ frappe.ui.form.on('Course Scheduling Tool', {
|
||||
},
|
||||
refresh(frm) {
|
||||
frm.disable_save();
|
||||
frm.trigger("render_days");
|
||||
frm.page.set_primary_action(__('Schedule Course'), () => {
|
||||
frm.call('schedule_course')
|
||||
frappe.dom.freeze(__("Scheduling..."));
|
||||
frm.call('schedule_course', { days: frm.days.get_checked_options() })
|
||||
.then(r => {
|
||||
frappe.dom.unfreeze();
|
||||
if (!r.message) {
|
||||
frappe.throw(__('There were errors creating Course Schedule'));
|
||||
}
|
||||
@@ -40,5 +43,60 @@ frappe.ui.form.on('Course Scheduling Tool', {
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
render_days: function(frm) {
|
||||
const days_html = $('<div class="days-editor">').appendTo(
|
||||
frm.fields_dict.days_html.wrapper
|
||||
);
|
||||
|
||||
if (!frm.days) {
|
||||
frm.days = frappe.ui.form.make_control({
|
||||
parent: days_html,
|
||||
df: {
|
||||
fieldname: "days",
|
||||
fieldtype: "MultiCheck",
|
||||
select_all: true,
|
||||
columns: 4,
|
||||
options: [
|
||||
{
|
||||
label: __("Monday"),
|
||||
value: "Monday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Tuesday"),
|
||||
value: "Tuesday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Wednesday"),
|
||||
value: "Wednesday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Thursday"),
|
||||
value: "Thursday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Friday"),
|
||||
value: "Friday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Saturday"),
|
||||
value: "Saturday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Sunday"),
|
||||
value: "Sunday",
|
||||
checked: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
render_input: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,661 +1,171 @@
|
||||
{
|
||||
"allow_copy": 1,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2015-09-23 15:37:38.108475",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_copy": 1,
|
||||
"creation": "2015-09-23 15:37:38.108475",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"student_group",
|
||||
"course",
|
||||
"program",
|
||||
"column_break_3",
|
||||
"academic_year",
|
||||
"academic_term",
|
||||
"section_break_6",
|
||||
"instructor",
|
||||
"instructor_name",
|
||||
"column_break_9",
|
||||
"room",
|
||||
"section_break_7",
|
||||
"days_html",
|
||||
"section_break_14",
|
||||
"from_time",
|
||||
"course_start_date",
|
||||
"column_break_15",
|
||||
"to_time",
|
||||
"course_end_date",
|
||||
"reschedule"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_group",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Group",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Group",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "student_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Group",
|
||||
"options": "Student Group",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Course",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Course",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "Course",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Program",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"label": "Program",
|
||||
"options": "Program",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Academic Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Academic Year",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"label": "Academic Year",
|
||||
"options": "Academic Year",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_term",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Academic Term",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Academic Term",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "academic_term",
|
||||
"fieldtype": "Link",
|
||||
"label": "Academic Term",
|
||||
"options": "Academic Term",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "instructor",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Instructor",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Instructor",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "instructor",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Instructor",
|
||||
"options": "Instructor",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "instructor.instructor_name",
|
||||
"fieldname": "instructor_name",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Instructor Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "instructor_name",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Instructor Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "room",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Room",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Room",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "room",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Room",
|
||||
"options": "Room",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "From Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "course_start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Course Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "course_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Course Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "day",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Day",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "reschedule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Reschedule"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reschedule",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reschedule",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "To TIme",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To TIme",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "course_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Course End Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "course_end_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Course End Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "days_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Days HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 1,
|
||||
"hide_toolbar": 1,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2018-05-16 22:43:29.363798",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Course Scheduling Tool",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-01 17:08:07.180557",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Course Scheduling Tool",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Academics User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"read": 1,
|
||||
"role": "Academics User",
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"restrict_to_domain": "Education",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -14,7 +14,7 @@ from erpnext.education.utils import OverlapError
|
||||
|
||||
class CourseSchedulingTool(Document):
|
||||
@frappe.whitelist()
|
||||
def schedule_course(self):
|
||||
def schedule_course(self, days):
|
||||
"""Creates course schedules as per specified parameters"""
|
||||
|
||||
course_schedules = []
|
||||
@@ -22,7 +22,7 @@ class CourseSchedulingTool(Document):
|
||||
rescheduled = []
|
||||
reschedule_errors = []
|
||||
|
||||
self.validate_mandatory()
|
||||
self.validate_mandatory(days)
|
||||
self.validate_date()
|
||||
self.instructor_name = frappe.db.get_value("Instructor", self.instructor, "instructor_name")
|
||||
|
||||
@@ -34,24 +34,22 @@ class CourseSchedulingTool(Document):
|
||||
self.course = course
|
||||
|
||||
if self.reschedule:
|
||||
rescheduled, reschedule_errors = self.delete_course_schedule(rescheduled, reschedule_errors)
|
||||
rescheduled, reschedule_errors = self.delete_course_schedule(
|
||||
rescheduled, reschedule_errors, days
|
||||
)
|
||||
|
||||
date = self.course_start_date
|
||||
while date < self.course_end_date:
|
||||
if self.day == calendar.day_name[getdate(date).weekday()]:
|
||||
if calendar.day_name[getdate(date).weekday()] in days:
|
||||
course_schedule = self.make_course_schedule(date)
|
||||
try:
|
||||
print("pass")
|
||||
course_schedule.save()
|
||||
except OverlapError:
|
||||
print("fail")
|
||||
course_schedules_errors.append(date)
|
||||
else:
|
||||
course_schedules.append(course_schedule)
|
||||
|
||||
date = add_days(date, 7)
|
||||
else:
|
||||
date = add_days(date, 1)
|
||||
date = add_days(date, 1)
|
||||
|
||||
return dict(
|
||||
course_schedules=course_schedules,
|
||||
@@ -60,8 +58,10 @@ class CourseSchedulingTool(Document):
|
||||
reschedule_errors=reschedule_errors,
|
||||
)
|
||||
|
||||
def validate_mandatory(self):
|
||||
def validate_mandatory(self, days):
|
||||
"""Validates all mandatory fields"""
|
||||
if not days:
|
||||
frappe.throw(_("Please select at least one day to schedule the course."))
|
||||
|
||||
fields = [
|
||||
"course",
|
||||
@@ -71,7 +71,6 @@ class CourseSchedulingTool(Document):
|
||||
"to_time",
|
||||
"course_start_date",
|
||||
"course_end_date",
|
||||
"day",
|
||||
]
|
||||
for d in fields:
|
||||
if not self.get(d):
|
||||
@@ -82,9 +81,8 @@ class CourseSchedulingTool(Document):
|
||||
if self.course_start_date > self.course_end_date:
|
||||
frappe.throw(_("Course Start Date cannot be greater than Course End Date."))
|
||||
|
||||
def delete_course_schedule(self, rescheduled, reschedule_errors):
|
||||
def delete_course_schedule(self, rescheduled, reschedule_errors, days):
|
||||
"""Delete all course schedule within the Date range and specified filters"""
|
||||
|
||||
schedules = frappe.get_list(
|
||||
"Course Schedule",
|
||||
fields=["name", "schedule_date"],
|
||||
@@ -98,7 +96,7 @@ class CourseSchedulingTool(Document):
|
||||
|
||||
for d in schedules:
|
||||
try:
|
||||
if self.day == calendar.day_name[getdate(d.schedule_date).weekday()]:
|
||||
if calendar.day_name[getdate(d.schedule_date).weekday()] in days:
|
||||
frappe.delete_doc("Course Schedule", d.name)
|
||||
rescheduled.append(d.name)
|
||||
except Exception:
|
||||
@@ -108,7 +106,6 @@ class CourseSchedulingTool(Document):
|
||||
def make_course_schedule(self, date):
|
||||
"""Makes a new Course Schedule.
|
||||
:param date: Date on which Course Schedule will be created."""
|
||||
|
||||
course_schedule = frappe.new_doc("Course Schedule")
|
||||
course_schedule.student_group = self.student_group
|
||||
course_schedule.course = self.course
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -253,10 +253,12 @@ def get_unmarked_days(employee, month, exclude_holidays=0):
|
||||
start_day = 1
|
||||
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
|
||||
|
||||
if joining_date and joining_date.month == month_map[month]:
|
||||
if joining_date and joining_date.year == today.year and joining_date.month == month_map[month]:
|
||||
start_day = joining_date.day
|
||||
|
||||
if relieving_date and relieving_date.month == month_map[month]:
|
||||
if (
|
||||
relieving_date and relieving_date.year == today.year and relieving_date.month == month_map[month]
|
||||
):
|
||||
end_day = relieving_date.day + 1
|
||||
|
||||
dates_of_month = [
|
||||
|
||||
@@ -13,6 +13,8 @@ frappe.listview_settings['Attendance'] = {
|
||||
onload: function(list_view) {
|
||||
let me = this;
|
||||
const months = moment.months();
|
||||
const curMonth = moment().format("MMMM");
|
||||
months.splice(months.indexOf(curMonth) + 1);
|
||||
list_view.page.add_inner_button(__("Mark Attendance"), function() {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Mark Attendance"),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"naming_series",
|
||||
"report_date",
|
||||
"status",
|
||||
"manual_inspection",
|
||||
"column_break_4",
|
||||
"inspection_type",
|
||||
"reference_type",
|
||||
@@ -231,6 +232,12 @@
|
||||
"label": "Status",
|
||||
"options": "\nAccepted\nRejected",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "manual_inspection",
|
||||
"fieldtype": "Check",
|
||||
"label": "Manual Inspection"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-search",
|
||||
@@ -238,10 +245,11 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-18 19:59:55.710300",
|
||||
"modified": "2022-10-04 22:00:13.995221",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Quality Inspection",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -262,5 +270,6 @@
|
||||
"search_fields": "item_code, report_date, reference_name",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
@@ -30,6 +30,9 @@ class QualityInspection(Document):
|
||||
if self.readings:
|
||||
self.inspect_and_set_status()
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_readings_status_mandatory()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_specification_details(self):
|
||||
if not self.quality_inspection_template:
|
||||
@@ -65,6 +68,11 @@ class QualityInspection(Document):
|
||||
def on_cancel(self):
|
||||
self.update_qc_reference()
|
||||
|
||||
def validate_readings_status_mandatory(self):
|
||||
for reading in self.readings:
|
||||
if not reading.status:
|
||||
frappe.throw(_("Row #{0}: Status is mandatory").format(reading.idx))
|
||||
|
||||
def update_qc_reference(self):
|
||||
quality_inspection = self.name if self.docstatus == 1 else ""
|
||||
|
||||
@@ -124,6 +132,16 @@ class QualityInspection(Document):
|
||||
# if not formula based check acceptance values set
|
||||
self.set_status_based_on_acceptance_values(reading)
|
||||
|
||||
if not self.manual_inspection:
|
||||
self.status = "Accepted"
|
||||
for reading in self.readings:
|
||||
if reading.status == "Rejected":
|
||||
self.status = "Rejected"
|
||||
frappe.msgprint(
|
||||
_("Status set to rejected as there are one or more rejected readings."), alert=True
|
||||
)
|
||||
break
|
||||
|
||||
def set_status_based_on_acceptance_values(self, reading):
|
||||
if not cint(reading.numeric):
|
||||
result = reading.get("reading_value") == reading.get("value")
|
||||
|
||||
@@ -160,7 +160,7 @@ class TestQualityInspection(FrappeTestCase):
|
||||
)
|
||||
|
||||
readings = [
|
||||
{"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "0.4"}
|
||||
{"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "1.0"}
|
||||
]
|
||||
|
||||
qa = create_quality_inspection(
|
||||
@@ -184,6 +184,38 @@ class TestQualityInspection(FrappeTestCase):
|
||||
se.cancel()
|
||||
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop")
|
||||
|
||||
def test_qi_status(self):
|
||||
make_stock_entry(
|
||||
item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100
|
||||
)
|
||||
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
|
||||
qa = create_quality_inspection(
|
||||
reference_type="Delivery Note", reference_name=dn.name, status="Accepted", do_not_save=True
|
||||
)
|
||||
qa.readings[0].manual_inspection = 1
|
||||
qa.save()
|
||||
|
||||
# Case - 1: When there are one or more readings with rejected status and parent manual inspection is unchecked, then parent status should be set to rejected.
|
||||
qa.status = "Accepted"
|
||||
qa.manual_inspection = 0
|
||||
qa.readings[0].status = "Rejected"
|
||||
qa.save()
|
||||
self.assertEqual(qa.status, "Rejected")
|
||||
|
||||
# Case - 2: When all readings have accepted status and parent manual inspection is unchecked, then parent status should be set to accepted.
|
||||
qa.status = "Rejected"
|
||||
qa.manual_inspection = 0
|
||||
qa.readings[0].status = "Accepted"
|
||||
qa.save()
|
||||
self.assertEqual(qa.status, "Accepted")
|
||||
|
||||
# Case - 3: When parent manual inspection is checked, then parent status should not be changed.
|
||||
qa.status = "Accepted"
|
||||
qa.manual_inspection = 1
|
||||
qa.readings[0].status = "Rejected"
|
||||
qa.save()
|
||||
self.assertEqual(qa.status, "Accepted")
|
||||
|
||||
|
||||
def create_quality_inspection(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -128,6 +128,9 @@ def repost(doc):
|
||||
if not frappe.db.exists("Repost Item Valuation", doc.name):
|
||||
return
|
||||
|
||||
# This is to avoid TooManyWritesError in case of large reposts
|
||||
frappe.db.MAX_WRITES_PER_TRANSACTION *= 4
|
||||
|
||||
doc.set_status("In Progress")
|
||||
if not frappe.flags.in_test:
|
||||
frappe.db.commit()
|
||||
|
||||
@@ -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.
|
@@ -785,7 +785,7 @@ Default BOM for {0} not found,Nomenclature par défaut {0} introuvable,
|
||||
Default BOM not found for Item {0} and Project {1},La nomenclature par défaut n'a pas été trouvée pour l'Article {0} et le Projet {1},
|
||||
Default Letter Head,En-Tête de Courrier par Défaut,
|
||||
Default Tax Template,Modèle de Taxes par Défaut,
|
||||
Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,L’Unité de Mesure par Défaut pour l’Article {0} ne peut pas être modifiée directement parce que vous avez déjà fait une (des) transaction (s) avec une autre unité de mesure. Vous devez créer un nouvel article pour utiliser une UDM par défaut différente.,
|
||||
Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,L’Unité de Mesure par Défaut pour l’Article {0} ne peut pas être modifiée directement parce que vous avez déjà fait une (des) transaction (s) avec une autre unité de mesure. Vous devez créer un nouvel article pour utiliser une UdM par défaut différente.,
|
||||
Default Unit of Measure for Variant '{0}' must be same as in Template '{1}',L’Unité de mesure par défaut pour la variante '{0}' doit être la même que dans le Modèle '{1}',
|
||||
Default settings for buying transactions.,Paramètres par défaut pour les transactions d'achat.,
|
||||
Default settings for selling transactions.,Paramètres par défaut pour les transactions de vente.,
|
||||
@@ -838,7 +838,7 @@ Difference Account,Compte d’Écart,
|
||||
"Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry","Le Compte d’Écart doit être un compte de type Actif / Passif, puisque cette Réconciliation de Stock est une écriture d'à-nouveau",
|
||||
Difference Amount,Écart de Montant,
|
||||
Difference Amount must be zero,L’Écart de Montant doit être égal à zéro,
|
||||
Different UOM for items will lead to incorrect (Total) Net Weight value. Make sure that Net Weight of each item is in the same UOM.,Différentes UDM pour les articles conduira à un Poids Net (Total) incorrect . Assurez-vous que le Poids Net de chaque article a la même unité de mesure .,
|
||||
Different UOM for items will lead to incorrect (Total) Net Weight value. Make sure that Net Weight of each item is in the same UOM.,Différentes UdM pour les articles conduira à un Poids Net (Total) incorrect . Assurez-vous que le Poids Net de chaque article a la même unité de mesure .,
|
||||
Direct Expenses,Charges Directes,
|
||||
Direct Income,Revenu direct,
|
||||
Disable,Désactiver,
|
||||
@@ -1409,7 +1409,7 @@ Lab Test,Test de laboratoire,
|
||||
Lab Test Report,Rapport de test de laboratoire,
|
||||
Lab Test Sample,Échantillon de test de laboratoire,
|
||||
Lab Test Template,Modèle de test de laboratoire,
|
||||
Lab Test UOM,UDM de test de laboratoire,
|
||||
Lab Test UOM,UdM de test de laboratoire,
|
||||
Lab Tests and Vital Signs,Tests de laboratoire et signes vitaux,
|
||||
Lab result datetime cannot be before testing datetime,La date et l'heure du résultat de laboratoire ne peuvent pas être avant la date et l'heure du test,
|
||||
Lab testing datetime cannot be before collection datetime,La date et l'heure du test de laboratoire ne peuvent pas être avant la date et l'heure de collecte,
|
||||
@@ -2806,7 +2806,7 @@ Stock Received But Not Billed,Stock Reçus Mais Non Facturés,
|
||||
Stock Reports,Rapports de stock,
|
||||
Stock Summary,Résumé du Stock,
|
||||
Stock Transactions,Transactions du Stock,
|
||||
Stock UOM,UDM du Stock,
|
||||
Stock UOM,UdM du Stock,
|
||||
Stock Value,Valeur du Stock,
|
||||
Stock balance in Batch {0} will become negative {1} for Item {2} at Warehouse {3},Solde du stock dans le Lot {0} deviendra négatif {1} pour l'Article {2} à l'Entrepôt {3},
|
||||
Stock cannot be updated against Delivery Note {0},Stock ne peut pas être mis à jour pour le Bon de Livraison {0},
|
||||
@@ -3161,9 +3161,9 @@ Trial Period End Date Cannot be before Trial Period Start Date,La date de fin de
|
||||
Trialling,Essai,
|
||||
Type of Business,Type de commerce,
|
||||
Types of activities for Time Logs,Types d'activités pour Journaux de Temps,
|
||||
UOM,UDM,
|
||||
UOM Conversion factor is required in row {0},Facteur de conversion de l'UDM est obligatoire dans la ligne {0},
|
||||
UOM coversion factor required for UOM: {0} in Item: {1},Facteur de coversion UDM requis pour l'UDM : {0} dans l'Article : {1},
|
||||
UOM,UdM,
|
||||
UOM Conversion factor is required in row {0},Facteur de conversion de l'UdM est obligatoire dans la ligne {0},
|
||||
UOM coversion factor required for UOM: {0} in Item: {1},Facteur de coversion UdM requis pour l'UdM : {0} dans l'Article : {1},
|
||||
URL,URL,
|
||||
Unable to find DocType {0},Impossible de trouver le DocType {0},
|
||||
Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually,Impossible de trouver le taux de change pour {0} à {1} pour la date clé {2}. Veuillez créer une entrée de taux de change manuellement,
|
||||
@@ -3294,7 +3294,7 @@ Wednesday,Mercredi,
|
||||
Week,Semaine,
|
||||
Weekdays,Jours de la semaine,
|
||||
Weekly,Hebdomadaire,
|
||||
"Weight is mentioned,\nPlease mention ""Weight UOM"" too","Poids est mentionné,\nVeuillez aussi mentionner ""UDM de Poids""",
|
||||
"Weight is mentioned,\nPlease mention ""Weight UOM"" too","Poids est mentionné,\nVeuillez aussi mentionner ""UdM de Poids""",
|
||||
Welcome email sent,Email de bienvenue envoyé,
|
||||
Welcome to ERPNext,Bienvenue sur ERPNext,
|
||||
What do you need help with?,Avec quoi avez vous besoin d'aide ?,
|
||||
@@ -4938,12 +4938,15 @@ Is Cumulative,Est cumulatif,
|
||||
Coupon Code Based,Code de coupon basé,
|
||||
Discount on Other Item,Remise sur un autre article,
|
||||
Apply Rule On Other,Appliquer la règle sur autre,
|
||||
Party Information,Informations sur la fête,
|
||||
Party Information,Informations sur le tier,
|
||||
Quantity and Amount,Quantité et montant,
|
||||
Min Qty,Qté Min,
|
||||
Max Qty,Qté Max,
|
||||
Min Amt,Min Amt,
|
||||
Max Amt,Max Amt,
|
||||
Min Amt,Montant Min,
|
||||
Max Amt,Montant Max,
|
||||
"If rate is zero them item will be treated as ""Free Item""",Si le prix est à 0 alors l'article sera traité comme article gratuit
|
||||
Is Recursive,Est récursif
|
||||
"Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on","La remise sera appliquée séquentiellement telque : acheter 1 => recupérer 1, acheter 2 => recupérer 2, acheter 3 => recupérer 3, etc..."
|
||||
Period Settings,Paramètres de période,
|
||||
Margin,Marge,
|
||||
Margin Type,Type de Marge,
|
||||
@@ -5053,7 +5056,7 @@ Quantity and Rate,Quantité et Prix,
|
||||
Received Qty,Qté Reçue,
|
||||
Accepted Qty,Quantité acceptée,
|
||||
Rejected Qty,Qté Rejetée,
|
||||
UOM Conversion Factor,Facteur de Conversion de l'UDM,
|
||||
UOM Conversion Factor,Facteur de Conversion de l'UdM,
|
||||
Discount on Price List Rate (%),Remise sur la Liste des Prix (%),
|
||||
Price List Rate (Company Currency),Taux de la Liste de Prix (Devise Société),
|
||||
Rate (Company Currency),Prix (Devise Société),
|
||||
@@ -5085,7 +5088,7 @@ Purchase Receipt Detail,Détail du reçu d'achat,
|
||||
Item Weight Details,Détails du poids de l'article,
|
||||
Weight Per Unit,Poids par unité,
|
||||
Total Weight,Poids total,
|
||||
Weight UOM,UDM de Poids,
|
||||
Weight UOM,UdM de Poids,
|
||||
Page Break,Saut de Page,
|
||||
Consider Tax or Charge for,Tenir Compte de la Taxe et des Frais pour,
|
||||
Valuation and Total,Valorisation et Total,
|
||||
@@ -5153,7 +5156,7 @@ Advance amount,Montant de l'Avance,
|
||||
Sales Invoice Item,Article de la Facture de Vente,
|
||||
Customer's Item Code,Code de l'Article du Client,
|
||||
Brand Name,Nom de la Marque,
|
||||
Qty as per Stock UOM,Qté par UDM du Stock,
|
||||
Qty as per Stock UOM,Qté par UdM du Stock,
|
||||
Discount and Margin,Remise et Marge,
|
||||
Rate With Margin,Prix Avec Marge,
|
||||
Discount (%) on Price List Rate with Margin,Remise (%) sur le prix de la Liste de Prix avec la Marge,
|
||||
@@ -5501,7 +5504,7 @@ Blanket Order Rate,Prix unitaire de commande avec limites,
|
||||
Returned Qty,Qté Retournée,
|
||||
Purchase Order Item Supplied,Article Fourni depuis la Commande d'Achat,
|
||||
BOM Detail No,N° de Détail de la nomenclature,
|
||||
Stock Uom,UDM du Stock,
|
||||
Stock Uom,UdM du Stock,
|
||||
Raw Material Item Code,Code d’Article de Matière Première,
|
||||
Supplied Qty,Qté Fournie,
|
||||
Purchase Receipt Item Supplied,Articles Fournis du Reçus d’Achat,
|
||||
@@ -6149,7 +6152,7 @@ Drug Name / Description,Nom / description du médicament,
|
||||
Dosage,Dosage,
|
||||
Dosage by Time Interval,Dosage par intervalle de temps,
|
||||
Interval,Intervalle,
|
||||
Interval UOM,UDM d'Intervalle,
|
||||
Interval UOM,UdM d'Intervalle,
|
||||
Hour,Heure,
|
||||
Update Schedule,Mettre à Jour le Calendrier,
|
||||
Exercise,Exercice,
|
||||
@@ -7023,7 +7026,7 @@ Petrol,Essence,
|
||||
Diesel,Diesel,
|
||||
Natural Gas,Gaz Naturel,
|
||||
Electric,Électrique,
|
||||
Fuel UOM,UDM Carburant,
|
||||
Fuel UOM,UdM Carburant,
|
||||
Last Carbon Check,Dernière Vérification Carbone,
|
||||
Wheels,Roues,
|
||||
Doors,Portes,
|
||||
@@ -7182,7 +7185,7 @@ Item to be manufactured or repacked,Article à produire ou à réemballer,
|
||||
Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Quantité d'article obtenue après production / reconditionnement des quantités données de matières premières,
|
||||
Set rate of sub-assembly item based on BOM,Définir le prix des articles de sous-assemblage en fonction de la nomenclature,
|
||||
Allow Alternative Item,Autoriser un article alternatif,
|
||||
Item UOM,UDM de l'Article,
|
||||
Item UOM,UdM de l'Article,
|
||||
Conversion Rate,Taux de Conversion,
|
||||
Rate Of Materials Based On,Prix des Matériaux Basé sur,
|
||||
With Operations,Avec des Opérations,
|
||||
@@ -7926,7 +7929,7 @@ Territory Manager,Responsable Régional,
|
||||
For reference,Pour référence,
|
||||
Territory Targets,Objectifs Régionaux,
|
||||
Set Item Group-wise budgets on this Territory. You can also include seasonality by setting the Distribution.,Définir des budgets par Groupes d'Articles sur ce Territoire. Vous pouvez également inclure de la saisonnalité en définissant la Répartition.,
|
||||
UOM Name,Nom UDM,
|
||||
UOM Name,Nom UdM,
|
||||
Check this to disallow fractions. (for Nos),Cochez cette case pour interdire les fractions. (Pour les numéros),
|
||||
Website Item Group,Groupe d'Articles du Site Web,
|
||||
Cross Listing of Item in multiple groups,Liste Croisée d'Articles dans plusieurs groupes,
|
||||
@@ -8198,10 +8201,10 @@ To Package No.,Au N° de Paquet,
|
||||
If more than one package of the same type (for print),Si plus d'un paquet du même type (pour l'impression),
|
||||
Package Weight Details,Détails du Poids du Paquet,
|
||||
The net weight of this package. (calculated automatically as sum of net weight of items),Le poids net de ce paquet. (Calculé automatiquement comme la somme du poids net des articles),
|
||||
Net Weight UOM,UDM Poids Net,
|
||||
Net Weight UOM,UdM Poids Net,
|
||||
Gross Weight,Poids Brut,
|
||||
The gross weight of the package. Usually net weight + packaging material weight. (for print),Le poids brut du colis. Habituellement poids net + poids du matériau d'emballage. (Pour l'impression),
|
||||
Gross Weight UOM,UDM du Poids Brut,
|
||||
Gross Weight UOM,UdM du Poids Brut,
|
||||
Packing Slip Item,Article Emballé,
|
||||
DN Detail,Détail du Bon de Livraison,
|
||||
STO-PICK-.YYYY.-,STO-PICK-.YYYY.-,
|
||||
@@ -8215,7 +8218,7 @@ Pick List Item,Élément de la liste de choix,
|
||||
Picked Qty,Quantité choisie,
|
||||
Price List Master,Données de Base des Listes de Prix,
|
||||
Price List Name,Nom de la Liste de Prix,
|
||||
Price Not UOM Dependent,Prix non dépendant de l'UOM,
|
||||
Price Not UOM Dependent,Prix non dépendant de l'UdM,
|
||||
Applicable for Countries,Applicable pour les Pays,
|
||||
Price List Country,Pays de la Liste des Prix,
|
||||
MAT-PRE-.YYYY.-,MAT-PRE-YYYY.-,
|
||||
@@ -8294,7 +8297,7 @@ Purchase Receipt No,N° du Reçu d'Achat,
|
||||
Inspection Required,Inspection obligatoire,
|
||||
From BOM,Depuis la nomenclature,
|
||||
For Quantity,Pour la Quantité,
|
||||
As per Stock UOM,Selon UDM du Stock,
|
||||
As per Stock UOM,Selon UdM du Stock,
|
||||
Including items for sub assemblies,Incluant les articles pour des sous-ensembles,
|
||||
Default Source Warehouse,Entrepôt Source par Défaut,
|
||||
Source Warehouse Address,Adresse de l'entrepôt source,
|
||||
@@ -8309,7 +8312,7 @@ Total Additional Costs,Total des Coûts Additionnels,
|
||||
Customer or Supplier Details,Détails du Client ou du Fournisseur,
|
||||
Per Transferred,Par transféré,
|
||||
Stock Entry Detail,Détails de l'Écriture de Stock,
|
||||
Basic Rate (as per Stock UOM),Prix de base (comme l’UDM du Stock),
|
||||
Basic Rate (as per Stock UOM),Prix de base (comme l’UdM du Stock),
|
||||
Basic Amount,Montant de Base,
|
||||
Additional Cost,Frais Supplémentaire,
|
||||
Serial No / Batch,N° de Série / Lot,
|
||||
@@ -8339,7 +8342,7 @@ Quantity Difference,Différence de Quantité,
|
||||
Amount Difference,Différence de Montant,
|
||||
Item Naming By,Nomenclature d'Article Par,
|
||||
Default Item Group,Groupe d'Éléments par Défaut,
|
||||
Default Stock UOM,UDM par Défaut des Articles,
|
||||
Default Stock UOM,UdM par Défaut des Articles,
|
||||
Sample Retention Warehouse,Entrepôt de stockage des échantillons,
|
||||
Default Valuation Method,Méthode de Valorisation par Défaut,
|
||||
Show Barcode Field,Afficher Champ Code Barre,
|
||||
@@ -8353,8 +8356,8 @@ Stock Frozen Upto,Stock Gelé Jusqu'au,
|
||||
Batch Identification,Identification par lots,
|
||||
Use Naming Series,Utiliser la série de noms,
|
||||
Naming Series Prefix,Préfix du nom de série,
|
||||
UOM Category,Catégorie d'unité de mesure (UDM),
|
||||
UOM Conversion Detail,Détails de Conversion de l'UDM,
|
||||
UOM Category,Catégorie d'unité de mesure (UdM),
|
||||
UOM Conversion Detail,Détails de Conversion de l'UdM,
|
||||
Variant Field,Champ de Variante,
|
||||
A logical Warehouse against which stock entries are made.,Un Entrepôt logique dans lequel les entrées en stock sont faites.,
|
||||
Warehouse Detail,Détail de l'Entrepôt,
|
||||
@@ -9869,8 +9872,8 @@ Allowed Items,Articles autorisés
|
||||
Party Specific Item,Restriction d'article disponible
|
||||
Restrict Items Based On,Type de critére de restriction
|
||||
Based On Value,critére de restriction
|
||||
Unit of Measure (UOM),Unité de mesure (UDM),
|
||||
Unit Of Measure (UOM),Unité de mesure (UDM),
|
||||
Unit of Measure (UOM),Unité de mesure (UdM),
|
||||
Unit Of Measure (UOM),Unité de mesure (UdM),
|
||||
CRM Settings,Paramètres CRM
|
||||
Do Not Explode,Ne pas décomposer
|
||||
Quick Access, Accés rapides
|
||||
|
||||
|
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