diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 1cc91fb5c9adff0eb2a6350fedb3b4fdea4a737c..ec683df69ba10a621322aff504d8310e4974c206 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -8,5 +8,27 @@ # # $ git config blame.ignoreRevsFile .git-blame-ignore-revs -# Reformat all files with Black and flake8 -fbfe3de6a2a90654aa1fdc9f4832b297363dc4bb +# Replace use of Class.extend with native JS class +1fe891b287a1b3f225d29ee3d07e7b1824aba9e7 + +# This commit just changes spaces to tabs for indentation in some files +5f473611bd6ed57703716244a054d3fb5ba9cd23 + +# Whitespace fix throughout codebase +4551d7d6029b6f587f6c99d4f8df5519241c6a86 +b147b85e6ac19a9220cd1e2958a6ebd99373283a + +# sort and cleanup imports +915b34391c2066dfc83e60a5813c5a877cebe7ac + +# removing six compatibility layer +8fe5feb6a4372bf5f2dfaf65fca41bbcc25c8ce7 + +# bulk format python code with black +494bd9ef78313436f0424b918f200dab8fc7c20b + +# bulk format python code with black +baec607ff5905b1c67531096a9cf50ec7ff00a5d + +# bulk refactor with sourcery +21a5111114e9d0f676bca4ff092c9b3456d5bf96 diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 10da6b023312395aec6f9b9e308270dd43de5959..786162c0cac9c9a791ec4d7aa645789ef85116db 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -47,7 +47,7 @@ def get_company_currency(company): """Returns the default company currency""" if not frappe.flags.company_currency: frappe.flags.company_currency = {} - if not company in frappe.flags.company_currency: + if company not in frappe.flags.company_currency: frappe.flags.company_currency[company] = frappe.db.get_value( "Company", company, "default_currency", cache=True ) @@ -81,7 +81,7 @@ def is_perpetual_inventory_enabled(company): if not hasattr(frappe.local, "enable_perpetual_inventory"): frappe.local.enable_perpetual_inventory = {} - if not company in frappe.local.enable_perpetual_inventory: + if company not in frappe.local.enable_perpetual_inventory: frappe.local.enable_perpetual_inventory[company] = ( frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 ) @@ -96,7 +96,7 @@ def get_default_finance_book(company=None): if not hasattr(frappe.local, "default_finance_book"): frappe.local.default_finance_book = {} - if not company in frappe.local.default_finance_book: + if company not in frappe.local.default_finance_book: frappe.local.default_finance_book[company] = frappe.get_cached_value( "Company", company, "default_finance_book" ) @@ -108,7 +108,7 @@ def get_party_account_type(party_type): if not hasattr(frappe.local, "party_account_types"): frappe.local.party_account_types = {} - if not party_type in frappe.local.party_account_types: + if party_type not in frappe.local.party_account_types: frappe.local.party_account_types[party_type] = ( frappe.db.get_value("Party Type", party_type, "account_type") or "" ) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index bef18ee1db484396a5af2289efda3cc22b5e23e0..487ab9662bac9179c064b31ddf08247d7b6f2da8 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -232,7 +232,7 @@ def calculate_monthly_amount( if amount + already_booked_amount_in_account_currency > item.net_amount: amount = item.net_amount - already_booked_amount_in_account_currency - if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): + if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date: partial_month = flt(date_diff(end_date, start_date)) / flt( date_diff(get_last_day(end_date), get_first_day(start_date)) ) diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 47b67400f348aaa2cd8939e8dccfdbd75c2750ba..417c248e5321ed10bab9d278872a242604837978 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -201,7 +201,7 @@ "index_web_pages_for_search": 1, "is_tree": 1, "links": [], - "modified": "2023-07-20 18:18:44.405723", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Account", @@ -264,7 +264,7 @@ "search_fields": "account_number", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "title_field": "title", "states": [], "track_changes": 1 diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index c911a05cc32eeb447d5296013853765ba21816da..3fa149ce7ca1bbc3fd441f9e4f4e6d7b2fc0418e 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -67,6 +67,9 @@ "frozen_accounts_modifier", "tab_break_dpet", "show_balance_in_coa", + "banking_tab", + "enable_party_matching", + "enable_fuzzy_matching", "reports_tab", "remarks_section", "general_ledger_remarks_length", @@ -411,17 +414,30 @@ "fieldtype": "Check", "label": "Show Taxes as Table in Print" }, + { + "fieldname": "banking_tab", + "fieldtype": "Tab Break", + "label": "Banking" + }, { "default": "0", - "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", - "fieldname": "ignore_account_closing_balance", + "description": "Auto match and set the Party in Bank Transactions", + "fieldname": "enable_party_matching", "fieldtype": "Check", - "label": "Ignore Account Closing Balance" + "label": "Enable Automatic Party Matching" }, { "default": "0", - "description": "Tax Amount will be rounded on a row(items) level", - "fieldname": "round_row_wise_tax", + "depends_on": "enable_party_matching", + "description": "Approximately match the description/party name against parties", + "fieldname": "enable_fuzzy_matching", + "fieldtype": "Check", + "label": "Enable Fuzzy Matching" + }, + { + "default": "0", + "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", + "fieldname": "ignore_account_closing_balance", "fieldtype": "Check", "label": "Ignore Account Closing Balance" }, @@ -430,7 +446,7 @@ "description": "Tax Amount will be rounded on a row(items) level", "fieldname": "round_row_wise_tax", "fieldtype": "Check", - "label": "Round Tax Amount Row-wise" + "label": "Ignore Account Closing Balance" }, { "fieldname": "reports_tab", @@ -466,7 +482,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-11-20 09:37:47.650347", + "modified": "2023-12-11 10:22:18.348446", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -492,7 +508,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ac3d44bb5e706dffb1b2ef7f7cad593aaa88c654..4048be10f61c802bd5bd4e0f4c87fddfd6839aa0 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,6 +14,54 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(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 + + acc_frozen_upto: DF.Date | None + add_taxes_from_item_tax_template: DF.Check + allow_multi_currency_invoices_against_single_party_account: DF.Check + allow_stale: DF.Check + auto_reconcile_payments: DF.Check + automatically_fetch_payment_terms: DF.Check + automatically_process_deferred_accounting_entry: DF.Check + book_asset_depreciation_entry_automatically: DF.Check + book_deferred_entries_based_on: DF.Literal["Days", "Months"] + book_deferred_entries_via_journal_entry: DF.Check + book_tax_discount_loss: DF.Check + check_supplier_invoice_uniqueness: DF.Check + credit_controller: DF.Link | None + default_payment_days: DF.Int + determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] + enable_common_party_accounting: DF.Check + enable_fuzzy_matching: DF.Check + enable_party_matching: DF.Check + frozen_accounts_modifier: DF.Link | None + general_ledger_remarks_length: DF.Int + ignore_account_closing_balance: DF.Check + make_payment_via_journal_entry: DF.Check + mandatory_accounting_journal: DF.Check + merge_similar_account_heads: DF.Check + over_billing_allowance: DF.Currency + post_change_gl_entries: DF.Check + receivable_payable_remarks_length: DF.Int + role_allowed_to_over_bill: DF.Link | None + round_row_wise_tax: DF.Check + show_balance_in_coa: DF.Check + show_inclusive_tax_in_print: DF.Check + show_payment_schedule_in_print: DF.Check + show_taxes_as_table_in_print: DF.Check + stale_days: DF.Int + submit_journal_entries: DF.Check + unlink_advance_payment_on_cancelation_of_order: DF.Check + unlink_payment_on_cancellation_of_invoice: DF.Check + validate_posting_date_chronology_in_sales_invoices: DF.Check + # end: auto-generated types + def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.js b/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.js index 77db12c074f6c30ca76a2136cf2da039575bcbf8..38493d5e5d67536ab75d108f0e2f80a99bb7b89f 100644 --- a/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.js +++ b/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.js @@ -15,6 +15,10 @@ frappe.ui.form.on('Adjustment Entry', { frappe.set_route("query-report", "General Ledger"); }, "fas fa-table"); } + + if (frm.doc.error) { + frm.dashboard.set_headline(frm.doc.error, "red") + } }, get_documents(frm) { frappe.call({ diff --git a/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.json b/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.json index e1d0755eb5a73f73e29a0c888995603786aa409d..40ccae532d07234d20e180ff49fdbe3484a4e157 100644 --- a/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.json +++ b/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.json @@ -12,6 +12,7 @@ "naming_series", "entry_type", "status", + "error", "column_break_4", "posting_date", "company", @@ -111,6 +112,7 @@ "label": "Additional Information" }, { + "depends_on": "eval:doc.docstatus==0", "fieldname": "get_documents", "fieldtype": "Button", "label": "Get documents" @@ -167,17 +169,24 @@ "fieldname": "status", "fieldtype": "Select", "label": "Status", - "options": "Draft\nSubmitted\nReversed\nCancelled", + "options": "Draft\nSubmitted\nReversed\nCancelled\nError", "read_only": 1 + }, + { + "fieldname": "error", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Error" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-03-15 09:39:41.487617", + "modified": "2023-12-15 13:51:26.563844", "modified_by": "Administrator", "module": "Accounts", "name": "Adjustment Entry", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -217,6 +226,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.py b/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.py index 9c0cd00489347a11c4f213140ed1e5d490222dca..1559725fe4e81f1ef3ba316777c4aa28f3bb32c8 100644 --- a/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.py +++ b/erpnext/accounts/doctype/adjustment_entry/adjustment_entry.py @@ -12,6 +12,7 @@ from frappe.utils import add_days, date_diff, flt, format_date, getdate, month_d from erpnext.accounts.general_ledger import ( check_freezing_date, get_accounting_journal, + get_accounting_number, make_entry, make_reverse_gl_entries, validate_accounting_period, @@ -24,6 +25,36 @@ ACCOUNTTYPE = {"Purchase Invoice": "expense_account", "Sales Invoice": "income_a class AdjustmentEntry(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 + + from erpnext.accounts.doctype.adjustment_entry_detail.adjustment_entry_detail import ( + AdjustmentEntryDetail, + ) + + accounting_journal: DF.Link | None + adjustment_account: DF.Link + amended_from: DF.Link | None + auto_repeat: DF.Link | None + company: DF.Link + details: DF.Table[AdjustmentEntryDetail] + entry_type: DF.Literal["Deferred charges", "Deferred income"] + error: DF.SmallText | None + naming_series: DF.Literal["ACC-ADJ-.YYYY.-"] + posting_date: DF.Date + reversal_date: DF.Date + status: DF.Literal["Draft", "Submitted", "Reversed", "Cancelled", "Error"] + title: DF.Data | None + total_credit: DF.Currency + total_debit: DF.Currency + total_posting_amount: DF.Currency + # end: auto-generated types + def on_submit(self): self._make_gl_entries() self.set_status() @@ -54,6 +85,17 @@ class AdjustmentEntry(Document): if gl_entries: validate_accounting_period(gl_entries) check_freezing_date(gl_entries[0]["posting_date"], False) + new_fiscal_year = get_fiscal_years(date, company=self.company) + + if not new_fiscal_year: + self.db_set("status", "Error") + error_msg = _("No fiscal year could be found to reverse this entry") + self.db_set("error", error_msg) + frappe.throw(error_msg) + + self.db_set("error", "") + fiscal_year = new_fiscal_year[0][0] + accounting_number = get_accounting_number(gl_entries[0]) for entry in gl_entries: name = entry["name"] @@ -71,6 +113,8 @@ class AdjustmentEntry(Document): entry["debit_in_account_currency"] = credit_in_account_currency entry["credit_in_account_currency"] = debit_in_account_currency entry["against"] = name + entry["accounting_entry_number"] = accounting_number + entry["fiscal_year"] = fiscal_year if not entry.get("accounting_journal"): get_accounting_journal(entry) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.js b/erpnext/accounts/doctype/bank_account/bank_account.js index af68d0ffd86793e0cfeea05188e4d0696833061f..d698bc28d54458ee569e0658acf627cb75ab5c09 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.js +++ b/erpnext/accounts/doctype/bank_account/bank_account.js @@ -12,11 +12,21 @@ frappe.ui.form.on('Bank Account', { } }; }); + frm.set_query("party_type", function() { return { query: "erpnext.setup.doctype.party_type.party_type.get_party_type", }; }); + + frm.set_query("linked_account", function() { + return { + filters: { + 'bank': frm.doc.bank, + 'disabled': 0, + } + }; + }); }, refresh: function(frm) { frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank Account' } diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 8121d4fb0e9bc890f4d9a1b9e51ddfb84d98700a..6a47dc02bd2ef8ff2630d405d329d13a54ada29c 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -17,6 +17,8 @@ "is_default", "is_company_account", "company", + "is_card", + "linked_account", "section_break_11", "party_type", "column_break_14", @@ -218,6 +220,21 @@ "fieldname": "disabled", "fieldtype": "Check", "label": "Disabled" + }, + { + "default": "0", + "fieldname": "is_card", + "fieldtype": "Check", + "label": "Is a debit/credit card" + }, + { + "depends_on": "eval:doc.is_card", + "fieldname": "linked_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Linked Account", + "mandatory_depends_on": "eval:doc.is_card", + "options": "Bank Account" } ], "links": [ @@ -227,7 +244,7 @@ "link_fieldname": "bank_account" } ], - "modified": "2023-09-22 21:31:34.763977", + "modified": "2023-12-13 18:10:11.200837", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", @@ -264,5 +281,6 @@ "search_fields": "bank,account", "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 6017a6235b81f36948f97e52c0eb5964bddb1925..5bd2c1ccaabf807bf3b4b3f06c1503a934a49340 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -13,6 +13,37 @@ from schwifty import IBAN class BankAccount(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 + + account: DF.Link | None + account_name: DF.Data + account_subtype: DF.Link | None + account_type: DF.Link | None + bank: DF.Link + bank_account_no: DF.Data | None + branch_code: DF.Data | None + company: DF.Link | None + disabled: DF.Check + iban: DF.Data | None + integration_id: DF.Data | None + is_card: DF.Check + is_company_account: DF.Check + is_default: DF.Check + last_integration_date: DF.Date | None + linked_account: DF.Link | None + mask: DF.Data | None + party: DF.DynamicLink | None + party_type: DF.Link | None + swift_number: DF.Data | None + website: DF.Data | None + # end: auto-generated types + def onload(self): """Load address and contacts in `__onload`""" load_address_and_contact(self) diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py new file mode 100644 index 0000000000000000000000000000000000000000..04dab4c28a05f040ae02266b9f95cc76b9fce66d --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py @@ -0,0 +1,183 @@ +from typing import Tuple, Union + +import frappe +from frappe.utils import flt +from rapidfuzz import fuzz, process + + +class AutoMatchParty: + """ + Matches by Account/IBAN and then by Party Name/Description sequentially. + Returns when a result is obtained. + + Result (if present) is of the form: (Party Type, Party,) + """ + + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self) -> Union[Tuple, None]: + result = None + result = AutoMatchbyAccountIBAN( + bank_party_account_number=self.bank_party_account_number, + bank_party_iban=self.bank_party_iban, + deposit=self.deposit, + ).match() + + fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching") + if not result and fuzzy_matching_enabled: + result = AutoMatchbyPartyNameDescription( + bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit + ).match() + + return result + + +class AutoMatchbyAccountIBAN: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self): + if not (self.bank_party_account_number or self.bank_party_iban): + return None + + result = self.match_account_in_party() + return result + + def match_account_in_party(self) -> Union[Tuple, None]: + """Check if there is a IBAN/Account No. match in Customer/Supplier/Employee""" + result = None + parties = get_parties_in_order(self.deposit) + or_filters = self.get_or_filters() + + for party in parties: + party_result = frappe.db.get_all( + "Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1 + ) + + if party == "Employee" and not party_result: + # Search in Bank Accounts first for Employee, and then Employee record + if "bank_account_no" in or_filters: + or_filters["bank_ac_no"] = or_filters.pop("bank_account_no") + + party_result = frappe.db.get_all( + party, or_filters=or_filters, pluck="name", limit_page_length=1 + ) + + if party_result: + result = ( + party, + party_result[0], + ) + break + + return result + + def get_or_filters(self) -> dict: + or_filters = {} + if self.bank_party_account_number: + or_filters["bank_account_no"] = self.bank_party_account_number + + if self.bank_party_iban: + or_filters["iban"] = self.bank_party_iban + + return or_filters + + +class AutoMatchbyPartyNameDescription: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self) -> Union[Tuple, None]: + # fuzzy search by customer/supplier & employee + if not (self.bank_party_name or self.description): + return None + + result = self.match_party_name_desc_in_party() + return result + + def match_party_name_desc_in_party(self) -> Union[Tuple, None]: + """Fuzzy search party name and/or description against parties in the system""" + result = None + parties = get_parties_in_order(self.deposit) + + for party in parties: + filters = {"status": "Active"} if party == "Employee" else {"disabled": 0} + field = party.lower() + "_name" + names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"]) + + for field in ["bank_party_name", "description"]: + if not self.get(field): + continue + + result, skip = self.fuzzy_search_and_return_result(party, names, field) + if result or skip: + break + + if result or skip: + # Skip If: It was hard to distinguish between close matches and so match is None + # OR if the right match was found + break + + return result + + def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]: + skip = False + result = process.extract( + query=self.get(field), + choices={row.get("name"): row.get("party_name") for row in names}, + scorer=fuzz.token_set_ratio, + ) + party_name, skip = self.process_fuzzy_result(result) + + if not party_name: + return None, skip + + return ( + party, + party_name, + ), skip + + def process_fuzzy_result(self, result: Union[list, None]): + """ + If there are multiple valid close matches return None as result may be faulty. + Return the result only if one accurate match stands out. + + Returns: Result, Skip (whether or not to discontinue matching) + """ + SCORE, PARTY_ID, CUTOFF = 1, 2, 80 + + if not result or not len(result): + return None, False + + first_result = result[0] + if len(result) == 1: + return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True + + second_result = result[1] + if first_result[SCORE] > CUTOFF: + # If multiple matches with the same score, return None but discontinue matching + # Matches were found but were too close to distinguish between + if first_result[SCORE] == second_result[SCORE]: + return None, True + + return first_result[PARTY_ID], True + else: + return None, False + + +def get_parties_in_order(deposit: float) -> list: + parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive + if flt(deposit) > 0: + parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay + + return parties diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 6e504668d8a06cf06add80ca03bb913723aaa074..4c1052a69ab26e51e7af1a28c64e0500d1aeb48c 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -42,6 +42,12 @@ frappe.ui.form.on('Bank Transaction', { } } }); + + frm.set_query('party_type', function() { + return { + query: "erpnext.setup.doctype.party_type.party_type.get_party_type" + }; + }); }, refresh(frm) { frm.page.clear_actions_menu(); @@ -54,12 +60,19 @@ frappe.ui.form.on('Bank Transaction', { if (frm.doc.status === "Unreconciled" && frm.doc.docstatus === 1) { frm.add_custom_button(__('Set status as Closed'), function () { frm.trigger("close_bank_transaction"); - }); + }, __("Actions")); } - frm.add_custom_button(__('Bank reconciliation dashboard'), function () { - frappe.set_route("bank-reconciliation"); - }); + if (frm.doc.docstatus == 1) { + frm.add_custom_button(__('Bank reconciliation'), function () { + frappe.set_route("bank-reconciliation"); + }); + + frm.add_custom_button(__('Unreconcile Transaction'), () => { + frm.call('remove_payment_entries') + .then( () => frm.refresh() ); + }, __("Actions")); + } }, close_bank_transaction(frm) { return frappe.call({ diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 1561e3f7b0b719f17f54435855cd37ac7859a790..119dab4ceac16e952d1c62c15a1d59927cafcb65 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -21,8 +21,10 @@ "currency", "section_break_4", "debit", + "deposit", "column_break_7", "credit", + "withdrawal", "section_break_10", "description", "section_break_14", @@ -35,7 +37,14 @@ "allocated_amount", "amended_from", "column_break_17", - "unallocated_amount" + "unallocated_amount", + "party_section", + "party_type", + "party", + "column_break_3czf", + "bank_party_name", + "bank_party_account_number", + "bank_party_iban" ], "fields": [ { @@ -62,6 +71,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "default": "Unreconciled", "fieldname": "status", "fieldtype": "Select", @@ -97,13 +107,13 @@ "fieldname": "debit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Debit" + "label": "Deposit" }, { "fieldname": "credit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Credit" + "label": "Withdrawal" }, { "fieldname": "column_break_7", @@ -187,7 +197,7 @@ "allow_on_submit": 1, "fieldname": "total_debit", "fieldtype": "Currency", - "label": "Total Debit", + "label": "Total Allocated Debit", "read_only": 1 }, { @@ -198,7 +208,7 @@ "allow_on_submit": 1, "fieldname": "total_credit", "fieldtype": "Currency", - "label": "Total Credit", + "label": "Total Allocated Credit", "read_only": 1 }, { @@ -231,11 +241,65 @@ "hidden": 1, "label": "Bank", "options": "Bank" + }, + { + "fieldname": "party_section", + "fieldtype": "Section Break", + "label": "Payment From / To" + }, + { + "allow_on_submit": 1, + "fieldname": "party_type", + "fieldtype": "Link", + "label": "Party Type", + "options": "DocType" + }, + { + "allow_on_submit": 1, + "fieldname": "party", + "fieldtype": "Dynamic Link", + "label": "Party", + "options": "party_type" + }, + { + "fieldname": "column_break_3czf", + "fieldtype": "Column Break" + }, + { + "fieldname": "bank_party_name", + "fieldtype": "Data", + "label": "Party Name/Account Holder (Bank Statement)" + }, + { + "fieldname": "bank_party_iban", + "fieldtype": "Data", + "label": "Party IBAN (Bank Statement)" + }, + { + "fieldname": "bank_party_account_number", + "fieldtype": "Data", + "label": "Party Account No. (Bank Statement)" + }, + { + "fieldname": "deposit", + "fieldtype": "Currency", + "hidden": 1, + "label": "Deposit", + "oldfieldname": "debit", + "options": "currency" + }, + { + "fieldname": "withdrawal", + "fieldtype": "Currency", + "hidden": 1, + "label": "Withdrawal", + "oldfieldname": "credit", + "options": "currency" } ], "is_submittable": 1, "links": [], - "modified": "2023-10-16 11:44:31.419057", + "modified": "2023-12-11 10:20:42.555879", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 5ed85904557ae94193ee4774ab3326d5a77d728f..c9376e242a6423ac4b99ecd8460826b5b5c3f022 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -28,14 +28,20 @@ class BankTransaction(StatusUpdater): bank: DF.Link | None bank_account: DF.Link bank_account_head: DF.Link | None + bank_party_account_number: DF.Data | None + bank_party_iban: DF.Data | None + bank_party_name: DF.Data | None category: DF.Link | None company: DF.Link credit: DF.Currency currency: DF.Link date: DF.Date debit: DF.Currency + deposit: DF.Currency description: DF.SmallText | None naming_series: DF.Literal["ACC-BTN-.YYYY.-"] + party: DF.DynamicLink | None + party_type: DF.Link | None payment_entries: DF.Table[BankTransactionPayments] reference_number: DF.Data | None status: DF.Literal["", "Pending", "Settled", "Unreconciled", "Reconciled", "Cancelled", "Closed"] @@ -43,25 +49,58 @@ class BankTransaction(StatusUpdater): total_debit: DF.Currency transaction_type: DF.Data | None unallocated_amount: DF.Currency + withdrawal: DF.Currency # end: auto-generated types - def after_insert(self): - self.unallocated_amount = flt(self.credit) - flt(self.debit) + def before_validate(self): + self.set_allocation_in_bank_transaction() def before_insert(self): self.check_similar_entries() self.check_transaction_references() + def validate(self): + # Keep for backward compatibility + if not (self.debit or self.credit) and (self.deposit or self.withdrawal): + self.credit = self.deposit + self.debit = self.withdrawal + else: + self.deposit = self.credit + self.withdrawal = self.debit + + self.validate_duplicate_references() + + def validate_duplicate_references(self): + """Make sure the same voucher is not allocated twice within the same Bank Transaction""" + if not self.payment_entries: + return + + pe = [] + for row in self.payment_entries: + reference = (row.payment_document, row.payment_entry) + if reference in pe: + frappe.throw( + _("{0} {1} is allocated twice in this Bank Transaction").format( + _(row.payment_document), row.payment_entry + ) + ) + pe.append(reference) + def before_save(self): self.check_bank_account_head() self.check_payment_types() self.calculate_totals() self.check_reconciliation_amounts() - def on_submit(self): - self.check_reconciliation_amounts() + def before_submit(self): self.set_allocation_in_linked_docs() + self.check_reconciliation_amounts() self.set_allocation_in_bank_transaction() + + if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): + self.auto_set_party() + + def on_submit(self): self.set_payment_entries_clearance_date() self.set_status() @@ -70,10 +109,11 @@ class BankTransaction(StatusUpdater): self.check_payment_types() self.calculate_totals() - def on_update_after_submit(self): - self.check_reconciliation_amounts() self.set_allocation_in_linked_docs() + self.check_reconciliation_amounts() self.set_allocation_in_bank_transaction() + + def on_update_after_submit(self): self.set_payment_entries_clearance_date() self.set_status(update=True) @@ -83,6 +123,7 @@ class BankTransaction(StatusUpdater): self.payment_entries = [] def on_cancel(self): + self.remove_payment_entries(cancel=True) self.set_status(update=True) def check_similar_entries(self): @@ -220,16 +261,13 @@ class BankTransaction(StatusUpdater): transaction_amount = flt(self.credit) - flt(self.debit) - self.db_set("allocated_amount", flt(allocated_amount) if allocated_amount else 0) - self.db_set( - "unallocated_amount", - (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount, + self.allocated_amount = flt(allocated_amount) if allocated_amount else 0 + self.unallocated_amount = ( + (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount ) if transaction_amount == self.allocated_amount: - self.db_set("status", "Reconciled") - - self.reload() + self.status = "Reconciled" def check_reconciliation_amounts(self): for payment_entry in self.payment_entries: @@ -330,6 +368,36 @@ class BankTransaction(StatusUpdater): self.date = auto_repeat_doc.next_schedule_date self.reference_number = f"""{formatdate(self.date, "YYYYMMDD")}-{frappe.generate_hash("", 10)}""" + @frappe.whitelist() + def remove_payment_entries(self, cancel=False): + for payment_entry in self.payment_entries: + self.remove(payment_entry) + + if cancel: + self.run_method("before_update_after_submit") + self.run_method("on_update_after_submit") + else: + self.save() + + def auto_set_party(self): + from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty + + if self.party_type and self.party: + return + + result = AutoMatchParty( + bank_party_account_number=self.bank_party_account_number, + bank_party_iban=self.bank_party_iban, + bank_party_name=self.bank_party_name, + description=self.description, + deposit=self.deposit, + ).match() + + if not result: + return + + self.party_type, self.party = result + def get_unreconciled_amount(payment_entry): return frappe.db.get_value( diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue index c9cd0174292d1409a76198fe2d818c1eb73767c7..e8df911a5283d4ca3b457fab776e8f49ad84de4f 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue index 992424e516bf5bc67a6f41accbb8848339217689..f62e5b30e7ee1d85ee36699a7c3459c93c47bcef 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue @@ -1,173 +1,160 @@ - diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue index 5947af3cb6b66effa6dd115d7832a8925b9634c6..92ce5177b06efae841978e8922be8538a7b5c666 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue @@ -1,24 +1,25 @@ @@ -73,6 +74,9 @@ const documents_formatted_amount = computed(() => { }) function reconcile_entries() { + if (is_reconciliation_disabled.value || btn_clicked.value) { + return; + } btn_clicked.value = true; if ((props.selected_transactions.length == 1 && props.selected_documents.length >= 1) || (props.selected_transactions.length >= 1 && props.selected_documents.length == 1)) { @@ -118,5 +122,11 @@ function call_reconciliation() { .actions-wrapper { padding: 15px; width: 50%; + margin: 0 auto; + + background-color: var(--fg-color); + // position: sticky; + // top: var(--navbar-height); + // z-index: 99; } \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue index 24c0826d6a5b2baa0457c16729ac22ef1e567791..09f58cb5fe72aaf6030291b423377f7d3e959b4a 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue @@ -10,10 +10,11 @@ v-for="(dt, i) in Object.keys(matching_documents)" :key="i" type="button" - class="btn btn-default" - :class="document_type==dt ? 'active': ''" - @click="change_doctype(dt)"> - {{ __(dt)}} + class="btn btn-xs px-2 py-1" + :class="document_type == dt ? 'btn-secondary-dark': 'btn-default'" + @click="change_doctype(dt)" + > + {{ __(dt)}} @@ -64,7 +65,15 @@ const columns = [ return frappe.datetime.str_to_user(cellValue) }, }, - { title: __('Party'), field: 'party', width: "20%" }, + { + title: __('Party'), + field: 'party', + width: 200, + headerFilter: "input", + headerSort: false, + headerFilterPlaceholder: __("Party"), + titleFormatter: () => "", + }, { title: __('Amount'), field: 'amount', width: "20%", formatter: function (cell, formatterParams, onRendered) { const cellValue = cell.getValue(); @@ -78,10 +87,20 @@ const columns = [ return format_currency(cellValue, cellRowData.currency) }, }, - { title: __('Reference'), field: 'reference_string', width: "30%" }, + { + title: __('Reference'), + field: 'reference_string', + width: 300, + headerFilter: "input", + headerSort: false, + headerFilterPlaceholder: __("Reference"), + titleFormatter: () => "", + }, { title: __('Reference Date'), field: 'reference_date' }, { - title: __("Link"), field: 'link', formatter: function (cell, formatterParams, onRendered) { + title: __("Link"), + field: 'link', + formatter: function (cell, formatterParams, onRendered) { const cellValue = cell.getValue(); if (!cellValue) { @@ -123,10 +142,11 @@ function get_linked_docs (match) { match: match } ).then((result) => { - const mapped_result = result.map(document => ({...document, + const mapped_result = result.map(document => ({ + ...document, date: document.posting_date, doctype: document_type.value, - link: `/app/Form/${document_type.value}/${document.name}` + link: frappe.utils.get_form_link(document_type.value, document.name), })) Object.assign(matching_documents, {[document_type.value]: mapped_result}); diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue index ada6da80089b786f94afab2475f3289736b16aee..e400e873d6dc0afa4876d99a7751c58837836cdf 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue @@ -7,9 +7,17 @@
@@ -20,8 +28,9 @@
- +
@@ -80,7 +89,7 @@ function get_transaction_list() { const mapped_transactions = r.map(transaction => ({ ...transaction, amount: transaction.unallocated_amount, - link: `/app/Form/Bank Transaction/${transaction.name}` + link: frappe.utils.get_form_link("Bank Transaction", transaction.name) })) tableData.value = mapped_transactions; @@ -110,12 +119,20 @@ function onSelectedRowsChange() { const columns = [ { - formatter: "rowSelection", titleFormatter: "rowSelection", titleFormatterParams: { + formatter: "rowSelection", + titleFormatter: "rowSelection", + cssClass: "text-center", + titleFormatterParams: { rowRange: "active" - }, hozAlign: "center", headerSort: false + }, + hozAlign: "center", + headerSort: false, }, { - title: __("Date"), field: "date", formatter: function (cell, formatterParams, onRendered) { + widthGrow: 2, + title: __("Date"), + field: "date", + formatter(cell, formatterParams, onRendered) { const cellValue = cell.getValue(); if (!cellValue) { @@ -125,9 +142,21 @@ const columns = [ return frappe.datetime.str_to_user(cellValue) }, }, - { title: __("Description"), field: "description", width: "60px", formatter: "html", headerFilter:"input" }, { - title: __("Amount"), field: "amount", formatter: function (cell, formatterParams, onRendered) { + widthGrow: 6, + title: __("Description"), + field: "description", + formatter: "html", + headerFilter: "input", + headerSort: false, + headerFilterPlaceholder: __("Description"), + titleFormatter: () => "", + }, + { + widthGrow: 2, + title: __("Amount"), + field: "amount", + formatter(cell, formatterParams, onRendered) { const cellValue = cell.getValue(); const row = cell.getRow(); const cellRowData = row.getData(); @@ -140,7 +169,10 @@ const columns = [ }, }, { - title: __("Link"), field: 'link', formatter: function (cell, formatterParams, onRendered) { + title: __("Link"), + field: 'link', + headerSort: false, + formatter(cell, formatterParams, onRendered) { const cellValue = cell.getValue(); if (!cellValue) { @@ -153,7 +185,7 @@ const columns = [ ] onMounted(() => { - tabulator.value = new Tabulator(transactionslist.value, { + const tabulatorInstance = new Tabulator(transactionslist.value, { data: tableData.value, columns: columns, selectable: true, @@ -162,6 +194,7 @@ onMounted(() => { paginationSizeSelector: [10, 20, 50, 100, true], placeholder: __("No data available for this period"), locale: true, + layout: "fitColumns", langs: { "default": { "pagination": { @@ -187,7 +220,9 @@ onMounted(() => { }, }); - tabulator.value.on("rowSelectionChanged", function (data, rows, selected, deselected) { + tabulator.value = tabulatorInstance; + + tabulatorInstance.on("rowSelectionChanged", function (data, rows, selected, deselected) { onSelectedRowsChange() }); diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/bank_reconciliation.js index 596dac249ea75870ddc8abab604605fd3ee0468e..41e33be39f2218ef37403170c6f222faa583826f 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/bank_reconciliation.js @@ -13,7 +13,11 @@ erpnext.accounts.bankReconciliationPage = class BankReconciliationPage { this.page = this.parent.page; this.company = frappe.defaults.get_user_default("Company"); - this.bank_account = frappe.boot.sysdefaults.default_bank_account_name; + const default_bank_account = frappe.boot.sysdefaults.default_bank_account_name + this.user_bank_account = frappe.defaults.get_user_default("Bank Account") || ( + frappe.perm.has_perm(default_bank_account, 0, 'read') ? default_bank_account : "" + ) + this.bank_account = this.user_bank_account; this.date_range = [frappe.datetime.add_months(frappe.datetime.get_today(),-1), frappe.datetime.get_today()]; frappe.utils.make_event_emitter(erpnext.bank_reconciliation); this.make(); @@ -46,7 +50,7 @@ erpnext.accounts.bankReconciliationPage = class BankReconciliationPage { label: __('Bank Account'), fieldname: 'bank_account', options: "Bank Account", - default: frappe.boot.sysdefaults.default_bank_account_name, + default: this.user_bank_account, reqd: 1, get_query: function() { if(!me.company) { diff --git a/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py index 1df9868067fd5328ce982d520e30ae44193cd83e..3e744d3ecc74b438d18554fa8db0bd9c3eaea7c8 100644 --- a/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py @@ -14,7 +14,7 @@ def reconcile_gocardless_payouts(bank_transactions): gocardless_transactions = [ transaction for transaction in bank_transactions - if "gocardless" in transaction.get("description").lower() + if "gocardless" in (transaction.get("description") or "").lower() ] if not gocardless_transactions: return diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index ed729fcca010b748175613e1f8a3464d57473fb7..31f44abe898a6fe05debf205fed896ae0062263f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -6,7 +6,6 @@ from typing import Optional import frappe from frappe import _, msgprint, qb, scrub from frappe.contacts.doctype.address.address import get_company_address, get_default_address -from frappe.contacts.doctype.contact.contact import get_contact_details from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import Abs, Date, Sum @@ -185,6 +184,14 @@ def _get_party_details( party_type, party.name, "tax_withholding_category" ) + # @dokos + if party_details.get("tax_category"): + account_fieldname = "debit_to" if party_type == "Customer" else "credit_to" + if account := get_party_account_from_tax_category( + party_type, company, tax_category=party_details["tax_category"] + ): + party_details[account_fieldname] = account + return party_details @@ -396,7 +403,7 @@ def set_account_and_due_date( @frappe.whitelist() def get_party_account( - party_type, party=None, company=None, down_payment=None, include_advance=False + party_type, party=None, company=None, down_payment=None, include_advance=False, tax_category=None ): """Returns the account for the given `party`. Will first search in party (Customer / Supplier) record, if not found, @@ -405,16 +412,25 @@ def get_party_account( if not company: frappe.throw(_("Please select a Company")) - if not party and party_type in ["Customer", "Supplier"]: - default_account_name = ( - "default_receivable_account" if party_type == "Customer" else "default_payable_account" - ) + default_account_name = ( + "default_receivable_account" if party_type == "Customer" else "default_payable_account" + ) + + account = None + if tax_category: # @dokos + account = get_party_account_from_tax_category(party_type, company, tax_category) + if not party and party_type in ["Customer", "Supplier"]: return frappe.get_cached_value("Company", company, default_account_name) - account = frappe.db.get_value( - "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account" - ) + if not account and party_type in ["Customer", "Supplier"]: + if party_tax_category := frappe.get_cached_value(party_type, party, "tax_category"): + account = get_party_account_from_tax_category(party_type, company, party_tax_category) + + if not account: + account = frappe.db.get_value( + "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account" + ) if not account and party_type in ["Customer", "Supplier"]: party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group" @@ -426,9 +442,6 @@ def get_party_account( ) if not account and party_type in ["Customer", "Supplier"]: - default_account_name = ( - "default_receivable_account" if party_type == "Customer" else "default_payable_account" - ) account = frappe.get_cached_value("Company", company, default_account_name) existing_gle_currency = get_party_gle_currency(party_type, party, company) @@ -449,6 +462,27 @@ def get_party_account( return account +# @dokos +def get_party_account_from_tax_category(party_type, company, tax_category=None): + if not tax_category: + return + + tax_category_parentfield = ( + "receivable_accounts" if party_type == "Customer" else "payable_accounts" + ) + + return frappe.db.get_value( + "Party Account", + { + "parenttype": "Tax Category", + "parent": tax_category, + "company": company, + "parentfield": tax_category_parentfield, + }, + "account", + ) + + def get_party_advance_account(party_type, party, company): account = frappe.db.get_value( "Party Account", @@ -1005,6 +1039,9 @@ def get_partywise_advanced_payment_amount( if party: query = query.where(ple.party == party) + if invoice_doctypes := frappe.get_hooks("invoice_doctypes"): + query = query.where(ple.voucher_type.notin(invoice_doctypes)) + data = query.run() if data: return frappe._dict(data) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 23ed84c89187a2322b55c597c29f49b7be859edd..16d1973f34216ce3a3ccbe44dff087cd31ac2ea9 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -123,7 +123,7 @@ class ReceivablePayableReport(object): else: key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) - if not key in self.voucher_balance: + if key not in self.voucher_balance: self.voucher_balance[key] = frappe._dict( voucher_type=ple.voucher_type, voucher_no=ple.voucher_no, @@ -938,7 +938,7 @@ class ReceivablePayableReport(object): return True def get_party_details(self, party): - if not party in self.party_details: + if party not in self.party_details: if self.account_type == "Receivable": fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"] @@ -1087,7 +1087,7 @@ class ReceivablePayableReport(object): ) if self.filters.show_remarks: - self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200), + self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200) def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120): if not fieldname: diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 65846d0b8331d73dfbe7080a1c088ffd00d6436b..f50a5563a29a08da1946e7940894ec998be6f91d 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -97,7 +97,7 @@ class Deferred_Item(object): if base_amount + already_booked_amount > self.base_net_amount: base_amount = self.base_net_amount - already_booked_amount - if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): + if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date: partial_month = flt(date_diff(end_date, start_date)) / flt( date_diff(get_last_day(end_date), get_first_day(start_date)) ) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 913169358f5e707095a758f46ed5aad1d50f678a..b42185e18fb317239a033572f4ecd3708b44466c 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -7,7 +7,17 @@ import re import frappe from frappe import _ -from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate +from frappe.utils import ( + add_days, + add_months, + cint, + cstr, + flt, + formatdate, + get_first_day, + getdate, + today, +) from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, @@ -46,6 +56,8 @@ def get_period_list( year_start_date = getdate(period_start_date) year_end_date = getdate(period_end_date) + year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date + months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity] period_list = [] diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 4a8185f51b684556520823975eab2e57da242400..ee442723abc957bde430458e25c6a59d7717168e 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -336,7 +336,8 @@ def get_conditions(filters): if accounting_dimensions: for dimension in accounting_dimensions: - if not dimension.disabled: + # Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section + if not dimension.disabled and dimension.document_type != "Finance Book": if filters.get(dimension.fieldname): if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): filters[dimension.fieldname] = get_dimension_with_children( diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 07483d744b9831dda54eb64b9c3b651097bd7cb9..19b4f8847f90c0585d622c4d7373537b54a61672 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -169,7 +169,7 @@ def set_total(node, value, complete_list, totals): totals[node["account"]] += value parent = node["parent_account"] - if not parent == "": + if parent != "": return set_total( next(item for item in complete_list if item["account"] == parent), value, complete_list, totals ) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 38060bb5b2e716e01a39db5e17982d9acf84bb6a..e4efefe7f5a2489d4405cc83e7734e75a3810a1d 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -695,7 +695,7 @@ class GrossProfitGenerator(object): def get_average_buying_rate(self, row, item_code): args = row - if not item_code in self.average_buying_rate: + if item_code not in self.average_buying_rate: args.update( { "voucher_type": row.parenttype, diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index ad196a903282017e439209711d6685702f11fb7f..9c6e2d0dc39be988b096785dbc62add19f5c80f1 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -319,7 +319,7 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice`.unrealized_profit_loss_account, - `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, + `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, diff --git a/erpnext/accounts/report/purchase_register/purchase_register.js b/erpnext/accounts/report/purchase_register/purchase_register.js index cad09b76698b461dd34a41539cdcd64c930c01b0..3fd81c62dfcdfe1b81f396e8c5fa7b3bb26f496d 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.js +++ b/erpnext/accounts/report/purchase_register/purchase_register.js @@ -22,6 +22,12 @@ frappe.query_reports["Purchase Register"] = { "fieldtype": "Link", "options": "Supplier" }, + { + "fieldname":"supplier_group", + "label": __("Supplier Group"), + "fieldtype": "Link", + "options": "Supplier Group" + }, { "fieldname":"company", "label": __("Company"), diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index ff8d5fc26a1679bebc20d0b1898ec54bc3f98ff6..cd0f947e09d101b12f9c4fb99fd7d9937f416e98 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -89,6 +89,8 @@ def _execute(filters=None, additional_table_columns=None): "payable_account": inv.credit_to, "mode_of_payment": inv.mode_of_payment, "project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project, + "bill_no": inv.bill_no, + "bill_date": inv.bill_date, "remarks": inv.remarks, "purchase_order": ", ".join(purchase_order), "purchase_receipt": ", ".join(purchase_receipt), @@ -407,6 +409,8 @@ def get_invoices(filters, additional_query_columns): if filters.get("supplier"): query = query.where(pi.supplier == filters.supplier) + if filters.get("supplier_group"): + query = query.where(pi.supplier_group == filters.supplier_group) query = get_conditions(filters, query, "Purchase Invoice") diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 3b94c165e8417ee740035b0c4848a27242990903..85c3e7e5b0cfd3feebde16d3eb36523f370416cb 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -154,7 +154,7 @@ def get_gle_map(documents): ) for d in gle: - if not d.voucher_no in gle_map: + if d.voucher_no not in gle_map: gle_map[d.voucher_no] = [d] else: gle_map[d.voucher_no].append(d) @@ -345,21 +345,16 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts): if filters.get("party"): party = [filters.get("party")] - query = query.where( - ((gle.account.isin(tds_accounts) & gle.against.isin(party))) - | ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))) - | gle.party.isin(party) + jv_condition = gle.against.isin(party) | ( + (gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")) ) else: party = frappe.get_all(filters.get("party_type"), pluck="name") - query = query.where( - ((gle.account.isin(tds_accounts) & gle.against.isin(party))) - | ( - (gle.voucher_type == "Journal Entry") - & ((gle.party_type == filters.get("party_type")) | (gle.party_type == "")) - ) - | gle.party.isin(party) + jv_condition = gle.against.isin(party) | ( + (gle.voucher_type == "Journal Entry") + & ((gle.party_type == filters.get("party_type")) | (gle.party_type == "")) ) + query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party)) return query diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ded02c11c5a3d3411d5f15e409f3d0948e0277cf..ab93ac8bae79f0f7a1de517a03c3410b1419efac 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -659,8 +659,10 @@ def update_reference_in_payment_entry( "total_amount": d.grand_total, "outstanding_amount": d.outstanding_amount, "allocated_amount": d.allocated_amount, - "exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(), - "exchange_gain_loss": d.exchange_gain_loss, + "exchange_rate": d.exchange_rate + if d.difference_amount is not None + else payment_entry.get_exchange_rate(), + "exchange_gain_loss": d.difference_amount, "account": d.account, } @@ -1050,11 +1052,11 @@ def get_outstanding_invoices( if ( min_outstanding and max_outstanding - and not (outstanding_amount >= min_outstanding and outstanding_amount <= max_outstanding) + and (outstanding_amount < min_outstanding or outstanding_amount > max_outstanding) ): continue - if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices: + if d.voucher_type != "Purchase Invoice" or d.voucher_no not in held_invoices: outstanding_invoices.append( frappe._dict( { diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f30be302e2eb03f582b9f4eb783e8c889c28e447..48a2696032af63677d326679dd8fd8cd6a365761 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -238,7 +238,7 @@ class Asset(AccountsController): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) if is_cwip_accounting_enabled(self.asset_category): - if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): + if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice: frappe.throw( _("Please create purchase receipt or purchase invoice for the item {0}").format( self.item_code diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 8d682b113838de8a81adbf3e27373edbf7a67334..5f0cec419c8b4a669de736f6293bafd32669d946 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -294,6 +294,10 @@ class AssetDepreciationSchedule(Document): n == 0 and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) and not self.opening_accumulated_depreciation + and get_updated_rate_of_depreciation_for_wdv_and_dd( + asset_doc, value_after_depreciation, row, False + ) + == row.rate_of_depreciation ): from_date = add_days( asset_doc.available_for_use_date, -1 @@ -567,7 +571,9 @@ def get_depreciation_amount( @erpnext.allow_regional -def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row): +def get_updated_rate_of_depreciation_for_wdv_and_dd( + asset, depreciable_value, fb_row, show_msg=True +): return fb_row.rate_of_depreciation diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 620aad80ed9a8131499eff332f4551ecdec109fc..267fe6f259272e72395b833132b209513f5fab84 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -65,7 +65,7 @@ class AssetMovement(Document): frappe.throw(_("Source and Target Location cannot be same")) if self.purpose == "Receipt": - if not (d.source_location) and not (d.target_location or d.to_employee): + if not (d.source_location) and not d.target_location and not d.to_employee: frappe.throw( _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset) ) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 93437545e9dbb46c2e31f0511df4a0c270ac41fb..2d7c0b3229eb7bdeed41db8e0652b3c102a753ae 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -488,6 +488,20 @@ class PurchaseOrder(BuyingController): return result + def update_ordered_qty_in_so_for_removed_items(self, removed_items): + """ + Updates ordered_qty in linked SO when item rows are removed using Update Items + """ + if not self.is_against_so(): + return + for item in removed_items: + prev_ordered_qty = frappe.get_cached_value( + "Sales Order Item", item.get("sales_order_item"), "ordered_qty" + ) + frappe.db.set_value( + "Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty + ) + def auto_create_subcontracting_order(self): if self.is_subcontracted and not self.is_old_subcontracting_flow: if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"): @@ -579,8 +593,6 @@ def make_purchase_receipt(source_name, target_doc=None): set_missing_values, ) - doc.set_onload("ignore_price_list", True) - return doc @@ -609,7 +621,9 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions target.set_advances() target.set_payment_schedule() - target.credit_to = get_party_account("Supplier", source.supplier, source.company) + target.credit_to = get_party_account( + "Supplier", source.supplier, source.company, tax_category=source.get("tax_category") + ) # @dokos def update_item(obj, target, source_parent): target.amount = flt(obj.amount) - flt(obj.billed_amt) @@ -618,8 +632,12 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions target.amount / flt(obj.rate) if (flt(obj.rate) and flt(obj.billed_amt)) else flt(obj.qty) ) - item = get_item_defaults(target.item_code, source_parent.company) - item_group = get_item_group_defaults(target.item_code, source_parent.company) + item = get_item_defaults( + target.item_code, source_parent.company, source_parent.get("tax_category") + ) # @dokos + item_group = get_item_group_defaults( + target.item_code, source_parent.company, source_parent.get("tax_category") + ) # @dokos target.cost_center = ( obj.cost_center or frappe.db.get_value("Project", obj.project, "cost_center") @@ -660,7 +678,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions postprocess, ignore_permissions=ignore_permissions, ) - doc.set_onload("ignore_price_list", True) return doc diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 69b769340eb5114d277122f485793df590c16a2d..dc43a2c63e0b13a005fa1e10b16f20a42649e5c4 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -79,6 +79,15 @@ class RequestforQuotation(BuyingController): supplier.quote_status = "Pending" self.send_to_supplier() + def before_print(self, settings=None): + """Use the first suppliers data to render the print preview.""" + if self.vendor or not self.suppliers: + # If a specific supplier is already set, via Tools > Download PDF, + # we don't want to override it. + return + + self.update_supplier_part_no(self.suppliers[0].supplier) + def on_cancel(self): self.db_set("status", "Cancelled") diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index f6e77c9974ae4f7c64c1fd1fe383612c71b78c71..4094aa1961e15a96277b8d9ef1b61e21fc83221e 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -486,7 +486,7 @@ "link_fieldname": "party" } ], - "modified": "2023-11-17 09:41:48.149567", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", @@ -549,7 +549,7 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "supplier_name", "track_changes": 1 diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index c519635f51884ee458da9461e3f07f079118543c..ca4c5cdf018f9fb78cb75947b869b5edfe4432e1 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -168,7 +168,6 @@ def make_purchase_order(source_name, target_doc=None): set_missing_values, ) - doclist.set_onload("ignore_price_list", True) return doclist diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index b6e46302ffe80ded057d1170553e271784e33dbc..b88efe13c005e8997e420b7df4708725adb6a62f 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -114,7 +114,7 @@ def prepare_data(data, filters): if filters.get("group_by_po"): po_name = row["purchase_order"] - if not po_name in purchase_order_map: + if po_name not in purchase_order_map: # create an entry row_copy = copy.deepcopy(row) purchase_order_map[po_name] = row_copy diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py index 07187352eb70e6094a5be0883018209c5b75abd6..d43101072bce31484fd7d9fe776b72484f4861bb 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py @@ -110,7 +110,7 @@ def prepare_data(data, filters): for row in data: # item wise map for charts - if not row["item_code"] in item_qty_map: + if row["item_code"] not in item_qty_map: item_qty_map[row["item_code"]] = { "qty": flt(row["stock_qty"], precision), "stock_qty": flt(row["stock_qty"], precision), @@ -127,7 +127,7 @@ def prepare_data(data, filters): if filters.get("group_by_mr"): # consolidated material request map for group by filter - if not row["material_request"] in material_request_map: + if row["material_request"] not in material_request_map: # create an entry with mr as key row_copy = copy.deepcopy(row) material_request_map[row["material_request"]] = row_copy diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 01ff28d8103d7750dd922ed9d96e5645f202ba09..73b7d458303905d16200ee2f6b9c304e10ee1506 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -126,7 +126,7 @@ def prepare_data(supplier_quotation_data, filters): # map for chart preparation of the form {'supplier1': {'qty': 'price'}} supplier = data.get("supplier_name") if filters.get("item_code"): - if not supplier in supplier_qty_price_map: + if supplier not in supplier_qty_price_map: supplier_qty_price_map[supplier] = {} supplier_qty_price_map[supplier][row["qty"]] = row["price"] @@ -169,7 +169,7 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): for supplier in suppliers: entry = supplier_qty_price_map[supplier] for qty in qty_list: - if not qty in data_points_map: + if qty not in data_points_map: data_points_map[qty] = [] if qty in entry: data_points_map[qty].append(entry[qty]) diff --git a/erpnext/change_log/fr/v4/v4_0_0.md b/erpnext/change_log/fr/v4/v4_0_0.md deleted file mode 100644 index 7ee173d67b56c790e9d39a1b171374ad68b84c85..0000000000000000000000000000000000000000 --- a/erpnext/change_log/fr/v4/v4_0_0.md +++ /dev/null @@ -1,3 +0,0 @@ -## Actifs immobilisés - -- Nouvelle option *3 ans* dans les documents de maintenance des actifs \ No newline at end of file diff --git a/erpnext/communication/doctype/communication_medium/communication_medium.json b/erpnext/communication/doctype/communication_medium/communication_medium.json index 92ffc7dac12a9cd777a04dda3acdc43b8c8abc10..4bb8f1bfcab9331d8608ec4a59b1407492878af5 100644 --- a/erpnext/communication/doctype/communication_medium/communication_medium.json +++ b/erpnext/communication/doctype/communication_medium/communication_medium.json @@ -63,7 +63,7 @@ "options": "" } ], - "modified": "2020-10-27 16:22:08.068542", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Communication", "name": "Communication Medium", @@ -83,6 +83,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 } diff --git a/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json index b278ca08f5cb5dacedaf0c3fb19a3fe45cab2be3..e0f5f1b43d22840cc2ca5f8684d81b03c794cff3 100644 --- a/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json +++ b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json @@ -44,13 +44,13 @@ } ], "istable": 1, - "modified": "2019-06-05 12:19:59.994979", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Communication", "name": "Communication Medium Timeslot", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b8f79ddfa565f516b363033906488581a242e0f5..e28ae6b722324c3cfcd36691a3a1a15905d196ef 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -165,6 +165,7 @@ class AccountsController(TransactionBase): self.disable_pricing_rule_on_internal_transfer() self.disable_tax_included_prices_for_internal_transfer() self.set_incoming_rate() + self.init_internal_values() if self.meta.get_field("currency"): self.calculate_taxes_and_totals() @@ -223,6 +224,16 @@ class AccountsController(TransactionBase): self.set_total_in_words() + def init_internal_values(self): + # init all the internal values as 0 on sa + if self.docstatus.is_draft(): + # TODO: Add all such pending values here + fields = ["billed_amt", "delivered_qty"] + for item in self.get("items"): + for field in fields: + if hasattr(item, field): + item.set(field, 0) + def _remove_references_in_unreconcile(self): upe = frappe.qb.DocType("Unreconcile Payment Entries") rows = ( @@ -287,6 +298,16 @@ class AccountsController(TransactionBase): def on_trash(self): self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() + self.remove_serial_and_batch_bundle() + + def remove_serial_and_batch_bundle(self): + bundles = frappe.get_all( + "Serial and Batch Bundle", + filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)}, + ) + + for bundle in bundles: + frappe.delete_doc("Serial and Batch Bundle", bundle.name) def validate_deferred_income_expense_account(self): field_map = { @@ -1006,7 +1027,9 @@ class AccountsController(TransactionBase): order_field = "purchase_order" order_doctype = "Purchase Order" - party_account = get_party_account(party_type, party=party, company=self.company) + party_account = get_party_account( + party_type, party=party, company=self.company, tax_category=self.get("tax_category") + ) # @dokos order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) @@ -1019,30 +1042,54 @@ class AccountsController(TransactionBase): ) res = journal_entries + payment_entries - - if self.doctype == "Sales Invoice" and order_list: - party_type = "Customer" - party = self.customer - party_account = (get_party_account(party_type, party, self.company),) - amount_field = "credit_in_account_currency" - order_doctype = "Sales Invoice" - - invoice_list = list( - set( - frappe.db.sql_list( - """ - SELECT si.name - FROM `tabSales Invoice` si - LEFT JOIN `tabSales Invoice Item` sii - ON sii.parent = si.name - WHERE si.is_down_payment_invoice = 1 - AND sii.sales_order in ({0}) - """.format( - ",".join([f'"{ol}"' for ol in order_list]) + if self.doctype in ("Sales Invoice", "Purchase Invoice") and order_list: + party_type = "Customer" if self.doctype == "Sales Invoice" else "Supplier" + party = self.customer if self.doctype == "Sales Invoice" else self.supplier + party_account = ( + get_party_account(party_type, party, self.company, tax_category=self.get("tax_category")), + ) # @dokos + amount_field = ( + "credit_in_account_currency" + if self.doctype == "Sales Invoice" + else "debit_in_account_currency" + ) + order_doctype = self.doctype + + invoice_list = [] + if self.doctype == "Sales Invoice": + invoice_list = list( + set( + frappe.db.sql_list( + """ + SELECT si.name + FROM `tabSales Invoice` si + LEFT JOIN `tabSales Invoice Item` sii + ON sii.parent = si.name + WHERE si.is_down_payment_invoice = 1 + AND sii.sales_order in ({0}) + """.format( + ",".join([f'"{ol}"' for ol in order_list]) + ) + ) + ) + ) + elif self.doctype == "Purchase Invoice": + invoice_list = list( + set( + frappe.db.sql_list( + """ + SELECT pi.name + FROM `tabPurchase Invoice` pi + LEFT JOIN `tabPurchase Invoice Item` pii + ON pii.parent = pi.name + WHERE pi.is_down_payment_invoice = 1 + AND pii.purchase_order in ({0}) + """.format( + ",".join([f'"{ol}"' for ol in order_list]) + ) ) ) ) - ) down_payments_je = get_advance_journal_entries( party_type, @@ -1078,6 +1125,9 @@ class AccountsController(TransactionBase): return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print")) def validate_advance_entries(self): + if self.get("is_down_payment_invoice"): + return + order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order" order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) @@ -1379,6 +1429,7 @@ class AccountsController(TransactionBase): "exchange_gain_loss": flt(d.get("exchange_gain_loss")), } ) + if not down_payment: lst.append(args) @@ -2242,8 +2293,12 @@ class AccountsController(TransactionBase): secondary_party_type, secondary_party = self.get_party() primary_party_type, primary_party = party_link.primary_role, party_link.primary_party - primary_account = get_party_account(primary_party_type, primary_party, self.company) - secondary_account = get_party_account(secondary_party_type, secondary_party, self.company) + primary_account = get_party_account( + primary_party_type, primary_party, self.company, tax_category=self.get("tax_category") + ) # @dokos + secondary_account = get_party_account( + secondary_party_type, secondary_party, self.company, tax_category=self.get("tax_category") + ) # @dokos jv = frappe.new_doc("Journal Entry") jv.voucher_type = "Journal Entry" @@ -2344,6 +2399,90 @@ class AccountsController(TransactionBase): else: frappe.throw(_("No updates pending for reposting")) + def validate_down_payment_advances(self): + for advance in self.get("advances"): + if ( + flt(advance.allocated_amount) <= flt(advance.advance_amount) + and advance.reference_type == "Payment Entry" + and cint(advance.is_down_payment) + ): + advance.allocated_amount = advance.advance_amount + + def make_down_payment_final_invoice_entries(self, gl_entries): + # In the case of a down payment with multiple payments, associated entries of + # the gl_entries list would be credited/debited multiple times if we didn't make + # sure that the pair of GL Entry was not already processed. + handled_down_payment_entries: set[str] = set() + + for d in self.get("advances"): + if ( + flt(d.allocated_amount) > 0 and d.reference_type == "Payment Entry" and cint(d.is_down_payment) + ): + payment_entry = frappe.get_doc(d.reference_type, d.reference_name) + down_payment_entries = [] + gl_entry = frappe.qb.DocType("GL Entry") + for ref in payment_entry.references: + down_payment_entries.extend( + ( + frappe.qb.from_(gl_entry) + .select( + "name", + "account", + "against", + "debit", + "debit_in_account_currency", + "credit", + "credit_in_account_currency", + ) + .where(gl_entry.voucher_type == ref.reference_doctype) + .where(gl_entry.voucher_no == ref.reference_name) + .where(gl_entry.is_cancelled == 0) + .for_update() + ).run(as_dict=1) + ) + + down_payment_accounts = [ + entry["against"] + for entry in down_payment_entries + if (entry["account"] == self.debit_to if self.doctype == "Sales Invoice" else self.credit_to) + ] + for down_payment_entry in down_payment_entries: + if down_payment_entry["account"] in down_payment_accounts and not [ + x for x in gl_entries if x["account"] == down_payment_entry["account"] + ]: + gl_entries.append( + self.get_gl_dict( + { + "account": down_payment_entry["account"], + "against": down_payment_entry["account"], + "party_type": "Customer" if self.doctype == "Sales Invoice" else "Supplier", + "party": self.customer if self.doctype == "Sales Invoice" else self.supplier, + "accounting_journal": self.accounting_journal, + }, + self.currency, + ) + ) + + for down_payment_entry in down_payment_entries: + if down_payment_entry["name"] in handled_down_payment_entries: + # Skip this down payment entry if it has already been handled, + # possibly for a previous payment entry. + continue + handled_down_payment_entries.add(down_payment_entry["name"]) + + for gl_entry in gl_entries: + if gl_entry["account"] == down_payment_entry["account"]: + if gl_entry["account"] not in down_payment_accounts: + gl_entry["debit"] -= down_payment_entry["debit"] + gl_entry["debit_in_account_currency"] -= down_payment_entry["debit_in_account_currency"] + gl_entry["credit"] -= down_payment_entry["credit"] + gl_entry["credit_in_account_currency"] -= down_payment_entry["credit_in_account_currency"] + else: + gl_entry["debit"] += down_payment_entry["credit"] + gl_entry["debit_in_account_currency"] += down_payment_entry["credit_in_account_currency"] + gl_entry["credit"] += down_payment_entry["debit"] + gl_entry["credit_in_account_currency"] += down_payment_entry["debit_in_account_currency"] + @frappe.whitelist() def get_tax_rate(account_head): @@ -3024,6 +3163,9 @@ def validate_and_delete_children(parent, data) -> bool: d.cancel() d.delete() + if parent.doctype == "Purchase Order": + parent.update_ordered_qty_in_so_for_removed_items(deleted_children) + # need to update ordered qty in Material Request first # bin uses Material Request Items to recalculate & update parent.update_prevdoc_status() @@ -3313,7 +3455,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if parent_doctype == "Purchase Order": update_last_purchase_rate(parent, is_submit=1) - parent.update_prevdoc_status() + + if any_qty_changed or items_added_or_removed or any_conversion_factor_changed: + parent.update_prevdoc_status() + parent.update_requested_qty() parent.update_ordered_qty() parent.update_ordered_and_reserved_qty() diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 817ef5cd86a4da5708d580ed4ea85c0e00d23413..18c5e6c84d6df669e2c53638d850889f5818529b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -118,6 +118,7 @@ class BuyingController(SubcontractingController): "company": self.company, "voucher_type": self.doctype, "voucher_no": self.name, + "voucher_detail_no": row.name, }, raise_error_if_no_rate=False, ) @@ -371,6 +372,7 @@ class BuyingController(SubcontractingController): "voucher_type": self.doctype, "voucher_no": self.name, "allow_zero_valuation": d.get("allow_zero_valuation"), + "voucher_detail_no": d.name, }, raise_error_if_no_rate=False, ) @@ -440,7 +442,7 @@ class BuyingController(SubcontractingController): if allow_to_edit_stock_qty: d.stock_qty = flt(d.stock_qty, d.precision("stock_qty")) - if d.get("received_stock_qty"): + if d.get("received_stock_qty") and d.meta.get_field("received_stock_qty"): d.received_stock_qty = flt(d.received_stock_qty, d.precision("received_stock_qty")) def validate_purchase_return(self): diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 842e9e601642e4f47f0625166d5e15f8bc23a03b..bb16d8beceb0777890ddfabbd5a062f279e72bd4 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -211,7 +211,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = meta.get_search_fields() columns = "" - extra_searchfields = [field for field in searchfields if not field in ["name", "description"]] + extra_searchfields = [field for field in searchfields if field not in ["name", "description"]] if extra_searchfields: columns += ", " + ", ".join(extra_searchfields) @@ -222,7 +222,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = searchfields + [ field - for field in [searchfield or "name", "item_code", "item_group", "item_name"] + for field in [ + searchfield or "name", + "item_code", + "item_group", + "item_name", + ] if field not in searchfields ] searchfields = " or ".join(field + " like %(txt)s" for field in searchfields) @@ -862,7 +867,7 @@ def get_fields(doctype, fields=None): meta = frappe.get_meta(doctype) fields.extend(meta.get_search_fields()) - if meta.title_field and not meta.title_field.strip() in fields: + if meta.title_field and meta.title_field.strip() not in fields: fields.insert(1, meta.title_field.strip()) return unique(fields) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 8f79ae09efce684c443f489ae6ae61f645c0308e..44cc8cf4b7d58b09b1168e9b44d8605ce7c160ca 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -586,8 +586,6 @@ def make_return_doc( set_missing_values, ) - doclist.set_onload("ignore_price_list", True) - return doclist diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 16ae6135023c2fc58f55cd5ece6d3c4ce934bff5..51b123b747cfd26bbc8f023d69b46045642a4a6a 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -444,6 +444,7 @@ class SellingController(StockController): "company": self.company, "voucher_type": self.doctype, "voucher_no": self.name, + "voucher_detail_no": d.name, "allow_zero_valuation": d.get("allow_zero_valuation"), }, raise_error_if_no_rate=False, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 07cad380fe2a4aa7922d49db17a0b21b7f282522..1b2f0210cdd521b336dbb9e858c85f009d7da4bc 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -642,7 +642,7 @@ class StockController(AccountsController): ) qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus") - if not qa_docstatus == 1: + if qa_docstatus != 1: link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection) msg = ( f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}" diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 3d55a087bd8dc08e1fbe485666ddc64995552897..f88e3ebf534efc94497e7511f475e6fbcb5b9fc0 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -880,6 +880,7 @@ class SubcontractingController(StockController): "posting_date": self.posting_date, "posting_time": self.posting_time, "qty": -1 * item.consumed_qty, + "voucher_detail_no": item.name, "serial_and_batch_bundle": item.serial_and_batch_bundle, } ) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2f5da34674ac0e52e9f86a955fcfe9be14c0de19..b155f4d65ffba104513aa49b8b023a11373faa3b 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -34,7 +34,7 @@ class Appointment(Document): "Appointment", filters={"scheduled_time": self.scheduled_time} ) number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents") - if not number_of_agents == 0: + if number_of_agents != 0: if number_of_appointments_in_same_slot >= number_of_agents: frappe.throw(_("Time slot is not available")) # Link lead @@ -89,7 +89,7 @@ class Appointment(Document): cal_event.save(ignore_permissions=True) def set_verified(self, email): - if not email == self.customer_email: + if email != self.customer_email: frappe.throw(_("Email verification failed.")) # Create new lead self.create_lead_and_link() diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.json b/erpnext/crm/doctype/crm_settings/crm_settings.json index 81f175c0ae629a336c6c95208d0a2aeec087a3a8..66cf9c1e9483f19e38f577d85aed49bc54bde87c 100644 --- a/erpnext/crm/doctype/crm_settings/crm_settings.json +++ b/erpnext/crm/doctype/crm_settings/crm_settings.json @@ -12,6 +12,7 @@ "column_break_4", "auto_creation_of_contact", "opportunity_section", + "allow_prospect_creation", "close_opportunity_after_days", "column_break_9", "quotation_section", @@ -73,25 +74,31 @@ "fieldname": "quotation_section", "fieldtype": "Section Break", "label": "Quotation" -}, -{ - "fieldname": "section_break_13", - "fieldtype": "Section Break", - "label": "Other Settings" -}, -{ - "default": "0", - "description": "All the Comments and Emails will be copied from one document to another newly created document(Lead -> Opportunity -> Quotation) throughout the CRM documents.", - "fieldname": "carry_forward_communication_and_comments", - "fieldtype": "Check", - "label": "Carry Forward Communication and Comments" + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "label": "Other Settings" + }, + { + "default": "0", + "description": "All the Comments and Emails will be copied from one document to another newly created document(Lead -> Opportunity -> Quotation) throughout the CRM documents.", + "fieldname": "carry_forward_communication_and_comments", + "fieldtype": "Check", + "label": "Carry Forward Communication and Comments" + }, + { + "default": "1", + "fieldname": "allow_prospect_creation", + "fieldtype": "Check", + "label": "Create a prospect when creating an opportunity from a lead" } ], "icon": "fa fa-cog", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-06-06 11:22:08.464253", + "modified": "2023-12-15 14:50:14.084318", "modified_by": "Administrator", "module": "CRM", "name": "CRM Settings", @@ -105,26 +112,26 @@ "role": "System Manager", "share": 1, "write": 1 -}, -{ - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Sales Manager", - "share": 1, - "write": 1 -}, -{ - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Sales Master Manager", - "share": 1, - "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Sales Master Manager", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.py b/erpnext/crm/doctype/crm_settings/crm_settings.py index 98cf7d845e07c8e154ea121151a790ed7c9c9dc7..fcdb19fd1d40b71bbee4fe6f381ce16240395a16 100644 --- a/erpnext/crm/doctype/crm_settings/crm_settings.py +++ b/erpnext/crm/doctype/crm_settings/crm_settings.py @@ -6,5 +6,22 @@ from frappe.model.document import Document class CRMSettings(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 + + allow_lead_duplication_based_on_emails: DF.Check + allow_prospect_creation: DF.Check + auto_creation_of_contact: DF.Check + campaign_naming_by: DF.Literal["Campaign Name", "Naming Series"] + carry_forward_communication_and_comments: DF.Check + close_opportunity_after_days: DF.Int + default_valid_till: DF.Data | None + # end: auto-generated types + def validate(self): frappe.db.set_default("campaign_naming_by", self.get("campaign_naming_by", "")) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 5a1b3e09ae817ceeb34c3fa1175e6ef731bf030e..a2a5d6d9e920fd8c83c57c0e72ee6f70e937d476 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -151,13 +151,13 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller if (!existing_prospect) { var fields = [ { - "label": "Create Prospect", + "label": __("Create Prospect"), "fieldname": "create_prospect", "fieldtype": "Check", - "default": 1 + "default": frappe.boot.sysdefaults.allow_prospect_creation }, { - "label": "Prospect Name", + "label": __("Prospect Name"), "fieldname": "prospect_name", "fieldtype": "Data", "default": frm.doc.company_name, @@ -176,7 +176,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller if (!existing_contact) { fields.push( { - "label": "Create Contact", + "label": __("Create Contact"), "fieldname": "create_contact", "fieldtype": "Check", "default": "1" diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 2b92df5e703b6e1e0e8ce4c061b4e8560ba47152..0bc7a3f2c2657c6a8969c1bc67819858cd9eee86 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -520,7 +520,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2023-08-28 22:28:00.104413", + "modified": "2023-12-01 18:46:49.468526", "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -581,6 +581,7 @@ ], "search_fields": "lead_name,lead_owner,status", "sender_field": "email_id", + "sender_name_field": "lead_name", "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d22cc5548a6522c23f86f6857da1e1b90be71505..f82904a01e9c4702ef0e28d672732470ff64997b 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -16,6 +16,7 @@ from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_ema from erpnext.accounts.party import set_taxes from erpnext.controllers.selling_controller import SellingController from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events +from erpnext.selling.doctype.customer.customer import parse_full_name class Lead(SellingController, CRMNote): @@ -39,7 +40,7 @@ class Lead(SellingController, CRMNote): if self.source == "Existing Customer" and self.customer: contact = frappe.db.get_value( "Dynamic Link", - {"link_doctype": "Customer", "link_name": self.customer}, + {"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer}, "parent", ) if contact: @@ -47,6 +48,10 @@ class Lead(SellingController, CRMNote): return self.contact_doc = self.create_contact() + # leads created by email inbox only have the full name set + if self.lead_name and not any([self.first_name, self.middle_name, self.last_name]): + self.first_name, self.middle_name, self.last_name = parse_full_name(self.lead_name) + def after_insert(self): self.link_to_contact() diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index e5ae1e11f7f5418c6ccf909536371ed4105a4b5b..dbc9a6dd60277a7708101a9f8351699b3782ec84 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.desk.doctype.tag.tag import add_tag from frappe.model.document import Document -from frappe.utils import add_months, formatdate, getdate, sbool, today, cint +from frappe.utils import add_months, cint, formatdate, getdate, sbool, today from plaid.errors import ItemError from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account @@ -256,8 +256,8 @@ def new_bank_transaction(transaction): "doctype": "Bank Transaction", "date": getdate(transaction["date"]), "bank_account": bank_account, - "debit": deposit, - "credit": withdrawal, + "credit": deposit, + "debit": withdrawal, "currency": transaction["iso_currency_code"], "reference_number": transaction["transaction_id"], "transaction_type": ( diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index f72b47250b602bbf4ca65d4cea78fcc3439a5de5..5a66708837e297ef6160878534a4dd6364887940 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -16,7 +16,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key="secret"): hmac.new(settings.get(secret_key).encode("utf8"), frappe.request.data, hashlib.sha256).digest() ) - if frappe.request.data and not sig == bytes(frappe.get_request_header(hmac_key).encode()): + if frappe.request.data and sig != bytes(frappe.get_request_header(hmac_key).encode()): frappe.throw(_("Unverified Webhook Data")) frappe.set_user(settings.modified_by) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 606676bb9470975abac2857afe2e3002b1ec9968..a42d87fb7cdeabc05b9d24b06dcf215c5535177b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -721,9 +721,14 @@ additional_timeline_content = { extend_bootinfo = [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", + "erpnext.startup.boot.bootinfo", ] +default_log_clearing_doctypes = { + "Repost Item Valuation": 60, +} + jinja = { "filters": [ "frappe.contacts.doctype.address.address.get_condensed_address", diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 0135a4f9712af5105e36eb494941bad68e462564..9d29fdb603788703c1e797282c661dbb6e003c79 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -64,7 +64,7 @@ def make_order(source_name): def update_item(source, target, source_parent): target_qty = source.get("qty") - source.get("ordered_qty") - target.qty = target_qty if not flt(target_qty) < 0 else 0 + target.qty = target_qty if flt(target_qty) >= 0 else 0 item = get_item_defaults(target.item_code, source_parent.company) if item: target.item_name = item.get("item_name") diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 229f8853fff08cb796b348e64c00237f3f19c193..dca1f58ebd3dcc1db2e42e6a3c8bc9c182a8ce52 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1316,7 +1316,7 @@ def get_bom_diff(bom1, bom2): # check for deletions for d in old_value: - if not d.get(identifier) in new_row_by_identifier: + if d.get(identifier) not in new_row_by_identifier: out.removed.append([df.fieldname, d.as_dict()]) return out @@ -1330,15 +1330,20 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): order_by = "idx desc, name, item_name" - fields = ["name", "item_group", "item_name", "description"] + fields = ["name", "item_name", "item_group", "description"] fields.extend( - [field for field in searchfields if not field in ["name", "item_group", "description"]] + [field for field in searchfields if field not in ["name", "item_group", "description"]] ) searchfields = searchfields + [ field - for field in [searchfield or "name", "item_code", "item_group", "item_name"] - if not field in searchfields + for field in [ + searchfield or "name", + "item_code", + "item_group", + "item_name", + ] + if field not in searchfields ] query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index f303531aee1c22fd9a8a593352f412341e5b4d13..ef30a3ce1f99995d3d86b963dbedcad5038b906b 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -185,8 +185,7 @@ class JobCard(Document): # override capacity for employee production_capacity = 1 - overlap_count = self.get_overlap_count(time_logs) - if time_logs and production_capacity > overlap_count: + if not self.has_overlap(production_capacity, time_logs): return {} if self.workstation_type and time_logs: @@ -196,16 +195,15 @@ class JobCard(Document): return time_logs[-1] - @staticmethod - def get_overlap_count(time_logs): - count = 1 + def has_overlap(self, production_capacity, time_logs): + overlap = False + if production_capacity == 1 and len(time_logs) > 0: + return True # Check overlap exists or not between the overlapping time logs with the current Job Card - for idx, row in enumerate(time_logs): - next_idx = idx - if idx + 1 < len(time_logs): - next_idx = idx + 1 - next_row = time_logs[next_idx] + for row in time_logs: + count = 1 + for next_row in time_logs: if row.name == next_row.name: continue @@ -225,7 +223,10 @@ class JobCard(Document): ): count += 1 - return count + if count > production_capacity: + return True + + return overlap def get_time_logs(self, args, doctype, check_next_available_slot=False): jc = frappe.qb.DocType("Job Card") diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json index a7102d7d2377e10536351b9049060704abba7c0c..1dae06febd8f853f9317aaf0295b487467b6ff06 100644 --- a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json @@ -64,7 +64,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-23 14:30:00.970916", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Time Log", @@ -72,6 +72,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index e2ec1fa2befdbc5ca4208893218fdb1a0c6bd14c..0bb1043ba109e83cf443b14a498545f09a12a010 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -429,7 +429,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-11-03 14:08:11.928027", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", @@ -451,6 +451,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 6efb76290504c54a520489521bba8da1aadf2e09..6ceb1123879801befc587947a942193d1bb059cc 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -740,7 +740,7 @@ class ProductionPlan(Document): key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) - if not key in material_request_map: + if key not in material_request_map: # make a new MR for the combination material_request_map[key] = frappe.new_doc("Material Request") material_request = material_request_map[key] @@ -1522,19 +1522,23 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): ) locations = get_available_item_locations( - item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True + item.get("item_code"), + warehouses, + item.get("quantity") * item.get("conversion_factor"), + company, + ignore_validation=True, ) required_qty = item.get("quantity") + if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"): + # Convert qty to stock UOM + required_qty = required_qty * item.get("conversion_factor") + # get available material by transferring to production warehouse for d in locations: if required_qty <= 0: return - conversion_factor = 1.0 - if purchase_uom != stock_uom and purchase_uom == item["uom"]: - conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"]) - new_dict = copy.deepcopy(item) quantity = required_qty if d.get("qty") > required_qty else d.get("qty") @@ -1544,10 +1548,11 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): "material_request_type": "Material Transfer", "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM "from_warehouse": d.get("warehouse"), + "conversion_factor": 1.0, } ) - required_qty -= quantity / conversion_factor + required_qty -= quantity new_mr_items.append(new_dict) # raise purchase request for remaining qty @@ -1559,7 +1564,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): required_qty = ceil(required_qty) - item["quantity"] = required_qty + item["quantity"] = required_qty / item.get("conversion_factor") new_mr_items.append(item) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index dd32c343588a92512cabc4b7ec966fe906c5d47b..cc9d9a0311e48e0312d4465d2dbff5f2c7514b65 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1283,12 +1283,14 @@ class TestProductionPlan(FrappeTestCase): for row in items: row = frappe._dict(row) if row.material_request_type == "Material Transfer": + self.assertTrue(row.uom == row.stock_uom) self.assertTrue(row.from_warehouse in [wh1, wh2]) self.assertEqual(row.quantity, 2) if row.material_request_type == "Purchase": + self.assertTrue(row.uom != row.stock_uom) self.assertTrue(row.warehouse == mrp_warhouse) - self.assertEqual(row.quantity, 12) + self.assertEqual(row.quantity, 12.0) def test_mr_qty_for_same_rm_with_different_sub_assemblies(self): from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom @@ -1404,6 +1406,58 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) + def test_material_request_qty_purchase_and_material_transfer(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + bom_item = make_item( + properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"} + ).name + + store_warehouse = create_warehouse("Store Warehouse", company="_Test Company") + rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company") + + make_stock_entry( + item_code=bom_item, + qty=60, + target=store_warehouse, + rate=99, + ) + + if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}): + doc = frappe.get_doc("Item", bom_item) + doc.append("uoms", {"uom": "Nos", "conversion_factor": 10}) + doc.save() + + make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan( + item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1 + ) + + pln.for_warehouse = rm_warehouse + items = get_items_for_material_requests( + pln.as_dict(), warehouses=[{"warehouse": store_warehouse}] + ) + + for row in items: + self.assertEqual(row.get("quantity"), 10.0) + self.assertEqual(row.get("material_request_type"), "Material Transfer") + self.assertEqual(row.get("uom"), "_Test UOM 1") + self.assertEqual(row.get("from_warehouse"), store_warehouse) + self.assertEqual(row.get("conversion_factor"), 1.0) + + items = get_items_for_material_requests( + pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}] + ) + + for row in items: + self.assertEqual(row.get("quantity"), 1.0) + self.assertEqual(row.get("material_request_type"), "Purchase") + self.assertEqual(row.get("uom"), "Nos") + self.assertEqual(row.get("conversion_factor"), 10.0) + def create_production_plan(**args): """ diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 437c99f14f595325a11aa51b5afcfc663d533798..583b0a323f1bb6acb1baf5b0f4018afa807d4310 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -592,7 +592,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-08-11 18:35:49.852069", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", @@ -623,7 +623,7 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "production_item", "track_changes": 1, diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 36a0cae5cca574b72fc9705d003a20c0123c05c9..dc3dd5c4ef7306bee6e831b6bb3a27f850c055b9 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -856,7 +856,7 @@ class WorkOrder(Document): validate_end_of_life(self.production_item) def validate_qty(self): - if not self.qty > 0: + if self.qty <= 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) if ( @@ -883,7 +883,7 @@ class WorkOrder(Document): max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0) - if not max_qty > 0: + if max_qty <= 0: frappe.throw( _("Cannot produce more item for {0}").format(self.production_item), OverProductionError ) @@ -894,7 +894,7 @@ class WorkOrder(Document): ) def validate_transfer_against(self): - if not self.docstatus == 1: + if self.docstatus != 1: # let user configure operations until they're ready to submit return if not self.operations: @@ -907,7 +907,7 @@ class WorkOrder(Document): def validate_operation_time(self): for d in self.operations: - if not d.time_in_mins > 0: + if d.time_in_mins <= 0: frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) def update_required_items(self): diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index 045956215003d27b973fda4064539b81a73f09fa..da8673894b9e34e1db1cb40122b4f8528a2594f7 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -150,7 +150,7 @@ "icon": "uil uil-wrench", "idx": 1, "links": [], - "modified": "2023-02-24 16:58:10.000588", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation", @@ -173,7 +173,7 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e41759409a8fffc8915a69b709a851df46df6c43..7c4e795930c11c9f05da603b2c3aa5f200b55d2b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -311,7 +311,6 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v13_0.drop_unused_sle_index_parts 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 erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request erpnext.patches.v14_0.update_entry_type_for_journal_entry erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers @@ -354,6 +353,7 @@ erpnext.patches.v15_0.set_reserved_stock_in_bin erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation erpnext.patches.v14_0.update_zero_asset_quantity_field execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") +execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format")) # @dokos erpnext.patches.dokos.v3_0.add_expiration_date_to_booking_legder diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py index 1c6654e57acdfa373ebad4ccfbec3dd75a6db368..27810d7abe9874714a2a7bab35978e3732a053bf 100644 --- a/erpnext/patches/v12_0/set_task_status.py +++ b/erpnext/patches/v12_0/set_task_status.py @@ -10,7 +10,7 @@ def execute(): ) if property_setter_name: property_setter = frappe.get_doc("Property Setter", property_setter_name) - if not "Completed" in property_setter.value: + if "Completed" not in property_setter.value: property_setter.value = property_setter.value + "\nCompleted" property_setter.save() diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py index 84c683acd2c8a142b459c208500d2a0a55bc62ac..cf9e185a659be98c05a7fe7dc648f843637cd044 100644 --- a/erpnext/patches/v13_0/update_sla_enhancements.py +++ b/erpnext/patches/v13_0/update_sla_enhancements.py @@ -46,7 +46,7 @@ def execute(): {"response_time": response_time, "resolution_time": resolution_time}, ) if priority.parenttype == "Service Level": - if not priority.parent in priority_dict: + if priority.parent not in priority_dict: priority_dict[priority.parent] = [] priority_dict[priority.parent].append(priority) diff --git a/erpnext/patches/v14_0/setup_clear_repost_logs.py b/erpnext/patches/v14_0/setup_clear_repost_logs.py deleted file mode 100644 index be9ddcab7ad9c728ed0f7fcafef62638021509c5..0000000000000000000000000000000000000000 --- a/erpnext/patches/v14_0/setup_clear_repost_logs.py +++ /dev/null @@ -1,8 +0,0 @@ -# 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() diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index 9a2a39fb78c120fd8ea1aea95a2a81ec7b181b49..ddce997d11cdb769dcf9979282e28ce42065f023 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -3,6 +3,7 @@ import frappe def execute(): frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule") + frappe.reload_doc("assets", "doctype", "Asset Finance Book") assets = get_details_of_draft_or_submitted_depreciable_assets() @@ -86,6 +87,8 @@ def get_asset_finance_books_map(): afb.frequency_of_depreciation, afb.rate_of_depreciation, afb.expected_value_after_useful_life, + afb.daily_prorata_based, + afb.shift_based, ) .where(asset.docstatus < 2) .orderby(afb.idx) diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js index c5ae3df126a5508c64ee0238a0f42b9b75ba44fe..77908ea2221917d092395869f51b537a5d31058c 100644 --- a/erpnext/portal/doctype/homepage/homepage.js +++ b/erpnext/portal/doctype/homepage/homepage.js @@ -2,14 +2,6 @@ // For license information, please see license.txt frappe.ui.form.on('Homepage', { - setup: function(frm) { - frm.fields_dict["products"].grid.get_field("item").get_query = function() { - return { - filters: {'published': 1} - } - } - }, - refresh: function(frm) { frm.add_custom_button(__('Set Meta Tags'), () => { frappe.utils.set_meta_tag('home'); diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index 18b2df9aa4c980a26c03ecdcdd4b0bc2ad10fcd8..8b3e91adc6861624db93c844d739a58d2b045c92 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -50,7 +50,7 @@ def create_customer_or_supplier(): party = frappe.new_doc(doctype) fullname = frappe.utils.get_fullname(user) - if not doctype == "Customer": + if doctype != "Customer": party.update( { "supplier_name": fullname, diff --git a/erpnext/projects/doctype/activity_type/activity_type.json b/erpnext/projects/doctype/activity_type/activity_type.json index 41ee57e0f28b2ef4f4629afb65cda9522bf00f8a..1eea1547a22263aa98b56b0bd33b57584aba69e9 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.json +++ b/erpnext/projects/doctype/activity_type/activity_type.json @@ -47,7 +47,7 @@ "icon": "uil uil-stopwatch", "idx": 1, "links": [], - "modified": "2023-02-20 16:33:50.446725", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Projects", "name": "Activity Type", @@ -83,6 +83,6 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/project/project_calendar.js b/erpnext/projects/doctype/project/project_calendar.js new file mode 100644 index 0000000000000000000000000000000000000000..ce669acc238c2db2d24832063b70bb52f86ff6ef --- /dev/null +++ b/erpnext/projects/doctype/project/project_calendar.js @@ -0,0 +1,16 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.views.calendar["Project"] = { + field_map: { + "start": "expected_start_date", + "end": "expected_end_date", + "id": "name", + "title": "project_name", + "allDay": "allDay", + "progress": "progress" + }, + gantt: true, + get_events_method: "frappe.desk.calendar.get_events", + default_resource: "customer" +} diff --git a/erpnext/projects/doctype/task_type/task_type.json b/erpnext/projects/doctype/task_type/task_type.json index b29f9a482995ffb4e1da739816cabe02af1564db..9261e285c7942e0e12c6f61cb407c1e3d0e2818c 100644 --- a/erpnext/projects/doctype/task_type/task_type.json +++ b/erpnext/projects/doctype/task_type/task_type.json @@ -21,7 +21,7 @@ } ], "links": [], - "modified": "2022-08-29 17:46:41.342979", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Projects", "name": "Task Type", @@ -39,32 +39,32 @@ "role": "System Manager", "share": 1, "write": 1 -}, -{ - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects Manager", - "share": 1, - "write": 1 -}, -{ - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects User", - "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1 } ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 348e7e22c821fdc14b467e56a9133e03013c90b4..71f2d622a4fb5692206edadd0295482d6878915f 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -36,14 +36,14 @@ erpnext.buying = { // no idea where me is coming from if(this.frm.get_field('shipping_address')) { - this.frm.set_query("shipping_address", function() { - if(me.frm.doc.customer) { + this.frm.set_query("shipping_address", () => { + if(this.frm.doc.customer) { return { query: 'frappe.contacts.doctype.address.address.address_query', - filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } + filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer } }; } else - return erpnext.queries.company_address_query(me.frm.doc) + return erpnext.queries.company_address_query(this.frm.doc) }); } } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0424df58db113f1362ff028668177864941abac3..bfa3ddad4413cfc3a00a27fef1748625ea5fb6e8 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1,6 +1,12 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +const gross_profit_calculation_rules = { + "Valuation Rate": "valuation_rate", + "Last Purchase Rate": "last_purchase_rate", + "Supplier Cost Price": "cost_price" +} + erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals { setup() { super.setup() @@ -374,7 +380,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe onload_post_render() { if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length - && !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) { + && !this.frm.doc.__onload?.load_after_mapping) { frappe.after_ajax(() => this.apply_default_taxes()); } else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"] && !this.frm.doc.is_pos) { @@ -398,6 +404,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } scan_barcode() { + frappe.flags.dialog_set = false; const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm}); barcode_scanner.process_scan(); } @@ -833,7 +840,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe args: { company: me.frm.doc.company, party_type: party_type, - party: party + party: party, + tax_category: me.frm.doc.tax_category // @dokos }, callback: function(r) { if(!r.exc && r.message) { @@ -970,9 +978,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let me = this; this.set_dynamic_labels(); let company_currency = this.get_company_currency(); - // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc + // Added `load_after_mapping` to determine if document is loading after mapping from another doc if(this.frm.doc.currency && this.frm.doc.currency !== company_currency - && !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) { + && !this.frm.doc.__onload?.load_after_mapping) { + this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency, function(exchange_rate) { if(exchange_rate != me.frm.doc.conversion_rate) { @@ -1003,7 +1012,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } if(flt(this.frm.doc.conversion_rate)>0.0) { - if(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) { + if(this.frm.doc.__onload?.load_after_mapping) { this.calculate_taxes_and_totals(); } else if (!this.in_apply_price_list){ this.apply_price_list(); @@ -1090,9 +1099,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.set_dynamic_labels(); var company_currency = this.get_company_currency(); - // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc + // Added `load_after_mapping` to determine if document is loading after mapping from another doc if(this.frm.doc.price_list_currency !== company_currency && - !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) { + !this.frm.doc.__onload?.load_after_mapping) { this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency, function(exchange_rate) { me.frm.set_value("plc_conversion_rate", exchange_rate); @@ -1485,7 +1494,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } // Target doc created from a mapped doc - if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) { + if (this.frm.doc.__onload?.load_after_mapping) { // Calculate totals even though pricing rule is not applied. // `apply_pricing_rule` is triggered due to change in data which most likely contributes to Total. if (calculate_taxes_and_totals) me.calculate_taxes_and_totals(); @@ -1834,6 +1843,34 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if(me.frm.updating_party_details) return; frappe.run_serially([ + () => { + // @dokos + if (in_list(["Sales Invoice", "Purchase Invoice"], me.frm.doc.doctype)) { + if(me.frm.doc.doctype=="Sales Invoice") { + var party_type = "Customer"; + var party_account_field = 'debit_to'; + } else { + var party_type = "Supplier"; + var party_account_field = 'credit_to'; + } + const party = me.frm.doc[frappe.model.scrub(party_type)]; + if(party && me.frm.doc.company) { + frappe.call({ + method: "erpnext.accounts.party.get_party_account", + args: { + company: me.frm.doc.company, + party_type: party_type, + party: party, + tax_category: me.frm.doc.tax_category, + } + }).then(r => { + if(!r.exc && r.message) { + me.frm.set_value(party_account_field, r.message); + } + }) + }; + } + }, () => this.update_item_tax_map(), () => erpnext.utils.set_taxes(this.frm, "tax_category"), ]); @@ -1950,9 +1987,36 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } set_gross_profit(item) { - if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype) && item.valuation_rate) { - var rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); - item.gross_profit = flt(((rate - item.valuation_rate) * item.stock_qty), precision("amount", item)); + if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype)) { + const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule] || "valuation_rate"; + const gross_profit_basis = item[gross_profit_basis_field]; + + const rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); + const gross_profit = flt(((rate - gross_profit_basis) * item.stock_qty), precision("amount", item)); + frappe.model.set_value(item.doctype, item.name, "gross_profit", gross_profit); + } + } + + gross_profit_percentage(doc, cdt, cdn) { + if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype)) { + const item = locals[cdt][cdn]; + if (item.gross_profit_percentage && item.gross_profit_calculation_rule) { + const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule] + const rate = item[gross_profit_basis_field] * (1 + (item.gross_profit_percentage / 100)) + + if (flt(item.rate, precision("rate", item)) != flt(rate, precision("rate", item))) { + frappe.model.set_value(cdt, cdn, "rate", flt(rate)); + } + } + } + } + + gross_profit_calculation_rule(doc, cdt, cdn) { + const item = frappe.get_doc(cdt, cdn); + if (item.gross_profit_percentage) { + this.gross_profit_percentage(doc, cdt, cdn) + } else { + this.set_gross_profit(item); } } diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 6431d8f184f402b61e4df7c5c19477dd6d094f9d..8b023a80b55bba374c8fefe59ae84886ca347403 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -2,10 +2,16 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), - "formatter": function(value, row, column, data, default_formatter) { + "formatter": function(value, row, column, data, default_formatter, filter) { if (data && column.fieldname=="account") { value = data.account_name || value; + if (filter && filter?.text && filter?.type == "contains") { + if (!value.toLowerCase().includes(filter.text)) { + return value; + } + } + if (data.account) { column.link_onclick = "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; diff --git a/erpnext/public/js/portal-calendar.bundle.js b/erpnext/public/js/portal-calendar.bundle.js index 5d4f829e236e045f80e05f4cc21974c46cf00afd..54bb4f1f45ee3868c3489f61e5fe51ca93f76fc0 100644 --- a/erpnext/public/js/portal-calendar.bundle.js +++ b/erpnext/public/js/portal-calendar.bundle.js @@ -1,10 +1,2 @@ -import "frappe/public/js/frappe/ui/floating_button/back_button"; - -frappe.back_button_specs["booking-search"] = { - label: __("Back to Search"), - icon: "search", -} -const back_button = new frappe.ui.BackButton() - import "./calendar_pages/event_slots_calendar.js" import "./calendar_pages/item_booking_calendar.js" diff --git a/erpnext/public/js/portal.bundle.js b/erpnext/public/js/portal.bundle.js index bca8ab6187149127a7aa685557ebc999c0c2f37d..36cd5e744ee14426b50f7fd1c0183f2dac86cf73 100644 --- a/erpnext/public/js/portal.bundle.js +++ b/erpnext/public/js/portal.bundle.js @@ -1,2 +1 @@ -import "./calendar_pages/item_booking_calendar.js" -import "./venue_registration_form.js" \ No newline at end of file +import "./calendar_pages/item_booking_calendar.js"; diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index a0752bb42dbfce13898fd6b302d7fa85efac00e6..8f21ec95713239454bde002cd100c47c80dab213 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -8,7 +8,7 @@ $.extend(erpnext, { if(!company && cur_frm) company = cur_frm.doc.company; if(company) - return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency; + return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency; else return frappe.boot.sysdefaults.currency; }, @@ -1080,7 +1080,7 @@ function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) { } function get_time_left(timestamp, agreement_status) { - const diff = moment(timestamp).diff(moment()); + const diff = moment(timestamp).diff(frappe.datetime.system_datetime(true)); const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed'; let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green'; return {'diff_display': diff_display, 'indicator': indicator}; diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 1f75cf0a953464a81729f723f68820a2be911f40..140d45aca84ebc79a69325ba04891e6ed0e5218e 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -114,13 +114,13 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { frappe.run_serially([ () => this.set_selector_trigger_flag(data), + () => this.set_serial_no(row, serial_no), + () => this.set_batch_no(row, batch_no), + () => this.set_barcode(row, barcode), () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => { this.show_scan_message(row.idx, row.item_code, qty); }), () => this.set_barcode_uom(row, uom), - () => this.set_serial_no(row, serial_no), - () => this.set_batch_no(row, batch_no), - () => this.set_barcode(row, barcode), () => this.clean_up(), () => this.revert_selector_flag(), () => resolve(row) @@ -131,10 +131,10 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { // batch and serial selector is reduandant when all info can be added by scan // this flag on item row is used by transaction.js to avoid triggering selector set_selector_trigger_flag(data) { - const {batch_no, serial_no, has_batch_no, has_serial_no} = data; + const {has_batch_no, has_serial_no} = data; - const require_selecting_batch = has_batch_no && !batch_no; - const require_selecting_serial = has_serial_no && !serial_no; + const require_selecting_batch = has_batch_no; + const require_selecting_serial = has_serial_no; if (!(require_selecting_batch || require_selecting_serial)) { frappe.flags.hide_serial_batch_dialog = true; diff --git a/erpnext/public/js/utils/ledger_preview.js b/erpnext/public/js/utils/ledger_preview.js index 85d4a7d51e94a957a293559eeddede1631aded9b..8c559bbc90f32aab3b44dc94790d1bfdeea423fc 100644 --- a/erpnext/public/js/utils/ledger_preview.js +++ b/erpnext/public/js/utils/ledger_preview.js @@ -14,7 +14,7 @@ erpnext.accounts.ledger_preview = { "docname": frm.doc.name }, "callback": function(response) { - me.make_dialog("Accounting Ledger Preview", "accounting_ledger_preview_html", response.message.gl_columns, response.message.gl_data); + me.make_dialog(__("Accounting Ledger Preview"), "accounting_ledger_preview_html", response.message.gl_columns, response.message.gl_data); } }) }, __("Preview")); diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 5514963c966c66beb4e78ba7de9e1152d8872e5b..a4face54403b7852e9309c4b76390c6dcf81e2ef 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -3,6 +3,12 @@ frappe.provide("erpnext.selling"); +const base_amount_field_for_cost_price = { + "Valuation Rate": "valuation_rate", + "Last Purchase Rate": "last_purchase_rate", + "Supplier Cost Price": "unit_cost_price" +} + erpnext.sales_common = { setup_selling_controller:function() { erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController { @@ -374,10 +380,87 @@ erpnext.sales_common = { this.frm.set_value("discount_amount", 0); this.frm.set_value("additional_discount_percentage", 0); } + + supplier(doc, cdt, cdn) { + const item = locals[cdt][cdn]; + const fields_to_update = ["unit_cost_price", "additional_costs_percentage", "additional_costs_amount"] + + if (item.supplier) { + frappe.call({ + method: "erpnext.stock.get_item_details.get_supplier_cost", + args: { + out: { + item_code: item.item_code, + uom: item.uom, + supplier: item.supplier, + transaction_date: doc.transaction_date, + qty: item.qty + } + } + }).then(r => { + if(!r.exc) { + fields_to_update.map(field => { + frappe.model.set_value(cdt, cdn, field, r.message[field] || 0.0); + }) + } + }); + } else { + fields_to_update.map(field => { + frappe.model.set_value(cdt, cdn, field, 0.0); + }) + } + } + + cost_price(doc, cdt, cdn) { + this.gross_profit_calculation_rule(doc, cdt, cdn); + } + + unit_cost_price(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + } + + additional_costs_percentage(doc, cdt, cdn) { + const item = frappe.get_doc(cdt, cdn) + const base_amount_field = base_amount_field_for_cost_price[item.gross_profit_calculation_rule] + const amount = flt(item[base_amount_field]) * flt(item.additional_costs_percentage) / 100.0 + + if (flt(amount) != flt(item.additional_costs_amount)) { + frappe.model.set_value(cdt, cdn, "additional_costs_amount", amount); + } + } + + additional_costs_amount(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + const item = frappe.get_doc(cdt, cdn) + const base_amount_field = base_amount_field_for_cost_price[item.gross_profit_calculation_rule] + let percentage = 0.0 + + if (item[base_amount_field]) { + percentage = item.additional_costs_amount / item[base_amount_field] * 100.0 + } + + if (flt(percentage) != flt(item.additional_costs_percentage)) { + frappe.model.set_value(cdt, cdn, "additional_costs_percentage", percentage); + } + } + + valuation_rate(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + } + + last_purchase_rate(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + } }; } } +const calculate_cost_price = (doc, cdt, cdn) => { + const item = frappe.get_doc(cdt, cdn) + const calculation_field = base_amount_field_for_cost_price[item.gross_profit_calculation_rule]; + frappe.model.set_value(cdt, cdn, "cost_price", item.qty * (item[calculation_field] + item.additional_costs_amount)); +} + erpnext.pre_sales = { set_as_lost: function(doctype) { frappe.ui.form.on(doctype, { diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 9267801839b2f5de6de2e2843f5923873279cbb5..7b9cdfef2a9608e29d9374c67ddec5fb53a1f3c0 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -31,8 +31,23 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { secondary_action: () => this.edit_full_form(), }); - this.dialog.set_value("qty", this.item.qty); this.dialog.show(); + + let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; + this.dialog.set_value("qty", qty).then(() => { + if (this.item.serial_no) { + this.dialog.set_value("scan_serial_no", this.item.serial_no); + frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', ''); + } else if (this.item.batch_no) { + this.dialog.set_value("scan_batch_no", this.item.batch_no); + frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', ''); + } + + this.dialog.fields_dict.entries.grid.refresh(); + }); + + this.$scan_btn = this.dialog.$wrapper.find(".link-btn"); + this.$scan_btn.css("display", "inline"); } get_serial_no_filters() { @@ -95,6 +110,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { if (this.item.has_serial_no) { fields.push({ fieldtype: 'Data', + options: 'Barcode', fieldname: 'scan_serial_no', label: __('Scan Serial No'), get_query: () => { @@ -106,15 +122,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }); } - if (this.item.has_batch_no && this.item.has_serial_no) { - fields.push({ - fieldtype: 'Column Break', - }); - } - - if (this.item.has_batch_no) { + if (this.item.has_batch_no && !this.item.has_serial_no) { fields.push({ fieldtype: 'Data', + options: 'Barcode', fieldname: 'scan_batch_no', label: __('Scan Batch No'), onchange: () => this.update_serial_batch_no() @@ -309,6 +320,14 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_auto_data() { + if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) { + return; + } + + if (this.item.serial_no || this.item.batch_no) { + return; + } + let { qty, based_on } = this.dialog.get_values(); if (!based_on) { @@ -340,16 +359,57 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { const { scan_serial_no, scan_batch_no } = this.dialog.get_values(); if (scan_serial_no) { - this.dialog.fields_dict.entries.df.data.push({ - serial_no: scan_serial_no + let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => { + if (d.serial_no === scan_serial_no) { + return d + } }); - this.dialog.fields_dict.scan_serial_no.set_value(''); + if (existing_row?.length) { + frappe.throw(__('Serial No {0} already exists', [scan_serial_no])); + } + + if (!this.item.has_batch_no) { + this.dialog.fields_dict.entries.df.data.push({ + serial_no: scan_serial_no + }); + + this.dialog.fields_dict.scan_serial_no.set_value(''); + } else { + frappe.call({ + method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_batch_no_from_serial_no', + args: { + serial_no: scan_serial_no, + }, + callback: (r) => { + if (r.message) { + this.dialog.fields_dict.entries.df.data.push({ + serial_no: scan_serial_no, + batch_no: r.message + }); + + this.dialog.fields_dict.scan_serial_no.set_value(''); + } + } + + }) + } } else if (scan_batch_no) { - this.dialog.fields_dict.entries.df.data.push({ - batch_no: scan_batch_no + let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => { + if (d.batch_no === scan_batch_no) { + return d + } }); + if (existing_row?.length) { + existing_row[0].qty += 1; + } else { + this.dialog.fields_dict.entries.df.data.push({ + batch_no: scan_batch_no, + qty: 1 + }); + } + this.dialog.fields_dict.scan_batch_no.set_value(''); } diff --git a/erpnext/public/js/venue_registration_form.js b/erpnext/public/js/venue_registration_form.js deleted file mode 100644 index d84c581d3a8ef2f7a4a7fe536f562df610df5711..0000000000000000000000000000000000000000 --- a/erpnext/public/js/venue_registration_form.js +++ /dev/null @@ -1,79 +0,0 @@ -frappe.ready(() => { - Object.assign(frappe, { - show_subscription_template_selection: () => { - if (!frappe.web_form.get_field("subscription_templates")) { - return - } - - frappe.call({ - method: "erpnext.venue.doctype.venue_registration_form.venue_registration_form.get_recurring_website_items" - }).then(r => { - if (r.message && r.message.length) { - const template_cards = r.message.map(item => { - const buttonId = `${frappe.scrub(item.name).replace('"', '\\"')}_subscription` - const template_card = ` -
-
${item.web_item_name}
-
${item.short_description || ""}
-
- - ` - let template_image = '' - if (item.website_image) { - template_image = `${frappe.utils.escape_html(item.web_item_name)}` - } - - return `
- ${template_image} - ${template_card} -
` - }).join(""); - - frappe.web_form.get_field("subscription_templates").wrapper.innerHTML = `
- ${template_cards} - -
`; - - let prevButton = null; - const webform = document.getElementsByClassName("web-form")[0]; - webform.addEventListener("click", (e) => { - const card = e.target.closest(".subscription-template-card"); - if (!card) { return; } - - const itemName = card.getAttribute("data-subscription") - if (!itemName) { return; } - - const button = card.querySelector("button"); - if (!button) { return; } - - if (prevButton !== null && prevButton !== button) { - const prevCard = prevButton.closest(".subscription-template-card"); - prevCard.classList.remove("active"); - prevButton.classList.remove("active"); - prevButton.innerText = __("Select"); - } - - card.classList.add("active"); - button.classList.add("active"); - button.innerText = __("Selected"); - - prevButton = button; - frappe.web_form.set_value("website_item", itemName); - }) - } - }) - } - }) -}) \ No newline at end of file diff --git a/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json b/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json index e53d18650c770bfd85cf8b33600d6f38c76bd381..9f0f80bc479c4fb32ecd54a89bd3f554470a49fc 100644 --- a/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json +++ b/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json @@ -15,7 +15,7 @@ } ], "istable": 1, - "modified": "2019-05-26 20:49:01.328146", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality Meeting Agenda", @@ -23,6 +23,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/regional/doctype/fec_import/fec_import.js b/erpnext/regional/doctype/fec_import/fec_import.js index 4365669585d0e085bab3e6e126a776750507f36e..dea587cc44902da3399a9b377e8e9bc35882c36f 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.js +++ b/erpnext/regional/doctype/fec_import/fec_import.js @@ -41,10 +41,18 @@ frappe.ui.form.on('FEC Import', { method: "upload_fec", doc: frm.doc, }).then(r => { - frappe.show_alert({ - message: __("Import finished. Please check the imported documents."), - indicator: "green" - }) + frm.reload_doc() + if (frm.doc.error) { + frappe.show_alert({ + message: __("An error occured while importing your FEC."), + indicator: "red" + }) + } else { + frappe.show_alert({ + message: __("Import finished. Please check the imported documents."), + indicator: "green" + }) + } }) }); diff --git a/erpnext/regional/doctype/fec_import/fec_import.json b/erpnext/regional/doctype/fec_import/fec_import.json index 3341281c2051ec186d674612fd966d3ffb60515d..f797374c1ba9bf452f70dab02cda94450a40ca95 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.json +++ b/erpnext/regional/doctype/fec_import/fec_import.json @@ -12,6 +12,8 @@ "naming_series", "description", "fec_file", + "section_break_rdza", + "error", "section_break_3", "company", "column_break_lduy", @@ -21,7 +23,11 @@ "column_break_7", "to_date", "section_break_9", - "import_journal" + "import_journal", + "additional_settings_section", + "field_delimiter", + "column_break_lbqk", + "date_format" ], "fields": [ { @@ -98,6 +104,40 @@ "fieldtype": "Link", "label": "Import Setttings", "options": "FEC Import Settings" + }, + { + "collapsible": 1, + "fieldname": "additional_settings_section", + "fieldtype": "Section Break", + "label": "Additional Settings" + }, + { + "default": "Tab", + "fieldname": "field_delimiter", + "fieldtype": "Select", + "label": "Field Delimiter", + "options": "Tab\nComma\nSemicolon\nSpace" + }, + { + "fieldname": "column_break_lbqk", + "fieldtype": "Column Break" + }, + { + "description": "Leave empty for default format.
For available formats please read the Python documentation", + "fieldname": "date_format", + "fieldtype": "Data", + "label": "Date Format" + }, + { + "fieldname": "section_break_rdza", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:doc.error", + "fieldname": "error", + "fieldtype": "Small Text", + "label": "Error", + "read_only": 1 } ], "index_web_pages_for_search": 1, @@ -108,7 +148,7 @@ "link_fieldname": "fec_import" } ], - "modified": "2023-08-28 14:27:25.146981", + "modified": "2023-12-02 22:20:22.136460", "modified_by": "Administrator", "module": "Regional", "name": "FEC Import", diff --git a/erpnext/regional/doctype/fec_import/fec_import.py b/erpnext/regional/doctype/fec_import/fec_import.py index 38f5294a6dd908b8468ddbd49389aeae8384a3c2..086d7d9f1350afc25f7c20d870e2f079bcf97572 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.py +++ b/erpnext/regional/doctype/fec_import/fec_import.py @@ -12,6 +12,8 @@ from frappe.model.document import Document from frappe.utils import cint, flt, get_year_start, getdate from frappe.utils.csvutils import read_csv_content +FIELD_DELIMITERS = {"Semicolon": ";", "Comma": ",", "Tab": "\t", "Space": " "} + class FECImport(Document): # begin: auto-generated types @@ -23,7 +25,10 @@ class FECImport(Document): from frappe.types import DF company: DF.Link | None + date_format: DF.Data | None + error: DF.SmallText | None fec_file: DF.Attach | None + field_delimiter: DF.Literal["Tab", "Comma", "Semicolon", "Space"] from_date: DF.Date | None import_journal: DF.Link | None import_settings: DF.Link | None @@ -67,9 +72,9 @@ class FECImport(Document): try: if closing_date := file_name.split("FEC")[1]: closing_date = closing_date.split(".txt")[0] - company_and_dates["to_date"] = datetime.datetime.strptime(closing_date, "%Y%m%d").strftime( - "%Y-%m-%d" - ) + company_and_dates["to_date"] = datetime.datetime.strptime( + closing_date, self.date_format or "%Y%m%d" + ).strftime("%Y-%m-%d") company_and_dates["from_date"] = get_year_start(closing_date, as_str=True) if siren := file_name.split("FEC")[0]: @@ -87,13 +92,14 @@ class FECImport(Document): if not self.import_settings: frappe.throw(_("Please select an import settings document")) + self.db_set("error", "") data = self.get_data() try: import_in_progress = FECImportDocumentCreator(settings=self, data=data) import_in_progress.import_data() except Exception: - print(frappe.get_traceback()) + self.db_set("error", frappe.get_traceback()) def get_data(self): fileid = frappe.db.get_value( @@ -103,7 +109,7 @@ class FECImport(Document): _file = frappe.get_doc("File", fileid) fcontent = _file.get_content() - rows = read_csv_content(fcontent, delimiter="\t") + rows = read_csv_content(fcontent, delimiter=FIELD_DELIMITERS.get(self.field_delimiter) or "\t") header = rows[0] data = rows[1:] @@ -399,7 +405,9 @@ class FECImportDocumentCreator: doc = frappe.new_doc("FEC Import Document") doc.fec_import = self.settings.name doc.settings = self.settings.import_settings - doc.gl_entries_date = datetime.datetime.strptime(date, "%Y%m%d").strftime("%Y-%m-%d") + doc.gl_entries_date = datetime.datetime.strptime( + date, self.settings.date_format or "%Y%m%d" + ).strftime("%Y-%m-%d") doc.gl_entry_reference = piece for line in self.grouped_data[date][piece]: @@ -435,7 +443,9 @@ class FECImportDocumentCreator: frappe.get_doc("FEC Import Document", d).run_method("process_document_in_background") def is_within_date_range(self, line): - posting_date = datetime.datetime.strptime(line.EcritureDate, "%Y%m%d").strftime("%Y-%m-%d") + posting_date = datetime.datetime.strptime( + line.EcritureDate, self.settings.date_format or "%Y%m%d" + ).strftime("%Y-%m-%d") if self.settings.from_date and getdate(posting_date) < getdate(self.settings.from_date): return False diff --git a/erpnext/regional/doctype/fec_import_document/fec_import_document.py b/erpnext/regional/doctype/fec_import_document/fec_import_document.py index fb6cfd74c13c14a73e300609a3d160e46aa47024..8f6f2702ce26956b75d05b898b436dcdabe9ed4d 100644 --- a/erpnext/regional/doctype/fec_import_document/fec_import_document.py +++ b/erpnext/regional/doctype/fec_import_document/fec_import_document.py @@ -194,10 +194,17 @@ class FECImportDocument(Document): return supplier.name def parse_dates(self, row): - row.posting_date = parse_date(row.ecrituredate) - row.transaction_date = parse_date(row.piecedate) - row.validation_date = parse_date(row.validdate) - row.reconciliation_date = parse_date(row.datelet) + row.posting_date = self.parse_date(row.ecrituredate) + row.transaction_date = self.parse_date(row.piecedate) + row.validation_date = self.parse_date(row.validdate) + row.reconciliation_date = self.parse_date(row.datelet) + + def parse_date(self, date): + if not date: + return + + date_format = frappe.get_cached_value("FEC Import", self.fec_import, "date_format") + return datetime.datetime.strptime(date, date_format or "%Y%m%d").strftime("%Y-%m-%d") @frappe.whitelist() def create_linked_document(self): @@ -518,13 +525,6 @@ class FECImportDocument(Document): frappe.db.set_value("Account", account, "is_group", 0) -def parse_date(date): - if not date: - return - - return datetime.datetime.strptime(date, "%Y%m%d").strftime("%Y-%m-%d") - - @frappe.whitelist() def bulk_process(docnames): docnames = frappe.parse_json(docnames) diff --git a/erpnext/regional/doctype/fec_import_line/fec_import_line.json b/erpnext/regional/doctype/fec_import_line/fec_import_line.json index 6c66431d5ed838f14ebb0bdf62156091542c508d..0443c323f66e8c574bf8ffb760aca757266935a3 100644 --- a/erpnext/regional/doctype/fec_import_line/fec_import_line.json +++ b/erpnext/regional/doctype/fec_import_line/fec_import_line.json @@ -44,6 +44,7 @@ ], "fields": [ { + "columns": 1, "fieldname": "journalcode", "fieldtype": "Data", "in_list_view": 1, @@ -69,6 +70,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "comptenum", "fieldtype": "Data", "in_list_view": 1, @@ -82,6 +84,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "compauxnum", "fieldtype": "Data", "in_list_view": 1, @@ -113,6 +116,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "debit", "fieldtype": "Float", "in_list_view": 1, @@ -120,6 +124,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "credit", "fieldtype": "Float", "in_list_view": 1, @@ -196,14 +201,18 @@ "fieldtype": "Column Break" }, { + "columns": 1, "fieldname": "party_type", "fieldtype": "Link", + "in_list_view": 1, "label": "Party Type", "options": "DocType" }, { + "columns": 2, "fieldname": "party", "fieldtype": "Dynamic Link", + "in_list_view": 1, "label": "Party", "options": "party_type" }, @@ -246,7 +255,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-21 16:32:37.859603", + "modified": "2023-12-02 22:13:44.357581", "modified_by": "Administrator", "module": "Regional", "name": "FEC Import Line", diff --git a/erpnext/regional/doctype/fec_import_line/fec_import_line.py b/erpnext/regional/doctype/fec_import_line/fec_import_line.py index d48e0eec9c05c37601564bef35edab9f1812fd34..1761ce9a499dfe8454524084b19c1aeb611c537e 100644 --- a/erpnext/regional/doctype/fec_import_line/fec_import_line.py +++ b/erpnext/regional/doctype/fec_import_line/fec_import_line.py @@ -5,4 +5,44 @@ from frappe.model.document import Document class FECImportLine(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 + + account: DF.Link | None + accounting_journal: DF.Link | None + compauxlib: DF.SmallText | None + compauxnum: DF.Data | None + comptelib: DF.SmallText | None + comptenum: DF.Data | None + credit: DF.Float + datelet: DF.Data | None + debit: DF.Float + ecrituredate: DF.Data | None + ecriturelet: DF.Data | None + ecriturelib: DF.SmallText | None + ecriturenum: DF.Data | None + hashed_data: DF.Data | None + idevise: DF.Data | None + journalcode: DF.Data | None + journallib: DF.SmallText | None + montantdevise: DF.Float + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + party: DF.DynamicLink | None + party_type: DF.Link | None + piecedate: DF.Data | None + pieceref: DF.Data | None + posting_date: DF.Date | None + reconciliation_date: DF.Date | None + transaction_date: DF.Date | None + validation_date: DF.Date | None + validdate: DF.Data | None + # end: auto-generated types + pass diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 567a85063affb2d25b71e85ebaa097951dcca1c1..b31feb599da11d7d8ee27b37ae38a4e1b28c131b 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -688,7 +688,7 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "customer_name", "track_changes": 1 diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 50af1b47883a5bfdae2aaf7b33a4b0989961345d..a3c92836da9fcc6761bf395849b95cbaa4bfcbcd 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -636,7 +636,8 @@ def get_customer_outstanding( """ select sum(debit) - sum(credit) from `tabGL Entry` where party_type = 'Customer' - and party = %s and company=%s {0}""".format( + and is_cancelled = 0 and party = %s + and company=%s {0}""".format( cond ), (customer, company), diff --git a/erpnext/selling/doctype/installation_note_item/installation_note_item.json b/erpnext/selling/doctype/installation_note_item/installation_note_item.json index 75cf7c09cdf3c1f391b00b561dcd093eb3df5b9f..e76075a9324ed103425c80c9bf659a3d3d67a3d0 100644 --- a/erpnext/selling/doctype/installation_note_item/installation_note_item.json +++ b/erpnext/selling/doctype/installation_note_item/installation_note_item.json @@ -112,7 +112,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-03-12 13:47:08.257955", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Selling", "name": "Installation Note Item", @@ -120,7 +120,7 @@ "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json index b6cc703e0e928a2ca23ae46795803edd2324ad2b..84fb87529aa754e35d7796b69c8bd9e941b4f307 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.json +++ b/erpnext/selling/doctype/product_bundle/product_bundle.json @@ -77,7 +77,7 @@ "icon": "fa fa-sitemap", "idx": 1, "links": [], - "modified": "2023-11-22 15:20:46.805114", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Selling", "name": "Product Bundle", @@ -114,6 +114,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 10590a6db698b1bbc64390676d4329a7638d3732..2c74f39e7fa7f1008d5e3b998e4788868a9c87b7 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -382,9 +382,6 @@ def _make_sales_order(source_name, target_doc=None, customer_group=None, ignore_ ignore_permissions=ignore_permissions, ) - # postprocess: fetch shipping address, set missing values - doclist.set_onload("ignore_price_list", True) - return doclist @@ -427,8 +424,6 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): ignore_permissions=ignore_permissions, ) - doclist.set_onload("ignore_price_list", True) - return doclist diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 4816d39a609dd04df4626a5dda876447990609cc..5d713b0c4de5d287a25ce8d5eb17e3900bfffe5c 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -52,10 +52,20 @@ "is_free_item", "is_alternative", "has_alternative_item", - "section_break_43", + "costs_section", "valuation_rate", + "last_purchase_rate", "column_break_45", "gross_profit", + "section_break_nqjy", + "supplier", + "unit_cost_price", + "additional_costs_percentage", + "additional_costs_amount", + "cost_price", + "column_break_kcol", + "gross_profit_calculation_rule", + "gross_profit_percentage", "item_weight_details", "weight_per_unit", "total_weight", @@ -139,7 +149,6 @@ "width": "300px" }, { - "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -626,10 +635,6 @@ "no_copy": 1, "print_hide": 1 }, - { - "fieldname": "section_break_43", - "fieldtype": "Section Break" - }, { "fieldname": "valuation_rate", "fieldtype": "Currency", @@ -687,12 +692,73 @@ "fieldtype": "Check", "label": "Is Recurring Item", "read_only": 1 + }, + { + "fieldname": "last_purchase_rate", + "fieldtype": "Currency", + "label": "Last Purchase Rate", + "read_only": 1 + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "unit_cost_price", + "fieldtype": "Currency", + "label": "Supplier Unit Price", + "read_only": 1 + }, + { + "fieldname": "cost_price", + "fieldtype": "Currency", + "label": "Cost Price", + "read_only": 1 + }, + { + "description": "Set a custom gross profit percentage to recalculate your item selling rate", + "fieldname": "gross_profit_percentage", + "fieldtype": "Percent", + "label": "Gross Profit Percentage" + }, + { + "fieldname": "additional_costs_percentage", + "fieldtype": "Percent", + "label": "Additional Costs Percentage" + }, + { + "fieldname": "additional_costs_amount", + "fieldtype": "Currency", + "label": "Additional Costs Amount" + }, + { + "fieldname": "costs_section", + "fieldtype": "Section Break", + "hide_border": 1, + "label": "Costs Calculation" + }, + { + "default": "Valuation Rate", + "fieldname": "gross_profit_calculation_rule", + "fieldtype": "Select", + "label": "Cost Price Based On", + "options": "Valuation Rate\nLast Purchase Rate\nSupplier Cost Price" + }, + { + "fieldname": "section_break_nqjy", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_kcol", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:24:24.619832", + "modified": "2023-12-13 09:57:16.215710", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.py b/erpnext/selling/doctype/quotation_item/quotation_item.py index 99f4cc8f085f265577054eb27a03143f3061e6d7..2a011673ccbb3be1171f3395c8bd097ea9be8ed8 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.py +++ b/erpnext/selling/doctype/quotation_item/quotation_item.py @@ -2,9 +2,82 @@ # License: GNU General Public License v3. See license.txt -import frappe +# import frappe from frappe.model.document import Document class QuotationItem(Document): - pass + # 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 + + actual_qty: DF.Float + additional_costs_amount: DF.Currency + additional_costs_percentage: DF.Percent + additional_notes: DF.Text | None + against_blanket_order: DF.Check + amount: DF.Currency + base_amount: DF.Currency + base_net_amount: DF.Currency + base_net_rate: DF.Currency + base_price_list_rate: DF.Currency + base_rate: DF.Currency + base_rate_with_margin: DF.Currency + blanket_order: DF.Link | None + blanket_order_rate: DF.Currency + brand: DF.Link | None + conversion_factor: DF.Float + cost_price: DF.Currency + customer_item_code: DF.Data | None + description: DF.TextEditor | None + discount_amount: DF.Currency + discount_percentage: DF.Percent + gross_profit: DF.Currency + gross_profit_calculation_rule: DF.Literal[ + "Valuation Rate", "Last Purchase Rate", "Supplier Cost Price" + ] + gross_profit_percentage: DF.Percent + has_alternative_item: DF.Check + image: DF.Attach | None + is_alternative: DF.Check + is_free_item: DF.Check + is_recurring_item: DF.Check + item_booking: DF.Link | None + item_code: DF.Link | None + item_group: DF.Link | None + item_name: DF.Data + item_tax_rate: DF.Code | None + item_tax_template: DF.Link | None + last_purchase_rate: DF.Currency + margin_rate_or_amount: DF.Float + margin_type: DF.Literal["", "Percentage", "Amount"] + net_amount: DF.Currency + net_rate: DF.Currency + page_break: DF.Check + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + prevdoc_docname: DF.DynamicLink | None + prevdoc_doctype: DF.Link | None + price_list_rate: DF.Currency + pricing_rules: DF.SmallText | None + projected_qty: DF.Float + qty: DF.Float + rate: DF.Currency + rate_with_margin: DF.Currency + stock_qty: DF.Float + stock_uom: DF.Link | None + stock_uom_rate: DF.Currency + supplier: DF.Link | None + total_weight: DF.Float + unit_cost_price: DF.Currency + uom: DF.Link + valuation_rate: DF.Currency + warehouse: DF.Link | None + weight_per_unit: DF.Float + weight_uom: DF.Link | None + # end: auto-generated types diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 2027f594db7dc7f1e0ccf8b9362ef388999b44b4..9b9ef3d5dae9754eddee3d8074f73c28ccb41c02 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -434,7 +434,7 @@ frappe.ui.form.on("Sales Order", { if (frm.doc.docstatus == 0) { frm.dashboard.clear_headline() frm.dashboard.set_headline_alert( - __("This order contains some recurring items.
A subscription will be automatically generated on submission."), + __("This order contains some recurring items.
A subscription will be automatically generated on submission if a recurring period is set."), "orange" ) } else if (frm.doc.subscription) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c1b3875e14da16a654fe4a9ea050f42e786ae613..e98e7f0cce7e81928faa195f9939385efb1222b4 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -865,8 +865,14 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate) target.qty = flt(source.qty) - flt(source.delivered_qty) - item = get_item_defaults(target.item_code, source_parent.company) - item_group = get_item_group_defaults(target.item_code, source_parent.company) + item = get_item_defaults( + target.item_code, source_parent.company, source_parent.get("tax_category") + ) # @dokos + item_group = get_item_group_defaults( + target.item_code, + source_parent.company, + source_parent.get("tax_category"), + ) # @dokos if item: target.cost_center = ( @@ -934,7 +940,6 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): # Should be called after mapping items. set_missing_values(so, target_doc) - target_doc.set_onload("ignore_price_list", True) return target_doc @@ -966,7 +971,9 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): if source.loyalty_points and source.order_type == "Shopping Cart": target.redeem_loyalty_points = 1 - target.debit_to = get_party_account("Customer", source.customer, source.company) + target.debit_to = get_party_account( + "Customer", source.customer, source.company, tax_category=source.get("tax_category") + ) # @dokos def update_item(source, target, source_parent): target.amount = flt(source.amount) - flt(source.billed_amt) @@ -980,8 +987,12 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): if source_parent.project: target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") if target.item_code: - item = get_item_defaults(target.item_code, source_parent.company) - item_group = get_item_group_defaults(target.item_code, source_parent.company) + item = get_item_defaults( + target.item_code, source_parent.company, source_parent.get("tax_category") + ) # @dokos + item_group = get_item_group_defaults( + target.item_code, source_parent.company, source_parent.get("tax_category") + ) # @dokos cost_center = item.get("selling_cost_center") or item_group.get("selling_cost_center") if cost_center: @@ -996,6 +1007,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): "field_map": { "party_account_currency": "party_account_currency", "payment_terms_template": "payment_terms_template", + "from_date": "from_date", + "to_date": "to_date", }, "field_no_map": ["payment_terms_template"], "validation": {"docstatus": ["=", 1]}, @@ -1021,8 +1034,6 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): if automatically_fetch_payment_terms: doclist.set_payment_schedule() - doclist.set_onload("ignore_price_list", True) - return doclist diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index b4893c394d14772615c5aa95b010754471c362c3..6a9212d264dfa3618172b7188f9d84fb74b8ea33 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -214,7 +214,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-25 14:03:03.966701", + "modified": "2023-12-04 12:03:59.034319", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index ad86e4e1ef595d0fb6eaa1b1fe28ca7b970c193d..52d8edfcf62fd2377415a1d0bf6e93eda20632fb 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -12,6 +12,38 @@ from frappe.utils import cint class SellingSettings(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 + + allow_against_multiple_purchase_orders: DF.Check + allow_multiple_items: DF.Check + allow_negative_rates_for_items: DF.Check + allow_sales_order_creation_for_expired_quotation: DF.Check + blanket_order_allowance: DF.Float + create_tasks_from_sales_order: DF.Check + cust_master_name: DF.Literal["Customer Name", "Naming Series", "Auto Name"] + customer_group: DF.Link | None + dn_required: DF.Literal["No", "Yes"] + dont_reserve_sales_order_qty_on_sales_return: DF.Check + editable_bundle_item_rates: DF.Check + editable_price_list_rate: DF.Check + enable_discount_accounting: DF.Check + hide_tax_id: DF.Check + maintain_same_rate_action: DF.Literal["Stop", "Warn"] + maintain_same_sales_rate: DF.Check + role_to_override_stop_action: DF.Link | None + sales_update_frequency: DF.Literal["Monthly", "Each Transaction", "Daily"] + selling_price_list: DF.Link | None + so_required: DF.Literal["No", "Yes"] + territory: DF.Link | None + validate_selling_price: DF.Check + # end: auto-generated types + def on_update(self): self.toggle_hide_tax_id() self.toggle_editable_rate_for_bundle_items() diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 8b3b6449a810c8f59882866e0318d4d14286746e..c922ab5b0eaafa7854d2933fd0bac3b71cc3103c 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -164,7 +164,7 @@ def prepare_data(data, so_elapsed_time, filters): if filters.get("group_by_so"): so_name = row["sales_order"] - if not so_name in sales_order_map: + if so_name not in sales_order_map: # create an entry row_copy = copy.deepcopy(row) sales_order_map[so_name] = row_copy diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py index 4b95c8d86171d0bbceab308f166c35d4122c9208..9f3ba0da8bdf9551636cb37948d55a6205d28aff 100644 --- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py +++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py @@ -3,8 +3,8 @@ import frappe -from frappe import _, msgprint -from frappe.utils import flt +from frappe import _, msgprint, qb +from frappe.query_builder import Criterion from erpnext import get_company_currency @@ -215,24 +215,33 @@ def get_conditions(filters, date_field): if items: conditions.append("dt_item.item_code in (%s)" % ", ".join(["%s"] * len(items))) values += items + else: + # return empty result, if no items are fetched after filtering on 'item group' and 'brand' + conditions.append("dt_item.item_code = Null") return " and ".join(conditions), values def get_items(filters): - if filters.get("item_group"): - key = "item_group" - elif filters.get("brand"): - key = "brand" - else: - key = "" + item = qb.DocType("Item") - items = [] - if key: - items = frappe.db.sql_list( - """select name from tabItem where %s = %s""" % (key, "%s"), (filters[key]) + item_query_conditions = [] + if filters.get("item_group"): + # Handle 'Parent' nodes as well. + item_group = qb.DocType("Item Group") + lft, rgt = frappe.db.get_all( + "Item Group", filters={"name": filters.get("item_group")}, fields=["lft", "rgt"], as_list=True + )[0] + item_group_query = ( + qb.from_(item_group) + .select(item_group.name) + .where((item_group.lft >= lft) & (item_group.rgt <= rgt)) ) + item_query_conditions.append(item.item_group.isin(item_group_query)) + if filters.get("brand"): + item_query_conditions.append(item.brand == filters.get("brand")) + items = qb.from_(item).select(item.name).where(Criterion.all(item_query_conditions)).run() return items diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py index dc503ff087b353790b83835003045f63c9bdcdc4..c0237b72be45990c4ba9e9524275019443a683f1 100644 --- a/erpnext/setup/doctype/authorization_control/authorization_control.py +++ b/erpnext/setup/doctype/authorization_control/authorization_control.py @@ -176,7 +176,10 @@ class AuthorizationControl(TransactionBase): # Remove user specific rules from global authorization rules for r in based_on: - if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: + if r in final_based_on and r not in [ + "Itemwise Discount", + "Item Group wise Discount", + ]: final_based_on.remove(r) # Check for authorization set on particular roles @@ -204,7 +207,10 @@ class AuthorizationControl(TransactionBase): # Remove role specific rules from global authorization rules for r in based_on: - if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: + if r in final_based_on and r not in [ + "Itemwise Discount", + "Item Group wise Discount", + ]: final_based_on.remove(r) # Check for global authorization diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 1573a9110ef6cc8c98bb9c93762353d3849c626d..e94642db2b93c4a33a626aecb2a3b3b1fd665088 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -778,7 +778,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-10-23 10:19:24.322898", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -838,7 +838,7 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/setup/doctype/department/department.json b/erpnext/setup/doctype/department/department.json index 542395d1bafc08cb83fa10ef4fc1ec5f1088dfae..ab61614deab6dcd9358b3824550778b5f3997f54 100644 --- a/erpnext/setup/doctype/department/department.json +++ b/erpnext/setup/doctype/department/department.json @@ -90,7 +90,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2023-08-28 17:26:46.826501", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Department", @@ -125,6 +125,6 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/setup/doctype/department/department.py b/erpnext/setup/doctype/department/department.py index 1745178574b1b9169ad48d573ee9d7daad6514f7..ba6042b378fc0a349ac5ef74a5ac612af5ce6291 100644 --- a/erpnext/setup/doctype/department/department.py +++ b/erpnext/setup/doctype/department/department.py @@ -26,7 +26,7 @@ class Department(NestedSet): def before_rename(self, old, new, merge=False): # renaming consistency with abbreviation - if not frappe.get_cached_value("Company", self.company, "abbr") in new: + if frappe.get_cached_value("Company", self.company, "abbr") not in new: new = get_abbreviated_name(new, self.company) return new diff --git a/erpnext/setup/doctype/designation/designation.json b/erpnext/setup/doctype/designation/designation.json index a5b2ac9128acb0f3c49b8d3f05b9eaf39c1fb3f1..1f44c5c6376009b73d952479f38aa14353bb882b 100644 --- a/erpnext/setup/doctype/designation/designation.json +++ b/erpnext/setup/doctype/designation/designation.json @@ -31,7 +31,7 @@ "icon": "fa fa-bookmark", "idx": 1, "links": [], - "modified": "2023-02-10 01:53:41.319386", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Designation", @@ -57,7 +57,7 @@ "quick_entry": 1, "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "translated_doctype": 1 -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index b25dfc09356bbd9aff5cf1fad3ad165c83bdc888..cd24db7bfd32419ae94f0a81f3943d6f97bf83d3 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -645,7 +645,7 @@ class EmailDigest(Document): ] def get_root_type_accounts(self, root_type): - if not root_type in self._accounts: + if root_type not in self._accounts: self._accounts[root_type] = [ d.name for d in frappe.db.get_all( diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index 021eaa120944090742e6e5c89f80819eb7e9f6b6..c07583c8f8bc8bfc37a2db453da77415b82020a0 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -81,7 +81,9 @@ "mode_of_payment", "bank_details_section", "bank_name", + "column_break_heye", "bank_ac_no", + "iban", "personal_details", "marital_status", "family_background", @@ -808,6 +810,16 @@ "fieldname": "column_break_104", "fieldtype": "Column Break" }, + { + "fieldname": "column_break_heye", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.salary_mode == 'Bank'", + "fieldname": "iban", + "fieldtype": "Data", + "label": "IBAN" + }, { "fieldname": "column_break_deui", "fieldtype": "Column Break" @@ -823,7 +835,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2023-11-17 09:47:27.355960", + "modified": "2023-12-11 10:29:00.809067", "modified_by": "Administrator", "module": "Setup", "name": "Employee", diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 1b094c9986d82b3b8e830b7bfc7b376656510871..5901d05a3b5b58ce9801d5569a2235bdfaae4ec2 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -187,7 +187,7 @@ class Employee(NestedSet): throw(_("Please enter relieving date.")) def validate_for_enabled_user_id(self, enabled): - if not self.status == "Active": + if self.status != "Active": return if enabled is None: diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 3b38e2dc500a69dd60fb7557b9432f98fecb35cb..b5aae96befb2908b7ea29ad0291832fb75f0d781 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -63,14 +63,25 @@ def get_child_item_groups(item_group_name): return child_item_groups or {} -def get_item_group_defaults(item, company): +def get_item_group_defaults(item, company, tax_category=None): item = frappe.get_cached_doc("Item", item) item_group = frappe.get_cached_doc("Item Group", item.item_group) + out = frappe._dict() for d in item_group.item_group_defaults or []: if d.company == company: row = copy.deepcopy(d.as_dict()) row.pop("name") - return row - - return frappe._dict() + out = row + break + + # @dokos + if tax_category: + for d in item_group.taxes: + if d.company == company and d.tax_category == tax_category: + if d.income_account: + out["income_account"] = d.income_account + if d.expense_account: + out["expense_account"] = d.expense_account + + return out diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.json b/erpnext/setup/doctype/sales_partner/sales_partner.json index cc01705b795d61562cafe48b9be6c8ebd06b8e58..adab4f658064bb207ecd99befbdb5371859abb57 100644 --- a/erpnext/setup/doctype/sales_partner/sales_partner.json +++ b/erpnext/setup/doctype/sales_partner/sales_partner.json @@ -192,7 +192,7 @@ "icon": "fa fa-user", "idx": 1, "links": [], - "modified": "2022-08-03 15:48:32.571826", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Sales Partner", @@ -225,6 +225,6 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/sales_person/sales_person.json b/erpnext/setup/doctype/sales_person/sales_person.json index 80a5b42575947ded34dbcdd807c109b2abe775bd..aa4912550b3c09a6293df8b1bea4a78715a036ef 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.json +++ b/erpnext/setup/doctype/sales_person/sales_person.json @@ -145,7 +145,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2023-02-24 16:58:05.718649", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Sales Person", @@ -183,6 +183,6 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.json b/erpnext/setup/doctype/supplier_group/supplier_group.json index e681fdbc4d933e3bb03c3f2723f610fbcb564774..48207af90eddf0df891360b4d3f86b23c7aedd43 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.json +++ b/erpnext/setup/doctype/supplier_group/supplier_group.json @@ -122,7 +122,7 @@ "index_web_pages_for_search": 1, "is_tree": 1, "links": [], - "modified": "2023-02-21 18:25:16.243227", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Supplier Group", @@ -191,6 +191,6 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/uom/uom.json b/erpnext/setup/doctype/uom/uom.json index 330b9944e996d51304921a854b4550ecbe434ea7..281edd65534ffc198ddb757fbc8c7e2b5734ecfa 100644 --- a/erpnext/setup/doctype/uom/uom.json +++ b/erpnext/setup/doctype/uom/uom.json @@ -40,7 +40,7 @@ "icon": "fa fa-compass", "idx": 1, "links": [], - "modified": "2022-07-25 14:47:10.251003", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "UOM", @@ -85,7 +85,7 @@ "quick_entry": 1, "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "translated_doctype": 1 } diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 96b03aef1e1e167bbe8e75407ff3441ae86c1b11..03342e5c52546b48b7cf3ff58aa823280dd21c98 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -31,8 +31,8 @@ def after_install(): add_company_to_session_defaults() add_standard_navbar_items() add_app_name() - setup_log_settings() setup_address_and_contact_permissions() + hide_workspaces() update_roles() frappe.db.commit() @@ -85,8 +85,6 @@ def set_single_defaults(): except frappe.ValidationError: pass - frappe.db.set_default("date_format", "dd-mm-yyyy") - setup_currency_exchange() set_venue_settings_defaults() @@ -192,7 +190,7 @@ def add_standard_navbar_items(): for item in erpnext_navbar_items: current_labels = [item.get("item_label") for item in current_navbar_items] - if not item.get("item_label") in current_labels: + if item.get("item_label") not in current_labels: navbar_settings.append("help_dropdown", item) for item in current_navbar_items: @@ -215,11 +213,11 @@ def add_app_name(): frappe.db.set_single_value("Website Settings", "app_name", "Dokos") -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) +def hide_workspaces(): + # @dokos + # for ws in ["Integration", "Settings"]: + # frappe.db.set_value("Workspace", ws, "public", 0) + pass def set_venue_settings_defaults(): diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 15f84d7478fa90975cecb3e65f0b73f7db08e68d..ef65dddcc0fb9bc6df4c0ecac1f7e0ccb06a9548 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -1,10 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +import os import frappe import copy -import os import json from frappe.utils import flt from erpnext.accounts.doctype.account.account import RootNotEditable @@ -90,6 +90,7 @@ def make_sales_and_purchase_tax_templates(company, tax_data, template_name=None) ) sales_tax_template["taxes"].append(tax_details) + if not sales_tax_template.get("taxes"): return @@ -181,7 +182,8 @@ def update_regional_tax_settings(country, company): frappe.scrub(country) ) frappe.get_attr(module_name)(country, company) + except (ImportError, AttributeError): + pass except Exception: # Log error and ignore if failed to setup regional tax settings frappe.log_error("Unable to setup regional tax settings") - pass diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 7d2d08e5c71b5c65c3569d3f2dd2237ad6308a39..78651ce00246a9c8a65570450a41030ffe69ba15 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -32,6 +32,10 @@ def boot_session(bootinfo): frappe.db.get_single_value("CRM Settings", "default_valid_till") ) + bootinfo.sysdefaults.allow_prospect_creation = cint( + frappe.db.get_single_value("CRM Settings", "allow_prospect_creation") + ) + bootinfo.sysdefaults.allow_sales_order_creation_for_expired_quotation = cint( frappe.db.get_single_value( "Selling Settings", "allow_sales_order_creation_for_expired_quotation" @@ -74,3 +78,11 @@ def update_page_info(bootinfo): "Sales Person Tree": {"title": _("Sales Person Tree"), "route": "Tree/Sales Person"}, } ) + + +def bootinfo(bootinfo): + if bootinfo.get("user") and bootinfo["user"].get("name"): + bootinfo["user"]["employee"] = "" + employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name") + if employee: + bootinfo["user"]["employee"] = employee diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 6eaf00cdf3dc169665b844ee7f2ac632edd1171f..a938acfa2cebb55015bd4dbed71042560d9a5a09 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -1,7 +1,5 @@ import frappe -from frappe import _ -from frappe.translate import get_dict, send_translations -from frappe.utils import cint +from frappe.utils.deprecations import deprecated def get_leaderboards(): @@ -73,12 +71,13 @@ def get_leaderboards(): @frappe.whitelist() def get_all_customers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", "between", [date_range[0], date_range[1]]]) - return frappe.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Sales Invoice", fields=["customer as name", "sum(outstanding_amount) as value"], filters=filters, @@ -88,26 +87,20 @@ def get_all_customers(date_range, company, field, limit=None): ) else: if field == "total_sales_amount": - select_field = "sum(so_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_sold": - select_field = "sum(so_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "so.posting_date") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select so.customer as name, {0} as value - FROM `tabSales Invoice` as so JOIN `tabSales Invoice Item` as so_item - ON so.name = so_item.parent - where so.docstatus = 1 {1} and so.company = %s - group by so.customer - order by value DESC - limit %s - """.format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, + return frappe.get_list( + "Sales Order", + fields=["customer as name", f"sum({select_field}) as value"], + filters=filters, + group_by="customer", + order_by="value desc", + limit=limit, ) @@ -115,55 +108,58 @@ def get_all_customers(date_range, company, field, limit=None): def get_all_items(date_range, company, field, limit=None): if field in ("available_stock_qty", "available_stock_value"): select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)" - return frappe.db.get_all( + results = frappe.db.get_all( "Bin", fields=["item_code as name", "{0} as value".format(select_field)], group_by="item_code", order_by="value desc", limit=limit, ) + readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name")) + return [item for item in results if item["name"] in readable_active_items] else: if field == "total_sales_amount": - select_field = "sum(order_item.base_net_amount)" - select_doctype = "Sales Invoice" + select_field = "base_net_amount" + select_doctype = "Sales Order" elif field == "total_purchase_amount": - select_field = "sum(order_item.base_net_amount)" - select_doctype = "Purchase Invoice" + select_field = "base_net_amount" + select_doctype = "Purchase Order" elif field == "total_qty_sold": - select_field = "sum(order_item.stock_qty)" - select_doctype = "Sales Invoice" + select_field = "stock_qty" + select_doctype = "Sales Order" elif field == "total_qty_purchased": - select_field = "sum(order_item.stock_qty)" - select_doctype = "Purchase Invoice" - - date_condition = get_date_condition(date_range, "sales_order.posting_date") + select_field = "stock_qty" + select_doctype = "Purchase Order" - return frappe.db.sql( - """ - select order_item.item_code as name, {0} as value - from `tab{1}` sales_order join `tab{1} Item` as order_item - on sales_order.name = order_item.parent - where sales_order.docstatus = 1 - and sales_order.company = %s {2} - group by order_item.item_code - order by value desc - limit %s - """.format( - select_field, select_doctype, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) + + child_doctype = f"{select_doctype} Item" + return frappe.get_list( + select_doctype, + fields=[ + f"`tab{child_doctype}`.item_code as name", + f"sum(`tab{child_doctype}`.{select_field}) as value", + ], + filters=filters, + order_by="value desc", + group_by=f"`tab{child_doctype}`.item_code", + limit=limit, + ) @frappe.whitelist() def get_all_suppliers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", "between", [date_range[0], date_range[1]]]) - return frappe.db.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Purchase Invoice", fields=["supplier as name", "sum(outstanding_amount) as value"], filters=filters, @@ -173,48 +169,40 @@ def get_all_suppliers(date_range, company, field, limit=None): ) else: if field == "total_purchase_amount": - select_field = "sum(purchase_order_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_purchased": - select_field = "sum(purchase_order_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "purchase_order.modified") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select purchase_order.supplier as name, {0} as value - FROM `tabPurchase Invoice` as purchase_order LEFT JOIN `tabPurchase Invoice Item` - as purchase_order_item ON purchase_order.name = purchase_order_item.parent - where - purchase_order.docstatus = 1 - {1} - and purchase_order.company = %s - group by purchase_order.supplier - order by value DESC - limit %s""".format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + return frappe.get_list( + "Purchase Order", + fields=["supplier as name", f"sum({select_field}) as value"], + filters=filters, + group_by="supplier", + order_by="value desc", + limit=limit, + ) @frappe.whitelist() def get_all_sales_partner(date_range, company, field, limit=None): if field == "total_sales_amount": - select_field = "sum(`base_net_total`)" + select_field = "base_net_total" elif field == "total_commission": - select_field = "sum(`total_commission`)" + select_field = "total_commission" - filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company} - if date_range: - date_range = frappe.parse_json(date_range) - filters["posting_date"] = ["between", [date_range[0], date_range[1]]] + filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) return frappe.get_list( "Sales Invoice", fields=[ - "`sales_partner` as name", - "{} as value".format(select_field), + "sales_partner as name", + f"sum({select_field}) as value", ], filters=filters, group_by="sales_partner", @@ -225,27 +213,29 @@ def get_all_sales_partner(date_range, company, field, limit=None): @frappe.whitelist() def get_all_sales_person(date_range, company, field=None, limit=0): - date_condition = get_date_condition(date_range, "sales_order.posting_date") + filters = [ + ["docstatus", "=", "1"], + ["company", "=", company], + ["Sales Team", "sales_person", "is", "set"], + ] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select sales_team.sales_person as name, sum(sales_order.base_net_total) as value - from `tabSales Invoice` as sales_order join `tabSales Team` as sales_team - on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Invoice' - where sales_order.docstatus = 1 - and sales_order.company = %s - {date_condition} - group by sales_team.sales_person - order by value DESC - limit %s - """.format( - date_condition=date_condition - ), - (company, cint(limit)), - as_dict=1, + return frappe.get_list( + "Sales Order", + fields=[ + "`tabSales Team`.sales_person as name", + "sum(`tabSales Team`.allocated_amount) as value", + ], + filters=filters, + group_by="`tabSales Team`.sales_person", + order_by="value desc", + limit=limit, ) +@deprecated def get_date_condition(date_range, field): date_condition = "" if date_range: @@ -255,3 +245,11 @@ def get_date_condition(date_range, field): field, frappe.db.escape(from_date), frappe.db.escape(to_date) ) return date_condition + + +def parse_date_range(date_range): + if date_range: + date_range = frappe.parse_json(date_range) + return date_range[0], date_range[1] + + return None, None diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index ff7256aa55dbc453ee8454202919245bafad0568..e1d847a5ed847b53b7bb98e6de9055e469ac6567 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -187,7 +187,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-11-01 16:51:17.079107", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Bin", @@ -219,6 +219,6 @@ "quick_entry": 1, "search_fields": "item_code,warehouse", "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index cfcab9c29cdebfc523cbdeaca4c990bb300aaab5..1583ad91d9dc484643ece9fe77b17aa5ed37290a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -846,8 +846,6 @@ def make_sales_invoice(source_name, target_doc=None): if automatically_fetch_payment_terms: doc.set_payment_schedule() - doc.set_onload("ignore_price_list", True) - return doc diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index cdf6f5da7e79370ca8d436d5cd932916742f510d..583f2effb679ee5e8fd0f6fda69c579e498296f6 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -373,14 +373,21 @@ $.extend(erpnext.item, { } } + // @dokos + frm.fields_dict["taxes"].grid.get_field("expense_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + query: "erpnext.controllers.queries.get_expense_account", + filters: { company: row.company } + } + } - frm.fields_dict['taxes'].grid.get_field("tax_type").get_query = function(doc, cdt, cdn) { + // @dokos + frm.fields_dict["taxes"].grid.get_field("income_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; return { - filters: [ - ['Account', 'account_type', 'in', - 'Tax, Chargeable, Income Account, Expense Account'], - ['Account', 'docstatus', '!=', 2] - ] + query: "erpnext.controllers.queries.get_income_account", + filters: { company: row.company } } } diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index da9f800177cf9fa3a2fd9ee31282c38f2cfee177..d173929dcc173ba8a6a04fe62b780d1a78de3240 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -76,10 +76,9 @@ "deferred_accounting_section", "enable_deferred_expense", "no_of_months_exp", - "column_break_9s9o", "enable_deferred_revenue", "no_of_months", - "section_break_avcp", + "default_values_section", "item_defaults", "purchasing_tab", "purchase_uom", @@ -105,6 +104,7 @@ "is_sales_item", "column_break3", "max_discount", + "gross_profit_calculation_rule", "customer_details", "customer_items", "item_tax_section_break", @@ -523,7 +523,7 @@ "fieldtype": "Table", "hidden": 1, "label": "Variant Attributes", - "mandatory_depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", + "mandatory_depends_on": "has_variants", "no_copy": 1, "options": "Item Variant Attribute" }, @@ -986,6 +986,19 @@ "fieldtype": "Link", "label": "Booked Item", "options": "Item" + }, + { + "default": "Valuation Rate", + "fieldname": "gross_profit_calculation_rule", + "fieldtype": "Select", + "label": "Gross Profit Calculation in Quotations and Sales Orders Based On", + "options": "Valuation Rate\nLast Purchase Rate\nSupplier Cost Price", + "reqd": 1 + }, + { + "fieldname": "default_values_section", + "fieldtype": "Section Break", + "label": "Default Values" } ], "icon": "fa fa-tag", @@ -994,7 +1007,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-10-23 13:46:32.688051", + "modified": "2023-12-14 11:52:58.865039", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f0a7eeac04bd3d9c767e2fedad5db8cd74cde848..a133de1d11ccc087657ae20418165329c8c6f7f5 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1182,7 +1182,7 @@ def check_stock_uom_with_bin(item, stock_uom): frappe.db.sql("""update `tabBin` set stock_uom=%s where item_code=%s""", (stock_uom, item)) -def get_item_defaults(item_code, company): +def get_item_defaults(item_code, company, tax_category=None): item = frappe.get_cached_doc("Item", item_code) out = item.as_dict() @@ -1192,6 +1192,16 @@ def get_item_defaults(item_code, company): row = copy.deepcopy(d.as_dict()) row.pop("name") out.update(row) + + # @dokos + if tax_category: + for d in item.taxes: + if d.company == company and d.tax_category == tax_category: + if d.income_account: + out["income_account"] = d.income_account + if d.expense_account: + out["expense_account"] = d.expense_account + return out diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index 23b4cf90b318341cd26ecf72041d3a051251568c..699f60e99fe2a2f562f4377f3d8657c0f422d946 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -1,460 +1,494 @@ [ - { - "description": "_Test Item 1", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item", - "item_group": "_Test Item Group", - "item_name": "_Test Item", - "apply_warehouse_wise_reorder_level": 1, - "opening_stock": 10, - "valuation_rate": 100, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "reorder_levels": [ - { - "material_request_type": "Purchase", - "warehouse": "_Test Warehouse - _TC", - "warehouse_reorder_level": 20, - "warehouse_reorder_qty": 20 - } - ], - "uoms": [ - { - "uom": "_Test UOM", - "conversion_factor": 1.0 - }, - { - "uom": "_Test UOM 1", - "conversion_factor": 10.0 - } - ], - "stock_uom": "_Test UOM" - }, - { - "description": "_Test Item 2", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item 2", - "item_group": "_Test Item Group", - "item_name": "_Test Item 2", - "stock_uom": "_Test UOM", - "opening_stock": 10, - "valuation_rate": 100, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Home Desktop 100 3", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Home Desktop 100", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Item Home Desktop 100", - "valuation_rate": 100, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "taxes": [ - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" - } - ], - "stock_uom": "_Test UOM 1" - }, - { - "description": "_Test Item Home Desktop 200 4", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Home Desktop 200", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Item Home Desktop 200", - "stock_uom": "_Test UOM 1", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Product Bundle Item 5", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 0, - "is_sub_contracted_item": 0, - "item_code": "_Test Product Bundle Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Product Bundle Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test FG Item 6", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "item_code": "_Test FG Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test FG Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Non Stock Item 7", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 0, - "is_sub_contracted_item": 0, - "item_code": "_Test Non Stock Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Non Stock Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Serialized Item 8", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 1, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Serialized Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Serialized Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Serialized Item 9", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 1, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Serialized Item With Series", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Serialized Item With Series", - "serial_no_series": "ABCD.#####", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Home Desktop Manufactured 10", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Home Desktop Manufactured", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Item Home Desktop Manufactured", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test FG Item 2 11", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "item_code": "_Test FG Item 2", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test FG Item 2", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Variant Item 12", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "item_code": "_Test Variant Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Variant Item", - "stock_uom": "_Test UOM", - "has_variants": 1, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "attributes": [ - { - "attribute": "Test Size" - } - ], - "apply_warehouse_wise_reorder_level": 1, - "reorder_levels": [ - { - "material_request_type": "Purchase", - "warehouse": "_Test Warehouse - _TC", - "warehouse_reorder_level": 20, - "warehouse_reorder_qty": 20 - } - ] - }, - { - "description": "_Test Item 1", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Warehouse Group Wise Reorder", - "item_group": "_Test Item Group", - "item_name": "_Test Item Warehouse Group Wise Reorder", - "apply_warehouse_wise_reorder_level": 1, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse Group-C1 - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "reorder_levels": [ - { - "warehouse_group": "_Test Warehouse Group - _TC", - "material_request_type": "Purchase", - "warehouse": "_Test Warehouse Group-C1 - _TC", - "warehouse_reorder_level": 20, - "warehouse_reorder_qty": 20 - } - ], - "stock_uom": "_Test UOM" - }, - { - "description": "_Test Item With Item Tax Template", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item With Item Tax Template", - "item_group": "_Test Item Group", - "item_name": "_Test Item With Item Tax Template", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "taxes": [ - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" - }, - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", - "tax_category": "_Test Tax Category 1" - } - ] - }, - { - "description": "_Test Item Inherit Group Item Tax Template 1", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Inherit Group Item Tax Template 1", - "item_group": "_Test Item Group Tax Parent", - "item_name": "_Test Item Inherit Group Item Tax Template 1", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Inherit Group Item Tax Template 2", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Inherit Group Item Tax Template 2", - "item_group": "_Test Item Group Tax Child Override", - "item_name": "_Test Item Inherit Group Item Tax Template 2", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Override Group Item Tax Template", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Override Group Item Tax Template", - "item_group": "_Test Item Group Tax Child Override", - "item_name": "_Test Item Override Group Item Tax Template", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "taxes": [ - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 20 - _TC" - }, - { - "doctype": "Item Tax", - "parentfield": "taxes", - "tax_category": "_Test Tax Category 1", - "item_tax_template": "_Test Item Tax Template 1 - _TC" - } - ] - }, - { - "description": "_Test", - "doctype": "Item", - "is_stock_item": 1, - "item_code": "138-CMS Shoe", - "item_group": "_Test Item Group", - "item_name": "138-CMS Shoe", - "stock_uom": "_Test UOM" - }, - { - "description": "_Test", - "doctype": "Item", - "is_stock_item": 1, - "item_code": "999-Down Payment", - "item_group": "_Test Item Group", - "item_name": "999-Down Payment", - "stock_uom": "_Test UOM" - } - ] + { + "description": "_Test Item 1", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item", + "item_group": "_Test Item Group", + "item_name": "_Test Item", + "apply_warehouse_wise_reorder_level": 1, + "opening_stock": 10, + "valuation_rate": 100, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "reorder_levels": [ + { + "material_request_type": "Purchase", + "warehouse": "_Test Warehouse - _TC", + "warehouse_reorder_level": 20, + "warehouse_reorder_qty": 20 + } + ], + "uoms": [ + { + "uom": "_Test UOM", + "conversion_factor": 1.0 + }, + { + "uom": "_Test UOM 1", + "conversion_factor": 10.0 + } + ], + "stock_uom": "_Test UOM" + }, + { + "description": "_Test Item 2", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item 2", + "item_group": "_Test Item Group", + "item_name": "_Test Item 2", + "stock_uom": "_Test UOM", + "opening_stock": 10, + "valuation_rate": 100, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Home Desktop 100 3", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Home Desktop 100", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Item Home Desktop 100", + "valuation_rate": 100, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "taxes": [ + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" + } + ], + "stock_uom": "_Test UOM 1" + }, + { + "description": "_Test Item Home Desktop 200 4", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Home Desktop 200", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Item Home Desktop 200", + "stock_uom": "_Test UOM 1", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Product Bundle Item 5", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 0, + "is_sub_contracted_item": 0, + "item_code": "_Test Product Bundle Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Product Bundle Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test FG Item 6", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "item_code": "_Test FG Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test FG Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Non Stock Item 7", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 0, + "is_sub_contracted_item": 0, + "item_code": "_Test Non Stock Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Non Stock Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Serialized Item 8", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 1, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Serialized Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Serialized Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Serialized Item 9", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 1, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Serialized Item With Series", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Serialized Item With Series", + "serial_no_series": "ABCD.#####", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Home Desktop Manufactured 10", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Home Desktop Manufactured", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Item Home Desktop Manufactured", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test FG Item 2 11", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "item_code": "_Test FG Item 2", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test FG Item 2", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Variant Item 12", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "item_code": "_Test Variant Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Variant Item", + "stock_uom": "_Test UOM", + "has_variants": 1, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "attributes": [ + { + "attribute": "Test Size" + } + ], + "apply_warehouse_wise_reorder_level": 1, + "reorder_levels": [ + { + "material_request_type": "Purchase", + "warehouse": "_Test Warehouse - _TC", + "warehouse_reorder_level": 20, + "warehouse_reorder_qty": 20 + } + ] + }, + { + "description": "_Test Item 1", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Warehouse Group Wise Reorder", + "item_group": "_Test Item Group", + "item_name": "_Test Item Warehouse Group Wise Reorder", + "apply_warehouse_wise_reorder_level": 1, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse Group-C1 - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "reorder_levels": [ + { + "warehouse_group": "_Test Warehouse Group - _TC", + "material_request_type": "Purchase", + "warehouse": "_Test Warehouse Group-C1 - _TC", + "warehouse_reorder_level": 20, + "warehouse_reorder_qty": 20 + } + ], + "stock_uom": "_Test UOM" + }, + { + "description": "_Test Item With Item Tax Template", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item With Item Tax Template", + "item_group": "_Test Item Group", + "item_name": "_Test Item With Item Tax Template", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "taxes": [ + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" + }, + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", + "tax_category": "_Test Tax Category 1" + } + ] + }, + { + "description": "_Test Item Inherit Group Item Tax Template 1", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Inherit Group Item Tax Template 1", + "item_group": "_Test Item Group Tax Parent", + "item_name": "_Test Item Inherit Group Item Tax Template 1", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Inherit Group Item Tax Template 2", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Inherit Group Item Tax Template 2", + "item_group": "_Test Item Group Tax Child Override", + "item_name": "_Test Item Inherit Group Item Tax Template 2", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Override Group Item Tax Template", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Override Group Item Tax Template", + "item_group": "_Test Item Group Tax Child Override", + "item_name": "_Test Item Override Group Item Tax Template", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "taxes": [ + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 20 - _TC" + }, + { + "doctype": "Item Tax", + "parentfield": "taxes", + "tax_category": "_Test Tax Category 1", + "item_tax_template": "_Test Item Tax Template 1 - _TC" + } + ] + }, + { + "description": "_Test", + "doctype": "Item", + "is_stock_item": 1, + "item_code": "138-CMS Shoe", + "item_group": "_Test Item Group", + "item_name": "138-CMS Shoe", + "stock_uom": "_Test UOM" + }, + { + "description": "_Test", + "doctype": "Item", + "is_down_payment_item": 1, + "item_code": "999-Down Payment", + "item_group": "_Test Item Group", + "item_name": "999-Down Payment", + "stock_uom": "_Test UOM" + } +] diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json index 2de6d4d4f47b24ff8007b8a32c1dcd9a5ba5eaee..28c54b45d32a72e7b1268c56955b37955581dad5 100644 --- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json +++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json @@ -55,14 +55,14 @@ "read_only": 1 }, { - "default": "0", - "fieldname": "is_default", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Default" + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Default" } ], - "modified": "2020-04-16 19:07:31.175919", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Item Manufacturer", @@ -107,7 +107,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "title_field": "item_code", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js index 6a7f0ba95738ade2d25850fc8eeda8994f10e4ae..fcf551afac7c165dce2a22ad699a44ac686d7d45 100644 --- a/erpnext/stock/doctype/item_price/item_price.js +++ b/erpnext/stock/doctype/item_price/item_price.js @@ -33,5 +33,25 @@ frappe.ui.form.on("Item Price", { } } }); + }, + + additional_costs_percentage(frm) { + if (frm.no_recalculation) { + frm.no_recalculation = false + } else { + const amount = frm.doc.price_list_rate * frm.doc.additional_costs_percentage / 100.0; + frm.no_recalculation = true + frm.set_value("additional_costs_amount", amount) + } + }, + + additional_costs_amount(frm) { + if (frm.no_recalculation) { + frm.no_recalculation = false + } else { + const percent = frm.doc.price_list_rate ? frm.doc.additional_costs_amount / frm.doc.price_list_rate * 100.0 : 0; + frm.no_recalculation = true + frm.set_value("additional_costs_percentage", percent) + } } }); diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index c775958d65dd73f3029a330c5d2da9fb6006ddf3..94692f3a1a01ca5a462df66bf23d214b7bcaf64a 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -32,6 +32,11 @@ "lead_time_days", "column_break_18", "valid_upto", + "additional_costs_tab", + "additional_costs_percentage", + "column_break_tmpu", + "additional_costs_amount", + "note_tab", "section_break_24", "note", "reference" @@ -181,11 +186,11 @@ }, { "default": "0", + "description": "Deprecated field. Will be removed in a future version.", "fieldname": "lead_time_days", "fieldtype": "Int", - "label": "Lead Time in days", "hidden": 1, - "description": "Deprecated field. Will be removed in a future version." + "label": "Lead Time in days" }, { "fieldname": "column_break_18", @@ -217,13 +222,38 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" + }, + { + "fieldname": "note_tab", + "fieldtype": "Tab Break", + "label": "Note" + }, + { + "depends_on": "eval:doc.buying", + "fieldname": "additional_costs_tab", + "fieldtype": "Tab Break", + "label": "Additional Costs" + }, + { + "fieldname": "additional_costs_percentage", + "fieldtype": "Percent", + "label": "Additional Costs Percentage" + }, + { + "fieldname": "column_break_tmpu", + "fieldtype": "Column Break" + }, + { + "fieldname": "additional_costs_amount", + "fieldtype": "Currency", + "label": "Additional Costs Amount" } ], "icon": "fa fa-flag", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-01-19 08:26:04.041861", + "modified": "2023-12-12 11:57:18.562754", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 45ed41b2a633955ece58c9c4a0d79d5aefea6fdd..bc2c09fc63d8bff1d714e5f1e58ec5e5ceeb4edb 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -15,6 +15,37 @@ class ItemPriceDuplicateItem(frappe.ValidationError): class ItemPrice(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 + + additional_costs_amount: DF.Currency + additional_costs_percentage: DF.Percent + batch_no: DF.Link | None + brand: DF.Link | None + buying: DF.Check + currency: DF.Link | None + customer: DF.Link | None + item_code: DF.Link + item_description: DF.Text | None + item_name: DF.Data | None + lead_time_days: DF.Int + note: DF.Text | None + packing_unit: DF.Int + price_list: DF.Link + price_list_rate: DF.Currency + reference: DF.Data | None + selling: DF.Check + supplier: DF.Link | None + uom: DF.Link | None + valid_from: DF.Date | None + valid_upto: DF.Date | None + # end: auto-generated types + def validate(self): self.validate_item() self.validate_dates() diff --git a/erpnext/stock/doctype/item_tax/item_tax.json b/erpnext/stock/doctype/item_tax/item_tax.json index 77ab25e4359c273f2211aa52c9cd9c8a8bee905f..488324c455915d798a3d220285ef6219113b4dc3 100644 --- a/erpnext/stock/doctype/item_tax/item_tax.json +++ b/erpnext/stock/doctype/item_tax/item_tax.json @@ -1,64 +1,112 @@ { - "actions": [], - "creation": "2013-02-22 01:28:01", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_tax_template", - "tax_category", - "valid_from", - "minimum_net_rate", - "maximum_net_rate" - ], - "fields": [ - { - "fieldname": "item_tax_template", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Tax Template", - "oldfieldname": "tax_type", - "oldfieldtype": "Link", - "options": "Item Tax Template", - "reqd": 1 - }, - { - "fieldname": "tax_category", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Tax Category", - "oldfieldname": "tax_rate", - "oldfieldtype": "Currency", - "options": "Tax Category" - }, - { - "fieldname": "valid_from", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Valid From" - }, - { - "fieldname": "maximum_net_rate", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Maximum Net Rate" - }, - { - "fieldname": "minimum_net_rate", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Minimum Net Rate" - } - ], - "idx": 1, - "istable": 1, - "links": [], - "modified": "2021-06-03 13:20:06.982303", - "modified_by": "Administrator", - "module": "Stock", - "name": "Item Tax", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC" - } \ No newline at end of file + "actions": [], + "creation": "2013-02-22 01:28:01", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "tax_determination_section", + "company", + "tax_category", + "item_tax_template", + "column_break_znrh", + "valid_from", + "minimum_net_rate", + "maximum_net_rate", + "income_expense_section", + "income_account", + "column_break_eamj", + "expense_account" + ], + "fields": [ + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "oldfieldname": "tax_type", + "oldfieldtype": "Link", + "options": "Item Tax Template", + "reqd": 1 + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Tax Category", + "oldfieldname": "tax_rate", + "oldfieldtype": "Currency", + "options": "Tax Category" + }, + { + "fieldname": "valid_from", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Valid From" + }, + { + "fieldname": "maximum_net_rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Maximum Net Rate" + }, + { + "fieldname": "minimum_net_rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Minimum Net Rate" + }, + { + "fetch_from": "item_tax_template.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1 + }, + { + "fieldname": "column_break_znrh", + "fieldtype": "Column Break" + }, + { + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "options": "Account" + }, + { + "fieldname": "column_break_eamj", + "fieldtype": "Column Break" + }, + { + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Expense Account", + "options": "Account" + }, + { + "fieldname": "tax_determination_section", + "fieldtype": "Section Break", + "label": "Tax Determination" + }, + { + "depends_on": "eval:doc.tax_category", + "description": "You can use the following fields to override the configuration set in Accounts > Default Values", + "fieldname": "income_expense_section", + "fieldtype": "Section Break", + "label": "Specific Income and Expense Accounts" + } + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2023-12-14 13:57:37.932925", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item Tax", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/erpnext/stock/doctype/item_tax/item_tax.py b/erpnext/stock/doctype/item_tax/item_tax.py index 5f877eaa6676d58a39f9f9ad6a0be5758c16e452..784db87945a4c7f84cc6f2b9dd1ee7a5916450e0 100644 --- a/erpnext/stock/doctype/item_tax/item_tax.py +++ b/erpnext/stock/doctype/item_tax/item_tax.py @@ -1,10 +1,29 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -import frappe from frappe.model.document import Document class ItemTax(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 + + company: DF.Link | None + expense_account: DF.Link | None + income_account: DF.Link | None + item_tax_template: DF.Link + maximum_net_rate: DF.Float + minimum_net_rate: DF.Float + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + tax_category: DF.Link | None + valid_from: DF.Date | None + # end: auto-generated types + pass diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index c2e9edbb0383fe509e762ccbc18a20be42176d01..2dd054d593559451a1552eee656b472d97a3f9a4 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -199,10 +199,8 @@ frappe.ui.form.on('Material Request', { get_item_data: function(frm, item, overwrite_warehouse=false) { if (item && !item.item_code) { return; } - - frm.call({ + frappe.call({ method: "erpnext.stock.get_item_details.get_item_details", - child: item, args: { args: { item_code: item.item_code, diff --git a/erpnext/stock/doctype/price_list/price_list.json b/erpnext/stock/doctype/price_list/price_list.json index 20e399bb3900dd11c64e03aabb28e51088e9d64d..2a74c77cc2c78230fccf7b76b07736cbd5dcf675 100644 --- a/erpnext/stock/doctype/price_list/price_list.json +++ b/erpnext/stock/doctype/price_list/price_list.json @@ -83,7 +83,7 @@ "icon": "fa fa-tags", "idx": 1, "links": [], - "modified": "2022-08-03 15:48:31.064198", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Price List", @@ -127,6 +127,6 @@ "search_fields": "currency", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 8647528ea50f27efad0c3932d60c75f9f5a2545c..6a379db1c3285fbb630d4b348e6d66f5b0d305c9 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1096,7 +1096,6 @@ def make_purchase_invoice(source_name, target_doc=None): set_missing_values, ) - doclist.set_onload("ignore_price_list", True) return doclist diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py index 0a04210e0b01de845aeabccb4785a04cc630a4de..56190f513b7a8a738fcbd85ea3f1e0b69aab1920 100644 --- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py @@ -149,7 +149,7 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None): pending_qty -= qty_to_allocate rule["free_space"] -= stock_qty_to_allocate - if not pending_stock_qty > 0: + if pending_stock_qty <= 0: break # if pending qty after applying all rules, add row without warehouse diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index bedd44e4d97539ed20ecf2ef34ced1b878ed405c..7eb8c3fe2f3eab323f424af4fdff96921b36d156 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -245,7 +245,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-23 11:56:50.282878", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", @@ -270,6 +270,6 @@ "search_fields": "item_code, report_date, reference_name", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index cda444510a8f21ada560ee9c39be8eb78c79656a..9f01ee9ae62d4383f103c7e8921c499ee76c171a 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -121,7 +121,7 @@ frappe.ui.form.on('Serial and Batch Bundle', { frappe.throw(__("Please attach CSV file")); } - if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) { + if (frm.doc.has_serial_no && !prompt_data.csv_file && !prompt_data.serial_nos) { frappe.throw(__("Please enter serial nos")); } }, diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json index c1500e261ff96e85a615f5f016169a675bef252a..482757eb61d882ea9f8c9f8200795f370f75af8d 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json @@ -1,7 +1,7 @@ { "actions": [], "autoname": "naming_series:", - "creation": "2022-09-29 14:56:38.338267", + "creation": "2023-08-11 17:22:12.907518", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -250,7 +250,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-28 12:56:03.072224", + "modified": "2023-12-07 17:56:55.528563", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Bundle", @@ -270,6 +270,118 @@ "share": 1, "submit": 1, "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 3c824fd590ab62405176df27fea64072e34484ea..cb5387ed65e349a90052081cabad7c8eb5492f62 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -216,6 +216,7 @@ class SerialandBatchBundle(Document): "serial_nos": [row.serial_no for row in self.entries if row.serial_no], "batch_nos": {row.batch_no: row for row in self.entries if row.batch_no}, "voucher_type": self.voucher_type, + "voucher_detail_no": self.voucher_detail_no, } ) @@ -474,6 +475,22 @@ class SerialandBatchBundle(Document): serial_batches = {} for row in self.entries: + if self.has_serial_no and not row.serial_no: + frappe.throw( + _("At row {0}: Serial No is mandatory for Item {1}").format( + bold(row.idx), bold(self.item_code) + ), + title=_("Serial No is mandatory"), + ) + + if self.has_batch_no and not row.batch_no: + frappe.throw( + _("At row {0}: Batch No is mandatory for Item {1}").format( + bold(row.idx), bold(self.item_code) + ), + title=_("Batch No is mandatory"), + ) + if row.serial_no: serial_nos.append(row.serial_no) @@ -656,6 +673,7 @@ class SerialandBatchBundle(Document): "item_code": self.item_code, "warehouse": self.warehouse, "batch_no": batches, + "consider_negative_batches": True, } ) ) @@ -666,6 +684,9 @@ class SerialandBatchBundle(Document): available_batches = get_available_batches_qty(available_batches) for batch_no in batches: if batch_no not in available_batches or available_batches[batch_no] < 0: + if flt(available_batches.get(batch_no)) < 0: + self.validate_negative_batch(batch_no, available_batches[batch_no]) + self.throw_error_message( f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}" ) @@ -757,6 +778,9 @@ def parse_csv_file_to_get_serial_batch(reader): if index == 0: has_serial_no = row[0] == "Serial No" has_batch_no = row[0] == "Batch No" + if not has_batch_no: + has_batch_no = row[1] == "Batch No" + continue if not row[0]: @@ -773,6 +797,13 @@ def parse_csv_file_to_get_serial_batch(reader): } ) + batch_nos.append( + { + "batch_no": row[1], + "qty": row[2], + } + ) + serial_nos.append(_dict) elif has_batch_no: batch_nos.append( @@ -808,6 +839,9 @@ def make_serial_nos(item_code, serial_nos): serial_nos_details = [] user = frappe.session.user for serial_no in serial_nos: + if frappe.db.exists("Serial No", serial_no): + continue + serial_nos_details.append( ( serial_no, @@ -838,7 +872,7 @@ def make_serial_nos(item_code, serial_nos): frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details)) - frappe.msgprint(_("Serial Nos are created successfully")) + frappe.msgprint(_("Serial Nos are created successfully"), alert=True) def make_batch_nos(item_code, batch_nos): @@ -849,6 +883,9 @@ def make_batch_nos(item_code, batch_nos): batch_nos_details = [] user = frappe.session.user for batch_no in batch_nos: + if frappe.db.exists("Batch", batch_no): + continue + batch_nos_details.append( (batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description) ) @@ -867,7 +904,7 @@ def make_batch_nos(item_code, batch_nos): frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details)) - frappe.msgprint(_("Batch Nos are created successfully")) + frappe.msgprint(_("Batch Nos are created successfully"), alert=True) def parse_serial_nos(data): @@ -1422,7 +1459,8 @@ def get_auto_batch_nos(kwargs): available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches ) - available_batches = list(filter(lambda x: x.qty > 0, available_batches)) + if not kwargs.consider_negative_batches: + available_batches = list(filter(lambda x: x.qty > 0, available_batches)) if not qty: return available_batches @@ -1708,3 +1746,8 @@ def get_stock_ledgers_batches(kwargs): batches[key].qty += d.qty return batches + + +@frappe.whitelist() +def get_batch_no_from_serial_no(serial_no): + return frappe.get_cached_value("Serial No", serial_no, "batch_no") diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index b8b9d06a86df1c4fb373cffb3071ef56a4b7515c..9e06c50e34fe11fb5d0280615fe445ada75b33fa 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -8,6 +8,7 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import flt, nowtime, today from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry class TestSerialandBatchBundle(FrappeTestCase): @@ -367,6 +368,58 @@ class TestSerialandBatchBundle(FrappeTestCase): # Batch does not belong to serial no self.assertRaises(frappe.exceptions.ValidationError, doc.save) + def test_auto_delete_draft_serial_and_batch_bundle(self): + serial_and_batch_code = "New Serial No Auto Delete 1" + make_item( + serial_and_batch_code, + { + "has_serial_no": 1, + "serial_no_series": "TEST-SER-VALL-.#####", + "is_stock_item": 1, + }, + ) + + ste = make_stock_entry( + item_code=serial_and_batch_code, + target="_Test Warehouse - _TC", + qty=1, + rate=500, + do_not_submit=True, + ) + + serial_no = "SN-TEST-AUTO-DEL" + if not frappe.db.exists("Serial No", serial_no): + frappe.get_doc( + { + "doctype": "Serial No", + "serial_no": serial_no, + "item_code": serial_and_batch_code, + "company": "_Test Company", + } + ).insert(ignore_permissions=True) + + bundle_doc = make_serial_batch_bundle( + { + "item_code": serial_and_batch_code, + "warehouse": "_Test Warehouse - _TC", + "voucher_type": "Stock Entry", + "posting_date": ste.posting_date, + "posting_time": ste.posting_time, + "qty": 1, + "serial_nos": [serial_no], + "type_of_transaction": "Inward", + "do_not_submit": True, + } + ) + + bundle_doc.reload() + ste.items[0].serial_and_batch_bundle = bundle_doc.name + ste.save() + ste.reload() + + ste.delete() + self.assertFalse(frappe.db.exists("Serial and Batch Bundle", bundle_doc.name)) + def get_batch_from_bundle(bundle): from erpnext.stock.serial_batch_bundle import get_batch_nos diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json index a6d192b77ea804a1b9a0e24f051d87669c9edcef..c2bc636a1747aa13bc46c62bce9918362e4b1c83 100644 --- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json +++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json @@ -27,7 +27,6 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Serial No", - "mandatory_depends_on": "eval:parent.has_serial_no == 1", "options": "Serial No", "search_index": 1 }, @@ -38,7 +37,6 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Batch No", - "mandatory_depends_on": "eval:parent.has_batch_no == 1", "options": "Batch", "search_index": 1 }, @@ -122,7 +120,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-07-03 15:29:50.199075", + "modified": "2023-12-10 19:47:48.227772", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 13620a3d28b860f205726a280fc553fb4f58d27a..fd7105563b6bffa3b010b725c64738e325a4cc0a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -780,10 +780,9 @@ frappe.ui.form.on('Stock Entry Detail', { }); refresh_field("items"); - let no_batch_serial_number_value = !d.serial_no; - if (d.has_batch_no && !d.has_serial_no) { - // check only batch_no for batched item - no_batch_serial_number_value = !d.batch_no; + let no_batch_serial_number_value = false; + if (d.has_serial_no || d.has_batch_no) { + no_batch_serial_number_value = true; } if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) { @@ -940,6 +939,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle } scan_barcode() { + frappe.flags.dialog_set = false; const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm}); barcode_scanner.process_scan(); } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b06de2e2fce1c25955e2525732197de50785b91d..9f1a5db995f5e5c2b91a87c4ebe66555b24e7ab6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -151,7 +151,7 @@ class StockEntry(StockController): self.calculate_rate_and_amount() self.validate_putaway_capacity() - if not self.get("purpose") == "Manufacture": + if self.get("purpose") != "Manufacture": # ignore scrap item wh difference and empty source/target wh # in Manufacture Entry self.reset_default_field_value("from_warehouse", "items", "s_warehouse") @@ -807,6 +807,7 @@ class StockEntry(StockController): "company": self.company, "allow_zero_valuation": item.allow_zero_valuation_rate, "serial_and_batch_bundle": item.serial_and_batch_bundle, + "voucher_detail_no": item.name, } ) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index d6fb9a16595d6272dcf03c9e9a5dc46fd8197fcf..7273d45e21f70476858d7089610c4babf72a7bed 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -936,6 +936,42 @@ class TestStockEntry(FrappeTestCase): stock_entry.insert() self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items]) + def test_nagative_stock_for_batch(self): + item = make_item( + "_Test Batch Negative Item", + { + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B-BATCH-.##", + "is_stock_item": 1, + }, + ) + + make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100) + + ste = frappe.new_doc("Stock Entry") + ste.purpose = "Material Issue" + ste.company = "_Test Company" + for qty in [50, 20, 30]: + ste.append( + "items", + { + "item_code": item.name, + "s_warehouse": "_Test Warehouse - _TC", + "qty": qty, + "uom": item.stock_uom, + "stock_uom": item.stock_uom, + "conversion_factor": 1, + "transfer_qty": qty, + }, + ) + + ste.set_stock_entry_type() + ste.insert() + make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100) + + self.assertRaises(frappe.ValidationError, ste.submit) + def test_same_serial_nos_in_repack_or_manufacture_entries(self): s1 = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = get_serial_nos_from_bundle(s1.get("items")[0].serial_and_batch_bundle) @@ -1694,6 +1730,45 @@ class TestStockEntry(FrappeTestCase): self.assertFalse(doc.is_enqueue_action()) frappe.flags.in_test = True + def test_negative_batch(self): + item_code = "Test Negative Batch Item - 001" + make_item( + item_code, + {"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"}, + ) + + se1 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se1.reload() + + batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) + + se2 = make_stock_entry( + item_code=item_code, + purpose="Material Issue", + batch_no=batch_no, + qty=10, + source="_Test Warehouse - _TC", + ) + + se2.reload() + + se3 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se3.reload() + + self.assertRaises(frappe.ValidationError, se1.cancel) + def make_serialized_item(**args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 591aa0d4dbfff73e23dda9554af52dc72d95aa59..4db07f3acf003c75764108f7171c589d0c2bfa35 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -589,7 +589,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-05-09 12:41:18.210864", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", @@ -597,6 +597,6 @@ "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json index 57d3dba4b1d7ca58a900020868e18a2f78596bbf..cee12a7e68344e63b665557b0f27075aacfabba3 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json @@ -29,7 +29,7 @@ } ], "links": [], - "modified": "2022-07-10 16:52:17.517739", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Type", @@ -87,7 +87,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1, "translated_doctype": 1 diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index c1b205132cf6548a78067b7a483e6fa5f3dd6a0d..bc145040a27779e79b4ace87616338c73c475ea9 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -174,7 +174,9 @@ class StockLedgerEntry(Document): if not self.serial_and_batch_bundle: self.throw_error_message(f"Serial No / Batch No are mandatory for Item {self.item_code}") - if self.serial_and_batch_bundle and not (item_detail.has_serial_no or item_detail.has_batch_no): + if ( + self.serial_and_batch_bundle and not item_detail.has_serial_no and not item_detail.has_batch_no + ): self.throw_error_message(f"Serial No and Batch No are not allowed for Item {self.item_code}") def throw_error_message(self, message, exception=frappe.ValidationError): diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 975cc3d456ad5dd4c32fa88beae1361d21b90d09..37ce5ed1eff7229f347f8d8df3114b0a0ae8f1ae 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -483,9 +483,9 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list) sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"]) svd_list = [-1 * d["stock_value_difference"] for d in sle_details] - expected_incoming_rates = expected_abs_svd = sorted([75.0, 125.0, 75.0, 125.0]) + expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0] - self.assertEqual(expected_abs_svd, sorted(svd_list), "Incorrect 'Stock Value Difference' values") + self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values") for dn, incoming_rate in zip(dns, expected_incoming_rates): self.assertTrue( dn.items[0].incoming_rate in expected_abs_svd, diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index ec5443081e79078921b1a9a0d7add7116f737647..651d558cf6ede3acfeefad86b81a5ecd4023407f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -209,7 +209,7 @@ frappe.ui.form.on("Stock Reconciliation", { set_amount_quantity: function(doc, cdt, cdn) { var d = frappe.model.get_doc(cdt, cdn); - if (d.qty & d.valuation_rate) { + if (d.qty && d.valuation_rate) { frappe.model.set_value(cdt, cdn, "amount", flt(d.qty) * flt(d.valuation_rate)); frappe.model.set_value(cdt, cdn, "quantity_difference", flt(d.qty) - flt(d.current_qty)); frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount)); diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 729ca9564e77bf0eeb5e6f23672b8dcfc8afbb64..b0fb1b3796ec663693e98e765390044cf5737bfb 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -894,7 +894,7 @@ def create_stock_reservation_entries_for_so_items( continue # Stock should be reserved from the Pick List if has Picked Qty. - if not from_voucher_type == "Pick List" and flt(item.picked_qty) > 0: + if from_voucher_type != "Pick List" and flt(item.picked_qty) > 0: frappe.throw( _("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format( item.idx, frappe.bold(item.item_code) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 1c5cccc903137a79f88e127ec6419bc0127a601e..3141b366769455ef0590a42e64e85d98552588cd 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -427,7 +427,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-19 12:35:30.068799", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -449,7 +449,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/stock/doctype/warehouse_type/warehouse_type.json b/erpnext/stock/doctype/warehouse_type/warehouse_type.json index 9993bfd93f2d5c92752aa850b028e773ccb1d1a8..b5661049bc369358ba29c103a82603c91fc6de7c 100644 --- a/erpnext/stock/doctype/warehouse_type/warehouse_type.json +++ b/erpnext/stock/doctype/warehouse_type/warehouse_type.json @@ -13,7 +13,7 @@ "label": "Description" } ], - "modified": "2019-05-27 18:35:33.354571", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse Type", @@ -103,6 +103,6 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 30493cd43a2dc178dca01e8cb2ac002de6cfd76d..fe8ff18d1624fe8d3b8512a5fcf03bce838c1a96 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -32,6 +32,12 @@ purchase_doctypes = [ "Purchase Invoice", ] +GROSS_PROFIT_CALCULATION_RULES = { + "Valuation Rate": "valuation_rate", + "Last Purchase Rate": "last_purchase_rate", + "Supplier Cost Price": "cost_price", +} + @frappe.whitelist() def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): @@ -136,6 +142,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.bom = args.get("bom") or get_default_bom(args.item_code) get_gross_profit(out) + get_supplier_cost(out) + if args.doctype == "Material Request": out.rate = args.rate or out.price_list_rate out.amount = flt(args.qty) * flt(out.rate) @@ -273,8 +281,10 @@ def get_basic_details(args, item, overwrite_warehouse=True): if item.variant_of and not item.taxes: item.update_template_tables() - item_defaults = get_item_defaults(item.name, args.company) - item_group_defaults = get_item_group_defaults(item.name, args.company) + item_defaults = get_item_defaults(item.name, args.company, args.get("tax_category")) # @dokos + item_group_defaults = get_item_group_defaults( + item.name, args.company, args.get("tax_category") + ) # @dokos brand_defaults = get_brand_defaults(item.name, args.company) defaults = frappe._dict( @@ -359,7 +369,6 @@ def get_basic_details(args, item, overwrite_warehouse=True): "net_amount": 0.0, "discount_percentage": 0.0, "discount_amount": flt(args.discount_amount) or 0.0, - "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "update_stock": args.get("update_stock") if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"] else 0, @@ -368,7 +377,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): else 0, "is_fixed_asset": item.is_fixed_asset, "last_purchase_rate": item.last_purchase_rate - if args.get("doctype") in ["Purchase Order"] + if args.get("doctype") in ["Purchase Order", "Quotation"] else 0, "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), @@ -378,9 +387,14 @@ def get_basic_details(args, item, overwrite_warehouse=True): "grant_commission": item.get("grant_commission"), "is_down_payment_item": item.get("is_down_payment_item"), "down_payment_rate": item.get("down_payment_percentage"), + "gross_profit_calculation_rule": item.get("gross_profit_calculation_rule"), } ) + default_supplier = get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults) + if default_supplier: + out.supplier = default_supplier + if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): out.update(calculate_service_end_date(args, item)) @@ -570,8 +584,8 @@ def get_item_tax_template(args, item, out): item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out) item_group = item_group_doc.parent_item_group - if args.child_doctype and item_tax_template: - out.update(get_fetch_values(args.child_doctype, "item_tax_template", item_tax_template)) + if args.get("child_doctype") and item_tax_template: + out.update(get_fetch_values(args.get("child_doctype"), "item_tax_template", item_tax_template)) def _get_item_tax_template(args, taxes, out=None, for_validate=False): @@ -899,6 +913,7 @@ def get_item_price(args, item_code, ignore_party=False): query = ( frappe.qb.from_(ip) .select(ip.name, ip.price_list_rate, ip.uom) + .select(ip.additional_costs_percentage, ip.additional_costs_amount) .where( (ip.item_code == item_code) & (ip.price_list == args.get("price_list")) @@ -927,7 +942,7 @@ def get_item_price(args, item_code, ignore_party=False): return query.run() -def get_price_list_rate_for(args, item_code): +def get_price_list_rate_for(args, item_code, with_additional_costs=False): """ :param customer: link to Customer DocType :param supplier: link to Supplier DocType @@ -948,6 +963,7 @@ def get_price_list_rate_for(args, item_code): item_price_data = 0 price_list_rate = get_item_price(item_price_args, item_code) + if price_list_rate: desired_qty = args.get("qty") if desired_qty and check_packing_list(price_list_rate[0][0], desired_qty, item_code): @@ -971,11 +987,16 @@ def get_price_list_rate_for(args, item_code): if item_price_data: if item_price_data[0][2] == args.get("uom"): - return item_price_data[0][1] + price = item_price_data[0][1] elif not args.get("price_list_uom_dependant"): - return flt(item_price_data[0][1] * flt(args.get("conversion_factor", 1))) + price = flt(item_price_data[0][1] * flt(args.get("conversion_factor", 1))) else: - return item_price_data[0][1] + price = item_price_data[0][1] + + if with_additional_costs and len(item_price_data[0]) > 4: + return price, item_price_data[0][3], item_price_data[0][4] + + return price def check_packing_list(price_list_rate_name, desired_qty, item_code): @@ -1359,8 +1380,12 @@ def get_valuation_rate(item_code, company, warehouse=None): def get_gross_profit(out): - if out.valuation_rate: - out.update({"gross_profit": ((out.base_rate - out.valuation_rate) * out.stock_qty)}) + gross_profit_calculation_rule = out.get("gross_profit_calculation_rule") or "Valuation Rate" + gross_profit_calculation_field = GROSS_PROFIT_CALCULATION_RULES.get(gross_profit_calculation_rule) + if out.get(gross_profit_calculation_field): + out.update( + {"gross_profit": ((out.base_rate - out.get(gross_profit_calculation_field)) * out.stock_qty)} + ) return out @@ -1413,3 +1438,41 @@ def get_blanket_order_details(args): blanket_order_details = blanket_order_details[0] if blanket_order_details else "" return blanket_order_details + + +@frappe.whitelist() +def get_supplier_cost(out): + if isinstance(out, str): + out = frappe.parse_json(out) + + price_list_args = out.copy() + price_list_args["customer"] = None + + if out.get("supplier"): + if not ( + buying_price_list := frappe.get_cached_value("Supplier", out["supplier"], "default_price_list") + ): + buying_price_list = frappe.db.get_single_value("Buying Settings", "buying_price_list") + + price_list_args["price_list"] = buying_price_list + + unit_cost_price = get_price_list_rate_for( + price_list_args, price_list_args.get("item_code"), with_additional_costs=True + ) + + if not unit_cost_price: + price_list_args["supplier"] = None + unit_cost_price = get_price_list_rate_for( + price_list_args, price_list_args.get("item_code"), with_additional_costs=True + ) + + if unit_cost_price: + out.update( + { + "unit_cost_price": unit_cost_price[0], + "additional_costs_percentage": unit_cost_price[1], + "additional_costs_amount": unit_cost_price[2], + } + ) + + return out diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index 8f92eab58e4acba8a43d9cac9ba38a00f22ebb85..3f5216bae87e555dd102d5a291b0498d010352f6 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -1,9 +1,12 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import copy + import frappe from frappe import _ +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle from erpnext.stock.stock_ledger import get_stock_ledger_entries @@ -15,8 +18,8 @@ def execute(filters=None): def get_columns(filters): columns = [ - {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"}, - {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"}, + {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 120}, + {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90}, { "label": _("Voucher Type"), "fieldtype": "Data", @@ -28,7 +31,7 @@ def get_columns(filters): "fieldtype": "Dynamic Link", "fieldname": "voucher_no", "options": "voucher_type", - "width": 180, + "width": 230, }, { "label": _("Company"), @@ -48,7 +51,7 @@ def get_columns(filters): "label": _("Status"), "fieldtype": "Data", "fieldname": "status", - "width": 120, + "width": 90, }, { "label": _("Serial No"), @@ -61,7 +64,7 @@ def get_columns(filters): "label": _("Valuation Rate"), "fieldtype": "Float", "fieldname": "valuation_rate", - "width": 150, + "width": 130, }, { "label": _("Qty"), @@ -101,15 +104,29 @@ def get_data(filters): } ) - serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}] + serial_nos = [] + if row.serial_no: + parsed_serial_nos = get_serial_nos_from_sle(row.serial_no) + for serial_no in parsed_serial_nos: + if filters.get("serial_no") and filters.get("serial_no") != serial_no: + continue + + serial_nos.append( + { + "serial_no": serial_no, + "valuation_rate": abs(row.stock_value_difference / row.actual_qty), + } + ) + if row.serial_and_batch_bundle: - serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []) + serial_nos.extend(bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])) for index, bundle_data in enumerate(serial_nos): if index == 0: - args.serial_no = bundle_data.get("serial_no") - args.valuation_rate = bundle_data.get("valuation_rate") - data.append(args) + new_args = copy.deepcopy(args) + new_args.serial_no = bundle_data.get("serial_no") + new_args.valuation_rate = bundle_data.get("valuation_rate") + data.append(new_args) else: data.append( { diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js index ea7bf5688e67c7882ccf37fc3cd1fbbb57f00906..e033fd9a9df039385a8dea1d39c5702ad96dc25b 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.js +++ b/erpnext/stock/report/stock_analytics/stock_analytics.js @@ -17,6 +17,7 @@ frappe.query_reports["Stock Analytics"] = { fieldtype: "Link", options:"Item", default: "", + get_query: () => ({filters: { 'is_stock_item': 1 }}), }, { fieldname: "value_quantity", diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index 6c5b58c6e450edadf619f26e8b4d932788441405..ab48181c48d23c3e4347cb872a571c77cdbc6db7 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -270,7 +270,7 @@ def get_items(filters): if item_code := filters.get("item_code"): return [item_code] else: - item_filters = {} + item_filters = {"is_stock_item": 1} if item_group := filters.get("item_group"): children = get_descendants_of("Item Group", item_group, ignore_permissions=True) item_filters["item_group"] = ("in", children + [item_group]) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index a59f9de42e75ca8ff9de2906666870d9db01359b..ed84a5c2d5abb70be967d2a3f9e913b6484aa89a 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -413,7 +413,7 @@ class StockBalanceReport(object): "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, - "options": "currency", + "options": "Company:company:default_currency", }, { "label": _("Opening Qty"), @@ -427,7 +427,7 @@ class StockBalanceReport(object): "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, - "options": "currency", + "options": "Company:company:default_currency", }, { "label": _("In Qty"), diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js index b1e4a74571eae49817242afbf20b155e602dfbfb..bf3a397feefc213ad645a20f37e10b0b355cabb6 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js @@ -13,10 +13,18 @@ const DIFFERENCE_FIELD_NAMES = [ frappe.query_reports["Stock Ledger Variance"] = { "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, { "fieldname": "item_code", "fieldtype": "Link", - "label": "Item", + "label": __("Item"), "options": "Item", get_query: function() { return { @@ -27,7 +35,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "warehouse", "fieldtype": "Link", - "label": "Warehouse", + "label": __("Warehouse"), "options": "Warehouse", get_query: function() { return { @@ -38,7 +46,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "difference_in", "fieldtype": "Select", - "label": "Difference In", + "label": __("Difference In"), "options": [ "", "Qty", @@ -49,7 +57,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "include_disabled", "fieldtype": "Check", - "label": "Include Disabled", + "label": __("Include Disabled"), } ], diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index 732f108ac41357c863ee7bf010aedb08672173db..189a90aa4711266be46bf43d929a3ee5a7953668 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -55,6 +55,11 @@ def get_columns(): "label": _("Warehouse"), "options": "Warehouse", }, + { + "fieldname": "valuation_method", + "fieldtype": "Data", + "label": _("Valuation Method"), + }, { "fieldname": "voucher_type", "fieldtype": "Link", @@ -194,6 +199,7 @@ def get_columns(): def get_data(filters=None): filters = frappe._dict(filters or {}) item_warehouse_map = get_item_warehouse_combinations(filters) + valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method") data = [] if item_warehouse_map: @@ -206,8 +212,17 @@ def get_data(filters=None): continue for row in report_data: - if has_difference(row, precision, filters.difference_in): - data.append(add_item_warehouse_details(row, item_warehouse)) + if has_difference( + row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method + ): + row.update( + { + "item_code": item_warehouse.item_code, + "warehouse": item_warehouse.warehouse, + "valuation_method": item_warehouse.valuation_method or valuation_method, + } + ) + data.append(row) break return data @@ -229,8 +244,14 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: .select( bin.item_code, bin.warehouse, + item.valuation_method, + ) + .where( + (item.is_stock_item == 1) + & (item.has_serial_no == 0) + & (warehouse.is_group == 0) + & (warehouse.company == filters.company) ) - .where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0)) ) if filters.item_code: @@ -243,37 +264,27 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: return query.run(as_dict=1) -def has_difference(row, precision, difference_in): - has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision) - has_value_difference = ( - flt(row.diff_value_diff, precision) - or flt(row.fifo_value_diff, precision) - or flt(row.fifo_difference_diff, precision) - ) - has_valuation_difference = flt(row.valuation_diff, precision) or flt( - row.fifo_valuation_diff, precision - ) +def has_difference(row, precision, difference_in, valuation_method): + if valuation_method == "Moving Average": + qty_diff = flt(row.difference_in_qty, precision) + 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) + ) + valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision) - if difference_in == "Qty" and has_qty_difference: + if difference_in == "Qty" and qty_diff: return True - elif difference_in == "Value" and has_value_difference: + elif difference_in == "Value" and value_diff: return True - elif difference_in == "Valuation" and has_valuation_difference: + elif difference_in == "Valuation" and valuation_diff: return True elif difference_in not in ["Qty", "Value", "Valuation"] and ( - has_qty_difference or has_value_difference or has_valuation_difference + qty_diff or value_diff or valuation_diff ): return True - - return False - - -def add_item_warehouse_details(row, item_warehouse): - row.update( - { - "item_code": item_warehouse.item_code, - "warehouse": item_warehouse.warehouse, - } - ) - - return row diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index f2215f57c9701fa408a3a64650c787e35fd19a4f..d1b9ea6c2f4dcb18a729b3cf5db805d60573cd0a 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -416,7 +416,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation): .orderby(bundle.posting_date, bundle.posting_time, bundle.creation) ) - # Important to exclude the current voucher + # Important to exclude the current voucher to calculate correct the stock value difference if self.sle.voucher_no: query = query.where(bundle.voucher_no != self.sle.voucher_no) @@ -549,8 +549,10 @@ class BatchNoValuation(DeprecatedBatchNoValuation): .groupby(child.batch_no) ) - # Important to exclude the current voucher - if self.sle.voucher_no: + # Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference + if self.sle.voucher_detail_no: + query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no) + elif self.sle.voucher_no: query = query.where(parent.voucher_no != self.sle.voucher_no) if timestamp_condition: diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9142a27f4c09189b60b2594b2d05a78c317f0284..9203f4570ad792465f0015bd04568d0509a44fa6 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1711,7 +1711,7 @@ def get_datetime_limit_condition(detail): def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code): return - if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"): + if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation": return neg_sle = get_future_sle_with_negative_qty(args) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 6a658460da96cbd1aaa36d00c1692562fab70cd6..68c084547cb87fa52ff964c0c779df9b45d730dc 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -7,6 +7,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created +from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.stock_balance import update_bin_qty from erpnext.stock.utils import get_bin @@ -218,6 +219,9 @@ class SubcontractingOrder(SubcontractingController): "Subcontracting Order", self.name, "status", status, update_modified=update_modified ) + if status == "Closed": + update_po_status("Closed", self.purchase_order) + @frappe.whitelist() def make_subcontracting_receipt(source_name, target_doc=None): diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index f14912fee606ec3ae49d40c12a6b2c1a3d31a1a9..7e024f5d86609c9d8063ca2e5dd13b22f881f188 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -60,7 +60,9 @@ frappe.ui.form.on("Issue", { frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", { reason: values.reason, - user: frappe.session.user_email + user: frappe.session.user_email, + doctype: frm.doc.doctype, + docname: frm.doc.name, }, () => { reset_sla.enable_primary_action(); frm.refresh(); diff --git a/erpnext/support/doctype/issue_priority/issue_priority.json b/erpnext/support/doctype/issue_priority/issue_priority.json index 38a187b56ff8f74dc8b887ab2431d6c2409c74d3..762362082922ed5de7076eb58bed8fbaae70c853 100644 --- a/erpnext/support/doctype/issue_priority/issue_priority.json +++ b/erpnext/support/doctype/issue_priority/issue_priority.json @@ -13,7 +13,7 @@ "label": "Description" } ], - "modified": "2019-05-20 17:06:38.095647", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Support", "name": "Issue Priority", @@ -34,6 +34,6 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index e88530aab7dcfb98d4782463ee68813cdbf59fd9..f5ea9b0fee7cb8caea093e605671ff416f49dd6a 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -65,7 +65,7 @@ class ServiceLevelAgreement(Document): priorities.append(priority.priority) # Check if repeated priority - if not len(set(priorities)) == len(priorities): + if len(set(priorities)) != len(priorities): repeated_priority = get_repeated(priorities) frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority)) @@ -93,7 +93,7 @@ class ServiceLevelAgreement(Document): ) # Check for repeated workday - if not len(set(support_days)) == len(support_days): + if len(set(support_days)) != len(support_days): repeated_days = get_repeated(support_days) frappe.throw(_("Workday {0} has been repeated.").format(repeated_days)) @@ -716,13 +716,13 @@ def change_service_level_agreement_and_priority(self): and frappe.db.get_single_value("Support Settings", "track_service_level_agreement") ): - if not self.priority == frappe.db.get_value("Issue", self.name, "priority"): + if self.priority != frappe.db.get_value("Issue", self.name, "priority"): self.set_response_and_resolution_time( priority=self.priority, service_level_agreement=self.service_level_agreement ) frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority)) - if not self.service_level_agreement == frappe.db.get_value( + if self.service_level_agreement != frappe.db.get_value( "Issue", self.name, "service_level_agreement" ): self.set_response_and_resolution_time( @@ -742,10 +742,12 @@ def get_response_and_resolution_duration(doc): return priority -def reset_service_level_agreement(doc, reason, user): +@frappe.whitelist() +def reset_service_level_agreement(doctype: str, docname: str, reason, user): if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"): frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings.")) + doc = frappe.get_doc(doctype, docname) frappe.get_doc( { "doctype": "Comment", diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html index 08e0432dcf83def5a427652c82f34bccc81ed085..b9b435c7c338cacd49d5301bd4f0ac31e5638458 100644 --- a/erpnext/templates/pages/home.html +++ b/erpnext/templates/pages/home.html @@ -26,6 +26,26 @@ {{ render_homepage_section(homepage.hero_section_doc) }} {% endif %} + {% if homepage.products %} +
+

{{ _('Products') }}

+ +
+ {% for item in homepage.products %} +
+
+ {{ item.item_name }} +
+
{{ item.item_name }}
+ {{ _('More details') }} +
+
+
+ {% endfor %} +
+
+ {% endif %} + {% if blogs %}

{{ _('Publications') }}

diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 6bfeb5e3039b93bb063b1d1ed409158d1efb07ce..ac9528b0e0fc4033757f2f249703d63a1109499d 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -8967,3 +8967,7 @@ Book Tax Loss on Early Payment Discount,Umsatzsteueranteil bei Skonto berücksic Split Early Payment Discount Loss into Income and Tax Loss,"Skontobetrag in Aufwand und Umsatzsteuerkorrektur aufteilen", Approve,Genehmigen, Reject,Ablehnen, +Lost Quotations,Verlorene Angebote, +Lost Quotations %,Verlorene Angebote %, +Lost Value,Verlorener Wert, +Lost Value %,Verlorener Wert %, diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 09835ddef3f2d8c0f8969018cf6dd8d8e40cc227..5199c1489e1ab6e2bb1d66e67e83a93438b4ca0b 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -820,6 +820,7 @@ Accounting Journal adjustment in progress,Ajustement de journal comptable en cou Accounting Journal in Dokos,Journaux comptables dans Dokos, Accounting Journal in FEC,Journaux comptables dans le FEC, Accounting Journal,Journal comptable, +Accounting Ledger Preview,Aperçu du grand livre, Accounting Ledger,Grand livre, Accounting Masters,Données de base, Accounting Period overlaps with {0},La période comptable chevauche {0}, @@ -846,6 +847,7 @@ Accounts Receivable Credit Account,Compte débiteur à créditer, Accounts Receivable Discounted Account,Compte débiteur d'affacturage, Accounts Receivable Summary,Récapitulatif des comptes clients, Accounts Receivable Unpaid Account,Compte débiteur d'impayés, +Accounts Receivable/Payable,Comptes clients/fournisseurs, Accounts Receivable,Comptes clients, Accounts Settings,Paramètres de comptabilité, Accounts User,Comptable, @@ -1049,11 +1051,13 @@ Additional Salary Component Exists.,Une composante de paie additionnelle existe, Additional Salary ,Salaire additionnel, Additional Salary for referral bonus can only be created against Employee Referral with status {0},Les salaires additionnels pour les bonus de recommandation ne peuvent être créé qu'en contrepartie d'une recommandation au statut {0}, Additional Salary,Salaire supplémentaire, +Additional Settings,Paramètres additionnels, Additional contact information,Informations de contact supplémentaires, Additional information regarding the customer.,Informations supplémentaires concernant le client., Address & Contact,Adresse & Contact, Address & Contacts,Adresse & Contacts, Address Desc,Adresse Desc, +Address Display,Affichage de l'adresse, Address Form,Formulaire d'adresse, Address HTML,Adresse HTML, Address Line 1,Adresse, @@ -1376,6 +1380,7 @@ An error has been appeared while reposting item valuation via {0},Une erreur est An error has occurred during {0}. Check {1} for more details,Une erreur est survenue durant {0}. Veuillez vérifier les {0} pour plus de détails,Error Log An error occured during the payment flow. Please create the payment and the invoice manually.,Une erreur a eu lieu pendant le flux de paiement. Veuillez créer le paiement et la facture manuellement, An error occured for certain Items while creating Material Requests based on Re-order level. Please rectify these issues :,Une erreur s'est produite pour certains articles lors de la création de demandes de matériel basées sur le niveau de réapprovisionnement. Veuillez rectifier ces problèmes :, +An error occured while importing your FEC.,Une erreur s'est produite lors de l'import de votre FEC., An error occured while trying to fetch your payment plan on Stripe.
Please check your error logs.,Une erreur a empếché la récupération de votre plan de paiement sur Stripe.
Veuillez regarder dans vos logs d'erreur., An error occured,Erreur lors de l'initialisation du paiement, An error occurred during the update process,Une erreur s'est produite lors du processus de mise à jour, @@ -1484,6 +1489,7 @@ Approving Role (above authorized value),Rôle Approbateur (valeurs autorisées c Approving Role cannot be same as role the rule is Applicable To,Le Rôle Approbateur ne peut pas être identique au rôle dont la règle est Applicable, Approving User (above authorized value),Utilisateur Approbateur (valeurs autorisées ci-dessus), Approving User cannot be same as user the rule is Applicable To,L'Utilisateur Approbateur ne peut pas être identique à l'utilisateur dont la règle est Applicable, +Approximately match the description/party name against parties,Cherche une correspondance approximative entre la description/nom du tiers et les tiers disponibles, "Apps using current key won't be able to access, are you sure?","Les applications utilisant la clé actuelle ne pourront plus y accéder, êtes-vous sûr?", April,Avril, Arabic name missing for {} in the company document,Nom arabe manquant pour {} dans le document de l'entreprise, @@ -1636,6 +1642,8 @@ Associated Document,Document associé, At least one mode of payment is required for POS invoice.,Au moins un mode de paiement est nécessaire pour une facture de point de vente, At least one of the Applicable Modules should be selected,Sélectionnez au moins un module, At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2},A la ligne #{0}: le numéro de séquence {1} ne peut pas être inférieur au numéro de séquence de la ligne précédente {2}, +At row {0}: Batch No is mandatory for Item {1},A la ligne {0}: Le numéro de lot est obligatoire pour l'article {1}, +At row {0}: Serial No is mandatory for Item {1},A la ligne {0}: Le numéro de série est obligatoire pour l'article {1}, Atleast one asset has to be selected.,Au moins un actif doit être sélectionné, Atleast one interview has to be selected.,Au moins un entretien doit être sélectionné., Atleast one invoice has to be selected.,Au moins une facture doit être sélectionnée, @@ -1677,7 +1685,9 @@ Authorized Signatory,Signataire Autorisé, Authorized Value,Valeur Autorisée, Auto Create Assets on Purchase,Créer automatiquement des actifs à l'achat, Auto Create Exchange Rate Revaluation,Créer une réévaluation automatique du taux de change, +Auto Create Purchase Receipt,Création automatique des reçus d'achat, Auto Create Serial and Batch Bundle For Outward,Créer des ensembles de numéro de série et lot automatiquement pour les sorties de stock, +Auto Create Subcontracting Order,Création automatique des commandes de sous-traitances, Auto Created Serial and Batch Bundle,Créer des ensembles de numéro de série et lot automatiquement, Auto Created,Créé automatiquement, Auto Creation of Contact,Création automatique d'un contact, @@ -1702,6 +1712,7 @@ Auto close Opportunity Replied after the no. of days mentioned above,Fermeture a Auto close Opportunity after 15 days,Fermer automatiquement les opportunités après 15 jours, Auto close Opportunity after the no. of days mentioned above,Fermer l'opportunité automatiquement après le nombre de jours indiqués ci-dessus, Auto insert Price List rate if missing,Insérer automatiquemenet le prix dans la liste de prix s'il n'existe pas, +Auto match and set the Party in Bank Transactions,Cherche automatiquement une correspondance entre une transaction bancaire et un tiers, Auto re-order,Réapprovisionnement automatique, Auto reconcile open entries,Réconcilier les écritures non réconciliées, Auto repeat document updated,Document de répétition automatique mis à jour, @@ -1925,6 +1936,7 @@ Bank account,Compte bancaire, Bank accounts added,Comptes bancaires ajoutés, Bank and Payments,Banque et Paiements, Bank reconciliation dashboard,Tableau de bord de rapprochement bancaire, +Bank reconciliation,Réconciliation bancaire, Bank statement balance,Solde du relevé bancaire, Bank transaction creation error,Erreur de création d'une transaction bancaire, Bank transactions creation in progress. Please wait...,Création des transactions bancaire en cours. Veuillez patienter..., @@ -1982,6 +1994,7 @@ Batch ID is mandatory,Le N° du lot est obligatoire, Batch ID,ID du Lot, Batch Identification,Identification par lots, Batch Item Expiry Status,Statut d'expiration des articles en lots, +Batch No is mandatory,Le numéro de lot est obligatoire, Batch No.,N° de lot, Batch No,N° du lot, Batch Nos are created successfully,Les n° de lot ont été créé avec succès, @@ -2645,6 +2658,7 @@ Column Labels : ,Libellés des colonnes, Column filters ignored,Filtres de colonne ignorés, Column in Bank File,Colonne dans le fichier de la banque, Combined invoice portion must equal 100%,La portion combinée de la facture doit être égale à 100%, +Comma,Virgule, Comments,Commentaires, Commercial,Commercial, Commission Rate %,Pourcentage de commission (%), @@ -2674,6 +2688,7 @@ Company Address is mandatory to fetch company GSTIN details.,L'adresse de la soc Company Address,Adresse de la Société, Company Bank Account,Compte bancaire de la société, Company Billing Address,Adresse de facturation de la société, +Company Creation,Création de la société, Company Description for website homepage,Description de la Société pour la page d'accueil du site web, Company Description,Description de l'entreprise, Company Details,Détails de la société, @@ -2686,6 +2701,7 @@ Company Name as per Imported Tally Data,Nom de la société dans les données im Company Name cannot be Company,Nom de la Société ne peut pas être Company, Company Name,Nom de la société, Company Not Linked,La société n'est pas référencée, +Company Search,Recherche de sociétés, Company Settings,des paramètres de l'entreprise, Company Shipping Address,Adresse d'expédition, Company Tagline for website homepage,Slogan de la Société pour la page d'accueil du site web, @@ -2919,6 +2935,7 @@ Cost Centers for Budgeting and Analysis,Centres de coûts pour la budgétisation Cost Centers,Centres de Coûts, Cost Configuration,Configuration des coûts, Cost Per Unit,Coût par unité, +Cost Price,Coût de revient, Cost Updated,Coût Mise à Jour, Cost as on,Coût à partir de, Cost center is mandatory for loans having rate of interest greater than 0,Le centre de coût est obligatoire pour les prêts dont le taux d'intérêt est supérieur à 0., @@ -3677,6 +3694,7 @@ Dependent Task {0} is not a Template Task,La tâche dépendant {0} n'est pas un Dependent Task,Tâche Dépendante, Dependent Tasks,Tâche dépendante, Depends on Tasks,Dépend des Tâches, +Deposit,Dépôt, Deprecated field. Will be removed in a future version.,Champ déprécié. Sera supprimé dans une future version., Deprecated: Kept for compatibility only but will be removed in the future,"Obsolète: Gardé uniquement pour compatibilité, mais sera supprimé à l'avenir", Depreciated Amount,Montant amorti, @@ -3732,6 +3750,7 @@ Difference Account,Compte d'écart, Difference Amount (Company Currency),Écart de montant (Devise de la Société), Difference Amount must be zero,L'Écart de montant doit être égal à zéro, Difference Amount,Écart de montant, +Difference In,Différence en, Difference Posting Date,Date de comptabilisation de la différence, Difference Qty,Qté de différence, Difference Value,Différence de valeur, @@ -3930,6 +3949,7 @@ Driver,Chauffeur, Driving License Categories,Catégories de permis de conduire, Driving License Category,Catégorie de permis de conduire, Drop Ship,Expédition Directe, +Drop a file,Déposez un fichier, Drop files here,Déposez des fichiers ici, Drug,Médicament, Due / Reference Date cannot be after {0},Date d'échéance / de référence ne peut pas être après le {0}, @@ -4116,6 +4136,7 @@ Enable Appointment Scheduling,Autoriser la prise de rendez-vous, Enable Attribute Filters,Autoriser les filtres d'attributs, Enable Auto Email,Autoriser les emails automatiques, Enable Auto Re-Order,Autoriser le réapprovisonnement automatique, +Enable Automatic Party Matching,Autoriser la correspondance automatique de tiers, Enable Capital Work in Progress Accounting,Autoriser la comptabilisation des immobilisations en cours, Enable Checkout,Activer Caisse, Enable Common Party Accounting,Activer la comptabilité des tiers communs, @@ -4128,6 +4149,7 @@ Enable Distributed Cost Center,Activer la distribution du centre de coût, Enable European Access,Autoriser l'accès aux pays européens, Enable Field Filters (Categories),Activer les filtres de champ (Catégories), Enable Field Filters,Autoriser les filtres de champ, +Enable Fuzzy Matching,Autoriser la recherche approximative, Enable Item Booking,Autoriser la réservation d'articles, Enable Perpetual Inventory For Non Stock Items,Autoriser l'inventaire permanent pour les articles non stockés, Enable Perpetual Inventory,Autoriser l'inventaire permanent, @@ -4487,6 +4509,7 @@ Fetched only {0} available serial numbers.,Seulement {0} numéros de série disp Fetching exchange rates ...,Récupération des taux de change..., Fetching...,Récupération..., Fichier des Ecritures Comptables,Fichier des Ecritures Comptables, +Field Delimiter,Délimiteur de champs, Field Name,Nom du champ, Field in Bank Transaction,Champ dans la transaction bancaire, Field,Champs, @@ -4745,8 +4768,11 @@ From and To dates are required,Les dates de début et de fin sont requises, From and To dates required,Les date Du et Au sont requises, From date can not be greater than than To date,La date de début ne peut pas être supérieure à la date de fin, From date cannot be greater than To date,La date de début ne peut pas être après la date de fin, +From date must be before To date,La date de début doit être avant la date de fin, +From date must be within fiscal year {0},La date de début doit être dans l'exercice fiscal {0}, From duration (Mins),Durée minimum (Mins), From employee is required while receiving Asset {0} to a target location,L'employé source est nécessaire pour recevoir l'actif {0} dans le lieu cible, +From payment order,Depuis l'ordre de paiement, From value must be less than to value in row {0},De la Valeur doit être inférieure à la valeur de la ligne {0}, From {0} | {1} {2},Du {0} | {1} {2}, From(Year),Du (Année), @@ -5012,7 +5038,11 @@ Gross Pay (Company Currency),Paie brute (Devise société), Gross Pay - Total Deduction - Loan Repayment,Salaire Brut - Déductions Totales - Remboursement de Prêt, Gross Profit %,Bénéfice Brut %, Gross Profit / Loss,Bénéfice/Perte Brut, +Gross Profit Based On,Marge brute basée sur, +Gross Profit Calculation in Quotations and Sales Orders Based On,Calcul de la marge brute dans les devis et les commandes basée sur, Gross Profit Percent,Pourcentage de bénéfice brut, +Gross Profit Percentage,Pourcentage de marge brute, +Gross Profit,Marge brute,Quotation Item Gross Profit,Bénéfice Brut, Gross Purchase Amount is mandatory,Montant d'Achat Brut est obligatoire, Gross Purchase Amount should be equal to purchase amount of one single Asset.,Le montant d'achat brut doit être égal au montant d'achat d'un seul actif., @@ -5089,6 +5119,7 @@ Has Variants,A des variantes, Hashed Data,Hash des données, Have Default Naming Series for Batch ID?,Vous avez une série de noms par défaut pour l'ID du lot ?, Head of Marketing and Sales,Responsable du Marketing et des Ventes, +Headcount,Nombre d'employés, Header Image,Image d'en-tête, Heads (or groups) against which Accounting Entries are made and balances are maintained.,Titres (ou groupes) sur lequel les entrées comptables sont faites et les soldes sont maintenus., Health Care,Soins de Santé, @@ -5168,6 +5199,7 @@ How Pricing Rule is applied?,Comment est appliquée la règle de prix ?, How frequently?,A quelle fréquence ?, How often should Project and Company be updated based on Sales Transactions ?,A quelle fréquence le projet et la société doivent-ils être mis à jour sur la base des transactions de vente ?, How often should Project and Company be updated based on Sales Transactions?,À quelle fréquence le projet et la société doivent-ils être mis à jour en fonction des transactions de vente ?, +How often should Project be updated of Total Purchase Cost ?,A quelle fréquence doit-on mettre à jour le coût total des achats d'un projet ?, How often should project and company be updated based on Sales Transactions.,Fréquence de mise à jour des montants de transaction de vente dans les projets et la société., How to Navigate in ERPNext,Comment naviguer dans Dokos, Hrs,Hrs, @@ -5291,6 +5323,7 @@ If this is undesirable please cancel the corresponding Payment Entry.,"Si ce n'e ","Si vous {0} {1} d'article {2}, le schéma {3} sera appliqué sur l'article.", "If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.","Si vous {0} {1} de l'article {2}, la règle {3} sera appliquée sur l'article", +"If your SEPA file is not generated from a payment order, From Date and To Date are mandatory","Si votre fichier SEPA n'est pas généré via un ordre de paiement, les dates de début et de fin sont obligatoies.", Ignore Account Closing Balance,Ignorer le solde comptable de clôture, Ignore Available Stock,Ignorer le stock disponible, Ignore Closing Balance,Ignore le solde de clôture, @@ -5385,6 +5418,7 @@ Include Ageing Summary,Inclure un récapitulatif agé, Include Default Book Assets,Include les actifs du livre comptable par défaut, Include Default Book Entries,Inclure les écritures du livre comptable par défaut, Include Descendants,Inclure les descendants, +Include Disabled,Inclure les désactivés, Include Expired,Inclure les devis expirés, Include Exploded Items,Inclure les articles éclatés, Include Item In Manufacturing,Inclure l'article dans la production, @@ -5584,6 +5618,7 @@ Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN H Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.,GSTIN invalide! Le numéro fourni ne correspond pas au format d'un n° GSTIN, Invalid GSTIN,GSTIN invalide, Invalid Gross Purchase Amount,Montant d'achat brut invalide, +Invalid Group By,Fonction de groupement invalide, Invalid Invoice,Facture non valide, Invalid Item Defaults,Défauts d'éléments non valides, Invalid Item,Article invalide, @@ -5601,6 +5636,7 @@ Invalid Process Loss Configuration,Configuration des pertes du processus invalid Invalid Purchase Invoice,Facture d'achat invalide, Invalid Purchase Receipt,Reçu d'achat invalide, Invalid Qty,Qté non valide, +Invalid Quantity,Quantité invalide, Invalid Schedule,Programme non valide, Invalid Scheduled Time,Heure planifiée invalide, Invalid Selling Price,Prix de vente invalide, @@ -6034,6 +6070,7 @@ Journal Entry {0} does not have account {1} or already matched against other vou Journal Entry,Écriture de journal, Journal Name,Nom du journal, Journal code,Code journal, +Journal entries for one supplier,Ecritures de journal pour un fournisseur, Journal name,Nom du journal, Journal type,Type de journal, JournalCode,JournalCode, @@ -6096,6 +6133,7 @@ Last Purchase Rate,Dernier Prix d'Achat, Last Stock Transaction for item {0} under warehouse {1} was on {2}.,"La dernière transaction de stock pour l'article {0}, dans l'entrepôt {1}, a eu lieu le {2}", Last Stock Transaction for item {0} was on {1}.,La dernière transation de stock pour l'article {0} était le {1}, Last Sync Datetime,Dernière date de synchronisation, +Last Update via Pappers,Dernière mise à jour via Pappers, Last Woocommerce Sync,Dernière synchronisation WooCommerce, Last carbon check date cannot be a future date,La date de dernière vérification carbone ne peut être dans le futur, Last day of the month,Dernier jour du mois, @@ -6149,6 +6187,7 @@ Leave Encashed?,Congés encaissés ?, "Leave blank for home.
This is relative to site URL, for example ""about"" will redirect to ""https://yoursitename.com/about""","Laisser vide pour ""home"".
Il s'agit d'une URL relative, par exemple ""about"" sera redirigé ver ""https://votresite.com/about""", Leave blank if the Supplier is blocked indefinitely,Laisser vide si le fournisseur est bloqué indéfiniment, Leave blank to use the standard Delivery Note format,Laissez vide pour utiliser le format de bon de livraison standard, +"Leave empty for default format.
For available formats please read the Python documentation","Laissez vide pour le format par défaut.
Pour connaître les formats disponibles veuillez lire la documentation Python", Leave empty if credits have a no validity end date,Laisser vide si les crédits n'ont pas de date de fin de validité, Leave empty if no expiration,Laisser vide si aucune durée d'expiration, Leave the field empty to make purchase orders for all suppliers,Laissez le champ vide pour passer des commandes pour tous les fournisseurs, @@ -6160,6 +6199,7 @@ Left Index,Index gauche, Left,A quitté l'entreprise,Employee Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.,Entité Juridique / Filiale avec un Plan de Comptes différent appartenant à l'Organisation., Legal Expenses,Frais Juridiques, +Legal Form,Forme juridique, Legal,Juridique, Legend,Légende, Length (cm),Longueur (cm), @@ -6358,10 +6398,14 @@ Loss Value,Valeur de la perte, Lost Opportunities,Opportunités perdues, Lost Opportunity,Opportunités perdues, Lost Quotation,Devis Perdu, +Lost Quotations %,% de devis perdus, +Lost Quotations,Devis perdus, Lost Reason Detail,Détail de la raison de la perte, Lost Reason,Raison de la Perte, Lost Reasons are required in case opportunity is Lost.,Les raisons de la perte sont requises en cas de perte d'opportunité., Lost Reasons,Raisons pour avoir perdu le devis, +Lost Value %,% de valeur perdue, +Lost Value,Valeur perdue, Lost,Perdu, Low,Bas, Lower Deduction Certificate,Certificat de déduction minorée, @@ -6535,6 +6579,7 @@ Manufacturing module is set up !,Le module de production est configuré, Manufacturing,Production, Map Custom Field to DocType,Mapper le champ personnalisé au type de document, Map Shopify Taxes / Shipping Charges to Dokos Account,Correspondance entre les taxes / frais de livraison de Shopify et les comptes dans Dokos, +Mapping Purchase Receipt ...,Création des reçus d'achat ..., Mapping Subcontracting Order ...,Mapping de la commande de sous-traitance ..., Mapping {0} ...,Création d'une correspondance avec {0}..., Mapping,Mapping, @@ -7600,6 +7645,7 @@ Parent Detail docname,Nom de document du détail parent, Parent Document,Document parent, Parent Item Booking,Réservation d'article parente, Parent Item Group,Groupe d'articles parent, +Parent Item {0} must not be a Fixed Asset,L'article parent {0} ne doit pas être un actif immobilisé, Parent Item {0} must not be a Stock Item,L'article parent {0} ne doit pas être un article stocké, Parent Item,Article Parent, Parent Location,Localisation parente, @@ -7639,13 +7685,16 @@ Partner Type,Type de Partenaire, Partner website,Site Partenaire, Partnership,Partenariat, Party Account Currency,Devise du Compte de Tiers, +Party Account No. (Bank Statement),Compte bancaire du tiers (Relevé bancaire), Party Account {0} currency ({1}) and document currency ({2}) should be same,La devise ({1}) du compte de tiers {0} et la devise du document ({2}) doivent être identiques, Party Account,Compte de Tiers, Party Balance,Solde du Tiers, Party Bank Account,Compte bancaire du tiers, Party Details,Détails du tiers, +Party IBAN (Bank Statement),IBAN du tiers (Relevé bancaire), Party Information,Informations additionnelles, Party Link,Lien avec la partie, +Party Name/Account Holder (Bank Statement),Nom du tiers/Titulaire du compte (Relevé bancaire), Party Name,Nom du tiers, Party Specific Item,Articles liés au tiers, Party Type and Party is mandatory for {0} account,Le type de tiers et le tiers sont obligatoires pour le compte {0}, @@ -7775,6 +7824,7 @@ Payment Unlink Error,Erreur de déliaison de paiement, Payment against {0} {1} cannot be greater than Outstanding Amount {2},Paiement pour {0} {1} ne peut pas être supérieur à Encours {2}, Payment amount cannot be less than or equal to 0,Le montant du paiement ne peut pas être inférieur ou égal à 0, Payment cannot be processed immediately for this payment request.,Le paiement ne peut pas être récolté automatiquement pour cette demande de paiement., +Payment entries in batch,Ecritures de paiement en lot, Payment entry cannot be created from payment request for doctype {0},Une écriture de paiement ne peut pas être créée depuis une demande de paiement pour le type de document {0}, Payment entry created,Ecriture de paiement créée, Payment entry with reference {0} not found,Ecriture de paiement avec la référence {0} introuvable, @@ -7798,6 +7848,7 @@ Payment succeeded. Refresh the page to pay again.,Paiement réussi. Veuillez raf Payment successfully initialized,Paiement initialisé avec succès, Payment term {0} not used in {1},Les conditions de paiement {0} ne sont pas utilisées dans {1}., Payment,Paiement, +Payments created,Paiements créés, Payments,Paiements, Payroll Cost Centers,Centres de coûts de la paie, Payroll Entry,Ecriture de paie, @@ -7978,8 +8029,10 @@ Please add Root Account for - {0},Veuillez ajouter le compte racine pour - {0}, Please add a Swift Number in bank account {0} for customer {1},Veuillez ajouter un BIC dans le compte bancaire {0} pour le client {1}, Please add a Temporary Opening account in Chart of Accounts,Veuillez ajouter un compte d'ouverture temporaire dans le plan comptable, Please add a bank account in mandate {0} for customer {1},Veuillez ajouter un compte bancaire dans le mandat {0} pour le client {1}, +Please add a bank account to supplier {0},Veuillez ajouter un compte bancaire au fournisseur {0}, Please add a price for the following billing intervals:,Veuillez ajouter un prix pour les intervalles de facturation suivants:, Please add an IBAN in bank account {0} for customer {1},Veuillez ajouter un IBAN dans le compte bancaire {0} pour le client {1}, +Please add an IBAN to bank account {0},Veuillez associer un IBAN au compte bancaire {0}, Please add at least one company bank account. 'Is company account' must be checked.,Veuillez ajouter au moins un compte bancaire. 'Est le compte de la société' doit être coché., Please add at least one payment gateway,Veuillez ajouter au moins une passerelle de paiement, Please add atleast one Serial No / Batch No,Veuillez ajouter au moins un numéro de série / lot, @@ -8031,6 +8084,7 @@ Please create Landed Cost Vouchers against Invoices that have 'Update Stock' ena Please create a new Accounting Dimension if required.,Veuillez créer une nouvelle dimension comptable si nécessaire., Please create a new invoice for this event with external reference {0},Veuillez créer une nouvelle facture pour cet événement avec la référence externe {0}, Please create a payment gateway account for currency {0},Veuillez créer un compte de passerelle de paiement pour la devise {0}, +Please create a {0} document to configure the generation of your XML file,Veuillez créer un document {0} pour configurer la génération de votre fichier XML, Please create adjustment Journal Entry for amount {0} ,Veuillez créer une écriture de journal pour le montant {0}, Please create an adjustment Journal Entry for amount {0} on {1},Veuillez créer une écriture de journal d'ajustement pour le montant {0} à la date du {1}, Please create at least one mode of payment for this bank account first,Veuillez créer au moins un mode de paiement pour ce compte bancaire, @@ -8710,6 +8764,7 @@ Publish in Website,Publier sur le site Web, Publish on website,Publier sur le site web, Publishable Key,Clé publiable, Published Date,Date de publication, +Published In Website,Publié sur le site web, Published in Website,Publié sur le site Web, Publishing Item ...,Élément de publication ..., Publishing,Édition, @@ -8770,6 +8825,7 @@ Purchase Receipt Required for item {},Reçu d'achat requis pour l'article {}, Purchase Receipt Required,Reçu d'achat requis, Purchase Receipt Trends,Evolution des reçus d'achat, Purchase Receipt doesn't have any Item for which Retain Sample is enabled.,Le reçu d'achat n'a aucun article pour lequel 'Garder un échantillon' est activé., +Purchase Receipt {0} created.,Reçu d'achats créé, Purchase Receipt {0} is not submitted,Le Reçu d'Achat {0} n'est pas soumis, Purchase Receipt,Reçu d'Achat, Purchase Receipts,Reçus d'Achats, @@ -8944,6 +9000,7 @@ Quote Status,Statut du Devis, Quoted Amount,Montant du devis, Quoted Item Comparison,Comparaison de articles des devis fournisseurs, Quotes to Leads or Customers.,Devis de Prospects ou Clients., +RCS Information,Informations du RCS, RFQs are not allowed for {0} due to a scorecard standing of {1},Les Appels d'Offres ne sont pas autorisés pour {0} en raison d'une note de {1} sur la fiche d'évaluation, RRULE,RRULE, RTT,RTT, @@ -9220,6 +9277,7 @@ Relieving Date must be greater than or equal to Date of Joining,La date de dépa Relieving Date,Date de départ, Remaining Balance,Solde restant, Remark,Remarque, +Remarks Column Length,Longueur de la colonne Remarques, Remarks,Remarques, Remember to set {field_label}. It is required by {regulation}.,N'oubliez pas de définir {field_label}. C'est obligatoire d'après {regulation}, Reminder to update GSTIN Sent,Rappel pour mettre à jour GSTIN envoyé, @@ -10295,6 +10353,7 @@ Selling ,Vente, "Selling must be checked, if Applicable For is selected as {0}","Vente doit être vérifiée, si ""Applicable pour"" est sélectionné comme {0}", Selling rate for item {0} is lower than its {1}. Selling rate should be atleast {2},Le prix de vente pour l'élément {0} est inférieur à son {1}. Le prix de vente devrait être au moins {2}, Selling,Vente, +Semicolon,Point Virgule, Send After (days),Envoyer après (jours), Send Attached Files,Envoyer les pièces jointes, Send Document Print,Envoyer une impression de document, @@ -10332,6 +10391,7 @@ Sepa Direct Debit Settings,Paramètres de prélèvement Sepa, Sepa Direct Debit,Prélèvement Sepa, Sepa Mandate,Mandat Sepa, Sepa Mandates,Mandats SEPA, +Sepa Payments,Paiement SEPA, Sepa mandate retrieval error,Erreur de récupération d'un mandat Sepa, Sepa mandate status update error,Erreur de mise à jour du statut d'un mandat Sepa, Sepa mandate validation failed for {0},La validation du mandat Sepa échoué pour {0}, @@ -11262,6 +11322,7 @@ Supplier Address,Adresse du Fournisseur, Supplier Addresses And Contacts,Adresses et contacts des fournisseurs, Supplier Analytics,Analyse des fournisseurs, Supplier Contact,Contact du fournisseur, +Supplier Cost Price,Prix du fournisseur, Supplier Delivery Note,Bon de livraison du Fournisseur, Supplier Detail,Détails du Fournisseur, Supplier Details,Détails du Fournisseur, @@ -11286,6 +11347,7 @@ Supplier Naming By,Nommage du fournisseur selon, Supplier Part No,N° de Pièce du Fournisseur, Supplier Part Number,Numéro de Pièce du Fournisseur, Supplier Portal Users,Utilisateurs du portail fournisseur, +Supplier Price,Prix du fournisseur, Supplier Primary Address,Adresse principale du fournisseur, Supplier Primary Contact,Contact principal du fournisseur, Supplier Quotation Comparison,Comparaison de devis fournisseur, @@ -11305,6 +11367,7 @@ Supplier Scorecard Variable,Variable de la Fiche d'Évaluation Fournisseur, Supplier Scorecard,Fiche d'évaluation des fournisseurs, Supplier Scorecards,Evaluation des fournisseurs, Supplier Type,Type de Fournisseur, +Supplier Unit Price,Prix unitaire du fournisseur, Supplier Warehouse mandatory for sub-contracted Purchase Receipt,Entrepôt Fournisseur obligatoire pour les Reçus d'Achat sous-traités, Supplier Warehouse mandatory for sub-contracted {0},Entrepôt fournisseur obligatoire pour le·la {0} sous-traité, Supplier Warehouse,Entrepôt Fournisseur, @@ -11384,6 +11447,7 @@ TLB-,TLB-, TS-.YYYY.-,TS-.YYYY.-, TS-,TS-, TVA-.YYYY.-.MM.-,TVA-.YYYY.-.MM.-, +Tab,Tabulation, Table for Item that will be shown in Web Site,Table pour l'Article qui sera affiché sur le site Web, Tabs,Séparateur d'onglet, Tag Line,Slogan, @@ -11858,7 +11922,7 @@ This operation will not erase your existing accounting journals.,Cette opératio This operation will not erase your existing accounts. Dokos will try to append them to their corresponding parents in the tree.,Cette opération n'écrasera pas vos comptes existants. Dokos essaiera de les associer avec les groupes correspondant dans l'arbre., This option can be checked to edit the 'Posting Date' and 'Posting Time' fields.,Cette option peut être cochée pour modifier les champs 'Date de comptabilisation' et 'Heure de comptabilisation'., This order contains some recurring items and is linked to subscription {0},Cette commande contient des articles récurrents et est liée à l'abonnement {0}, -This order contains some recurring items.
A subscription will be automatically generated on submission.,Cette commande contient des articles récurrents.
Un abonnement sera automatiquement généré à la validation., +This order contains some recurring items.
A subscription will be automatically generated on submission if a recurring period is set.,Cette commande contient des articles récurrents.
Un abonnement sera automatiquement généré à la validation si une période de récurrence est définie., This payment has already been done.
Please contact us if you have any question.,Ce paiement a déjà été fait.
Veuillez nous contacter si vous avez des questions., This payment has not been paid out yet,Ce paiement n'a pas été encore été versé, "This payment request is linked to subscription {0}","Cette demande de paiement est liée à l'abonnement {0}", @@ -12068,6 +12132,8 @@ Total Additional Costs,Total des coûts additionnels, Total Advance,Acompte/avance total, Total Allocated Amount (Company Currency),Montant total alloué (Devise société), Total Allocated Amount,Montant total alloué, +Total Allocated Credit,Crédit alloué total, +Total Allocated Debit,Débit alloué total, Total Allocations,Allocations totales, Total Amount Credited,Montant total crédité, Total Amount Currency,Montant total en devise, @@ -12186,6 +12252,7 @@ Total Projected Qty,Qté Totale Prévue, Total Projected Quantity in Stock,Quantité projetée totale en stock, Total Purchase Amount,Montant total des achats, Total Purchase Cost (via Purchase Invoice),Coût d'Achat Total (via les factures d'achat), +Total Purchase Cost has been updated,Le coût d'achats total a été mis à jour, Total Purchases Last Month,Total Achats du mois dernier, Total Purchases This Month,Total Achats du mois en cours, Total Purchases This Year,Total Achats cette année, @@ -12417,6 +12484,7 @@ Under Review,En cours de révision, Under Warranty,Sous Garantie, Undue Booked Interest,Intérêt comptabilisé non dû, Unfulfilled,Non-rempli, +Unit Cost Price,Coût de revient unitaire, Unit Of Measure,Unité de mesure, Unit of Measure (UOM),Unité de mesure, Unit of Measure Field,Champ Unité de mesure, @@ -12454,7 +12522,9 @@ Unrealized Profit / Loss Account,Compte de bénéfice/perte non réalisée, Unrealized Profit / Loss account for intra-company transfers,Bénéfice/perte non réalisé pour les transferts intra-société, Unrealized Profit/Loss account for intra-company transfers,Compte de bénéfice/perte non réalisé pour les transferts intra-société, Unreconcile Payment Entries,Dé-réconcilier les écritures de paiement, +Unreconcile Payment,Dé-réconcilier le paiement, Unreconcile Payments,Dé-réconcilier les paiements, +Unreconcile Transaction,Dé-réconcilier la transaction, Unreconciled Amount,Montant non rapproché, Unreconciled Entries,Entrées non réconciliées, Unreconciled Payment Details,Détails des paiements non rapprochés, @@ -12516,9 +12586,11 @@ Update Series,Mettre à Jour les Séries, Update Stock Opening Balance,Mise à jour du solde d'ouverture des stocks, Update Stock,Mettre à jour le stock, Update Taxes for Items,Mettre à jour les taxes pour les articles, +Update Total Purchase Cost,Mettre à jour le coût total des achats, Update Woocommerce products from Dokos items,Mettre à jour les produits WooCommerce à partir des articles Dokos, Update bank payment dates with journals.,Mettre à jour les dates de paiement bancaires avec les journaux., Update data every (Days),Mettre à jour tous les (Jours), +Update frequency of Project,Mettre à jour la fréquence du projet, Update in progress. It might take a while.,Mise à jour en cours. Ça peut prendre un moment., Update latest price in all BOMs,Mettre à jour le prix le plus récent dans toutes les nomenclatures, Update rate as per last purchase,Mettre à jour les prix selon le dernier prix achat, @@ -12726,6 +12798,7 @@ View General Ledger,Afficher le grand livre, View Journals,Voir les journaux, View Leads,Voir Prospects, View Ledger,Grand livre, +View Ledgers,Voir les livres comptables, View More,Voir plus, View Now,Voir Maintenant, View Orders,Afficher les commandes, @@ -12912,6 +12985,7 @@ Wishlist is empty!,La liste de préférences est vide !, Wishlist,Liste de préférences, With Items,Avec Articles, With Operations,Avec des Opérations, +Withdrawal,Retrait, Without category,Sans catégorie, Won Opportunities,Opportunités gagnées, Won Opportunity (Last 1 Month),Opportunités gagnées (30 jours glissants), @@ -13115,6 +13189,7 @@ You need to enable Shopping Cart,Vous devez activer le Panier, YouTube Interactions,Interactions Youtube, Your Company set in Dokos,Votre société dans Dokos, Your FEC is being prepared,Votre FEC est en cours de préparation, +Your FEC is ready - {0},Votre FEC est prêt - {0}, Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5},Votre session d'entretien a été reprogrammée de {0} {1} - {2} à {3} {4} - {5}, Your Name (required),Votre nom *, Your Organization,L'activité de la société, @@ -13289,6 +13364,7 @@ out of 5,sur 5, overwrite,écraser, pages,pages, paid_amount,paid_amount, +pain.001.001.03,pain.001.001.03, pain.008.001.02,pain.008.001.02, pain.008.002.02,pain.008.002.02, pain.008.003.02,pain.008.003.02, @@ -13505,6 +13581,7 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s {0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts.,{0} {1} a déjà été partiellement payé·e. Veuillez utiliser le bouton 'Obtenir les factures en attente' ou 'Obtenir les commandes en attente' pour récupérer les derniers montants restant dûs., {0} {1} has been modified. Please refresh.,{0} {1} a été modifié. Veuillez actualiser., {0} {1} has not been submitted so the action cannot be completed,"{0} {1} n'a pas été soumis, donc l'action ne peut pas être complétée", +{0} {1} is allocated twice in this Bank Transaction,{0} {0} est alloué deux fois dans cette transaction bancaire, "{0} {1} is associated with {2}, but Party Account is {3}","{0} {1} est associé à {2}, mais le compte tiers est {3}", {0} {1} is cancelled or closed,{0} {1} est annulé ou fermé, {0} {1} is cancelled or stopped,{0} {1} est annulé ou arrêté, @@ -13593,6 +13670,7 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s {},{},} {},{}, ← Back to upload files,← Retour au téléchargement de fichiers, +🏃 Your Shortcuts,🏃 Vos Raccourcis, 🏃Your Shortcuts,🏃Vos raccourcis, 📊 Reports ,📊 Rapports, 📖 General Ledger,📖 Grand livre, @@ -13600,3 +13678,4 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s 🗃️ CRM Data & Transactions,🗃️ Données CRM & Transactions, 🗃️ Manufacturing Data & Transactions,🗃️ Données de production & Transactions, 🗃️ Projects Data,🗃️ Données des projets, +🗃️ Reports & Masters,🗃️ Rapports & Données de base, diff --git a/erpnext/utilities/doctype/sms_log/README.md b/erpnext/utilities/doctype/sms_log/README.md deleted file mode 100644 index 9ee2b79ef07e653f17a69cbe0fa60616707ee26b..0000000000000000000000000000000000000000 --- a/erpnext/utilities/doctype/sms_log/README.md +++ /dev/null @@ -1 +0,0 @@ -Log of SMS sent via SMS Center. \ No newline at end of file diff --git a/erpnext/utilities/doctype/sms_log/__init__.py b/erpnext/utilities/doctype/sms_log/__init__.py deleted file mode 100644 index 8b137891791fe96927ad78e64b0aad7bded08bdc..0000000000000000000000000000000000000000 --- a/erpnext/utilities/doctype/sms_log/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/erpnext/utilities/doctype/sms_log/sms_log.js b/erpnext/utilities/doctype/sms_log/sms_log.js deleted file mode 100644 index f5358e84093cc7bff55fd38427fed151b4941fed..0000000000000000000000000000000000000000 --- a/erpnext/utilities/doctype/sms_log/sms_log.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('SMS Log', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/utilities/doctype/sms_log/sms_log.json b/erpnext/utilities/doctype/sms_log/sms_log.json deleted file mode 100644 index 09ab4d1de8a718b34328952e58495edee7f84d15..0000000000000000000000000000000000000000 --- a/erpnext/utilities/doctype/sms_log/sms_log.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "actions": [], - "autoname": "SYS-SMS-.#####", - "creation": "2012-03-27 14:36:47", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "sender_name", - "sent_on", - "column_break0", - "message", - "sec_break1", - "no_of_requested_sms", - "requested_numbers", - "column_break1", - "no_of_sent_sms", - "sent_to" - ], - "fields": [ - { - "fieldname": "sender_name", - "fieldtype": "Data", - "label": "Sender Name", - "read_only": 1 - }, - { - "fieldname": "sent_on", - "fieldtype": "Date", - "label": "Sent On", - "read_only": 1 - }, - { - "fieldname": "column_break0", - "fieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "message", - "fieldtype": "Small Text", - "label": "Message", - "read_only": 1 - }, - { - "fieldname": "sec_break1", - "fieldtype": "Section Break", - "options": "Simple" - }, - { - "fieldname": "no_of_requested_sms", - "fieldtype": "Int", - "label": "No of Requested SMS", - "read_only": 1 - }, - { - "fieldname": "requested_numbers", - "fieldtype": "Code", - "label": "Requested Numbers", - "read_only": 1 - }, - { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "no_of_sent_sms", - "fieldtype": "Int", - "label": "No of Sent SMS", - "read_only": 1 - }, - { - "fieldname": "sent_to", - "fieldtype": "Code", - "label": "Sent To", - "read_only": 1 - } - ], - "icon": "uil uil-comments-alt", - "idx": 1, - "links": [], - "modified": "2023-02-20 16:33:50.446725", - "modified_by": "Administrator", - "module": "Utilities", - "name": "SMS Log", - "owner": "Administrator", - "permissions": [ - { - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager" - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/utilities/doctype/sms_log/sms_log.py b/erpnext/utilities/doctype/sms_log/sms_log.py deleted file mode 100644 index ff220d8bcb9c3814f8bb4c84616f1124652c9a26..0000000000000000000000000000000000000000 --- a/erpnext/utilities/doctype/sms_log/sms_log.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe.model.document import Document - - -class SMSLog(Document): - pass diff --git a/erpnext/utilities/doctype/sms_log/test_sms_log.py b/erpnext/utilities/doctype/sms_log/test_sms_log.py deleted file mode 100644 index 8590d81616f7b5a5732c419c3c26ee1cb7903515..0000000000000000000000000000000000000000 --- a/erpnext/utilities/doctype/sms_log/test_sms_log.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - - -import unittest - -import frappe - -# test_records = frappe.get_test_records('SMS Log') - - -class TestSMSLog(unittest.TestCase): - pass diff --git a/erpnext/venue/custom/event.json b/erpnext/venue/custom/event.json index 1e093f1f4e389518d89df78e9e24554cce7dba2d..d136d2ec506fb1eaabfb16cfcfe33e5979deeeca 100644 --- a/erpnext/venue/custom/event.json +++ b/erpnext/venue/custom/event.json @@ -1,132 +1,144 @@ { - "custom_fields": [ - { - "description": "Leave this field empty or write 0 to not set a limit.", - "dt": "Event", - "fieldname": "max_number_of_registrations", - "fieldtype": "Int", - "insert_after": "status", - "is_system_generated": 1, - "label": "Maximum number of registrations", - "non_negative": 1 - }, - { - "depends_on": "eval:doc.published", - "dt": "Event", - "fieldname": "registrations_section", - "fieldtype": "Section Break", - "insert_after": "role", - "is_system_generated": 1, - "label": "Registrations", - "module": "Venue", - "name": "Event-registrations_section" - }, - { - "default": "0", - "dt": "Event", - "fieldname": "allow_registrations", - "fieldtype": "Check", - "insert_after": "registrations_section", - "is_system_generated": 1, - "label": "Allow registrations", - "module": "Venue", - "name": "Event-allow_registrations" - }, - { - "default": "0", - "depends_on": "eval:doc.allow_registrations", - "description": "Allows for the registration of multiple people (e.g. family) using a single user account/email address.", - "dt": "Event", - "fieldname": "allow_multiple_registrations", - "fieldtype": "Check", - "hidden": 1, - "insert_after": "allow_registrations", - "is_system_generated": 1, - "label": "Allow users to register multiple times", - "module": "Venue", - "name": "Event-allow_multiple_registrations" - }, - { - "default": "0", - "depends_on": "eval:doc.allow_registrations", - "dt": "Event", - "fieldname": "allow_cancellations", - "fieldtype": "Check", - "insert_after": "allow_multiple_registrations", - "is_system_generated": 1, - "label": "Allow cancellations", - "module": "Venue", - "name": "Event-allow_cancellations" - }, - { - "depends_on": "eval:doc.allow_registrations", - "dt": "Event", - "fieldname": "registration_form", - "fieldtype": "Link", - "insert_after": "allow_cancellations", - "is_system_generated": 1, - "label": "Registration Form", - "module": "Venue", - "name": "Event-registration_form", - "options": "Web Form" - }, - { - "default": "0", - "depends_on": "eval:doc.registration_form&&doc.allow_registrations", - "dt": "Event", - "fieldname": "registration_amount", - "fieldtype": "Currency", - "insert_after": "registration_form", - "is_system_generated": 1, - "label": "Registration amount", - "non_negative": 1 - }, - { - "dt": "Event", - "fieldname": "column_break_38", - "fieldtype": "Column Break", - "insert_after": "amount", - "is_system_generated": 1, - "name": "Event-column_break_38" - }, - { - "default": null, - "depends_on": "eval:!doc.registration_form", - "dt": "Event", - "fieldname": "success_message", - "fieldtype": "HTML", - "insert_after": "column_break_38", - "is_system_generated": 1, - "label": "Registration success message" - } - ], - "custom_perms": [], - "doctype": "Event", - "links": [ - { - "group": "Registrations", - "is_child_table": 0, - "link_doctype": "Event Registration", - "link_fieldname": "event", - "name": "6b3d53940f", - "parent": "Event", - "parent_doctype": null, - "parentfield": "links", - "parenttype": "DocType", - "table_fieldname": null - } - ], - "property_setters": [ - { - "doc_type": "Event", - "doctype_or_field": "DocType", - "module": "Venue", - "name": "Event-main-links_order", - "property": "links_order", - "property_type": "Small Text", - "value": "[\"6b3d53940f\"]", - "is_system_generated": 1 - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "description": "Leave this field empty or write 0 to not set a limit.", + "dt": "Event", + "fieldname": "max_number_of_registrations", + "fieldtype": "Int", + "insert_after": "status", + "is_system_generated": 1, + "label": "Maximum number of registrations", + "non_negative": 1 + }, + { + "depends_on": "eval:doc.published", + "dt": "Event", + "fieldname": "published_address", + "fieldtype": "Link", + "insert_after": "role", + "is_system_generated": 1, + "label": "Address", + "options": "Address", + "module": "Venue", + "name": "Event-published_address" + }, + { + "depends_on": "eval:doc.published", + "dt": "Event", + "fieldname": "registrations_section", + "fieldtype": "Section Break", + "insert_after": "published_address", + "is_system_generated": 1, + "label": "Registrations", + "module": "Venue", + "name": "Event-registrations_section" + }, + { + "default": "0", + "dt": "Event", + "fieldname": "allow_registrations", + "fieldtype": "Check", + "insert_after": "registrations_section", + "is_system_generated": 1, + "label": "Allow registrations", + "module": "Venue", + "name": "Event-allow_registrations" + }, + { + "default": "0", + "depends_on": "eval:doc.allow_registrations", + "description": "Allows for the registration of multiple people (e.g. family) using a single user account/email address.", + "dt": "Event", + "fieldname": "allow_multiple_registrations", + "fieldtype": "Check", + "hidden": 1, + "insert_after": "allow_registrations", + "is_system_generated": 1, + "label": "Allow users to register multiple times", + "module": "Venue", + "name": "Event-allow_multiple_registrations" + }, + { + "default": "0", + "depends_on": "eval:doc.allow_registrations", + "dt": "Event", + "fieldname": "allow_cancellations", + "fieldtype": "Check", + "insert_after": "allow_multiple_registrations", + "is_system_generated": 1, + "label": "Allow cancellations", + "module": "Venue", + "name": "Event-allow_cancellations" + }, + { + "depends_on": "eval:doc.allow_registrations", + "dt": "Event", + "fieldname": "registration_form", + "fieldtype": "Link", + "insert_after": "allow_cancellations", + "is_system_generated": 1, + "label": "Registration Form", + "module": "Venue", + "name": "Event-registration_form", + "options": "Web Form" + }, + { + "default": "0", + "depends_on": "eval:doc.registration_form&&doc.allow_registrations", + "dt": "Event", + "fieldname": "registration_amount", + "fieldtype": "Currency", + "insert_after": "registration_form", + "is_system_generated": 1, + "label": "Registration amount", + "non_negative": 1 + }, + { + "dt": "Event", + "fieldname": "column_break_38", + "fieldtype": "Column Break", + "insert_after": "amount", + "is_system_generated": 1, + "name": "Event-column_break_38" + }, + { + "default": null, + "depends_on": "eval:!doc.registration_form", + "dt": "Event", + "fieldname": "success_message", + "fieldtype": "HTML", + "insert_after": "column_break_38", + "is_system_generated": 1, + "label": "Registration success message" + } + ], + "custom_perms": [], + "doctype": "Event", + "links": [ + { + "group": "Registrations", + "is_child_table": 0, + "link_doctype": "Event Registration", + "link_fieldname": "event", + "name": "6b3d53940f", + "parent": "Event", + "parent_doctype": null, + "parentfield": "links", + "parenttype": "DocType", + "table_fieldname": null + } + ], + "property_setters": [ + { + "doc_type": "Event", + "doctype_or_field": "DocType", + "module": "Venue", + "name": "Event-main-links_order", + "property": "links_order", + "property_type": "Small Text", + "value": "[\"6b3d53940f\"]", + "is_system_generated": 1 + } + ], + "sync_on_migrate": 1 +} diff --git a/erpnext/venue/web_template/__init__.py b/erpnext/venue/web_template/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/erpnext/venue/web_template/event_card/__init__.py b/erpnext/venue/web_template/event_card/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/erpnext/venue/web_template/event_card/event_card.html b/erpnext/venue/web_template/event_card/event_card.html new file mode 100644 index 0000000000000000000000000000000000000000..4c719bdc0411f8cb347eecc895e6a12e5a7364c9 --- /dev/null +++ b/erpnext/venue/web_template/event_card/event_card.html @@ -0,0 +1,155 @@ +{%- macro event_card(event, is_full_width=False, align="Left") -%} + {# {%- set align_events_class = resolve_class({ + 'align-events-end': align == 'Right', + 'align-events-center': align == 'Center', + 'align-events-start': align == 'Left', + }) -%} #} + {# {%- set col_size = 3 if is_full_width else 4 -%} #} + {%- set title = event.subject -%} + {%- set title = title[:50] + "..." if title|len > 50 else title -%} + {%- set image = event.image -%} + {%- set description = event.short_description-%} + + + {% if image %} +
+ {{ title }} +
+ {% else %} +
+ {{ frappe.utils.get_abbr(title) }} +
+ {% endif %} + + {{ event_card_body(title, description, event, align) }} +
+{%- endmacro -%} + + +{%- macro event_card_date(date) -%} + {% if date %} + {% set date = frappe.utils.get_datetime(date) %} + + {% endif %} +{%- endmacro -%} + +{%- macro event_card_body(title, description, event, align) -%} + {# {%- set align_class = resolve_class({ + 'text-right': align == 'Right', + 'text-center': align == 'Center', + 'text-left': align == 'Left', + }) -%} #} + {% if event.published_address %} + {% set address = frappe.get_doc("Address", event.published_address) %} + {% endif %} + +
+
+
+ {{ title or '' }} +
+
+ +
+ {% if event.repeat_this_event %} + + {% endif %} + + + + {{ event_card_date(event.starts_on) }} + +
+ + {% if address %} +
+ + {{ address | get_condensed_address }} +
+ {% endif %} + +
+ {% if event.registration_amount %} + + {{ frappe.format(event.registration_amount, df=event.meta.get("fields", {"fieldname": "registration_amount"})[0]) }} + {% else %} + {{ _("Free") }} + {% endif %} +
+ + {# #} +
+{%- endmacro -%} + + +{% set primary_action = "/events" %} +{% set primary_action_label = _("View List") %} + +
+
+
+ {%- if title -%} +

+ {{ title }} +

+ {%- endif -%} + {%- if subtitle -%} +

+ {{ subtitle }} +

+ {%- endif -%} +
+
+ {%- if primary_action -%} + + {{ primary_action_label }} + + {%- endif -%} +
+
+ +
+ {%- set event_list = frappe.get_all( + 'Event', + {'published': 1, 'event_type': 'Public'}, + order_by='starts_on asc', + limit=limit if limit else 24, + ) -%} + {%- for event in event_list: -%} + {%- set event = frappe.get_doc('Event', event) -%} + {{ event_card(event, is_full_width=True, align='Center') }} + {%- endfor -%} +
+ + {%- if primary_action and (event_list | length) >= 24 -%} + + {%- endif -%} +
diff --git a/erpnext/venue/web_template/event_card/event_card.json b/erpnext/venue/web_template/event_card/event_card.json new file mode 100644 index 0000000000000000000000000000000000000000..e7fbf27adda347bb0dc5cad26f398d8d151a0d93 --- /dev/null +++ b/erpnext/venue/web_template/event_card/event_card.json @@ -0,0 +1,28 @@ +{ + "creation": "2023-12-07 17:29:26.161880", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "default": "16", + "fieldname": "limit", + "fieldtype": "Int", + "label": "Limit", + "reqd": 0 + } + ], + "modified": "2023-12-07 17:29:26.161880", + "modified_by": "Administrator", + "module": "Venue", + "name": "Event Card", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Section" +} diff --git a/pyproject.toml b/pyproject.toml index 88a915219a962cb4154d8a0555bd6a09458ca6a8..13cfd8899d5e1b421547f375432d12287c3ff43b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "pycountry~=22.3.5", "Unidecode~=1.3.6", "barcodenumber~=0.5.0", + "rapidfuzz~=2.15.0", "holidays~=0.28", # integration dependencies