mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-01 16:54:36 +00:00
Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e351a9e0b | ||
|
|
8f97b07463 | ||
|
|
e5cd4195d9 | ||
|
|
8a17680e75 | ||
|
|
bc5523318c | ||
|
|
7027fdae39 | ||
|
|
5b5a85ee3b | ||
|
|
98bb446e98 | ||
|
|
076027a68c | ||
|
|
82cdf49d32 | ||
|
|
cc0f9d4ed5 | ||
|
|
23a0a5337e | ||
|
|
e18d0eca3c | ||
|
|
08bca7d252 | ||
|
|
ee74c555d5 | ||
|
|
030765618b | ||
|
|
cea7a32abc | ||
|
|
e5e6b5d81c | ||
|
|
60f5611fb9 | ||
|
|
653cb9fc3b | ||
|
|
5bdec0b512 | ||
|
|
bcbeaea05c | ||
|
|
251440dfec | ||
|
|
39680ed28b | ||
|
|
e039a14f6f | ||
|
|
a05bf0a7ca | ||
|
|
3af2b9b423 | ||
|
|
d7306d2ec4 | ||
|
|
ffa4deac26 | ||
|
|
a80f8ed2f8 | ||
|
|
1214b93e07 | ||
|
|
b2da76f02c | ||
|
|
9b40b7f240 | ||
|
|
701ccc36c8 | ||
|
|
dfd43141e3 | ||
|
|
6debf39ee8 | ||
|
|
fc175e2b1f | ||
|
|
fae941c4e3 | ||
|
|
91920f4d84 | ||
|
|
6957e2a85c | ||
|
|
7c1595e2c2 | ||
|
|
6878536a57 | ||
|
|
f87c3c6157 | ||
|
|
7d63bcbeb6 | ||
|
|
9fc8acf707 | ||
|
|
f2f06aaa56 | ||
|
|
5728300f03 | ||
|
|
e530f0b2fb | ||
|
|
8ebf34629d | ||
|
|
72351e38f2 | ||
|
|
6fa81089d6 | ||
|
|
fb6791abe3 | ||
|
|
bf833c4f09 | ||
|
|
5848147a92 | ||
|
|
150319d0cb | ||
|
|
b9ff172e13 | ||
|
|
5854f1791c | ||
|
|
c9c7222ded | ||
|
|
68d9161a66 | ||
|
|
04f50ea76a | ||
|
|
4928058ed8 | ||
|
|
8d0f715087 | ||
|
|
c379baf7a2 | ||
|
|
c03ec80d1a | ||
|
|
c1ae5b0af5 | ||
|
|
cdd13cd95f | ||
|
|
5abcb478d0 | ||
|
|
2aada1a0d9 | ||
|
|
e093c32d06 | ||
|
|
93b8cc3042 | ||
|
|
93ec57dd44 | ||
|
|
f92b5011da | ||
|
|
7477bb96c8 | ||
|
|
6c3fbf03f9 | ||
|
|
0d8112af76 | ||
|
|
088c168f96 | ||
|
|
ae26e550d0 | ||
|
|
5652af04ba | ||
|
|
4b0c9b6115 | ||
|
|
65ac84e020 | ||
|
|
648a017eda | ||
|
|
5fc4dfaad6 | ||
|
|
6e3ad109d2 | ||
|
|
0ba2a4d084 | ||
|
|
21deb02d81 | ||
|
|
978924a7e4 | ||
|
|
89b9a06204 | ||
|
|
f7988dea1b | ||
|
|
88346b17e9 | ||
|
|
fcfe0cb9e9 | ||
|
|
93e029df91 | ||
|
|
014896a595 | ||
|
|
b7119318a6 | ||
|
|
956b0520d7 | ||
|
|
f365e6b4a2 | ||
|
|
9533a64f6a | ||
|
|
46075901ba | ||
|
|
c9f4f60425 | ||
|
|
9c6d020e49 | ||
|
|
b1571932d0 | ||
|
|
bc649b371f | ||
|
|
6e5fd55ed4 | ||
|
|
5edaf83733 | ||
|
|
85d108b810 | ||
|
|
ae6ad1e47d | ||
|
|
98d5cb30d1 | ||
|
|
8cd2539755 | ||
|
|
59e2ab702f | ||
|
|
ef687e22b8 | ||
|
|
517e40e1b8 | ||
|
|
0fe5e9a9d1 | ||
|
|
c11a31b390 | ||
|
|
82e41a2721 | ||
|
|
5c065e8a64 | ||
|
|
188b18dc5b | ||
|
|
5d7c4c182a | ||
|
|
798717b12b | ||
|
|
7a9f384b18 | ||
|
|
fd4bcd9f7f | ||
|
|
f1dd4d0449 | ||
|
|
5f8f574e20 | ||
|
|
2bd8bd224b | ||
|
|
ba3643514e |
@@ -2,7 +2,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.8.0"
|
||||
__version__ = "14.10.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enabling ensure each Sales Invoice has a unique value in Supplier Invoice No. field",
|
||||
"description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
|
||||
"fieldname": "check_supplier_invoice_uniqueness",
|
||||
"fieldtype": "Check",
|
||||
"label": "Check Supplier Invoice Number Uniqueness"
|
||||
@@ -354,7 +354,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-07-11 13:37:50.605141",
|
||||
"modified": "2022-11-27 21:49:52.538655",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -37,6 +37,14 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
|
||||
if (frm.doc.account && frm.doc.from_date && frm.doc.to_date) {
|
||||
frm.add_custom_button(__('Get Payment Entries'), () =>
|
||||
frm.trigger("get_payment_entries")
|
||||
);
|
||||
|
||||
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
|
||||
}
|
||||
},
|
||||
|
||||
update_clearance_date: function(frm) {
|
||||
@@ -46,22 +54,30 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
callback: function(r, rt) {
|
||||
frm.refresh_field("payment_entries");
|
||||
frm.refresh_fields();
|
||||
|
||||
if (!frm.doc.payment_entries.length) {
|
||||
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
|
||||
frm.change_custom_button_type('Update Clearance Date', null, 'default');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_payment_entries: function(frm) {
|
||||
return frappe.call({
|
||||
method: "get_payment_entries",
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
frm.refresh_field("payment_entries");
|
||||
frm.refresh_fields();
|
||||
|
||||
$(frm.fields_dict.payment_entries.wrapper).find("[data-fieldname=amount]").each(function(i,v){
|
||||
if (i !=0){
|
||||
$(v).addClass("text-right")
|
||||
}
|
||||
})
|
||||
if (frm.doc.payment_entries.length) {
|
||||
frm.add_custom_button(__('Update Clearance Date'), () =>
|
||||
frm.trigger("update_clearance_date")
|
||||
);
|
||||
|
||||
frm.change_custom_button_type('Get Payment Entries', null, 'default');
|
||||
frm.change_custom_button_type('Update Clearance Date', null, 'primary');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_copy": 1,
|
||||
"creation": "2013-01-10 16:34:05",
|
||||
"doctype": "DocType",
|
||||
@@ -13,11 +14,8 @@
|
||||
"bank_account",
|
||||
"include_reconciled_entries",
|
||||
"include_pos_transactions",
|
||||
"get_payment_entries",
|
||||
"section_break_10",
|
||||
"payment_entries",
|
||||
"update_clearance_date",
|
||||
"total_amount"
|
||||
"payment_entries"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -76,11 +74,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Include POS Transactions"
|
||||
},
|
||||
{
|
||||
"fieldname": "get_payment_entries",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Payment Entries"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break"
|
||||
@@ -91,25 +84,14 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Payment Entries",
|
||||
"options": "Bank Clearance Detail"
|
||||
},
|
||||
{
|
||||
"fieldname": "update_clearance_date",
|
||||
"fieldtype": "Button",
|
||||
"label": "Update Clearance Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Amount",
|
||||
"options": "account_currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "fa fa-check",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"modified": "2020-04-06 16:12:06.628008",
|
||||
"links": [],
|
||||
"modified": "2022-11-28 17:24:13.008692",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Clearance",
|
||||
@@ -126,5 +108,6 @@
|
||||
"quick_entry": 1,
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
@@ -179,7 +179,6 @@ class BankClearance(Document):
|
||||
)
|
||||
|
||||
self.set("payment_entries", [])
|
||||
self.total_amount = 0.0
|
||||
default_currency = erpnext.get_default_currency()
|
||||
|
||||
for d in entries:
|
||||
@@ -198,7 +197,6 @@ class BankClearance(Document):
|
||||
d.pop("debit")
|
||||
d.pop("account_currency")
|
||||
row.update(d)
|
||||
self.total_amount += flt(amount)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_clearance_date(self):
|
||||
|
||||
@@ -169,5 +169,6 @@ def auto_create_fiscal_year():
|
||||
|
||||
|
||||
def get_from_and_to_date(fiscal_year):
|
||||
fields = ["year_start_date as from_date", "year_end_date as to_date"]
|
||||
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)
|
||||
fields = ["year_start_date", "year_end_date"]
|
||||
cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
|
||||
return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date)
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController {
|
||||
settings = {};
|
||||
|
||||
setup(doc) {
|
||||
this.setup_posting_date_time_check();
|
||||
super.setup(doc);
|
||||
@@ -12,21 +14,37 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
|
||||
company() {
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
this.frm.set_value("set_warehouse", "");
|
||||
this.frm.set_value("taxes_and_charges", "");
|
||||
}
|
||||
|
||||
onload(doc) {
|
||||
super.onload();
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
|
||||
|
||||
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||
this.frm.script_manager.trigger("is_pos");
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
this.frm.set_query("set_warehouse", function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company ? doc.company : '',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
}
|
||||
|
||||
onload_post_render(frm) {
|
||||
this.pos_profile(frm);
|
||||
}
|
||||
|
||||
refresh(doc) {
|
||||
super.refresh();
|
||||
|
||||
if (doc.docstatus == 1 && !doc.is_return) {
|
||||
this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
|
||||
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
@@ -36,6 +54,18 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
this.frm.return_print_format = "Sales Invoice Return";
|
||||
this.frm.set_value('consolidated_invoice', '');
|
||||
}
|
||||
|
||||
this.frm.set_query("customer", (function () {
|
||||
const customer_groups = this.settings?.customer_groups;
|
||||
|
||||
if (!customer_groups?.length) return {};
|
||||
|
||||
return {
|
||||
filters: {
|
||||
customer_group: ["in", customer_groups],
|
||||
}
|
||||
}
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
is_pos() {
|
||||
@@ -88,6 +118,25 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
});
|
||||
}
|
||||
|
||||
pos_profile(frm) {
|
||||
if (!frm.pos_profile || frm.pos_profile == '') {
|
||||
this.update_customer_groups_settings([]);
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data",
|
||||
args: { "pos_profile": frm.pos_profile },
|
||||
callback: ({ message: profile }) => {
|
||||
this.update_customer_groups_settings(profile?.customer_groups);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
update_customer_groups_settings(customer_groups) {
|
||||
this.settings.customer_groups = customer_groups?.map((group) => group.name)
|
||||
}
|
||||
|
||||
amount(){
|
||||
this.write_off_outstanding_amount_automatically()
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"tax_category",
|
||||
"column_break_49",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"section_break_51",
|
||||
"taxes",
|
||||
"totals",
|
||||
@@ -1534,13 +1535,19 @@
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-file-text",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-22 12:44:29.935567",
|
||||
"modified": "2022-11-27 16:28:45.559785",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -231,7 +231,9 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if (
|
||||
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
|
||||
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
|
||||
and not self.is_return
|
||||
and not self.is_internal_supplier
|
||||
):
|
||||
self.validate_rate_with_reference_doc(
|
||||
[
|
||||
@@ -606,7 +608,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def make_supplier_gl_entry(self, gl_entries):
|
||||
# Checked both rounding_adjustment and rounded_total
|
||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||
# because rounded_total had value even before introduction of posting GLE based on rounded total
|
||||
grand_total = (
|
||||
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
)
|
||||
@@ -809,10 +811,7 @@ class PurchaseInvoice(BuyingController):
|
||||
else item.deferred_expense_account
|
||||
)
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
dummy, amount = self.get_amount_and_base_amount(item, None)
|
||||
else:
|
||||
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
|
||||
dummy, amount = self.get_amount_and_base_amount(item, None)
|
||||
|
||||
if provisional_accounting_for_non_stock_items:
|
||||
if item.purchase_receipt:
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
"discount_amount",
|
||||
"base_rate_with_margin",
|
||||
"sec_break2",
|
||||
"apply_tds",
|
||||
"rate",
|
||||
"amount",
|
||||
"item_tax_template",
|
||||
@@ -50,6 +49,7 @@
|
||||
"pricing_rules",
|
||||
"stock_uom_rate",
|
||||
"is_free_item",
|
||||
"apply_tds",
|
||||
"section_break_22",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@@ -871,16 +871,16 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-26 16:05:37.304788",
|
||||
"modified": "2022-11-29 13:01:20.438217",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"taxes_and_charges",
|
||||
"column_break_38",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"column_break_55",
|
||||
"tax_category",
|
||||
"section_break_40",
|
||||
@@ -919,6 +920,7 @@
|
||||
"fieldtype": "Table",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Sales Taxes and Charges",
|
||||
"oldfieldname": "other_charges",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Sales Taxes and Charges"
|
||||
@@ -2097,6 +2099,12 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Write Off",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -2109,7 +2117,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2022-11-15 09:33:47.870616",
|
||||
"modified": "2022-12-05 16:18:14.532114",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -226,6 +226,42 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
orders = []
|
||||
|
||||
po = create_purchase_order(supplier="Test TDS Supplier4", rate=20000, do_not_save=True)
|
||||
po.extend(
|
||||
"items",
|
||||
[
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
|
||||
"qty": 1,
|
||||
"rate": 20000,
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Stock Received But Not Billed - _TC",
|
||||
"apply_tds": 0,
|
||||
},
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
|
||||
"qty": 1,
|
||||
"rate": 35000,
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Stock Received But Not Billed - _TC",
|
||||
"apply_tds": 1,
|
||||
},
|
||||
],
|
||||
)
|
||||
po.save()
|
||||
po.submit()
|
||||
orders.append(po)
|
||||
|
||||
self.assertEqual(po.taxes[0].tax_amount, 5500)
|
||||
|
||||
# cancel orders to avoid clashing
|
||||
for d in reversed(orders):
|
||||
d.cancel()
|
||||
|
||||
def test_multi_category_single_supplier(self):
|
||||
frappe.db.set_value(
|
||||
"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"
|
||||
@@ -348,6 +384,39 @@ def create_purchase_invoice(**args):
|
||||
return pi
|
||||
|
||||
|
||||
def create_purchase_order(**args):
|
||||
# return purchase order doc object
|
||||
item = frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name")
|
||||
|
||||
args = frappe._dict(args)
|
||||
po = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Order",
|
||||
"transaction_date": today(),
|
||||
"schedule_date": today(),
|
||||
"apply_tds": 0 if args.do_not_apply_tds else 1,
|
||||
"supplier": args.supplier,
|
||||
"company": "_Test Company",
|
||||
"taxes_and_charges": "",
|
||||
"currency": "INR",
|
||||
"taxes": [],
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Order Item",
|
||||
"item_code": item,
|
||||
"qty": args.qty or 1,
|
||||
"rate": args.rate or 10000,
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Stock Received But Not Billed - _TC",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
po.save()
|
||||
return po
|
||||
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
# return sales invoice doc object
|
||||
item = frappe.db.get_value("Item", {"item_name": "TCS Item"}, "name")
|
||||
|
||||
@@ -394,20 +394,22 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
|
||||
)
|
||||
round_off_account_exists = False
|
||||
round_off_gle = frappe._dict()
|
||||
for d in gl_map:
|
||||
if d.account == round_off_account:
|
||||
round_off_gle = d
|
||||
if d.debit:
|
||||
debit_credit_diff -= flt(d.debit)
|
||||
else:
|
||||
debit_credit_diff += flt(d.credit)
|
||||
round_off_account_exists = True
|
||||
round_off_account_exists = False
|
||||
|
||||
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
|
||||
gl_map.remove(round_off_gle)
|
||||
return
|
||||
if gl_map[0].voucher_type != "Period Closing Voucher":
|
||||
for d in gl_map:
|
||||
if d.account == round_off_account:
|
||||
round_off_gle = d
|
||||
if d.debit:
|
||||
debit_credit_diff -= flt(d.debit) - flt(d.credit)
|
||||
else:
|
||||
debit_credit_diff += flt(d.credit)
|
||||
round_off_account_exists = True
|
||||
|
||||
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
|
||||
gl_map.remove(round_off_gle)
|
||||
return
|
||||
|
||||
if not round_off_gle:
|
||||
for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
|
||||
@@ -430,7 +432,6 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
||||
)
|
||||
|
||||
update_accounting_dimensions(round_off_gle)
|
||||
|
||||
if not round_off_account_exists:
|
||||
gl_map.append(round_off_gle)
|
||||
|
||||
|
||||
@@ -14,9 +14,17 @@ def execute(filters=None):
|
||||
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
columns = get_columns(filters)
|
||||
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
|
||||
(
|
||||
tds_docs,
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
invoice_total_map,
|
||||
) = get_tds_docs(filters)
|
||||
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map
|
||||
)
|
||||
final_result = group_by_supplier_and_category(res)
|
||||
|
||||
return columns, final_result
|
||||
|
||||
@@ -8,11 +8,19 @@ from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
validate_filters(filters)
|
||||
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
|
||||
(
|
||||
tds_docs,
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
invoice_net_total_map,
|
||||
) = get_tds_docs(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
|
||||
)
|
||||
return columns, res
|
||||
|
||||
|
||||
@@ -22,7 +30,9 @@ def validate_filters(filters):
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
|
||||
def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map):
|
||||
def get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
|
||||
):
|
||||
supplier_map = get_supplier_pan_map()
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
@@ -50,7 +60,10 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
if entry.account in tds_accounts:
|
||||
tds_deducted += entry.credit - entry.debit
|
||||
|
||||
total_amount_credited += entry.credit
|
||||
if invoice_net_total_map.get(name):
|
||||
total_amount_credited = invoice_net_total_map.get(name)
|
||||
else:
|
||||
total_amount_credited += entry.credit
|
||||
|
||||
if tds_deducted:
|
||||
row = {
|
||||
@@ -179,9 +192,10 @@ def get_tds_docs(filters):
|
||||
purchase_invoices = []
|
||||
payment_entries = []
|
||||
journal_entries = []
|
||||
tax_category_map = {}
|
||||
or_filters = {}
|
||||
journal_entry_party_map = {}
|
||||
tax_category_map = frappe._dict()
|
||||
invoice_net_total_map = frappe._dict()
|
||||
or_filters = frappe._dict()
|
||||
journal_entry_party_map = frappe._dict()
|
||||
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
|
||||
|
||||
tds_accounts = frappe.get_all(
|
||||
@@ -218,16 +232,22 @@ def get_tds_docs(filters):
|
||||
tds_documents.append(d.voucher_no)
|
||||
|
||||
if purchase_invoices:
|
||||
get_tax_category_map(purchase_invoices, "Purchase Invoice", tax_category_map)
|
||||
get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, invoice_net_total_map)
|
||||
|
||||
if payment_entries:
|
||||
get_tax_category_map(payment_entries, "Payment Entry", tax_category_map)
|
||||
get_doc_info(payment_entries, "Payment Entry", tax_category_map)
|
||||
|
||||
if journal_entries:
|
||||
journal_entry_party_map = get_journal_entry_party_map(journal_entries)
|
||||
get_tax_category_map(journal_entries, "Journal Entry", tax_category_map)
|
||||
get_doc_info(journal_entries, "Journal Entry", tax_category_map)
|
||||
|
||||
return tds_documents, tds_accounts, tax_category_map, journal_entry_party_map
|
||||
return (
|
||||
tds_documents,
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
invoice_net_total_map,
|
||||
)
|
||||
|
||||
|
||||
def get_journal_entry_party_map(journal_entries):
|
||||
@@ -244,17 +264,18 @@ def get_journal_entry_party_map(journal_entries):
|
||||
return journal_entry_party_map
|
||||
|
||||
|
||||
def get_tax_category_map(vouchers, doctype, tax_category_map):
|
||||
tax_category_map.update(
|
||||
frappe._dict(
|
||||
frappe.get_all(
|
||||
doctype,
|
||||
filters={"name": ("in", vouchers)},
|
||||
fields=["name", "tax_withholding_category"],
|
||||
as_list=1,
|
||||
)
|
||||
)
|
||||
)
|
||||
def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None):
|
||||
if doctype == "Purchase Invoice":
|
||||
fields = ["name", "tax_withholding_category", "base_tax_withholding_net_total"]
|
||||
else:
|
||||
fields = ["name", "tax_withholding_category"]
|
||||
|
||||
entries = frappe.get_all(doctype, filters={"name": ("in", vouchers)}, fields=fields)
|
||||
|
||||
for entry in entries:
|
||||
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
||||
if doctype == "Purchase Invoice":
|
||||
invoice_net_total_map.update({entry.name: entry.base_tax_withholding_net_total})
|
||||
|
||||
|
||||
def get_tax_rate_map(filters):
|
||||
|
||||
@@ -28,7 +28,7 @@ def get_currency(filters):
|
||||
filters["presentation_currency"] if filters.get("presentation_currency") else company_currency
|
||||
)
|
||||
|
||||
report_date = filters.get("to_date")
|
||||
report_date = filters.get("to_date") or filters.get("period_end_date")
|
||||
|
||||
if not report_date:
|
||||
fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"]
|
||||
|
||||
@@ -224,7 +224,10 @@ class TestAsset(AssetSetup):
|
||||
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
|
||||
self.assertEquals(
|
||||
accumulated_depr_amount,
|
||||
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Scrapped")
|
||||
self.assertTrue(asset.journal_entry_for_scrap)
|
||||
@@ -261,7 +264,7 @@ class TestAsset(AssetSetup):
|
||||
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
|
||||
asset.precision("gross_purchase_amount"),
|
||||
)
|
||||
this_month_depr_amount = 9000.0 if get_last_day(date) == date else 0
|
||||
this_month_depr_amount = 9000.0 if is_last_day_of_the_month(date) else 0
|
||||
|
||||
self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount)
|
||||
|
||||
@@ -1573,3 +1576,9 @@ def set_depreciation_settings_in_company(company=None):
|
||||
|
||||
def enable_cwip_accounting(asset_category, enable=1):
|
||||
frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable)
|
||||
|
||||
|
||||
def is_last_day_of_the_month(dt):
|
||||
last_day_of_the_month = get_last_day(dt)
|
||||
|
||||
return getdate(dt) == getdate(last_day_of_the_month)
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
"column_break_26",
|
||||
"total",
|
||||
"net_total",
|
||||
"tax_withholding_net_total",
|
||||
"base_tax_withholding_net_total",
|
||||
"section_break_48",
|
||||
"pricing_rules",
|
||||
"raw_material_details",
|
||||
@@ -65,6 +67,7 @@
|
||||
"tax_category",
|
||||
"column_break_50",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"section_break_52",
|
||||
"taxes",
|
||||
"totals",
|
||||
@@ -1220,6 +1223,26 @@
|
||||
"label": "Additional Info",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Base Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_99",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -1227,13 +1250,19 @@
|
||||
{
|
||||
"fieldname": "column_break_103",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-17 12:34:36.033363",
|
||||
"modified": "2022-11-17 17:28:07.729943",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"pricing_rules",
|
||||
"stock_uom_rate",
|
||||
"is_free_item",
|
||||
"apply_tds",
|
||||
"section_break_29",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@@ -889,13 +890,19 @@
|
||||
{
|
||||
"fieldname": "column_break_54",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-26 16:47:41.364387",
|
||||
"modified": "2022-11-29 16:47:41.364387",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -22,6 +22,13 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
frm.set_query('warehouse', 'items', () => ({
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"sec_break_email_2",
|
||||
"message_for_supplier",
|
||||
"terms_section_break",
|
||||
"incoterm",
|
||||
"tc_name",
|
||||
"terms",
|
||||
"printing_settings",
|
||||
@@ -271,13 +272,19 @@
|
||||
"fieldname": "schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Required Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-06 17:47:49.909000",
|
||||
"modified": "2022-11-17 17:26:33.770993",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
@@ -345,5 +352,6 @@
|
||||
"search_fields": "status, transaction_date",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -45,6 +45,7 @@
|
||||
"tax_category",
|
||||
"column_break_36",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"section_break_38",
|
||||
"taxes",
|
||||
"totals",
|
||||
@@ -823,6 +824,12 @@
|
||||
{
|
||||
"fieldname": "column_break_85",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
@@ -830,7 +837,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-27 18:20:09.462037",
|
||||
"modified": "2022-11-17 17:27:32.179686",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
||||
@@ -239,6 +239,14 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
item.set(field_map.get(self.doctype), default_deferred_account)
|
||||
|
||||
def validate_auto_repeat_subscription_dates(self):
|
||||
if (
|
||||
self.get("from_date")
|
||||
and self.get("to_date")
|
||||
and getdate(self.from_date) > getdate(self.to_date)
|
||||
):
|
||||
frappe.throw(_("To Date cannot be before From Date"), title=_("Invalid Auto Repeat Date"))
|
||||
|
||||
def validate_deferred_start_and_end_date(self):
|
||||
for d in self.items:
|
||||
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
||||
@@ -2303,7 +2311,7 @@ def get_due_date(term, posting_date=None, bill_date=None):
|
||||
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
|
||||
due_date = add_days(get_last_day(date), term.credit_days)
|
||||
elif term.due_date_based_on == "Month(s) after the end of the invoice month":
|
||||
due_date = add_months(get_last_day(date), term.credit_months)
|
||||
due_date = get_last_day(add_months(date, term.credit_months))
|
||||
return due_date
|
||||
|
||||
|
||||
@@ -2315,7 +2323,7 @@ def get_discount_date(term, posting_date=None, bill_date=None):
|
||||
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
|
||||
discount_validity = add_days(get_last_day(date), term.discount_validity)
|
||||
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
|
||||
discount_validity = add_months(get_last_day(date), term.discount_validity)
|
||||
discount_validity = get_last_day(add_months(date, term.discount_validity))
|
||||
return discount_validity
|
||||
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class BuyingController(SubcontractingController):
|
||||
self.validate_from_warehouse()
|
||||
self.set_supplier_address()
|
||||
self.validate_asset_return()
|
||||
self.validate_auto_repeat_subscription_dates()
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
self.validate_purchase_receipt_if_update_stock()
|
||||
@@ -321,17 +322,18 @@ class BuyingController(SubcontractingController):
|
||||
)
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
if self.doctype == "Purchase Receipt" or self.get("update_stock"):
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
|
||||
|
||||
@@ -40,6 +40,7 @@ class SellingController(StockController):
|
||||
self.set_customer_address()
|
||||
self.validate_for_duplicate_items()
|
||||
self.validate_target_warehouse()
|
||||
self.validate_auto_repeat_subscription_dates()
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
|
||||
@@ -441,30 +442,31 @@ 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(
|
||||
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(
|
||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
||||
d.precision("rate"),
|
||||
)
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
if self.doctype == "Delivery Note" or self.get("update_stock"):
|
||||
if d.doctype == "Packed Item":
|
||||
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(
|
||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
||||
d.precision("rate"),
|
||||
)
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
|
||||
elif self.get("return_against"):
|
||||
# Get incoming rate of return entry from reference document
|
||||
@@ -581,6 +583,7 @@ class SellingController(StockController):
|
||||
"customer_address": "address_display",
|
||||
"shipping_address_name": "shipping_address",
|
||||
"company_address": "company_address_display",
|
||||
"dispatch_address_name": "dispatch_address",
|
||||
}
|
||||
|
||||
for address_field, address_display_field in address_dict.items():
|
||||
|
||||
@@ -217,7 +217,7 @@ class SalesPipelineAnalytics(object):
|
||||
|
||||
def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info):
|
||||
if self.filters.get("assigned_to"):
|
||||
for data in json.loads(info.get("opportunity_owner")):
|
||||
for data in json.loads(info.get("opportunity_owner") or "[]"):
|
||||
if data == self.filters.get("assigned_to"):
|
||||
self.set_formatted_data(period, data, count_or_amount, assigned_to)
|
||||
else:
|
||||
|
||||
@@ -120,7 +120,7 @@ def link_open_tasks(ref_doctype, ref_docname, doc):
|
||||
todo_doc = frappe.get_doc("ToDo", todo.name)
|
||||
todo_doc.reference_type = doc.doctype
|
||||
todo_doc.reference_name = doc.name
|
||||
todo_doc.db_update()
|
||||
todo_doc.save()
|
||||
|
||||
|
||||
def link_open_events(ref_doctype, ref_docname, doc):
|
||||
|
||||
@@ -191,7 +191,9 @@ def get_total_pledged_security_value(loan):
|
||||
|
||||
for security, qty in pledged_securities.items():
|
||||
after_haircut_percentage = 100 - hair_cut_map.get(security)
|
||||
security_value += (loan_security_price_map.get(security) * qty * after_haircut_percentage) / 100
|
||||
security_value += (
|
||||
loan_security_price_map.get(security, 0) * qty * after_haircut_percentage
|
||||
) / 100
|
||||
|
||||
return security_value
|
||||
|
||||
|
||||
@@ -33,6 +33,11 @@ frappe.ui.form.on('Job Card', {
|
||||
return;
|
||||
}
|
||||
|
||||
let has_stock_entry = frm.doc.__onload &&
|
||||
frm.doc.__onload.has_stock_entry ? true : false;
|
||||
|
||||
frm.toggle_enable("for_quantity", !has_stock_entry);
|
||||
|
||||
if (!frm.is_new() && has_items && frm.doc.docstatus < 2) {
|
||||
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
|
||||
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;
|
||||
|
||||
@@ -57,6 +57,10 @@ class JobCard(Document):
|
||||
)
|
||||
self.set_onload("job_card_excess_transfer", excess_transfer)
|
||||
self.set_onload("work_order_closed", self.is_work_order_closed())
|
||||
self.set_onload("has_stock_entry", self.has_stock_entry())
|
||||
|
||||
def has_stock_entry(self):
|
||||
return frappe.db.exists("Stock Entry", {"job_card": self.name, "docstatus": ["!=", 2]})
|
||||
|
||||
def before_validate(self):
|
||||
self.set_wip_warehouse()
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Warehouse",
|
||||
"label": "For Warehouse",
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -173,7 +173,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-23 18:17:58.400462",
|
||||
"modified": "2022-11-26 14:59:25.879631",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Material Request Plan Item",
|
||||
@@ -182,5 +182,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
frappe.ui.form.on('Production Plan', {
|
||||
|
||||
before_save: function(frm) {
|
||||
before_save(frm) {
|
||||
// preserve temporary names on production plan item to re-link sub-assembly items
|
||||
frm.doc.po_items.forEach(item => {
|
||||
item.temporary_name = item.name;
|
||||
});
|
||||
},
|
||||
setup: function(frm) {
|
||||
setup(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
'Work Order': 'Work Order / Subcontract PO',
|
||||
'Material Request': 'Material Request',
|
||||
@@ -70,7 +70,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
refresh(frm) {
|
||||
if (frm.doc.docstatus === 1) {
|
||||
frm.trigger("show_progress");
|
||||
|
||||
@@ -158,7 +158,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
set_field_options("projected_qty_formula", projected_qty_formula);
|
||||
},
|
||||
|
||||
close_open_production_plan: (frm, close=false) => {
|
||||
close_open_production_plan(frm, close=false) {
|
||||
frappe.call({
|
||||
method: "set_status",
|
||||
freeze: true,
|
||||
@@ -170,7 +170,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
make_work_order: function(frm) {
|
||||
make_work_order(frm) {
|
||||
frappe.call({
|
||||
method: "make_work_order",
|
||||
freeze: true,
|
||||
@@ -181,7 +181,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
make_material_request: function(frm) {
|
||||
make_material_request(frm) {
|
||||
|
||||
frappe.confirm(__("Do you want to submit the material request"),
|
||||
function() {
|
||||
@@ -193,7 +193,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
);
|
||||
},
|
||||
|
||||
create_material_request: function(frm, submit) {
|
||||
create_material_request(frm, submit) {
|
||||
frm.doc.submit_material_request = submit;
|
||||
|
||||
frappe.call({
|
||||
@@ -206,7 +206,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
get_sales_orders: function(frm) {
|
||||
get_sales_orders(frm) {
|
||||
frappe.call({
|
||||
method: "get_open_sales_orders",
|
||||
doc: frm.doc,
|
||||
@@ -216,7 +216,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
get_material_request: function(frm) {
|
||||
get_material_request(frm) {
|
||||
frappe.call({
|
||||
method: "get_pending_material_requests",
|
||||
doc: frm.doc,
|
||||
@@ -226,7 +226,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
get_items: function (frm) {
|
||||
get_items(frm) {
|
||||
frm.clear_table('prod_plan_references');
|
||||
|
||||
frappe.call({
|
||||
@@ -238,7 +238,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
}
|
||||
});
|
||||
},
|
||||
combine_items: function (frm) {
|
||||
combine_items(frm) {
|
||||
frm.clear_table("prod_plan_references");
|
||||
|
||||
frappe.call({
|
||||
@@ -254,14 +254,14 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
combine_sub_items: (frm) => {
|
||||
combine_sub_items(frm) {
|
||||
if (frm.doc.sub_assembly_items.length > 0) {
|
||||
frm.clear_table("sub_assembly_items");
|
||||
frm.trigger("get_sub_assembly_items");
|
||||
}
|
||||
},
|
||||
|
||||
get_sub_assembly_items: function(frm) {
|
||||
get_sub_assembly_items(frm) {
|
||||
frm.dirty();
|
||||
|
||||
frappe.call({
|
||||
@@ -274,9 +274,25 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
get_items_for_mr: function(frm) {
|
||||
toggle_for_warehouse(frm) {
|
||||
frm.toggle_reqd("for_warehouse", true);
|
||||
},
|
||||
|
||||
get_items_for_mr(frm) {
|
||||
if (!frm.doc.for_warehouse) {
|
||||
frappe.throw(__("To make material requests, 'Make Material Request for Warehouse' field is mandatory"));
|
||||
frm.trigger("toggle_for_warehouse");
|
||||
frappe.throw(__("Select the Warehouse"));
|
||||
}
|
||||
|
||||
frm.events.get_items_for_material_requests(frm, [{
|
||||
warehouse: frm.doc.for_warehouse
|
||||
}]);
|
||||
},
|
||||
|
||||
transfer_materials(frm) {
|
||||
if (!frm.doc.for_warehouse) {
|
||||
frm.trigger("toggle_for_warehouse");
|
||||
frappe.throw(__("Select the Warehouse"));
|
||||
}
|
||||
|
||||
if (frm.doc.ignore_existing_ordered_qty) {
|
||||
@@ -287,18 +303,10 @@ frappe.ui.form.on('Production Plan', {
|
||||
title: title,
|
||||
fields: [
|
||||
{
|
||||
'label': __('Target Warehouse'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'target_warehouse',
|
||||
'read_only': true,
|
||||
'default': frm.doc.for_warehouse
|
||||
},
|
||||
{
|
||||
'label': __('Source Warehouses (Optional)'),
|
||||
'label': __('Transfer From Warehouses'),
|
||||
'fieldtype': 'Table MultiSelect',
|
||||
'fieldname': 'warehouses',
|
||||
'options': 'Production Plan Material Request Warehouse',
|
||||
'description': __('If source warehouse selected then system will create the material request with type Material Transfer from Source to Target warehouse. If not selected then will create the material request with type Purchase for the target warehouse.'),
|
||||
get_query: function () {
|
||||
return {
|
||||
filters: {
|
||||
@@ -307,6 +315,13 @@ frappe.ui.form.on('Production Plan', {
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
'label': __('For Warehouse'),
|
||||
'fieldtype': 'Link',
|
||||
'fieldname': 'target_warehouse',
|
||||
'read_only': true,
|
||||
'default': frm.doc.for_warehouse
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -320,8 +335,8 @@ frappe.ui.form.on('Production Plan', {
|
||||
}
|
||||
},
|
||||
|
||||
get_items_for_material_requests: function(frm, warehouses) {
|
||||
const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
|
||||
get_items_for_material_requests(frm, warehouses) {
|
||||
let set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
|
||||
'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty',
|
||||
'reserved_qty_for_production', 'material_request_type'];
|
||||
|
||||
@@ -335,13 +350,13 @@ frappe.ui.form.on('Production Plan', {
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value('mr_items', []);
|
||||
$.each(r.message, function(i, d) {
|
||||
var item = frm.add_child('mr_items');
|
||||
for (let key in d) {
|
||||
if (d[key] && in_list(set_fields, key)) {
|
||||
item[key] = d[key];
|
||||
r.message.forEach(row => {
|
||||
let d = frm.add_child('mr_items');
|
||||
set_fields.forEach(field => {
|
||||
if (row[field]) {
|
||||
d[field] = row[field];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
refresh_field('mr_items');
|
||||
@@ -349,13 +364,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
},
|
||||
|
||||
for_warehouse: function(frm) {
|
||||
if (frm.doc.mr_items && frm.doc.for_warehouse) {
|
||||
frm.trigger("get_items_for_mr");
|
||||
}
|
||||
},
|
||||
|
||||
download_materials_required: function(frm) {
|
||||
download_materials_required(frm) {
|
||||
const fields = [{
|
||||
fieldname: 'warehouses',
|
||||
fieldtype: 'Table MultiSelect',
|
||||
@@ -381,7 +390,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
}, __('Select Warehouses to get Stock for Materials Planning'), __('Get Stock'));
|
||||
},
|
||||
|
||||
show_progress: function(frm) {
|
||||
show_progress(frm) {
|
||||
var bars = [];
|
||||
var message = '';
|
||||
var title = '';
|
||||
@@ -416,7 +425,7 @@ frappe.ui.form.on('Production Plan', {
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Production Plan Item", {
|
||||
item_code: function(frm, cdt, cdn) {
|
||||
item_code(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
if (row.item_code) {
|
||||
frappe.call({
|
||||
@@ -435,7 +444,7 @@ frappe.ui.form.on("Production Plan Item", {
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Material Request Plan Item", {
|
||||
warehouse: function(frm, cdt, cdn) {
|
||||
warehouse(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
if (row.warehouse && row.item_code && frm.doc.company) {
|
||||
frappe.call({
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
"get_sub_assembly_items",
|
||||
"combine_sub_items",
|
||||
"sub_assembly_items",
|
||||
"download_materials_request_plan_section_section",
|
||||
"download_materials_required",
|
||||
"material_request_planning",
|
||||
"include_non_stock_items",
|
||||
"include_subcontracted_items",
|
||||
@@ -45,8 +47,8 @@
|
||||
"ignore_existing_ordered_qty",
|
||||
"column_break_25",
|
||||
"for_warehouse",
|
||||
"download_materials_required",
|
||||
"get_items_for_mr",
|
||||
"transfer_materials",
|
||||
"section_break_27",
|
||||
"mr_items",
|
||||
"other_details",
|
||||
@@ -206,7 +208,7 @@
|
||||
{
|
||||
"fieldname": "material_request_planning",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Material Requirement Planning"
|
||||
"label": "Material Request Planning"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
@@ -235,12 +237,12 @@
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "download_materials_required",
|
||||
"fieldtype": "Button",
|
||||
"label": "Download Required Materials"
|
||||
"label": "Download Materials Request Plan"
|
||||
},
|
||||
{
|
||||
"fieldname": "get_items_for_mr",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Raw Materials For Production"
|
||||
"label": "Get Raw Materials for Purchase"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_27",
|
||||
@@ -304,7 +306,7 @@
|
||||
{
|
||||
"fieldname": "for_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Make Material Request for Warehouse",
|
||||
"label": "Raw Materials Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@@ -378,13 +380,24 @@
|
||||
"fieldname": "combine_sub_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consolidate Sub Assembly Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "transfer_materials",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Raw Materials for Transfer"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "download_materials_request_plan_section_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Download Materials Request Plan Section"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-calendar",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-25 09:15:25.017664",
|
||||
"modified": "2022-11-26 14:51:08.774372",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan",
|
||||
|
||||
@@ -312,6 +312,9 @@ class ProductionPlan(Document):
|
||||
def add_items(self, items):
|
||||
refs = {}
|
||||
for data in items:
|
||||
if not data.pending_qty:
|
||||
continue
|
||||
|
||||
item_details = get_item_details(data.item_code)
|
||||
if self.combine_items:
|
||||
if item_details.bom_no in refs:
|
||||
@@ -518,6 +521,9 @@ class ProductionPlan(Document):
|
||||
subcontracted_po.setdefault(row.supplier, []).append(row)
|
||||
continue
|
||||
|
||||
if row.type_of_manufacturing == "Material Request":
|
||||
continue
|
||||
|
||||
work_order_data = {
|
||||
"wip_warehouse": default_warehouses.get("wip_warehouse"),
|
||||
"fg_warehouse": default_warehouses.get("fg_warehouse"),
|
||||
@@ -1158,6 +1164,7 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
|
||||
|
||||
subquery = frappe.qb.from_(wh).select(wh.name).where(wh.company == company)
|
||||
|
||||
warehouse = ""
|
||||
if not all_warehouse:
|
||||
warehouse = for_warehouse or row.get("source_warehouse") or row.get("default_warehouse")
|
||||
|
||||
@@ -1223,6 +1230,21 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
|
||||
doc["mr_items"] = []
|
||||
|
||||
po_items = doc.get("po_items") if doc.get("po_items") else doc.get("items")
|
||||
|
||||
if doc.get("sub_assembly_items"):
|
||||
for sa_row in doc.sub_assembly_items:
|
||||
sa_row = frappe._dict(sa_row)
|
||||
if sa_row.type_of_manufacturing == "Material Request":
|
||||
po_items.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": sa_row.production_item,
|
||||
"required_qty": sa_row.qty,
|
||||
"include_exploded_items": 0,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# Check for empty table or empty rows
|
||||
if not po_items or not [row.get("item_code") for row in po_items if row.get("item_code")]:
|
||||
frappe.throw(
|
||||
|
||||
@@ -840,6 +840,34 @@ class TestProductionPlan(FrappeTestCase):
|
||||
self.assertEqual(row.uom, "Nos")
|
||||
self.assertEqual(row.qty, 1)
|
||||
|
||||
def test_material_request_for_sub_assembly_items(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
bom_tree = {
|
||||
"Fininshed Goods1 For MR": {
|
||||
"SubAssembly1 For MR": {"SubAssembly1-1 For MR": {"ChildPart1 For MR": {}}}
|
||||
}
|
||||
}
|
||||
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item, planned_qty=10, ignore_existing_ordered_qty=1, do_not_submit=1
|
||||
)
|
||||
|
||||
plan.get_sub_assembly_items()
|
||||
|
||||
mr_items = []
|
||||
for row in plan.sub_assembly_items:
|
||||
mr_items.append(row.production_item)
|
||||
row.type_of_manufacturing = "Material Request"
|
||||
|
||||
plan.save()
|
||||
items = get_items_for_material_requests(plan.as_dict())
|
||||
|
||||
validate_mr_items = [d.get("item_code") for d in items]
|
||||
for item_code in mr_items:
|
||||
self.assertTrue(item_code in validate_mr_items)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "For Warehouse",
|
||||
"label": "FG Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@@ -216,7 +216,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-24 04:54:09.940224",
|
||||
"modified": "2022-11-25 14:15:40.061514",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan Item",
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Manufacturing Type",
|
||||
"options": "In House\nSubcontract"
|
||||
"options": "In House\nSubcontract\nMaterial Request"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
@@ -188,7 +188,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-30 21:31:10.527559",
|
||||
"modified": "2022-11-28 13:50:15.116082",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan Sub Assembly Item",
|
||||
|
||||
@@ -635,6 +635,10 @@ class TestWorkOrder(FrappeTestCase):
|
||||
bom.submit()
|
||||
bom_name = bom.name
|
||||
|
||||
ste1 = test_stock_entry.make_stock_entry(
|
||||
item_code=rm1, target="_Test Warehouse - _TC", qty=32, basic_rate=5000.0
|
||||
)
|
||||
|
||||
work_order = make_wo_order_test_record(
|
||||
item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
|
||||
)
|
||||
@@ -659,11 +663,29 @@ class TestWorkOrder(FrappeTestCase):
|
||||
work_order.insert()
|
||||
work_order.submit()
|
||||
self.assertEqual(work_order.has_batch_no, 1)
|
||||
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
|
||||
batches = frappe.get_all("Batch", filters={"reference_name": work_order.name})
|
||||
self.assertEqual(len(batches), 3)
|
||||
batches = [batch.name for batch in batches]
|
||||
|
||||
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 10))
|
||||
for row in ste1.get("items"):
|
||||
if row.is_finished_item:
|
||||
self.assertEqual(row.item_code, fg_item)
|
||||
self.assertEqual(row.qty, 10)
|
||||
self.assertTrue(row.batch_no in batches)
|
||||
batches.remove(row.batch_no)
|
||||
|
||||
ste1.submit()
|
||||
|
||||
remaining_batches = []
|
||||
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 20))
|
||||
for row in ste1.get("items"):
|
||||
if row.is_finished_item:
|
||||
self.assertEqual(row.item_code, fg_item)
|
||||
self.assertEqual(row.qty, 10)
|
||||
remaining_batches.append(row.batch_no)
|
||||
|
||||
self.assertEqual(sorted(remaining_batches), sorted(batches))
|
||||
|
||||
frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ frappe.query_reports["Job Card Summary"] = {
|
||||
options: ["", "Open", "Work In Progress", "Completed", "On Hold"]
|
||||
},
|
||||
{
|
||||
label: __("Sales Orders"),
|
||||
fieldname: "sales_order",
|
||||
label: __("Work Orders"),
|
||||
fieldname: "work_order",
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Sales Order', txt);
|
||||
return frappe.db.get_link_options('Work Order', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -36,10 +36,14 @@ def get_data(filters):
|
||||
"total_time_in_mins",
|
||||
]
|
||||
|
||||
for field in ["work_order", "workstation", "operation", "status", "company"]:
|
||||
for field in ["work_order", "production_item"]:
|
||||
if filters.get(field):
|
||||
query_filters[field] = ("in", filters.get(field))
|
||||
|
||||
for field in ["workstation", "operation", "status", "company"]:
|
||||
if filters.get(field):
|
||||
query_filters[field] = filters.get(field)
|
||||
|
||||
data = frappe.get_all("Job Card", fields=fields, filters=query_filters)
|
||||
|
||||
if not data:
|
||||
|
||||
@@ -39,10 +39,14 @@ def get_data(filters):
|
||||
"lead_time",
|
||||
]
|
||||
|
||||
for field in ["sales_order", "production_item", "status", "company"]:
|
||||
for field in ["sales_order", "production_item"]:
|
||||
if filters.get(field):
|
||||
query_filters[field] = ("in", filters.get(field))
|
||||
|
||||
for field in ["status", "company"]:
|
||||
if filters.get(field):
|
||||
query_filters[field] = filters.get(field)
|
||||
|
||||
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
|
||||
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
|
||||
|
||||
|
||||
@@ -317,4 +317,6 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
||||
erpnext.patches.v13_0.update_schedule_type_in_loans
|
||||
erpnext.patches.v14_0.update_tds_fields
|
||||
erpnext.patches.v14_0.update_partial_tds_fields
|
||||
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
|
||||
erpnext.patches.v14_0.setup_clear_repost_logs
|
||||
@@ -1,5 +1,8 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.setup.install import setup_currency_exchange
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("accounts", "doctype", "currency_exchange_settings")
|
||||
setup_currency_exchange()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.setup.doctype.incoterm.incoterm import create_incoterms
|
||||
|
||||
|
||||
def execute():
|
||||
create_incoterms()
|
||||
migrate_shipments()
|
||||
|
||||
|
||||
def migrate_shipments():
|
||||
if not frappe.db.count("Shipment"):
|
||||
return
|
||||
|
||||
OLD_VALUES = [
|
||||
"EXW (Ex Works)",
|
||||
"FCA (Free Carrier)",
|
||||
"FOB (Free On Board)",
|
||||
"FAS (Free Alongside Ship)",
|
||||
"CPT (Carriage Paid To)",
|
||||
"CIP (Carriage and Insurance Paid to)",
|
||||
"CFR (Cost and Freight)",
|
||||
"DPU (Delivered At Place Unloaded)",
|
||||
"DAP (Delivered At Place)",
|
||||
"DDP (Delivered Duty Paid)",
|
||||
]
|
||||
shipment = frappe.qb.DocType("Shipment")
|
||||
for old_value in OLD_VALUES:
|
||||
frappe.qb.update(shipment).set(shipment.incoterm, old_value[:3]).where(
|
||||
shipment.incoterm == old_value
|
||||
).run()
|
||||
8
erpnext/patches/v14_0/setup_clear_repost_logs.py
Normal file
8
erpnext/patches/v14_0/setup_clear_repost_logs.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
from erpnext.setup.install import setup_log_settings
|
||||
|
||||
|
||||
def execute():
|
||||
setup_log_settings()
|
||||
@@ -25,5 +25,21 @@ def execute():
|
||||
).where(
|
||||
purchase_invoice.docstatus == 1
|
||||
).run()
|
||||
|
||||
purchase_order = frappe.qb.DocType("Purchase Order")
|
||||
|
||||
frappe.qb.update(purchase_order).set(
|
||||
purchase_order.tax_withholding_net_total, purchase_order.net_total
|
||||
).set(
|
||||
purchase_order.base_tax_withholding_net_total, purchase_order.base_net_total
|
||||
).where(
|
||||
purchase_order.company == company.name
|
||||
).where(
|
||||
purchase_order.apply_tds == 1
|
||||
).where(
|
||||
purchase_order.transaction_date >= fiscal_year_details.year_start_date
|
||||
).where(
|
||||
purchase_order.docstatus == 1
|
||||
).run()
|
||||
except FiscalYearError:
|
||||
pass
|
||||
@@ -47,29 +47,36 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
await this.calculate_shipping_charges();
|
||||
|
||||
// Advance calculation applicable to Sales /Purchase Invoice
|
||||
if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) {
|
||||
// Advance calculation applicable to Sales/Purchase Invoice
|
||||
if (
|
||||
in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.docstatus < 2
|
||||
&& !this.frm.doc.is_return
|
||||
) {
|
||||
this.calculate_total_advance(update_paid_amount);
|
||||
}
|
||||
|
||||
if (in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_pos &&
|
||||
this.frm.doc.is_return) {
|
||||
if (this.frm.doc.doctype == "Sales Invoice") {
|
||||
this.set_total_amount_to_default_mop();
|
||||
}
|
||||
if (
|
||||
in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.s_pos
|
||||
&& this.frm.doc.is_return
|
||||
) {
|
||||
this.set_total_amount_to_default_mop();
|
||||
this.calculate_paid_amount();
|
||||
}
|
||||
|
||||
// Sales person's commission
|
||||
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
|
||||
if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
|
||||
this.calculate_commission();
|
||||
this.calculate_contribution();
|
||||
}
|
||||
|
||||
// Update paid amount on return/debit note creation
|
||||
if(this.frm.doc.doctype === "Purchase Invoice" && this.frm.doc.is_return
|
||||
&& (this.frm.doc.grand_total > this.frm.doc.paid_amount)) {
|
||||
if (
|
||||
this.frm.doc.doctype === "Purchase Invoice"
|
||||
&& this.frm.doc.is_return
|
||||
&& (this.frm.doc.grand_total > this.frm.doc.paid_amount)
|
||||
) {
|
||||
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
|
||||
}
|
||||
|
||||
@@ -775,21 +782,30 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
|
||||
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
|
||||
|
||||
if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
|
||||
var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance
|
||||
- this.frm.doc.write_off_amount), precision("grand_total"));
|
||||
if (this.frm.doc.party_account_currency == this.frm.doc.currency) {
|
||||
var total_amount_to_pay = flt(
|
||||
grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount,
|
||||
precision("grand_total")
|
||||
);
|
||||
} else {
|
||||
var total_amount_to_pay = flt(
|
||||
(flt(base_grand_total, precision("base_grand_total"))
|
||||
- this.frm.doc.total_advance - this.frm.doc.base_write_off_amount),
|
||||
(
|
||||
flt(
|
||||
base_grand_total,
|
||||
precision("base_grand_total")
|
||||
)
|
||||
- this.frm.doc.total_advance - this.frm.doc.base_write_off_amount
|
||||
),
|
||||
precision("base_grand_total")
|
||||
);
|
||||
}
|
||||
|
||||
this.frm.doc.payments.find(pay => {
|
||||
if (pay.default) {
|
||||
pay.amount = total_amount_to_pay;
|
||||
}
|
||||
});
|
||||
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"tax_category",
|
||||
"column_break_34",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"section_break_36",
|
||||
"taxes",
|
||||
"section_break_39",
|
||||
@@ -1052,13 +1053,19 @@
|
||||
{
|
||||
"fieldname": "column_break_108",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"idx": 82,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-11 13:06:33.479650",
|
||||
"modified": "2022-11-17 17:20:54.984348",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"tax_category",
|
||||
"column_break_49",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"section_break_40",
|
||||
"taxes",
|
||||
"section_break_43",
|
||||
@@ -1623,13 +1624,19 @@
|
||||
{
|
||||
"fieldname": "column_break_152",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-11 13:06:10.469796",
|
||||
"modified": "2022-11-17 17:22:00.413878",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
0
erpnext/setup/doctype/incoterm/__init__.py
Normal file
0
erpnext/setup/doctype/incoterm/__init__.py
Normal file
8
erpnext/setup/doctype/incoterm/incoterm.js
Normal file
8
erpnext/setup/doctype/incoterm/incoterm.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Incoterm", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
168
erpnext/setup/doctype/incoterm/incoterm.json
Normal file
168
erpnext/setup/doctype/incoterm/incoterm.json
Normal file
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:code",
|
||||
"creation": "2022-11-17 15:17:34.717467",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"code",
|
||||
"title",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "code",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Code",
|
||||
"length": 3,
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Description"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"group": "Selling",
|
||||
"link_doctype": "Quotation",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Selling",
|
||||
"link_doctype": "Sales Order",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Buying",
|
||||
"link_doctype": "Request for Quotation",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Buying",
|
||||
"link_doctype": "Supplier Quotation",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Buying",
|
||||
"link_doctype": "Purchase Order",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Stock",
|
||||
"link_doctype": "Delivery Note",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Stock",
|
||||
"link_doctype": "Purchase Receipt",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Stock",
|
||||
"link_doctype": "Shipment",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Accounts",
|
||||
"link_doctype": "Sales Invoice",
|
||||
"link_fieldname": "incoterm"
|
||||
},
|
||||
{
|
||||
"group": "Accounts",
|
||||
"link_doctype": "Purchase Invoice",
|
||||
"link_fieldname": "incoterm"
|
||||
}
|
||||
],
|
||||
"modified": "2022-11-17 22:35:52.084553",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Incoterm",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Stock User"
|
||||
}
|
||||
],
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "name",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"translated_doctype": 1
|
||||
}
|
||||
24
erpnext/setup/doctype/incoterm/incoterm.py
Normal file
24
erpnext/setup/doctype/incoterm/incoterm.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class Incoterm(Document):
|
||||
pass
|
||||
|
||||
|
||||
def create_incoterms():
|
||||
"""Create Incoterm records from incoterms.csv."""
|
||||
import os
|
||||
from csv import DictReader
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "incoterms.csv"), "r") as f:
|
||||
for incoterm in DictReader(f):
|
||||
if frappe.db.exists("Incoterm", incoterm["code"]):
|
||||
continue
|
||||
|
||||
doc = frappe.new_doc("Incoterm")
|
||||
doc.update(incoterm)
|
||||
doc.save()
|
||||
12
erpnext/setup/doctype/incoterm/incoterms.csv
Normal file
12
erpnext/setup/doctype/incoterm/incoterms.csv
Normal file
@@ -0,0 +1,12 @@
|
||||
code,title
|
||||
EXW,Ex Works
|
||||
FCA,Free Carrier
|
||||
FAS,Free Alongside Ship
|
||||
FOB,Free On Board
|
||||
CPT,Carriage Paid To
|
||||
CIP,Carriage and Insurance Paid to
|
||||
CFR,Cost and Freight
|
||||
CIF,"Cost, Insurance and Freight"
|
||||
DAP,Delivered At Place
|
||||
DPU,Delivered At Place Unloaded
|
||||
DDP,Delivered Duty Paid
|
||||
|
9
erpnext/setup/doctype/incoterm/test_incoterm.py
Normal file
9
erpnext/setup/doctype/incoterm/test_incoterm.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestIncoterm(FrappeTestCase):
|
||||
pass
|
||||
@@ -10,6 +10,7 @@ from frappe.utils import cint
|
||||
|
||||
from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import DEFAULT_MAPPERS
|
||||
from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
|
||||
from erpnext.setup.doctype.incoterm.incoterm import create_incoterms
|
||||
|
||||
from .default_success_action import get_default_success_action
|
||||
|
||||
@@ -25,9 +26,11 @@ def after_install():
|
||||
create_default_cash_flow_mapper_templates()
|
||||
create_default_success_action()
|
||||
create_default_energy_point_rules()
|
||||
create_incoterms()
|
||||
add_company_to_session_defaults()
|
||||
add_standard_navbar_items()
|
||||
add_app_name()
|
||||
setup_log_settings()
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
@@ -195,3 +198,10 @@ def add_standard_navbar_items():
|
||||
|
||||
def add_app_name():
|
||||
frappe.db.set_value("System Settings", None, "app_name", "ERPNext")
|
||||
|
||||
|
||||
def setup_log_settings():
|
||||
log_settings = frappe.get_single("Log Settings")
|
||||
log_settings.append("logs_to_clear", {"ref_doctype": "Repost Item Valuation", "days": 60})
|
||||
|
||||
log_settings.save(ignore_permissions=True)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,7 @@
|
||||
"tax_category",
|
||||
"column_break_39",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"section_break_41",
|
||||
"taxes",
|
||||
"section_break_44",
|
||||
@@ -1381,13 +1382,19 @@
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-truck",
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-11 13:06:58.655635",
|
||||
"modified": "2022-11-17 17:22:42.860790",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -71,6 +71,8 @@ frappe.ui.form.on('Inventory Dimension', {
|
||||
if (r.message && r.message.length) {
|
||||
frm.set_df_property("fetch_from_parent", "options",
|
||||
[""].concat(r.message));
|
||||
} else {
|
||||
frm.set_df_property("fetch_from_parent", "hidden", 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,20 +11,20 @@
|
||||
"reference_document",
|
||||
"column_break_4",
|
||||
"disabled",
|
||||
"section_break_7",
|
||||
"field_mapping_section",
|
||||
"source_fieldname",
|
||||
"column_break_9",
|
||||
"target_fieldname",
|
||||
"applicable_for_documents_tab",
|
||||
"apply_to_all_doctypes",
|
||||
"column_break_13",
|
||||
"document_type",
|
||||
"istable",
|
||||
"type_of_transaction",
|
||||
"fetch_from_parent",
|
||||
"column_break_16",
|
||||
"condition",
|
||||
"istable",
|
||||
"applicable_condition_example_section",
|
||||
"condition",
|
||||
"conditional_rule_examples_section",
|
||||
"html_19"
|
||||
],
|
||||
"fields": [
|
||||
@@ -52,13 +52,13 @@
|
||||
{
|
||||
"fieldname": "applicable_for_documents_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Applicable For Documents"
|
||||
"label": "Applicable For"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Applicable to Document",
|
||||
"label": "Apply to Document",
|
||||
"mandatory_depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"options": "DocType"
|
||||
},
|
||||
@@ -72,6 +72,7 @@
|
||||
"fetch_from": "document_type.istable",
|
||||
"fieldname": "istable",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": " Is Child Table",
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -79,13 +80,13 @@
|
||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"fieldname": "condition",
|
||||
"fieldtype": "Code",
|
||||
"label": "Applicable Condition"
|
||||
"label": "Conditional Rule"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "1",
|
||||
"fieldname": "apply_to_all_doctypes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply to All Inventory Document Types"
|
||||
"label": "Apply to All Inventory Documents"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -93,10 +94,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "target_fieldname",
|
||||
"fieldtype": "Data",
|
||||
@@ -115,13 +112,11 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "field_mapping_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1,
|
||||
"label": "Field Mapping"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_16",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"fieldname": "type_of_transaction",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type of Transaction",
|
||||
@@ -136,23 +131,33 @@
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"fieldname": "applicable_condition_example_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Applicable Condition Examples"
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Set fieldname or DocType name like Supplier, Customer etc.",
|
||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"description": "Set fieldname from which you want to fetch the data from the parent form.",
|
||||
"fieldname": "fetch_from_parent",
|
||||
"fieldtype": "Select",
|
||||
"label": "Fetch Value From Parent Form"
|
||||
"label": "Fetch Value From"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"fieldname": "conditional_rule_examples_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Conditional Rule Examples"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-02 13:29:04.098469",
|
||||
"modified": "2022-11-15 15:50:16.767105",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Inventory Dimension",
|
||||
|
||||
@@ -33,10 +33,22 @@ class InventoryDimension(Document):
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_reference_document()
|
||||
|
||||
def before_save(self):
|
||||
self.do_not_update_document()
|
||||
self.reset_value()
|
||||
self.validate_reference_document()
|
||||
self.set_source_and_target_fieldname()
|
||||
self.set_type_of_transaction()
|
||||
self.set_fetch_value_from()
|
||||
|
||||
def set_type_of_transaction(self):
|
||||
if self.apply_to_all_doctypes:
|
||||
self.type_of_transaction = "Both"
|
||||
|
||||
def set_fetch_value_from(self):
|
||||
if self.apply_to_all_doctypes:
|
||||
self.fetch_from_parent = self.reference_document
|
||||
|
||||
def do_not_update_document(self):
|
||||
if self.is_new() or not self.has_stock_ledger():
|
||||
|
||||
@@ -140,14 +140,13 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
||||
|
||||
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
|
||||
create_inventory_dimension(
|
||||
reference_document="Rack",
|
||||
type_of_transaction="Both",
|
||||
dimension_name="Rack",
|
||||
apply_to_all_doctypes=1,
|
||||
fetch_from_parent="Rack",
|
||||
inv_dimension = create_inventory_dimension(
|
||||
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
|
||||
)
|
||||
|
||||
self.assertEqual(inv_dimension.type_of_transaction, "Both")
|
||||
self.assertEqual(inv_dimension.fetch_from_parent, "Rack")
|
||||
|
||||
create_custom_field(
|
||||
"Purchase Receipt", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack")
|
||||
)
|
||||
|
||||
@@ -370,9 +370,6 @@ frappe.ui.form.on("Material Request Item", {
|
||||
if (flt(d.qty) < flt(d.min_order_qty)) {
|
||||
frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
|
||||
}
|
||||
|
||||
const item = locals[doctype][name];
|
||||
frm.events.get_item_data(frm, item, false);
|
||||
},
|
||||
|
||||
from_warehouse: function(frm, doctype, name) {
|
||||
|
||||
@@ -596,7 +596,9 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
if source.material_request_type == "Customer Provided":
|
||||
target.purpose = "Material Receipt"
|
||||
|
||||
target.set_missing_values()
|
||||
target.set_transfer_qty()
|
||||
target.set_actual_qty()
|
||||
target.calculate_rate_and_amount(raise_error_if_no_rate=False)
|
||||
target.set_stock_entry_type()
|
||||
target.set_job_card_data()
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ def make_packing_list(doc):
|
||||
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
|
||||
|
||||
if set_price_from_children: # create/update bundle item wise price dict
|
||||
update_product_bundle_rate(parent_items_price, pi_row)
|
||||
update_product_bundle_rate(parent_items_price, pi_row, item_row)
|
||||
|
||||
if parent_items_price:
|
||||
set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
|
||||
@@ -247,7 +247,7 @@ def get_cancelled_doc_packed_item_details(old_packed_items):
|
||||
return prev_doc_packed_items_map
|
||||
|
||||
|
||||
def update_product_bundle_rate(parent_items_price, pi_row):
|
||||
def update_product_bundle_rate(parent_items_price, pi_row, item_row):
|
||||
"""
|
||||
Update the price dict of Product Bundles based on the rates of the Items in the bundle.
|
||||
|
||||
@@ -259,7 +259,7 @@ def update_product_bundle_rate(parent_items_price, pi_row):
|
||||
if not rate:
|
||||
parent_items_price[key] = 0.0
|
||||
|
||||
parent_items_price[key] += flt(pi_row.rate)
|
||||
parent_items_price[key] += flt((pi_row.rate * pi_row.qty) / item_row.stock_qty)
|
||||
|
||||
|
||||
def set_product_bundle_rate_amount(doc, parent_items_price):
|
||||
|
||||
@@ -126,8 +126,8 @@ class TestPackedItem(FrappeTestCase):
|
||||
so.packed_items[1].rate = 200
|
||||
so.save()
|
||||
|
||||
self.assertEqual(so.items[0].rate, 350)
|
||||
self.assertEqual(so.items[0].amount, 700)
|
||||
self.assertEqual(so.items[0].rate, 700)
|
||||
self.assertEqual(so.items[0].amount, 1400)
|
||||
|
||||
def test_newly_mapped_doc_packed_items(self):
|
||||
"Test impact on packed items in newly mapped DN from SO."
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"tax_category",
|
||||
"column_break_53",
|
||||
"shipping_rule",
|
||||
"incoterm",
|
||||
"taxes_section",
|
||||
"taxes",
|
||||
"totals",
|
||||
@@ -1218,13 +1219,19 @@
|
||||
{
|
||||
"fieldname": "column_break_104",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "Incoterm"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-truck",
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-11 13:02:31.776256",
|
||||
"modified": "2022-11-17 17:29:30.067536",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -173,7 +173,9 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
if (
|
||||
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
|
||||
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
|
||||
and not self.is_return
|
||||
and not self.is_internal_supplier
|
||||
):
|
||||
self.validate_rate_with_reference_doc(
|
||||
[["Purchase Order", "purchase_order", "purchase_order_item"]]
|
||||
|
||||
@@ -34,6 +34,22 @@ frappe.ui.form.on('Repost Item Valuation', {
|
||||
frm.trigger('setup_realtime_progress');
|
||||
},
|
||||
|
||||
based_on: function(frm) {
|
||||
var fields_to_reset = [];
|
||||
|
||||
if (frm.doc.based_on == 'Transaction') {
|
||||
fields_to_reset = ['item_code', 'warehouse'];
|
||||
} else if (frm.doc.based_on == 'Item and Warehouse') {
|
||||
fields_to_reset = ['voucher_type', 'voucher_no'];
|
||||
}
|
||||
|
||||
if (fields_to_reset) {
|
||||
fields_to_reset.forEach(field => {
|
||||
frm.set_value(field, undefined);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setup_realtime_progress: function(frm) {
|
||||
frappe.realtime.on('item_reposting_progress', data => {
|
||||
if (frm.doc.name !== data.name) {
|
||||
|
||||
@@ -50,13 +50,15 @@
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Posting Date",
|
||||
"read_only_depends_on": "eval: doc.based_on == \"Transaction\"",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "voucher_no.posting_time",
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time"
|
||||
"label": "Posting Time",
|
||||
"read_only_depends_on": "eval: doc.based_on == \"Transaction\""
|
||||
},
|
||||
{
|
||||
"default": "Queued",
|
||||
@@ -195,7 +197,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-13 12:20:22.182322",
|
||||
"modified": "2022-11-28 16:00:05.637440",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Repost Item Valuation",
|
||||
|
||||
@@ -5,7 +5,9 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime
|
||||
from frappe.query_builder import DocType, Interval
|
||||
from frappe.query_builder.functions import Now
|
||||
from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime
|
||||
from frappe.utils.user import get_users_with_role
|
||||
from rq.timeouts import JobTimeoutException
|
||||
|
||||
@@ -21,10 +23,43 @@ RecoverableErrors = (JobTimeoutException, QueryDeadlockError, QueryTimeoutError)
|
||||
|
||||
|
||||
class RepostItemValuation(Document):
|
||||
@staticmethod
|
||||
def clear_old_logs(days=None):
|
||||
days = days or 90
|
||||
table = DocType("Repost Item Valuation")
|
||||
frappe.db.delete(
|
||||
table,
|
||||
filters=(
|
||||
(table.modified < (Now() - Interval(days=days)))
|
||||
& (table.status.isin(["Completed", "Skipped"]))
|
||||
),
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.set_status(write=False)
|
||||
self.reset_field_values()
|
||||
self.set_company()
|
||||
self.validate_accounts_freeze()
|
||||
|
||||
def validate_accounts_freeze(self):
|
||||
acc_settings = frappe.db.get_value(
|
||||
"Accounts Settings",
|
||||
"Accounts Settings",
|
||||
["acc_frozen_upto", "frozen_accounts_modifier"],
|
||||
as_dict=1,
|
||||
)
|
||||
if not acc_settings.acc_frozen_upto:
|
||||
return
|
||||
if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto):
|
||||
if (
|
||||
acc_settings.frozen_accounts_modifier
|
||||
and frappe.session.user in get_users_with_role(acc_settings.frozen_accounts_modifier)
|
||||
):
|
||||
frappe.msgprint(_("Caution: This might alter frozen accounts."))
|
||||
return
|
||||
frappe.throw(
|
||||
_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto)
|
||||
)
|
||||
|
||||
def reset_field_values(self):
|
||||
if self.based_on == "Transaction":
|
||||
@@ -240,7 +275,7 @@ def _get_directly_dependent_vouchers(doc):
|
||||
def notify_error_to_stock_managers(doc, traceback):
|
||||
recipients = get_users_with_role("Stock Manager")
|
||||
if not recipients:
|
||||
get_users_with_role("System Manager")
|
||||
recipients = get_users_with_role("System Manager")
|
||||
|
||||
subject = _("Error while reposting item valuation")
|
||||
message = (
|
||||
|
||||
@@ -6,8 +6,7 @@ from unittest.mock import MagicMock, call
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import nowdate
|
||||
from frappe.utils.data import add_to_date, today
|
||||
from frappe.utils import add_days, add_to_date, now, nowdate, today
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.utils import repost_gle_for_stock_vouchers
|
||||
@@ -86,6 +85,33 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
|
||||
msg=f"Exepcted false from : {case}",
|
||||
)
|
||||
|
||||
def test_clear_old_logs(self):
|
||||
# create 10 logs
|
||||
for i in range(1, 20):
|
||||
repost_doc = frappe.get_doc(
|
||||
doctype="Repost Item Valuation",
|
||||
item_code="_Test Item",
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
based_on="Item and Warehouse",
|
||||
posting_date=nowdate(),
|
||||
status="Skipped",
|
||||
posting_time="00:01:00",
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
repost_doc.load_from_db()
|
||||
repost_doc.modified = add_days(now(), days=-i * 10)
|
||||
repost_doc.db_update_all()
|
||||
|
||||
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
|
||||
self.assertTrue(len(logs) > 10)
|
||||
|
||||
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import RepostItemValuation
|
||||
|
||||
RepostItemValuation.clear_old_logs(days=1)
|
||||
|
||||
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
|
||||
self.assertTrue(len(logs) == 0)
|
||||
|
||||
def test_create_item_wise_repost_item_valuation_entries(self):
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory",
|
||||
@@ -327,3 +353,26 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
|
||||
# outstanding should not be affected
|
||||
sinv.reload()
|
||||
self.assertEqual(sinv.outstanding_amount, 100)
|
||||
|
||||
def test_account_freeze_validation(self):
|
||||
today = nowdate()
|
||||
|
||||
riv = frappe.get_doc(
|
||||
doctype="Repost Item Valuation",
|
||||
item_code="_Test Item",
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
based_on="Item and Warehouse",
|
||||
posting_date=today,
|
||||
posting_time="00:01:00",
|
||||
)
|
||||
riv.flags.dont_run_in_test = True # keep it queued
|
||||
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
accounts_settings.acc_frozen_upto = today
|
||||
accounts_settings.frozen_accounts_modifier = ""
|
||||
accounts_settings.save()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, riv.save)
|
||||
|
||||
accounts_settings.acc_frozen_upto = ""
|
||||
accounts_settings.save()
|
||||
|
||||
@@ -412,9 +412,9 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Select",
|
||||
"fieldtype": "Link",
|
||||
"label": "Incoterm",
|
||||
"options": "EXW (Ex Works)\nFCA (Free Carrier)\nCPT (Carriage Paid To)\nCIP (Carriage and Insurance Paid to)\nDPU (Delivered At Place Unloaded)\nDAP (Delivered At Place)\nDDP (Delivered Duty Paid)"
|
||||
"options": "Incoterm"
|
||||
},
|
||||
{
|
||||
"fieldname": "shipment_delivery_note",
|
||||
@@ -433,10 +433,11 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-13 17:14:18.181818",
|
||||
"modified": "2022-11-17 17:23:27.025802",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Shipment",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -470,5 +471,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -1079,7 +1079,8 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
|
||||
if (frm.doc.purpose === 'Material Receipt') return;
|
||||
|
||||
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
||||
new erpnext.SerialNoBatchSelector({
|
||||
if (frm.batch_selector?.dialog?.display) return;
|
||||
frm.batch_selector = new erpnext.SerialNoBatchSelector({
|
||||
frm: frm,
|
||||
item: item,
|
||||
warehouse_details: get_warehouse_type_and_name(item),
|
||||
|
||||
@@ -659,6 +659,13 @@ class StockEntry(StockController):
|
||||
|
||||
if d.allow_zero_valuation_rate:
|
||||
d.basic_rate = 0.0
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {1}"
|
||||
).format(d.idx, d.item_code),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
elif d.is_finished_item:
|
||||
if self.purpose == "Manufacture":
|
||||
d.basic_rate = self.get_basic_rate_for_manufactured_item(
|
||||
@@ -1538,6 +1545,7 @@ class StockEntry(StockController):
|
||||
"reference_name": self.pro_doc.name,
|
||||
"reference_doctype": self.pro_doc.doctype,
|
||||
"qty_to_produce": (">", 0),
|
||||
"batch_qty": ("=", 0),
|
||||
}
|
||||
|
||||
fields = ["qty_to_produce as qty", "produced_qty", "name"]
|
||||
@@ -2231,14 +2239,14 @@ class StockEntry(StockController):
|
||||
d.qty -= process_loss_dict[d.item_code][1]
|
||||
|
||||
def set_serial_no_batch_for_finished_good(self):
|
||||
args = {}
|
||||
serial_nos = ""
|
||||
if self.pro_doc.serial_no:
|
||||
self.get_serial_nos_for_fg(args)
|
||||
serial_nos = self.get_serial_nos_for_fg()
|
||||
|
||||
for row in self.items:
|
||||
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
|
||||
if args.get("serial_no"):
|
||||
row.serial_no = "\n".join(args["serial_no"][0 : cint(row.qty)])
|
||||
if serial_nos:
|
||||
row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
|
||||
|
||||
def get_serial_nos_for_fg(self, args):
|
||||
fields = [
|
||||
@@ -2251,14 +2259,14 @@ class StockEntry(StockController):
|
||||
filters = [
|
||||
["Stock Entry", "work_order", "=", self.work_order],
|
||||
["Stock Entry", "purpose", "=", "Manufacture"],
|
||||
["Stock Entry", "docstatus", "=", 1],
|
||||
["Stock Entry", "docstatus", "<", 2],
|
||||
["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item],
|
||||
]
|
||||
|
||||
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||
|
||||
if self.pro_doc.serial_no:
|
||||
args["serial_no"] = self.get_available_serial_nos(stock_entries)
|
||||
return self.get_available_serial_nos(stock_entries)
|
||||
|
||||
def get_available_serial_nos(self, stock_entries):
|
||||
used_serial_nos = []
|
||||
|
||||
@@ -230,7 +230,7 @@ class StockReconciliation(StockController):
|
||||
|
||||
if item.has_serial_no or item.has_batch_no:
|
||||
has_serial_no = True
|
||||
self.get_sle_for_serialized_items(row, sl_entries)
|
||||
self.get_sle_for_serialized_items(row, sl_entries, item)
|
||||
else:
|
||||
if row.serial_no or row.batch_no:
|
||||
frappe.throw(
|
||||
@@ -282,7 +282,7 @@ class StockReconciliation(StockController):
|
||||
if has_serial_no and sl_entries:
|
||||
self.update_valuation_rate_for_serial_no()
|
||||
|
||||
def get_sle_for_serialized_items(self, row, sl_entries):
|
||||
def get_sle_for_serialized_items(self, row, sl_entries, item):
|
||||
from erpnext.stock.stock_ledger import get_previous_sle
|
||||
|
||||
serial_nos = get_serial_nos(row.serial_no)
|
||||
@@ -348,6 +348,9 @@ class StockReconciliation(StockController):
|
||||
if row.qty:
|
||||
args = self.get_sle_for_items(row)
|
||||
|
||||
if item.has_serial_no and item.has_batch_no:
|
||||
args["qty_after_transaction"] = row.qty
|
||||
|
||||
args.update(
|
||||
{
|
||||
"actual_qty": row.qty,
|
||||
|
||||
@@ -644,6 +644,38 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
)
|
||||
self.assertEqual(len(active_sr_no), 0)
|
||||
|
||||
def test_serial_no_batch_no_item(self):
|
||||
item = self.make_item(
|
||||
"Test Serial No Batch No Item",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"has_batch_no": 1,
|
||||
"serial_no_series": "SRS9.####",
|
||||
"batch_number_series": "BNS9.####",
|
||||
"create_new_batch": 1,
|
||||
},
|
||||
)
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=item.name,
|
||||
warehouse=warehouse,
|
||||
qty=1,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
sl_entry = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Stock Reconciliation", "voucher_no": sr.name},
|
||||
["actual_qty", "qty_after_transaction"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(flt(sl_entry.actual_qty), 1.0)
|
||||
self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0)
|
||||
|
||||
|
||||
def create_batch_item_with_batch(item_name, batch_id):
|
||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _, throw
|
||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||
from frappe.utils import cint, flt
|
||||
from frappe.utils import cint
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
from pypika.terms import ExistsCriterion
|
||||
|
||||
@@ -166,60 +164,7 @@ def get_children(doctype, parent=None, company=None, is_root=False):
|
||||
["company", "in", (company, None, "")],
|
||||
]
|
||||
|
||||
warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
|
||||
|
||||
company_currency = ""
|
||||
if company:
|
||||
company_currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||
|
||||
warehouse_wise_value = get_warehouse_wise_stock_value(company)
|
||||
|
||||
# return warehouses
|
||||
for wh in warehouses:
|
||||
wh["balance"] = warehouse_wise_value.get(wh.value)
|
||||
if company_currency:
|
||||
wh["company_currency"] = company_currency
|
||||
return warehouses
|
||||
|
||||
|
||||
def get_warehouse_wise_stock_value(company):
|
||||
warehouses = frappe.get_all(
|
||||
"Warehouse", fields=["name", "parent_warehouse"], filters={"company": company}
|
||||
)
|
||||
parent_warehouse = {d.name: d.parent_warehouse for d in warehouses}
|
||||
|
||||
filters = {"warehouse": ("in", [data.name for data in warehouses])}
|
||||
bin_data = frappe.get_all(
|
||||
"Bin",
|
||||
fields=["sum(stock_value) as stock_value", "warehouse"],
|
||||
filters=filters,
|
||||
group_by="warehouse",
|
||||
)
|
||||
|
||||
warehouse_wise_stock_value = defaultdict(float)
|
||||
for row in bin_data:
|
||||
if not row.stock_value:
|
||||
continue
|
||||
|
||||
warehouse_wise_stock_value[row.warehouse] = row.stock_value
|
||||
update_value_in_parent_warehouse(
|
||||
warehouse_wise_stock_value, parent_warehouse, row.warehouse, row.stock_value
|
||||
)
|
||||
|
||||
return warehouse_wise_stock_value
|
||||
|
||||
|
||||
def update_value_in_parent_warehouse(
|
||||
warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value
|
||||
):
|
||||
parent_warehouse = parent_warehouse_dict.get(warehouse)
|
||||
if not parent_warehouse:
|
||||
return
|
||||
|
||||
warehouse_wise_stock_value[parent_warehouse] += flt(stock_value)
|
||||
update_value_in_parent_warehouse(
|
||||
warehouse_wise_stock_value, parent_warehouse_dict, parent_warehouse, stock_value
|
||||
)
|
||||
return frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -17,11 +17,4 @@ frappe.treeview_settings['Warehouse'] = {
|
||||
description: __("Child nodes can be only created under 'Group' type nodes")}
|
||||
],
|
||||
ignore_fields:["parent_warehouse"],
|
||||
onrender: function(node) {
|
||||
if (node.data && node.data.balance!==undefined) {
|
||||
$('<span class="balance-area pull-right">'
|
||||
+ format_currency((node.data.balance), node.data.company_currency)
|
||||
+ '</span>').insertBefore(node.$ul);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,12 @@ def execute(filters=None):
|
||||
if sle.serial_no:
|
||||
update_available_serial_nos(available_serial_nos, sle)
|
||||
|
||||
if sle.actual_qty:
|
||||
sle["in_out_rate"] = flt(sle.stock_value_difference / sle.actual_qty, precision)
|
||||
|
||||
elif sle.voucher_type == "Stock Reconciliation":
|
||||
sle["in_out_rate"] = sle.valuation_rate
|
||||
|
||||
data.append(sle)
|
||||
|
||||
if include_uom:
|
||||
@@ -185,10 +191,18 @@ def get_columns(filters):
|
||||
"convertible": "rate",
|
||||
},
|
||||
{
|
||||
"label": _("Valuation Rate"),
|
||||
"label": _("Avg Rate (Balance Stock)"),
|
||||
"fieldname": "valuation_rate",
|
||||
"fieldtype": "Currency",
|
||||
"width": 110,
|
||||
"width": 180,
|
||||
"options": "Company:company:default_currency",
|
||||
"convertible": "rate",
|
||||
},
|
||||
{
|
||||
"label": _("Valuation Rate"),
|
||||
"fieldname": "in_out_rate",
|
||||
"fieldtype": "Currency",
|
||||
"width": 140,
|
||||
"options": "Company:company:default_currency",
|
||||
"convertible": "rate",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Warehouse Wise Stock Balance"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
}
|
||||
],
|
||||
"initial_depth": 3,
|
||||
"tree": true,
|
||||
"parent_field": "parent_warehouse",
|
||||
"name_field": "warehouse"
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2022-12-06 14:15:31.924345",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{}",
|
||||
"modified": "2022-12-06 14:16:55.969214",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Warehouse Wise Stock Balance",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Stock Ledger Entry",
|
||||
"report_name": "Warehouse Wise Stock Balance",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
|
||||
class StockBalanceFilter(TypedDict):
|
||||
company: Optional[str]
|
||||
warehouse: Optional[str]
|
||||
|
||||
|
||||
SLEntry = Dict[str, Any]
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = [], []
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]:
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(sle)
|
||||
.select(sle.warehouse, Sum(sle.stock_value_difference).as_("stock_balance"))
|
||||
.where((sle.docstatus < 2) & (sle.is_cancelled == 0))
|
||||
.groupby(sle.warehouse)
|
||||
)
|
||||
|
||||
if filters.get("company"):
|
||||
query = query.where(sle.company == filters.get("company"))
|
||||
|
||||
data = query.run(as_list=True)
|
||||
return frappe._dict(data) if data else frappe._dict()
|
||||
|
||||
|
||||
def get_warehouses(report_filters: StockBalanceFilter):
|
||||
return frappe.get_all(
|
||||
"Warehouse",
|
||||
fields=["name", "parent_warehouse", "is_group"],
|
||||
filters={"company": report_filters.company, "disabled": 0},
|
||||
order_by="lft",
|
||||
)
|
||||
|
||||
|
||||
def get_data(filters: StockBalanceFilter):
|
||||
warehouse_balance = get_warehouse_wise_balance(filters)
|
||||
warehouses = get_warehouses(filters)
|
||||
|
||||
for warehouse in warehouses:
|
||||
warehouse["stock_balance"] = warehouse_balance.get(warehouse.name, 0)
|
||||
|
||||
update_indent(warehouses)
|
||||
|
||||
return warehouses
|
||||
|
||||
|
||||
def update_indent(warehouses):
|
||||
for warehouse in warehouses:
|
||||
|
||||
def add_indent(warehouse, indent):
|
||||
warehouse.indent = indent
|
||||
for child in warehouses:
|
||||
if child.parent_warehouse == warehouse.name:
|
||||
warehouse.stock_balance += child.stock_balance
|
||||
add_indent(child, indent + 1)
|
||||
|
||||
if warehouse.is_group:
|
||||
add_indent(warehouse, warehouse.indent or 0)
|
||||
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 200,
|
||||
},
|
||||
{"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150},
|
||||
]
|
||||
@@ -263,8 +263,8 @@ def repost_future_sle(
|
||||
|
||||
def validate_item_warehouse(args):
|
||||
for field in ["item_code", "warehouse", "posting_date", "posting_time"]:
|
||||
if not args.get(field):
|
||||
validation_msg = f"The field {frappe.unscrub(args.get(field))} is required for the reposting"
|
||||
if args.get(field) in [None, ""]:
|
||||
validation_msg = f"The field {frappe.unscrub(field)} is required for the reposting"
|
||||
frappe.throw(_(validation_msg))
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"label": "Warehouse wise Stock Value"
|
||||
}
|
||||
],
|
||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
|
||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
|
||||
"creation": "2020-03-02 15:43:10.096528",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
@@ -207,80 +207,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Stock Reports",
|
||||
"link_count": 0,
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Ledger",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Ledger",
|
||||
"link_type": "Report",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Balance",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Balance",
|
||||
"link_type": "Report",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Projected Qty",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Projected Qty",
|
||||
"link_type": "Report",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Stock Summary",
|
||||
"link_count": 0,
|
||||
"link_to": "stock-balance",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Ageing",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Ageing",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Item Price Stock",
|
||||
"link_count": 0,
|
||||
"link_to": "Item Price Stock",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@@ -705,15 +631,100 @@
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Stock Reports",
|
||||
"link_count": 7,
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Ledger",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Ledger",
|
||||
"link_type": "Report",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Balance",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Balance",
|
||||
"link_type": "Report",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Projected Qty",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Projected Qty",
|
||||
"link_type": "Report",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Stock Summary",
|
||||
"link_count": 0,
|
||||
"link_to": "stock-balance",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Stock Ageing",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Ageing",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Item",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Item Price Stock",
|
||||
"link_count": 0,
|
||||
"link_to": "Item Price Stock",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Warehouse Wise Stock Balance",
|
||||
"link_count": 0,
|
||||
"link_to": "Warehouse Wise Stock Balance",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2022-01-13 17:47:38.339931",
|
||||
"modified": "2022-12-06 17:03:56.397272",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock",
|
||||
"owner": "Administrator",
|
||||
"parent_page": "",
|
||||
"public": 1,
|
||||
"quick_lists": [],
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 24.0,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<div class="product-container mt-4 {{ padding_top }} {{ info_col }}">
|
||||
<div class="item-content {{ 'mt-minus-2' if (show_tabs and tabs) else '' }}">
|
||||
<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
|
||||
<div class="product-page-content">
|
||||
<!-- Product Specifications Table Section -->
|
||||
{% if show_tabs and tabs %}
|
||||
<div class="category-tabs">
|
||||
|
||||
@@ -9905,3 +9905,14 @@ Total Asset,Aktiva,
|
||||
Total Liability,Verbindlichkeiten,
|
||||
Total Equity,Eigenkapital,
|
||||
Warehouse wise Stock Value,Warenwert nach Lager,
|
||||
Ex Works,Ab Werk,
|
||||
Free Carrier,Frei Frachtführer,
|
||||
Free Alongside Ship,Frei Längsseite Schiff,
|
||||
Free on Board,Frei an Bord,
|
||||
Carriage Paid To,Frachtfrei,
|
||||
Carriage and Insurance Paid to,Frachtfrei versichert,
|
||||
Cost and Freight,Kosten und Fracht,
|
||||
"Cost, Insurance and Freight","Kosten, Versicherung und Fracht",
|
||||
Delivered at Place,Geliefert benannter Ort,
|
||||
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
|
||||
Delivered Duty Paid,Geliefert verzollt,
|
||||
|
||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user