Compare commits

..

48 Commits

Author SHA1 Message Date
Deepesh Garg
d424f0f861 fix: Default year start and end date in reports
(cherry picked from commit 2341061852)

# Conflicts:
#	erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.js
#	erpnext/accounts/report/profitability_analysis/profitability_analysis.js
#	erpnext/accounts/report/trial_balance/trial_balance.js
#	erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js
#	erpnext/buying/report/procurement_tracker/procurement_tracker.js
#	erpnext/buying/report/purchase_analytics/purchase_analytics.js
#	erpnext/manufacturing/report/job_card_summary/job_card_summary.js
#	erpnext/manufacturing/report/production_analytics/production_analytics.js
#	erpnext/selling/report/sales_analytics/sales_analytics.js
#	erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js
#	erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js
#	erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js
#	erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js
2024-11-22 12:00:06 +00:00
Deepesh Garg
30073698f4 fix: Default year start and end dates in reports
(cherry picked from commit 4496a6760e)

# Conflicts:
#	erpnext/accounts/doctype/account/account.js
#	erpnext/accounts/doctype/account/account_tree.js
#	erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.js
#	erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js
#	erpnext/accounts/report/budget_variance_report/budget_variance_report.js
#	erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
#	erpnext/accounts/report/gross_profit/gross_profit.js
#	erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.js
#	erpnext/accounts/report/profitability_analysis/profitability_analysis.js
#	erpnext/accounts/report/trial_balance/trial_balance.js
#	erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js
#	erpnext/buying/report/procurement_tracker/procurement_tracker.js
#	erpnext/buying/report/purchase_analytics/purchase_analytics.js
#	erpnext/crm/report/campaign_efficiency/campaign_efficiency.js
#	erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.js
#	erpnext/manufacturing/report/job_card_summary/job_card_summary.js
#	erpnext/manufacturing/report/production_analytics/production_analytics.js
#	erpnext/public/js/purchase_trends_filters.js
#	erpnext/public/js/sales_trends_filters.js
#	erpnext/public/js/utils.js
#	erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js
#	erpnext/selling/report/sales_analytics/sales_analytics.js
#	erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js
#	erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js
#	erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.js
#	erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js
#	erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.js
#	erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js
#	erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js
#	erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js
2024-11-22 12:00:06 +00:00
ruthra kumar
8a45258778 Merge pull request #44286 from frappe/mergify/bp/version-14-hotfix/pr-44246
fix: Get submitted documents in validate_for_closed_fiscal_year (backport #44246)
2024-11-22 16:25:37 +05:30
rohitwaghchaure
d10a9d3225 Merge pull request #44288 from frappe/mergify/bp/version-14-hotfix/pr-44191
fix: patch (backport #44191)
2024-11-22 16:25:03 +05:30
rohitwaghchaure
3643c60c67 fix: patch (#44191)
(cherry picked from commit 495528a758)
2024-11-22 10:51:44 +00:00
ruthra kumar
92e252e37a Merge pull request #44284 from frappe/mergify/bp/version-14-hotfix/pr-44266
fix: make free qty round on large transaction qty (backport #44266)
2024-11-22 16:15:19 +05:30
vimalraj27
b9efeeb71a fix: Get submitted documents in validate_for_closed_fiscal_year
(cherry picked from commit c607e5f940)
2024-11-22 15:56:34 +05:30
venkat102
d695fb5723 test: add unit test to validate free qty round on large transaction qty
(cherry picked from commit 013a6fc6ec)
2024-11-22 10:20:54 +00:00
venkat102
2eb70026ae fix: make free qty round on large transaction qty
(cherry picked from commit f9b8165385)
2024-11-22 10:20:53 +00:00
ruthra kumar
18cbc956bb Merge pull request #44271 from frappe/mergify/bp/version-14-hotfix/pr-44194
fix: include current invoice amount when tax_on_excess_amount is checked (backport #44194)
2024-11-22 11:29:21 +05:30
venkat102
47e4fd3c76 test: add unit test for tax on excess amount
(cherry picked from commit 4820273595)
2024-11-22 05:37:26 +00:00
venkat102
071adcf291 fix: include current invoice amount when tax_on_excess_amount is checked
(cherry picked from commit b74f2896cd)
2024-11-22 05:37:26 +00:00
mergify[bot]
ee647f2381 fix: added Stock UOM field for RM in work order (backport #44185) (#44236)
* fix: added Stock UOM field for RM in work order (#44185)

fix: added UOM field for RM in work order
(cherry picked from commit cc571aca8f)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order_item/work_order_item.json
#	erpnext/manufacturing/doctype/work_order_item/work_order_item.py
#	erpnext/patches.txt

* chore: fix conflicts

* chore: fix conflicts

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-11-21 14:52:18 +05:30
ruthra kumar
24126b03dd Merge pull request #44244 from frappe/mergify/bp/version-14-hotfix/pr-44197
fix: payment reco for jv with negative dr or cr amount (backport #44197)
2024-11-20 13:12:55 +05:30
ljain112
5fa4fd8825 fix: added test cases
(cherry picked from commit 6f9ea6422d)
2024-11-20 07:18:19 +00:00
ljain112
23fb4f348f fix: payment reco for jv with negative dr or cr amount
(cherry picked from commit fee79b9445)
2024-11-20 07:18:19 +00:00
ruthra kumar
227c912ece Merge pull request #44241 from frappe/mergify/bp/version-14-hotfix/pr-44240
fix: non group pos warehouse (backport #44240)
2024-11-20 12:17:33 +05:30
ruthra kumar
5bfbdb805c Merge pull request #44238 from frappe/mergify/bp/version-14-hotfix/pr-44220
refactor: Update `Payment Request` search query in PE's reference (backport #44220)
2024-11-20 12:16:21 +05:30
Nihantra C. Patel
ec40131d4d fix: non group pos warehouse
(cherry picked from commit d526be0394)
2024-11-20 06:41:34 +00:00
ruthra kumar
859672c419 Merge pull request #44232 from frappe/mergify/bp/version-14-hotfix/pr-44207
fix: validate sales team to ensure all sales person are enabled (backport #44207)
2024-11-20 11:51:56 +05:30
Abdeali Chharchhoda
e8c3617628 refactor: Update Payment Request search query in PE's reference
(cherry picked from commit 4ab3499a17)
2024-11-20 06:21:41 +00:00
ruthra kumar
09b21b8cb4 Merge pull request #44234 from frappe/mergify/bp/version-14-hotfix/pr-44203
fix: disable conversion to user tz for sales order calender (backport #44203)
2024-11-20 11:51:21 +05:30
ljain112
356da69179 fix: disable conversion to user tz for sales order calender
(cherry picked from commit cdf098c193)
2024-11-20 05:17:52 +00:00
ljain112
bd0f11ef4f fix: validate sales team to ensure all sales person are enabled
(cherry picked from commit 548dbb33eb)
2024-11-20 05:11:35 +00:00
ruthra kumar
9f3dfb3d18 Merge pull request #44216 from frappe/mergify/bp/version-14-hotfix/pr-44104
fix: check if pricing rule matches with coupon code (backport #44104)
2024-11-19 17:37:33 +05:30
Nikolas Beckel
24b5b3c8e0 fix: check if pricing rule matches with coupon code (#44104)
* fix: check if pricing rule matches with coupon code

* fix: correct linting error

(cherry picked from commit 9d31bf7647)
2024-11-19 11:41:48 +00:00
mergify[bot]
a7de8c1143 feat: new DocTypes "Code List" and "Common Code" (backport #43425) (#44172)
Co-authored-by: David <dgx.arnold@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2024-11-18 20:59:41 +01:00
ruthra kumar
2fcab327aa Merge pull request #44181 from frappe/mergify/bp/version-14-hotfix/pr-44127
fix: set default Party Type based on Payment Type in Payment Entry (backport #44127)
2024-11-18 10:49:10 +05:30
vishakhdesai
f3cbbef346 fix: set default party type in Payment Entry
(cherry picked from commit 19222690d3)
2024-11-18 10:16:59 +05:30
ruthra kumar
339beff023 Merge pull request #44179 from frappe/mergify/bp/version-14-hotfix/pr-44147
fix: set conversion factor before applying price list (backport #44147)
2024-11-18 10:14:34 +05:30
ruthra kumar
85d398efcc Merge pull request #44177 from frappe/mergify/bp/version-14-hotfix/pr-44157
fix: apply "cannot_add_rows" directly to table field for more efficient solution (backport #44157)
2024-11-18 10:14:12 +05:30
vishakhdesai
e09f101336 fix: set conversion factor before applying price list
(cherry picked from commit 9749fe23cc)
2024-11-18 04:38:32 +00:00
UmakanthKaspa
f9b52b292e refactor: set 'cannot_add_rows' directly in the allocations table field (optimized approach)
(cherry picked from commit 5dd8eafdfc)
2024-11-18 04:36:12 +00:00
Türker Tunalı
8a94d7bea8 fix:Unable to pause job card (#42839)
fix: unable to pause job card

Pause operation creates "Value missing for: Completed Qty" error. That field was mandatory but it's non mandatory in v15.
2024-11-15 17:29:58 +05:30
mergify[bot]
f871f08f47 fix: stock ledger variance report filter options (backport #44137) (#44149)
fix: stock ledger variance report filter options (#44137)

(cherry picked from commit e8bbf6492f)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-11-15 15:05:03 +05:30
ruthra kumar
313ea3983f Merge pull request #44161 from frappe/mergify/bp/version-14-hotfix/pr-43414
fix: Get Entries not showing accounts with no gain or loss in Exchange Rate Revaluation issue fixed (backport #43414)
2024-11-15 13:09:35 +05:30
Vishakh Desai
c3e2ff2fa5 fix: linters
(cherry picked from commit 9cc22b4cac)
2024-11-15 07:18:49 +00:00
Vishakh Desai
33e835c4d3 fix: Get Entries not showing accounts with no gain or loss in Exchange Rate Revaluation issue
(cherry picked from commit 6de6f55b39)
2024-11-15 07:18:49 +00:00
ruthra kumar
c9fa4af4fe Merge pull request #44159 from frappe/mergify/bp/version-14-hotfix/pr-44158
fix: broken UI on currency exchange (backport #44158)
2024-11-15 12:28:18 +05:30
ruthra kumar
36898f6797 fix: broken UI on currency exchange
(cherry picked from commit e91b65e7bd)
2024-11-15 06:52:40 +00:00
ruthra kumar
b9a0f4fed8 Merge pull request #44154 from frappe/mergify/bp/version-14-hotfix/pr-44089
fix: apply posting date sorting to invoices in Payment Reconciliation similar to payments (backport #44089)
2024-11-15 10:54:40 +05:30
UmakanthKaspa
1019d98c5a fix: remove trailing whitespace
(cherry picked from commit d6703eb88b)
2024-11-15 05:00:03 +00:00
UmakanthKaspa
cc9b22ce9f fix: apply posting date sorting to invoices in Payment Reconciliation similar to payments
(cherry picked from commit 0bd83d920d)
2024-11-15 05:00:02 +00:00
ruthra kumar
f5135cd4a4 Merge pull request #44152 from frappe/mergify/bp/version-14-hotfix/pr-44148
Fix: Disable "Add Row" button in allocations table during UnReconcile process (backport #44148)
2024-11-15 10:22:43 +05:30
UmakanthKaspa
137ef78d96 fix: correctly set 'cannot_add_rows' property on allocations table field
(cherry picked from commit 13ca2700f8)
2024-11-15 04:47:46 +00:00
ruthra kumar
5452f8ac4a Merge pull request #44139 from frappe/mergify/bp/version-14-hotfix/pr-43189
fix: broken apply on other item (backport #43189)
2024-11-14 13:45:41 +05:30
ruthra kumar
f03e301250 fix: broken apply on other item pricing rule
(cherry picked from commit e5119a749c)
2024-11-14 07:54:10 +00:00
Raffael Meyer
c23868a14d fix: backport german translations from develop (#44046) 2024-11-13 18:17:50 +01:00
76 changed files with 3915 additions and 86 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "14.76.0"
__version__ = "14.67.1"
def get_default_company(user=None):

View File

@@ -103,6 +103,7 @@ frappe.ui.form.on("Account", {
__("View")
);
<<<<<<< HEAD
frm.add_custom_button(
__("Convert to Group"),
function () {
@@ -116,6 +117,29 @@ frappe.ui.form.on("Account", {
},
__("Actions")
);
=======
} else if (cint(frm.doc.is_group) == 0
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
frm.add_custom_button(__('General Ledger'), function () {
frappe.route_options = {
"account": frm.doc.name,
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
"company": frm.doc.company
};
frappe.set_route("query-report", "General Ledger");
}, __('View'));
frm.add_custom_button(__('Convert to Group'), function () {
return frappe.call({
doc: frm.doc,
method: 'convert_ledger_to_group',
callback: function() {
frm.refresh();
}
});
}, __('Actions'));
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
}
},

View File

@@ -278,11 +278,18 @@ frappe.treeview_settings["Account"] = {
label: __("View Ledger"),
click: function (node, btn) {
frappe.route_options = {
<<<<<<< HEAD
account: node.label,
from_date: frappe.sys_defaults.year_start_date,
to_date: frappe.sys_defaults.year_end_date,
company:
frappe.treeview_settings["Account"].treeview.page.fields_dict.company.get_value(),
=======
"account": node.label,
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
"company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
};
frappe.set_route("query-report", "General Ledger");
},

View File

@@ -52,6 +52,21 @@ class ExchangeRateRevaluation(Document):
if not (self.company and self.posting_date):
frappe.throw(_("Please select Company and Posting Date to getting entries"))
def before_submit(self):
self.remove_accounts_without_gain_loss()
def remove_accounts_without_gain_loss(self):
self.accounts = [account for account in self.accounts if account.gain_loss]
if not self.accounts:
frappe.throw(_("At least one account with exchange gain or loss is required"))
frappe.msgprint(
_("Removing rows without exchange gain or loss"),
alert=True,
indicator="yellow",
)
def on_cancel(self):
self.ignore_linked_doctypes = "GL Entry"
@@ -226,23 +241,23 @@ class ExchangeRateRevaluation(Document):
new_exchange_rate = get_exchange_rate(d.account_currency, company_currency, posting_date)
new_balance_in_base_currency = flt(d.balance_in_account_currency * new_exchange_rate)
gain_loss = flt(new_balance_in_base_currency, precision) - flt(d.balance, precision)
if gain_loss:
accounts.append(
{
"account": d.account,
"party_type": d.party_type,
"party": d.party,
"account_currency": d.account_currency,
"balance_in_base_currency": d.balance,
"balance_in_account_currency": d.balance_in_account_currency,
"zero_balance": d.zero_balance,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency,
"new_balance_in_account_currency": d.balance_in_account_currency,
"gain_loss": gain_loss,
}
)
accounts.append(
{
"account": d.account,
"party_type": d.party_type,
"party": d.party,
"account_currency": d.account_currency,
"balance_in_base_currency": d.balance,
"balance_in_account_currency": d.balance_in_account_currency,
"zero_balance": d.zero_balance,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency,
"new_balance_in_account_currency": d.balance_in_account_currency,
"gain_loss": gain_loss,
}
)
# Handle Accounts with '0' balance in Account/Base Currency
for d in [x for x in account_details if x.zero_balance]:
@@ -266,23 +281,22 @@ class ExchangeRateRevaluation(Document):
current_exchange_rate * d.balance_in_account_currency
)
if gain_loss:
accounts.append(
{
"account": d.account,
"party_type": d.party_type,
"party": d.party,
"account_currency": d.account_currency,
"balance_in_base_currency": d.balance,
"balance_in_account_currency": d.balance_in_account_currency,
"zero_balance": d.zero_balance,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency,
"new_balance_in_account_currency": new_balance_in_account_currency,
"gain_loss": gain_loss,
}
)
accounts.append(
{
"account": d.account,
"party_type": d.party_type,
"party": d.party,
"account_currency": d.account_currency,
"balance_in_base_currency": d.balance,
"balance_in_account_currency": d.balance_in_account_currency,
"zero_balance": d.zero_balance,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency,
"new_balance_in_account_currency": new_balance_in_account_currency,
"gain_loss": gain_loss,
}
)
return accounts

View File

@@ -15,6 +15,10 @@ frappe.ui.form.on('Payment Entry', {
}
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
if (frm.is_new()) {
set_default_party_type(frm);
}
},
setup: function(frm) {
@@ -320,6 +324,7 @@ frappe.ui.form.on('Payment Entry', {
},
payment_type: function(frm) {
set_default_party_type(frm);
if(frm.doc.payment_type == "Internal Transfer") {
$.each(["party", "party_balance", "paid_from", "paid_to",
"references", "total_allocated_amount"], function(i, field) {
@@ -1511,3 +1516,16 @@ frappe.ui.form.on('Payment Entry Deduction', {
frm.events.set_unallocated_amount(frm);
},
});
function set_default_party_type(frm) {
if (frm.doc.party) return;
let party_type;
if (frm.doc.payment_type == "Receive") {
party_type = "Customer";
} else if (frm.doc.payment_type == "Pay") {
party_type = "Supplier";
}
if (party_type) frm.set_value("party_type", party_type);
}

View File

@@ -144,12 +144,14 @@ class PaymentReconciliation(Document):
if self.get("cost_center"):
conditions.append(jea.cost_center == self.cost_center)
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency"
)
conditions.append(jea[dr_or_cr].gt(0))
account_type = erpnext.get_party_account_type(self.party_type)
if account_type == "Receivable":
dr_or_cr = jea.credit_in_account_currency - jea.debit_in_account_currency
elif account_type == "Payable":
dr_or_cr = jea.debit_in_account_currency - jea.credit_in_account_currency
conditions.append(dr_or_cr.gt(0))
if self.bank_cash_account:
conditions.append(jea.against_account.like(f"%%{self.bank_cash_account}%%"))
@@ -164,7 +166,7 @@ class PaymentReconciliation(Document):
je.posting_date,
je.remark.as_("remarks"),
jea.name.as_("reference_row"),
jea[dr_or_cr].as_("amount"),
dr_or_cr.as_("amount"),
jea.is_advance,
jea.exchange_rate,
jea.account_currency.as_("currency"),
@@ -299,6 +301,10 @@ class PaymentReconciliation(Document):
if self.invoice_limit:
non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]
non_reconciled_invoices = sorted(
non_reconciled_invoices, key=lambda k: k["posting_date"] or getdate(nowdate())
)
self.add_invoice_entries(non_reconciled_invoices)
def add_invoice_entries(self, non_reconciled_invoices):

View File

@@ -615,6 +615,42 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_negative_debit_or_credit_journal_against_invoice(self):
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
# credit debtors account to record a payment
je = self.create_journal_entry(self.bank, self.debit_to, amount, transaction_date)
je.accounts[1].party_type = "Customer"
je.accounts[1].party = self.customer
je.accounts[1].credit_in_account_currency = 0
je.accounts[1].debit_in_account_currency = -1 * amount
je.save()
je.submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Difference amount should not be calculated for base currency accounts
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
# assert outstanding
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(si.outstanding_amount, 0)
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_journal_against_journal(self):
transaction_date = nowdate()
sales = "Sales - _PR"
@@ -937,6 +973,100 @@ class TestPaymentReconciliation(FrappeTestCase):
frappe.db.get_value("Journal Entry", jea_parent.parent, "voucher_type"), "Exchange Gain Or Loss"
)
def test_difference_amount_via_negative_debit_or_credit_journal_entry(self):
# Make Sale Invoice
si = self.create_sales_invoice(
qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
)
si.customer = self.customer4
si.currency = "EUR"
si.conversion_rate = 85
si.debit_to = self.debtors_eur
si.save().submit()
# Make payment using Journal Entry
je1 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 100, nowdate())
je1.multi_currency = 1
je1.accounts[0].exchange_rate = 1
je1.accounts[0].credit_in_account_currency = -8000
je1.accounts[0].credit = -8000
je1.accounts[0].debit_in_account_currency = 0
je1.accounts[0].debit = 0
je1.accounts[1].party_type = "Customer"
je1.accounts[1].party = self.customer4
je1.accounts[1].exchange_rate = 80
je1.accounts[1].credit_in_account_currency = 100
je1.accounts[1].credit = 8000
je1.accounts[1].debit_in_account_currency = 0
je1.accounts[1].debit = 0
je1.save()
je1.submit()
je2 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 200, nowdate())
je2.multi_currency = 1
je2.accounts[0].exchange_rate = 1
je2.accounts[0].credit_in_account_currency = -16000
je2.accounts[0].credit = -16000
je2.accounts[0].debit_in_account_currency = 0
je2.accounts[0].debit = 0
je2.accounts[1].party_type = "Customer"
je2.accounts[1].party = self.customer4
je2.accounts[1].exchange_rate = 80
je2.accounts[1].credit_in_account_currency = 200
je1.accounts[1].credit = 16000
je1.accounts[1].debit_in_account_currency = 0
je1.accounts[1].debit = 0
je2.save()
je2.submit()
pr = self.create_payment_reconciliation()
pr.party = self.customer4
pr.receivable_payable_account = self.debtors_eur
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 2)
# Test exact payment allocation
invoices = [x.as_dict() for x in pr.invoices]
payments = [pr.payments[0].as_dict()]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
self.assertEqual(pr.allocation[0].allocated_amount, 100)
self.assertEqual(pr.allocation[0].difference_amount, -500)
# Test partial payment allocation (with excess payment entry)
pr.set("allocation", [])
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.invoices]
payments = [pr.payments[1].as_dict()]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].difference_account = "Exchange Gain/Loss - _PR"
self.assertEqual(pr.allocation[0].allocated_amount, 100)
self.assertEqual(pr.allocation[0].difference_amount, -500)
# Check if difference journal entry gets generated for difference amount after reconciliation
pr.reconcile()
total_credit_amount = frappe.db.get_all(
"Journal Entry Account",
{"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name},
"sum(credit) as amount",
group_by="reference_name",
)[0].amount
# total credit includes the exchange gain/loss amount
self.assertEqual(flt(total_credit_amount, 2), 8500)
jea_parent = frappe.db.get_all(
"Journal Entry Account",
filters={"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name, "credit": 500},
fields=["parent"],
)[0]
self.assertEqual(
frappe.db.get_value("Journal Entry", jea_parent.parent, "voucher_type"), "Exchange Gain Or Loss"
)
def test_difference_amount_via_payment_entry(self):
# Make Sale Invoice
si = self.create_sales_invoice(

View File

@@ -838,17 +838,18 @@ def validate_payment(doc, method=None):
@frappe.whitelist()
def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters):
# permission checks in `get_list()`
reference_doctype = filters.get("reference_doctype")
reference_name = filters.get("reference_doctype")
filters = frappe._dict(filters)
if not reference_doctype or not reference_name:
if not filters.reference_doctype or not filters.reference_name:
return []
if txt:
filters.name = ["like", f"%{txt}%"]
open_payment_requests = frappe.get_list(
"Payment Request",
filters={
"reference_doctype": filters["reference_doctype"],
"reference_name": filters["reference_name"],
**filters,
"status": ["!=", "Paid"],
"outstanding_amount": ["!=", 0], # for compatibility with old data
"docstatus": 1,

View File

@@ -359,7 +359,20 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
if isinstance(pricing_rule, str):
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
update_pricing_rule_uom(pricing_rule, args)
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or []
fetch_other_item = True if pricing_rule.apply_rule_on_other else False
pricing_rule.apply_rule_on_other_items = (
get_pricing_rule_items(pricing_rule, other_items=fetch_other_item) or []
)
if pricing_rule.coupon_code_based == 1:
if not args.coupon_code:
return item_details
coupon_code = frappe.db.get_value(
doctype="Coupon Code", filters={"pricing_rule": pricing_rule.name}, fieldname="name"
)
if args.coupon_code != coupon_code:
continue
if pricing_rule.get("suggestion"):
continue
@@ -386,9 +399,6 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
pricing_rule.apply_rule_on_other_items
)
if pricing_rule.coupon_code_based == 1 and args.coupon_code is None:
return item_details
if not pricing_rule.validate_applied_rule:
if pricing_rule.price_or_product_discount == "Price":
apply_price_discount_rule(pricing_rule, item_details, args)

View File

@@ -987,6 +987,45 @@ class TestPricingRule(FrappeTestCase):
so.save()
self.assertEqual(len(so.items), 1)
def test_pricing_rule_for_product_free_item_round_free_qty(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"rate": 0,
"min_qty": 100,
"max_qty": 0,
"price_or_product_discount": "Product",
"same_item": 1,
"free_qty": 10,
"round_free_qty": 1,
"is_recursive": 1,
"recurse_for": 100,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
# With pricing rule
so = make_sales_order(item_code="_Test Item", qty=100)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 10)
so = make_sales_order(item_code="_Test Item", qty=150)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 10)
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")

View File

@@ -642,7 +642,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = math.floor(qty)
qty = (flt(transaction_qty) // pricing_rule.recurse_for) * (pricing_rule.free_qty or 1)
if not qty:
return

View File

@@ -27,7 +27,7 @@ class RepostAccountingLedger(Document):
latest_pcv = (
frappe.db.get_all(
"Period Closing Voucher",
filters={"company": self.company},
filters={"company": self.company, "docstatus": 1},
order_by="posting_date desc",
pluck="posting_date",
limit=1,

View File

@@ -536,7 +536,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
tax_details.tax_on_excess_amount
):
supp_credit_amt = net_total - cumulative_threshold
supp_credit_amt = net_total + tax_withholding_net_total - cumulative_threshold
if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0):
tds_amount = get_lower_deduction_amount(

View File

@@ -161,6 +161,45 @@ class TestTaxWithholdingCategory(FrappeTestCase):
for d in reversed(invoices):
d.cancel()
def test_cumulative_threshold_with_tax_on_excess_amount(self):
invoices = []
frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
# Invoice with tax and without exceeding single and cumulative thresholds
for _ in range(2):
pi = create_purchase_invoice(supplier="Test TDS Supplier3", rate=10000, do_not_save=True)
pi.apply_tds = 1
pi.append(
"taxes",
{
"category": "Total",
"charge_type": "Actual",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"tax_amount": 500,
"description": "Test",
"add_deduct_tax": "Add",
},
)
pi.save()
pi.submit()
invoices.append(pi)
# Third Invoice exceeds single threshold and not exceeding cumulative threshold
pi1 = create_purchase_invoice(supplier="Test TDS Supplier3", rate=20000)
pi1.apply_tds = 1
pi1.save()
pi1.submit()
invoices.append(pi1)
# Cumulative threshold is 10,000
# Threshold calculation should be only on the third invoice
self.assertTrue(len(pi1.taxes) > 0)
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
for d in reversed(invoices):
d.cancel()
def test_cumulative_threshold_tcs(self):
frappe.db.set_value(
"Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"

View File

@@ -12,6 +12,7 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
reqd: 1,
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -24,6 +25,20 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
=======
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
"reqd": 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "asset_category",

View File

@@ -4,11 +4,19 @@
frappe.query_reports["Bank Clearance Summary"] = {
filters: [
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
width: "80",
=======
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"width": "80"
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "to_date",

View File

@@ -44,7 +44,11 @@ function get_filters() {
fieldtype: "Link",
options: "Fiscal Year",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
<<<<<<< HEAD
reqd: 1,
=======
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "to_fiscal_year",
@@ -52,7 +56,11 @@ function get_filters() {
fieldtype: "Link",
options: "Fiscal Year",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
<<<<<<< HEAD
reqd: 1,
=======
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "period",

View File

@@ -35,6 +35,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
},
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -47,6 +48,20 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
=======
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"reqd": 1
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
"reqd": 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "finance_book",

View File

@@ -12,6 +12,7 @@ frappe.query_reports["Gross Profit"] = {
reqd: 1,
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -24,6 +25,20 @@ frappe.query_reports["Gross Profit"] = {
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
=======
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"reqd": 1
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
"reqd": 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "sales_invoice",

View File

@@ -15,7 +15,15 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "to_date",

View File

@@ -56,6 +56,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
},
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -66,6 +67,22 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
label: __("To Date"),
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
=======
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
<<<<<<< HEAD
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "show_zero_values",

View File

@@ -34,6 +34,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
},
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -44,6 +45,22 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
label: __("To Date"),
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
=======
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
<<<<<<< HEAD
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "cost_center",

View File

@@ -33,6 +33,7 @@ frappe.query_reports["Trial Balance for Party"] = {
},
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -43,6 +44,22 @@ frappe.query_reports["Trial Balance for Party"] = {
label: __("To Date"),
fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
=======
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
<<<<<<< HEAD
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "party_type",

View File

@@ -579,6 +579,16 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"):
frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
rev_dr_or_cr = (
"debit_in_account_currency"
if d["dr_or_cr"] == "credit_in_account_currency"
else "credit_in_account_currency"
)
if jv_detail.get(rev_dr_or_cr):
d["dr_or_cr"] = rev_dr_or_cr
d["allocated_amount"] = d["allocated_amount"] * -1
d["unadjusted_amount"] = d["unadjusted_amount"] * -1
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
# adjust the unreconciled balance
amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"])

View File

@@ -27,13 +27,29 @@ frappe.query_reports["Procurement Tracker"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
],
};

View File

@@ -35,15 +35,33 @@ frappe.query_reports["Purchase Analytics"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "company",

View File

@@ -167,6 +167,9 @@ class SellingController(StockController):
total = 0.0
sales_team = self.get("sales_team")
self.validate_sales_team(sales_team)
for sales_person in sales_team:
self.round_floats_in(sales_person)
@@ -186,6 +189,20 @@ class SellingController(StockController):
if sales_team and total != 100.0:
throw(_("Total allocated percentage for sales team should be 100"))
def validate_sales_team(self, sales_team):
sales_persons = [d.sales_person for d in sales_team]
if not sales_persons:
return
sales_person_status = frappe.db.get_all(
"Sales Person", filters={"name": ["in", sales_persons]}, fields=["name", "enabled"]
)
for row in sales_person_status:
if not row.enabled:
frappe.throw(_("Sales Person <b>{0}</b> is disabled.").format(row.name))
def validate_max_discount(self):
for d in self.get("items"):
if d.item_code:

View File

@@ -3,6 +3,7 @@
frappe.query_reports["Campaign Efficiency"] = {
filters: [
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -15,4 +16,18 @@ frappe.query_reports["Campaign Efficiency"] = {
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
],
=======
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
}
]
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
};

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
<<<<<<< HEAD
frappe.query_reports["Lead Owner Efficiency"] = {
filters: [
{
@@ -16,3 +17,20 @@ frappe.query_reports["Lead Owner Efficiency"] = {
},
],
};
=======
frappe.query_reports["Lead Owner Efficiency"] = {
"filters": [
{
"fieldname": "from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
}
]};
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)

0
erpnext/edi/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Code List", {
refresh: (frm) => {
if (!frm.doc.__islocal) {
frm.add_custom_button(__("Import Genericode File"), function () {
erpnext.edi.import_genericode(frm);
});
}
},
setup: (frm) => {
frm.savetrash = () => {
frm.validate_form_action("Delete");
frappe.confirm(
__(
"Are you sure you want to delete {0}?<p>This action will also delete all associated Common Code documents.</p>",
[frm.docname.bold()]
),
function () {
return frappe.call({
method: "frappe.client.delete",
args: {
doctype: frm.doctype,
name: frm.docname,
},
freeze: true,
freeze_message: __("Deleting {0} and all associated Common Code documents...", [
frm.docname,
]),
callback: function (r) {
if (!r.exc) {
frappe.utils.play_sound("delete");
frappe.model.clear_doc(frm.doctype, frm.docname);
window.history.back();
}
},
});
}
);
};
frm.set_query("default_common_code", function (doc) {
return {
filters: {
code_list: doc.name,
},
};
});
},
});

View File

@@ -0,0 +1,112 @@
{
"actions": [],
"allow_copy": 1,
"allow_rename": 1,
"autoname": "prompt",
"creation": "2024-09-29 06:55:03.920375",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"title",
"canonical_uri",
"url",
"default_common_code",
"column_break_nkls",
"version",
"publisher",
"publisher_id",
"section_break_npxp",
"description"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Title"
},
{
"fieldname": "publisher",
"fieldtype": "Data",
"in_standard_filter": 1,
"label": "Publisher"
},
{
"columns": 1,
"fieldname": "version",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Version"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"fieldname": "canonical_uri",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Canonical URI"
},
{
"fieldname": "column_break_nkls",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_npxp",
"fieldtype": "Section Break"
},
{
"fieldname": "publisher_id",
"fieldtype": "Data",
"in_standard_filter": 1,
"label": "Publisher ID"
},
{
"fieldname": "url",
"fieldtype": "Data",
"label": "URL",
"options": "URL"
},
{
"description": "This value shall be used when no matching Common Code for a record is found.",
"fieldname": "default_common_code",
"fieldtype": "Link",
"label": "Default Common Code",
"options": "Common Code"
}
],
"index_web_pages_for_search": 1,
"links": [
{
"link_doctype": "Common Code",
"link_fieldname": "code_list"
}
],
"modified": "2024-11-16 17:01:40.260293",
"modified_by": "Administrator",
"module": "EDI",
"name": "Code List",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "description",
"show_title_field_in_link": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title"
}

View File

@@ -0,0 +1,125 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from typing import TYPE_CHECKING
import frappe
from frappe.model.document import Document
if TYPE_CHECKING:
from lxml.etree import Element
class CodeList(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
canonical_uri: DF.Data | None
default_common_code: DF.Link | None
description: DF.SmallText | None
publisher: DF.Data | None
publisher_id: DF.Data | None
title: DF.Data | None
url: DF.Data | None
version: DF.Data | None
# end: auto-generated types
def on_trash(self):
if not frappe.flags.in_bulk_delete:
self.__delete_linked_docs()
def __delete_linked_docs(self):
self.db_set("default_common_code", None)
linked_docs = frappe.get_all(
"Common Code",
filters={"code_list": self.name},
fields=["name"],
)
for doc in linked_docs:
frappe.delete_doc("Common Code", doc.name)
def get_codes_for(self, doctype: str, name: str) -> tuple[str]:
"""Get the applicable codes for a doctype and name"""
return get_codes_for(self.name, doctype, name)
def get_docnames_for(self, doctype: str, code: str) -> tuple[str]:
"""Get the mapped docnames for a doctype and code"""
return get_docnames_for(self.name, doctype, code)
def get_default_code(self) -> str | None:
"""Get the default common code for this code list"""
return (
frappe.db.get_value("Common Code", self.default_common_code, "common_code")
if self.default_common_code
else None
)
def from_genericode(self, root: "Element"):
"""Extract Code List details from genericode XML"""
self.title = root.find(".//Identification/ShortName").text
self.version = root.find(".//Identification/Version").text
self.canonical_uri = root.find(".//CanonicalUri").text
# optionals
self.description = getattr(root.find(".//Identification/LongName"), "text", None)
self.publisher = getattr(root.find(".//Identification/Agency/ShortName"), "text", None)
if not self.publisher:
self.publisher = getattr(root.find(".//Identification/Agency/LongName"), "text", None)
self.publisher_id = getattr(root.find(".//Identification/Agency/Identifier"), "text", None)
self.url = getattr(root.find(".//Identification/LocationUri"), "text", None)
def get_codes_for(code_list: str, doctype: str, name: str) -> tuple[str]:
"""Return the common code for a given record"""
CommonCode = frappe.qb.DocType("Common Code")
DynamicLink = frappe.qb.DocType("Dynamic Link")
codes = (
frappe.qb.from_(CommonCode)
.join(DynamicLink)
.on((CommonCode.name == DynamicLink.parent) & (DynamicLink.parenttype == "Common Code"))
.select(CommonCode.common_code)
.where(
(DynamicLink.link_doctype == doctype)
& (DynamicLink.link_name == name)
& (CommonCode.code_list == code_list)
)
.distinct()
.orderby(CommonCode.common_code)
).run()
return tuple(c[0] for c in codes) if codes else ()
def get_docnames_for(code_list: str, doctype: str, code: str) -> tuple[str]:
"""Return the record name for a given common code"""
CommonCode = frappe.qb.DocType("Common Code")
DynamicLink = frappe.qb.DocType("Dynamic Link")
docnames = (
frappe.qb.from_(CommonCode)
.join(DynamicLink)
.on((CommonCode.name == DynamicLink.parent) & (DynamicLink.parenttype == "Common Code"))
.select(DynamicLink.link_name)
.where(
(DynamicLink.link_doctype == doctype)
& (CommonCode.common_code == code)
& (CommonCode.code_list == code_list)
)
.distinct()
.orderby(DynamicLink.idx)
).run()
return tuple(d[0] for d in docnames) if docnames else ()
def get_default_code(code_list: str) -> str | None:
"""Return the default common code for a given code list"""
code_id = frappe.db.get_value("Code List", code_list, "default_common_code")
return frappe.db.get_value("Common Code", code_id, "common_code") if code_id else None

View File

@@ -0,0 +1,218 @@
frappe.provide("erpnext.edi");
erpnext.edi.import_genericode = function (listview_or_form) {
let doctype = "Code List";
let docname = undefined;
if (listview_or_form.doc !== undefined) {
docname = listview_or_form.doc.name;
}
new frappe.ui.FileUploader({
method: "erpnext.edi.doctype.code_list.code_list_import.import_genericode",
doctype: doctype,
docname: docname,
allow_toggle_private: false,
allow_take_photo: false,
on_success: function (_file_doc, r) {
listview_or_form.refresh();
show_column_selection_dialog(r.message);
},
});
};
function show_column_selection_dialog(context) {
let title_description = __("If there is no title column, use the code column for the title.");
let default_title = get_default(context.columns, ["name", "Name", "code-name", "scheme-name"]);
let fields = [
{
fieldtype: "HTML",
fieldname: "code_list_info",
options: `<div class="text-muted">${__(
"You are importing data for the code list:"
)} ${frappe.utils.get_form_link(
"Code List",
context.code_list,
true,
context.code_list_title
)}</div>`,
},
{
fieldtype: "Section Break",
},
{
fieldname: "import_column",
label: __("Import"),
fieldtype: "Column Break",
},
{
fieldname: "title_column",
label: __("as Title"),
fieldtype: "Select",
reqd: 1,
options: context.columns,
default: default_title,
description: default_title ? null : title_description,
},
{
fieldname: "code_column",
label: __("as Code"),
fieldtype: "Select",
options: context.columns,
reqd: 1,
default: get_default(context.columns, ["code", "Code", "value"]),
},
{
fieldname: "filters_column",
label: __("Filter"),
fieldtype: "Column Break",
},
];
if (context.columns.length > 2) {
fields.splice(5, 0, {
fieldname: "description_column",
label: __("as Description"),
fieldtype: "Select",
options: [null].concat(context.columns),
default: get_default(context.columns, [
"description",
"Description",
"remark",
__("description"),
__("Description"),
]),
});
}
// Add filterable columns
for (let column in context.filterable_columns) {
fields.push({
fieldname: `filter_${column}`,
label: __("by {}", [column]),
fieldtype: "Select",
options: [null].concat(context.filterable_columns[column]),
});
}
fields.push(
{
fieldname: "preview_section",
label: __("Preview"),
fieldtype: "Section Break",
},
{
fieldname: "preview_html",
fieldtype: "HTML",
}
);
let d = new frappe.ui.Dialog({
title: __("Select Columns and Filters"),
fields: fields,
primary_action_label: __("Import"),
size: "large", // This will make the modal wider
primary_action(values) {
let filters = {};
for (let field in values) {
if (field.startsWith("filter_") && values[field]) {
filters[field.replace("filter_", "")] = values[field];
}
}
frappe
.xcall("erpnext.edi.doctype.code_list.code_list_import.process_genericode_import", {
code_list_name: context.code_list,
file_name: context.file,
code_column: values.code_column,
title_column: values.title_column,
description_column: values.description_column,
filters: filters,
})
.then((count) => {
frappe.msgprint(__("Import completed. {0} common codes created.", [count]));
});
d.hide();
},
});
d.fields_dict.code_column.df.onchange = () => update_preview(d, context);
d.fields_dict.title_column.df.onchange = (e) => {
let field = d.fields_dict.title_column;
if (!e.target.value) {
field.df.description = title_description;
field.refresh();
} else {
field.df.description = null;
field.refresh();
}
update_preview(d, context);
};
// Add onchange events for filterable columns
for (let column in context.filterable_columns) {
d.fields_dict[`filter_${column}`].df.onchange = () => update_preview(d, context);
}
d.show();
update_preview(d, context);
}
/**
* Return the first key from the keys array that is found in the columns array.
*/
function get_default(columns, keys) {
return keys.find((key) => columns.includes(key));
}
function update_preview(dialog, context) {
let code_column = dialog.get_value("code_column");
let title_column = dialog.get_value("title_column");
let description_column = dialog.get_value("description_column");
let html = '<table class="table table-bordered"><thead><tr>';
if (title_column) html += `<th>${__("Title")}</th>`;
if (code_column) html += `<th>${__("Code")}</th>`;
if (description_column) html += `<th>${__("Description")}</th>`;
// Add headers for filterable columns
for (let column in context.filterable_columns) {
if (dialog.get_value(`filter_${column}`)) {
html += `<th>${__(column)}</th>`;
}
}
html += "</tr></thead><tbody>";
for (let i = 0; i < 3; i++) {
html += "<tr>";
if (title_column) {
let title = context.example_values[title_column][i] || "";
html += `<td title="${title}">${truncate(title)}</td>`;
}
if (code_column) {
let code = context.example_values[code_column][i] || "";
html += `<td title="${code}">${truncate(code)}</td>`;
}
if (description_column) {
let description = context.example_values[description_column][i] || "";
html += `<td title="${description}">${truncate(description)}</td>`;
}
// Add values for filterable columns
for (let column in context.filterable_columns) {
if (dialog.get_value(`filter_${column}`)) {
let value = context.example_values[column][i] || "";
html += `<td title="${value}">${truncate(value)}</td>`;
}
}
html += "</tr>";
}
html += "</tbody></table>";
dialog.fields_dict.preview_html.$wrapper.html(html);
}
function truncate(value, maxLength = 40) {
if (typeof value !== "string") return "";
return value.length > maxLength ? value.substring(0, maxLength - 3) + "..." : value;
}

View File

@@ -0,0 +1,140 @@
import json
import frappe
import requests
from frappe import _
from lxml import etree
URL_PREFIXES = ("http://", "https://")
@frappe.whitelist()
def import_genericode():
doctype = "Code List"
docname = frappe.form_dict.docname
content = frappe.local.uploaded_file
# recover the content, if it's a link
if (file_url := frappe.local.uploaded_file_url) and file_url.startswith(URL_PREFIXES):
try:
# If it's a URL, fetch the content and make it a local file (for durable audit)
response = requests.get(frappe.local.uploaded_file_url)
response.raise_for_status()
frappe.local.uploaded_file = content = response.content
frappe.local.uploaded_filename = frappe.local.uploaded_file_url.split("/")[-1]
frappe.local.uploaded_file_url = None
except Exception as e:
frappe.throw(f"<pre>{e!s}</pre>", title=_("Fetching Error"))
if file_url := frappe.local.uploaded_file_url:
file_path = frappe.utils.file_manager.get_file_path(file_url)
with open(file_path.encode(), mode="rb") as f:
content = f.read()
# Parse the xml content
parser = etree.XMLParser(remove_blank_text=True)
try:
root = etree.fromstring(content, parser=parser)
except Exception as e:
frappe.throw(f"<pre>{e!s}</pre>", title=_("Parsing Error"))
# Extract the name (CanonicalVersionUri) from the parsed XML
name = root.find(".//CanonicalVersionUri").text
docname = docname or name
if frappe.db.exists(doctype, docname):
code_list = frappe.get_doc(doctype, docname)
if code_list.name != name:
frappe.throw(_("The uploaded file does not match the selected Code List."))
else:
# Create a new Code List document with the extracted name
code_list = frappe.new_doc(doctype)
code_list.name = name
code_list.from_genericode(root)
code_list.save()
# Attach the file and provide a recoverable identifier
file_doc = frappe.get_doc(
{
"doctype": "File",
"attached_to_doctype": "Code List",
"attached_to_name": code_list.name,
"folder": "Home/Attachments",
"file_name": frappe.local.uploaded_filename,
"file_url": frappe.local.uploaded_file_url,
"is_private": 1,
"content": content,
}
).save()
# Get available columns and example values
columns, example_values, filterable_columns = get_genericode_columns_and_examples(root)
return {
"code_list": code_list.name,
"code_list_title": code_list.title,
"file": file_doc.name,
"columns": columns,
"example_values": example_values,
"filterable_columns": filterable_columns,
}
@frappe.whitelist()
def process_genericode_import(
code_list_name: str,
file_name: str,
code_column: str,
title_column: str | None = None,
description_column: str | None = None,
filters: str | None = None,
):
from erpnext.edi.doctype.common_code.common_code import import_genericode
column_map = {"code": code_column, "title": title_column, "description": description_column}
return import_genericode(code_list_name, file_name, column_map, json.loads(filters) if filters else None)
def get_genericode_columns_and_examples(root):
columns = []
example_values = {}
filterable_columns = {}
# Get column names
for column in root.findall(".//Column"):
column_id = column.get("Id")
columns.append(column_id)
example_values[column_id] = []
filterable_columns[column_id] = set()
# Get all values and count unique occurrences
for row in root.findall(".//SimpleCodeList/Row"):
for value in row.findall("Value"):
column_id = value.get("ColumnRef")
if column_id not in columns:
# Handle undeclared column
columns.append(column_id)
example_values[column_id] = []
filterable_columns[column_id] = set()
simple_value = value.find("./SimpleValue")
if simple_value is None:
continue
filterable_columns[column_id].add(simple_value.text)
# Get example values (up to 3) and filter columns with cardinality <= 5
for row in root.findall(".//SimpleCodeList/Row")[:3]:
for value in row.findall("Value"):
column_id = value.get("ColumnRef")
simple_value = value.find("./SimpleValue")
if simple_value is None:
continue
example_values[column_id].append(simple_value.text)
filterable_columns = {k: list(v) for k, v in filterable_columns.items() if len(v) <= 5}
return columns, example_values, filterable_columns

View File

@@ -0,0 +1,8 @@
frappe.listview_settings["Code List"] = {
onload: function (listview) {
listview.page.add_inner_button(__("Import Genericode File"), function () {
erpnext.edi.import_genericode(listview);
});
},
hide_name_column: true,
};

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestCodeList(FrappeTestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Common Code", {
// refresh(frm) {
// },
// });

View File

@@ -0,0 +1,103 @@
{
"actions": [],
"autoname": "hash",
"creation": "2024-09-29 07:01:18.133067",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"code_list",
"title",
"common_code",
"description",
"column_break_wxsw",
"additional_data",
"section_break_rhgh",
"applies_to"
],
"fields": [
{
"fieldname": "code_list",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Code List",
"options": "Code List",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Title",
"length": 300,
"reqd": 1
},
{
"fieldname": "column_break_wxsw",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_rhgh",
"fieldtype": "Section Break"
},
{
"fieldname": "applies_to",
"fieldtype": "Table",
"label": "Applies To",
"options": "Dynamic Link"
},
{
"fieldname": "common_code",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Common Code",
"length": 300,
"reqd": 1,
"search_index": 1
},
{
"fieldname": "additional_data",
"fieldtype": "Code",
"label": "Additional Data",
"max_height": "190px",
"read_only": 1
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Description",
"max_height": "60px"
}
],
"links": [],
"modified": "2024-11-06 07:46:17.175687",
"modified_by": "Administrator",
"module": "EDI",
"name": "Common Code",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "common_code,description",
"show_title_field_in_link": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title"
}

View File

@@ -0,0 +1,114 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import hashlib
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils.data import get_link_to_form
from lxml import etree
class CommonCode(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.core.doctype.dynamic_link.dynamic_link import DynamicLink
from frappe.types import DF
additional_data: DF.Code | None
applies_to: DF.Table[DynamicLink]
code_list: DF.Link
common_code: DF.Data
description: DF.SmallText | None
title: DF.Data
# end: auto-generated types
def validate(self):
self.validate_distinct_references()
def validate_distinct_references(self):
"""Ensure no two Common Codes of the same Code List are linked to the same document."""
for link in self.applies_to:
existing_links = frappe.get_all(
"Common Code",
filters=[
["name", "!=", self.name],
["code_list", "=", self.code_list],
["Dynamic Link", "link_doctype", "=", link.link_doctype],
["Dynamic Link", "link_name", "=", link.link_name],
],
fields=["name", "common_code"],
)
if existing_links:
existing_link = existing_links[0]
frappe.throw(
_("{0} {1} is already linked to Common Code {2}.").format(
link.link_doctype,
link.link_name,
get_link_to_form("Common Code", existing_link["name"], existing_link["common_code"]),
)
)
def from_genericode(self, column_map: dict, xml_element: "etree.Element"):
"""Populate the Common Code document from a genericode XML element
Args:
column_map (dict): A mapping of column names to XML column references. Keys: code, title, description
code (etree.Element): The XML element representing a code in the genericode file
"""
title_column = column_map.get("title")
code_column = column_map["code"]
description_column = column_map.get("description")
self.common_code = xml_element.find(f"./Value[@ColumnRef='{code_column}']/SimpleValue").text
if title_column:
simple_value_title = xml_element.find(f"./Value[@ColumnRef='{title_column}']/SimpleValue")
self.title = simple_value_title.text if simple_value_title is not None else self.common_code
if description_column:
simple_value_descr = xml_element.find(f"./Value[@ColumnRef='{description_column}']/SimpleValue")
self.description = simple_value_descr.text if simple_value_descr is not None else None
self.additional_data = etree.tostring(xml_element, encoding="unicode", pretty_print=True)
def simple_hash(input_string, length=6):
return hashlib.blake2b(input_string.encode(), digest_size=length // 2).hexdigest()
def import_genericode(code_list: str, file_name: str, column_map: dict, filters: dict | None = None):
"""Import genericode file and create Common Code entries"""
file_path = frappe.utils.file_manager.get_file_path(file_name)
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse(file_path, parser=parser)
root = tree.getroot()
# Construct the XPath expression
xpath_expr = ".//SimpleCodeList/Row"
filter_conditions = [
f"Value[@ColumnRef='{column_ref}']/SimpleValue='{value}'" for column_ref, value in filters.items()
]
if filter_conditions:
xpath_expr += "[" + " and ".join(filter_conditions) + "]"
elements = root.xpath(xpath_expr)
total_elements = len(elements)
for i, xml_element in enumerate(elements, start=1):
common_code: "CommonCode" = frappe.new_doc("Common Code")
common_code.code_list = code_list
common_code.from_genericode(column_map, xml_element)
common_code.save()
frappe.publish_progress(i / total_elements * 100, title=_("Importing Common Codes"))
return total_elements
def on_doctype_update():
frappe.db.add_index("Common Code", ["code_list", "common_code"])

View File

@@ -0,0 +1,8 @@
frappe.listview_settings["Common Code"] = {
onload: function (listview) {
listview.page.add_inner_button(__("Import Genericode File"), function () {
erpnext.edi.import_genericode(listview);
});
},
hide_name_column: true,
};

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestCommonCode(FrappeTestCase):
pass

View File

@@ -25,6 +25,14 @@ doctype_js = {
"Newsletter": "public/js/newsletter.js",
"Contact": "public/js/contact.js",
}
doctype_list_js = {
"Code List": [
"edi/doctype/code_list/code_list_import.js",
],
"Common Code": [
"edi/doctype/code_list/code_list_import.js",
],
}
override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"}

View File

@@ -43,7 +43,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "Completed Qty",
"reqd": 1
"reqd": 0
},
{
"fieldname": "employee",
@@ -74,4 +74,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}
}

View File

@@ -15,6 +15,7 @@
"include_item_in_manufacturing",
"qty_section",
"required_qty",
"stock_uom",
"rate",
"amount",
"column_break_11",
@@ -138,11 +139,19 @@
"in_list_view": 1,
"label": "Returned Qty ",
"read_only": 1
},
{
"fetch_from": "item_code.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2024-02-11 15:45:32.318374",
"modified": "2024-11-19 15:48:16.823384",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Item",
@@ -153,4 +162,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -37,14 +37,31 @@ frappe.query_reports["Job Card Summary"] = {
label: __("From Posting Date"),
fieldname: "from_date",
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
label: __("To Posting Date"),
fieldname: "to_date",
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1,
},
{

View File

@@ -16,15 +16,33 @@ frappe.query_reports["Production Analytics"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "range",

View File

@@ -19,4 +19,5 @@ Loan Management
Telephony
Bulk Transaction
E-commerce
Subcontracting
Subcontracting
EDI

View File

@@ -367,3 +367,4 @@ erpnext.patches.v14_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1
erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter
erpnext.patches.v14_0.update_stock_uom_in_work_order_item

View File

@@ -38,7 +38,7 @@ def execute():
data = frappe.db.sql(
"""
SELECT
name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time, company
name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time, company, creation
FROM
`tabStock Ledger Entry`
WHERE
@@ -67,6 +67,7 @@ def execute():
"voucher_type": d.voucher_type,
"voucher_no": d.voucher_no,
"sle_id": d.name,
"creation": d.creation,
},
allow_negative_stock=True,
)

View File

@@ -0,0 +1,15 @@
import frappe
def execute():
frappe.db.sql(
"""
UPDATE
`tabWork Order Item`, `tabItem`
SET
`tabWork Order Item`.stock_uom = `tabItem`.stock_uom
WHERE
`tabWork Order Item`.item_code = `tabItem`.name
AND `tabWork Order Item`.docstatus = 1
"""
)

View File

@@ -1104,6 +1104,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
callback: function(r) {
if(!r.exc) {
frappe.model.set_value(cdt, cdn, 'conversion_factor', r.message.conversion_factor);
me.apply_price_list(item, true);
}
}
});

View File

@@ -24,11 +24,19 @@ erpnext.get_purchase_trends_filters = function () {
default: "Monthly",
},
{
<<<<<<< HEAD
fieldname: "fiscal_year",
label: __("Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
=======
"fieldname":"fiscal_year",
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options":'Fiscal Year',
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today())
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "period_based_on",

View File

@@ -40,11 +40,19 @@ erpnext.get_sales_trends_filters = function () {
default: "",
},
{
<<<<<<< HEAD
fieldname: "fiscal_year",
label: __("Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
=======
"fieldname":"fiscal_year",
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options":'Fiscal Year',
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "company",

View File

@@ -397,11 +397,48 @@ $.extend(erpnext.utils, {
}
},
<<<<<<< HEAD
get_fiscal_year: function (date, with_dates = false, boolean = false) {
if (!frappe.boot.setup_complete) {
return;
}
if (!date) {
=======
pick_serial_and_batch_bundle(frm, cdt, cdn, type_of_transaction, warehouse_field) {
let item_row = frappe.get_doc(cdt, cdn);
item_row.type_of_transaction = type_of_transaction;
frappe.db.get_value("Item", item_row.item_code, ["has_batch_no", "has_serial_no"])
.then((r) => {
item_row.has_batch_no = r.message.has_batch_no;
item_row.has_serial_no = r.message.has_serial_no;
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => {
if (r) {
let update_values = {
"serial_and_batch_bundle": r.name,
"qty": Math.abs(r.total_qty)
}
if (!warehouse_field) {
warehouse_field = "warehouse";
}
if (r.warehouse) {
update_values[warehouse_field] = r.warehouse;
}
frappe.model.set_value(item_row.doctype, item_row.name, update_values);
}
});
});
});
},
get_fiscal_year: function(date, with_dates=false) {
if(!date) {
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
date = frappe.datetime.get_today();
}
@@ -415,8 +452,15 @@ $.extend(erpnext.utils, {
async: false,
callback: function (r) {
if (r.message) {
<<<<<<< HEAD
if (with_dates) fiscal_year = r.message;
else fiscal_year = r.message[0];
=======
if (with_dates)
fiscal_year = r.message;
else
fiscal_year = r.message[0];
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
}
},
});

View File

@@ -100,6 +100,7 @@ erpnext.accounts.unreconcile_payment = {
fieldtype: "Table",
read_only: 1,
fields: child_table_fields,
cannot_add_rows: true,
},
];
@@ -123,7 +124,6 @@ erpnext.accounts.unreconcile_payment = {
title: "UnReconcile Allocations",
fields: unreconcile_dialog_fields,
size: "large",
cannot_add_rows: true,
primary_action_label: "UnReconcile",
primary_action(values) {
let selected_allocations = values.allocations.filter((x) => x.__checked);

View File

@@ -918,7 +918,10 @@ def get_events(start, end, filters=None):
""",
{"start": start, "end": end},
as_dict=True,
update={"allDay": 0},
update={
"allDay": 0,
"convertToUserTz": 0,
},
)
return data

View File

@@ -8,6 +8,7 @@ frappe.views.calendar["Sales Order"] = {
id: "name",
title: "customer_name",
allDay: "allDay",
convertToUserTz: "convertToUserTz",
},
gantt: true,
filters: [

View File

@@ -277,7 +277,7 @@ erpnext.PointOfSale.ItemDetails = class {
};
this.warehouse_control.df.get_query = () => {
return {
filters: { company: this.events.get_frm().doc.company },
filters: { company: this.events.get_frm().doc.company, is_group: 0 },
};
};
this.warehouse_control.refresh();

View File

@@ -20,6 +20,7 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
reqd: 1,
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
@@ -33,6 +34,21 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
},
=======
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
"reqd": 1
}
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);

View File

@@ -43,15 +43,33 @@ frappe.query_reports["Sales Analytics"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1,
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1,
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
reqd: 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "company",

View File

@@ -16,7 +16,11 @@ frappe.query_reports["Sales Partner Target Variance based on Item Group"] = {
label: __("Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today())
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "doctype",

View File

@@ -21,7 +21,15 @@ frappe.query_reports["Sales Person Commission Summary"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "to_date",

View File

@@ -20,7 +20,15 @@ frappe.query_reports["Sales Person-wise Transaction Summary"] = {
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "to_date",

View File

@@ -16,7 +16,11 @@ frappe.query_reports["Territory Target Variance Based On Item Group"] = {
label: __("Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today())
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "doctype",

View File

@@ -1,30 +1,32 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
extend_cscript(cur_frm.cscript, {
onload: function () {
if (cur_frm.doc.__islocal) {
cur_frm.set_value("to_currency", frappe.defaults.get_global_default("currency"));
frappe.ui.form.on("Currency Exchange", {
onload: function (frm) {
if (frm.doc.__islocal) {
frm.set_value("to_currency", frappe.defaults.get_global_default("currency"));
}
},
refresh: function () {
cur_frm.cscript.set_exchange_rate_label();
refresh: function (frm) {
// Don't trigger on Quick Entry form
if (typeof frm.is_dialog === "undefined") {
frm.trigger("set_exchange_rate_label");
}
},
from_currency: function () {
cur_frm.cscript.set_exchange_rate_label();
from_currency: function (frm) {
frm.trigger("set_exchange_rate_label");
},
to_currency: function () {
cur_frm.cscript.set_exchange_rate_label();
to_currency: function (frm) {
frm.trigger("set_exchange_rate_label");
},
set_exchange_rate_label: function () {
if (cur_frm.doc.from_currency && cur_frm.doc.to_currency) {
var default_label = __(frappe.meta.docfield_map[cur_frm.doctype]["exchange_rate"].label);
cur_frm.fields_dict.exchange_rate.set_label(
default_label + repl(" (1 %(from_currency)s = [?] %(to_currency)s)", cur_frm.doc)
set_exchange_rate_label: function (frm) {
if (frm.doc.from_currency && frm.doc.to_currency) {
var default_label = __(frappe.meta.docfield_map[frm.doctype]["exchange_rate"].label);
frm.fields_dict.exchange_rate.set_label(
default_label + repl(" (1 %(from_currency)s = [?] %(to_currency)s)", frm.doc)
);
}
},

View File

@@ -4,12 +4,21 @@
frappe.query_reports["Batch Item Expiry Status"] = {
filters: [
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
width: "80",
default: frappe.sys_defaults.year_start_date,
reqd: 1,
=======
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"width": "80",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"reqd": 1,
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "to_date",

View File

@@ -12,12 +12,21 @@ frappe.query_reports["Batch-Wise Balance History"] = {
reqd: 1,
},
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
width: "80",
default: frappe.sys_defaults.year_start_date,
reqd: 1,
=======
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"width": "80",
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"reqd": 1
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
},
{
fieldname: "to_date",

View File

@@ -22,14 +22,32 @@ frappe.query_reports["Incorrect Serial No Valuation"] = {
fieldtype: "Date",
fieldname: "from_date",
reqd: 1,
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
label: __("To Date"),
fieldtype: "Date",
fieldname: "to_date",
reqd: 1,
<<<<<<< HEAD
<<<<<<< HEAD
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
},
],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[2],
=======
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
}
]
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
};

View File

@@ -4,10 +4,21 @@
frappe.query_reports["Itemwise Recommended Reorder Level"] = {
filters: [
{
<<<<<<< HEAD
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.sys_defaults.year_start_date,
=======
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
<<<<<<< HEAD
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), with_dates=true)[1],
>>>>>>> 4496a6760e (fix: Default year start and end dates in reports)
=======
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
>>>>>>> 2341061852 (fix: Default year start and end date in reports)
},
{
fieldname: "to_date",

View File

@@ -47,7 +47,23 @@ frappe.query_reports["Stock Ledger Variance"] = {
fieldname: "difference_in",
fieldtype: "Select",
label: __("Difference In"),
options: ["", "Qty", "Value", "Valuation"],
options: [
{
// Check "Stock Ledger Invariant Check" report with A - B column
label: __("Quantity (A - B)"),
value: "Qty",
},
{
// Check "Stock Ledger Invariant Check" report with G - D column
label: __("Value (G - D)"),
value: "Value",
},
{
// Check "Stock Ledger Invariant Check" report with I - K column
label: __("Valuation (I - K)"),
value: "Valuation",
},
],
},
{
fieldname: "include_disabled",

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from frappe import _
from frappe.utils import cint, flt
@@ -270,12 +272,16 @@ def has_difference(row, precision, difference_in, valuation_method):
value_diff = flt(row.diff_value_diff, precision)
valuation_diff = flt(row.valuation_diff, precision)
else:
qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
value_diff = (
flt(row.diff_value_diff, precision)
or flt(row.fifo_value_diff, precision)
or flt(row.fifo_difference_diff, precision)
)
qty_diff = flt(row.difference_in_qty, precision)
value_diff = flt(row.diff_value_diff, precision)
if row.stock_queue and json.loads(row.stock_queue):
value_diff = value_diff or (
flt(row.fifo_value_diff, precision) or flt(row.fifo_difference_diff, precision)
)
qty_diff = qty_diff or flt(row.fifo_qty_diff, precision)
valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision)
if difference_in == "Qty" and qty_diff:

File diff suppressed because it is too large Load Diff