diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3cbbb7dfed..7375ca14727 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,12 +59,14 @@ repos: rev: v0.2.0 hooks: - id: ruff - name: "Run ruff linter and apply fixes" - args: ["--fix"] + name: "Run ruff import sorter" + args: ["--select=I", "--fix"] + + - id: ruff + name: "Run ruff linter" - id: ruff-format - name: "Format Python code" - + name: "Run ruff formatter" ci: autoupdate_schedule: weekly diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index 411b90f84ee..ff44723afee 100644 --- a/erpnext/accounts/doctype/account/account.js +++ b/erpnext/accounts/doctype/account/account.js @@ -22,8 +22,7 @@ frappe.ui.form.on("Account", { // hide fields if group frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0); - // disable fields - frm.toggle_enable(["is_group", "company"], false); + frm.toggle_enable(["is_group", "company", "account_number"], frm.is_new()); if (cint(frm.doc.is_group) == 0) { frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account); diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 78f73efff11..0c9232015d9 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -55,8 +55,7 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Account Number", - "read_only": 1 + "label": "Account Number" }, { "default": "0", @@ -72,7 +71,6 @@ "oldfieldname": "company", "oldfieldtype": "Link", "options": "Company", - "read_only": 1, "remember_last_selected_value": 1, "reqd": 1 }, diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 3b939aa3920..49d5396c0f3 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -222,7 +222,7 @@ frappe.treeview_settings["Account"] = { "General Ledger", "Balance Sheet", "Profit and Loss Statement", - "Cash Flow Statement", + "Cash Flow", "Accounts Payable", "Accounts Receivable", ]) { diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json index b6673795bea..d599ea65ea6 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/fr_plan_comptable_general_avec_code.json @@ -1525,7 +1525,8 @@ "41-Clients et comptes rattach\u00e9s (PASSIF)": { "Clients cr\u00e9diteurs": { "Clients - Avances et acomptes re\u00e7us sur commandes": { - "account_number": "4191" + "account_number": "4191", + "account_type": "Income Account" }, "Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": { "account_number": "4196" @@ -3141,4 +3142,4 @@ "account_number": "7" } } -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js index 5b9a52e8f8b..4f59085db0a 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js @@ -3,4 +3,23 @@ frappe.ui.form.on("Accounts Settings", { refresh: function (frm) {}, + enable_immutable_ledger: function (frm) { + if (!frm.doc.enable_immutable_ledger) { + return; + } + + let msg = __("Enabling this will change the way how cancelled transactions are handled."); + msg += " "; + msg += __("Please enable only if the understand the effects of enabling this."); + msg += "
"; + msg += "Do you still want to enable immutable ledger?"; + + frappe.confirm( + msg, + () => {}, + () => { + frm.set_value("enable_immutable_ledger", 0); + } + ); + }, }); diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 0e238e08f62..9982b4f9d88 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -12,6 +12,7 @@ "unlink_advance_payment_on_cancelation_of_order", "column_break_13", "delete_linked_ledger_entries", + "enable_immutable_ledger", "invoicing_features_section", "check_supplier_invoice_uniqueness", "automatically_fetch_payment_terms", @@ -105,7 +106,7 @@ }, { "default": "0", - "description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field", + "description": "Enabling this ensures each Purchase Invoice has a unique value in Supplier Invoice No. field within a particular fiscal year", "fieldname": "check_supplier_invoice_uniqueness", "fieldtype": "Check", "label": "Check Supplier Invoice Number Uniqueness" @@ -454,6 +455,13 @@ "fieldname": "remarks_section", "fieldtype": "Section Break", "label": "Remarks Column Length" + }, + { + "default": "0", + "description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well", + "fieldname": "enable_immutable_ledger", + "fieldtype": "Check", + "label": "Enable Immutable Ledger" } ], "icon": "icon-cog", @@ -461,7 +469,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-01-30 14:04:26.553554", + "modified": "2024-03-15 12:11:36.085158", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -490,4 +498,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 07d1a65265b..34f0f24047b 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -39,6 +39,7 @@ class AccountsSettings(Document): determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] enable_common_party_accounting: DF.Check enable_fuzzy_matching: DF.Check + enable_immutable_ledger: DF.Check enable_party_matching: DF.Check frozen_accounts_modifier: DF.Link | None general_ledger_remarks_length: DF.Int diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index a1de91faad2..b15745d834c 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -59,6 +59,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", { ); frm.add_custom_button(__("Auto Reconcile"), function () { + if (!frm.doc.bank_account) { + frappe.msgprint(__("Please select Bank Account")); + return; + } frappe.call({ method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers", args: { diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 05787665a76..42b1a54dea6 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -495,12 +495,12 @@ def check_matching( bank_account, company, transaction, - document_types, - from_date, - to_date, - filter_by_reference_date, - from_reference_date, - to_reference_date, + document_types=None, + from_date=None, + to_date=None, + filter_by_reference_date=None, + from_reference_date=None, + to_reference_date=None, ): exact_match = True if "exact_match" in document_types else False @@ -540,14 +540,14 @@ def get_queries( bank_account, company, transaction, - document_types, - from_date, - to_date, - filter_by_reference_date, - from_reference_date, - to_reference_date, - exact_match, - common_filters, + document_types=None, + from_date=None, + to_date=None, + filter_by_reference_date=None, + from_reference_date=None, + to_reference_date=None, + exact_match=None, + common_filters=None, ): # get queries to get matching vouchers account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from" @@ -580,15 +580,15 @@ def get_matching_queries( bank_account, company, transaction, - document_types, - exact_match, - account_from_to, - from_date, - to_date, - filter_by_reference_date, - from_reference_date, - to_reference_date, - common_filters, + document_types=None, + exact_match=None, + account_from_to=None, + from_date=None, + to_date=None, + filter_by_reference_date=None, + from_reference_date=None, + to_reference_date=None, + common_filters=None, ): queries = [] currency = get_account_currency(bank_account) diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py index 75f02e659f4..a1271b9c32a 100644 --- a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py +++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py @@ -68,6 +68,9 @@ class AutoMatchbyAccountIBAN: party, or_filters=or_filters, pluck="name", limit_page_length=1 ) + if "bank_ac_no" in or_filters: + or_filters["bank_account_no"] = or_filters.pop("bank_ac_no") + if party_result: result = ( party, diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py index 784ad27dd47..34b2ec68743 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py @@ -4,7 +4,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import add_days, format_date, getdate +from frappe.utils import add_days, flt, format_date, getdate class MainCostCenterCantBeChild(frappe.ValidationError): @@ -60,7 +60,7 @@ class CostCenterAllocation(Document): self.validate_child_cost_centers() def validate_total_allocation_percentage(self): - total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])]) + total_percentage = sum([flt(d.percentage) for d in self.get("allocation_percentages", [])]) if total_percentage != 100: frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation) diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index 2719c83ba35..ad58761e1bf 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -141,7 +141,19 @@ class Dunning(AccountsController): def on_cancel(self): super().on_cancel() - self.ignore_linked_doctypes = ["GL Entry"] + self.ignore_linked_doctypes = [ + "GL Entry", + "Stock Ledger Entry", + "Repost Item Valuation", + "Repost Payment Ledger", + "Repost Payment Ledger Items", + "Repost Accounting Ledger", + "Repost Accounting Ledger Items", + "Unreconcile Payment", + "Unreconcile Payment Entries", + "Payment Ledger Entry", + "Serial and Batch Bundle", + ] def resolve_dunning(doc, state): diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json index fb997847448..66db37fe13b 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json @@ -82,7 +82,7 @@ "icon": "fa fa-calendar", "idx": 1, "links": [], - "modified": "2024-01-30 12:35:38.645968", + "modified": "2024-05-27 17:29:55.560840", "modified_by": "Administrator", "module": "Accounts", "name": "Fiscal Year", @@ -127,6 +127,10 @@ { "read": 1, "role": "Stock Manager" + }, + { + "read": 1, + "role": "Auditor" } ], "show_name_in_global_search": 1, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4de20a17389..3ec47ff022a 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -454,7 +454,7 @@ class JournalEntry(AccountsController): self.voucher_type == "Depreciation Entry" and d.reference_type == "Asset" and d.reference_name - and d.account_type == "Depreciation" + and frappe.get_cached_value("Account", d.account, "root_type") == "Expense" and d.debit ): asset = frappe.get_doc("Asset", d.reference_name) @@ -1031,6 +1031,17 @@ class JournalEntry(AccountsController): def build_gl_map(self): gl_map = [] + + company_currency = erpnext.get_company_currency(self.company) + if self.multi_currency: + for row in self.get("accounts"): + if row.account_currency != company_currency: + self.currency = row.account_currency + self.conversion_rate = row.exchange_rate + break + else: + self.currency = company_currency + for d in self.get("accounts"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): r = [d.user_remark, self.remark] diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index d6ba193aa0e..d420bcca342 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -20,6 +20,7 @@ "party", "party_name", "book_advance_payments_in_separate_party_account", + "reconcile_on_advance_payment_date", "column_break_11", "bank_account", "party_bank_account", @@ -88,6 +89,7 @@ "custom_remarks", "remarks", "base_in_words", + "is_opening", "column_break_16", "letter_head", "print_heading", @@ -750,6 +752,7 @@ "fieldtype": "Check", "hidden": 1, "label": "Book Advance Payments in Separate Party Account", + "no_copy": 1, "read_only": 1 }, { @@ -765,6 +768,26 @@ "label": "In Words", "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "fetch_from": "company.reconcile_on_advance_payment_date", + "fieldname": "reconcile_on_advance_payment_date", + "fieldtype": "Check", + "hidden": 1, + "label": "Reconcile on Advance Payment Date", + "no_copy": 1, + "read_only": 1 + }, + { + "default": "No", + "depends_on": "eval: doc.book_advance_payments_in_separate_party_account == 1", + "fieldname": "is_opening", + "fieldtype": "Select", + "label": "Is Opening", + "options": "No\nYes", + "print_hide": 1, + "search_index": 1 } ], "index_web_pages_for_search": 1, @@ -778,7 +801,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-04-11 11:25:07.366347", + "modified": "2024-05-31 17:07:06.197249", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9f98649248d..69075e5cf6c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -116,11 +116,13 @@ class PaymentEntry(AccountsController): self.book_advance_payments_in_separate_party_account = False if self.party_type not in ("Customer", "Supplier"): + self.is_opening = "No" return if not frappe.db.get_value( "Company", self.company, "book_advance_payments_in_separate_party_account" ): + self.is_opening = "No" return # Important to set this flag for the gl building logic to work properly @@ -132,6 +134,7 @@ class PaymentEntry(AccountsController): if (account_type == "Payable" and self.party_type == "Customer") or ( account_type == "Receivable" and self.party_type == "Supplier" ): + self.is_opening = "No" return if self.references: @@ -141,6 +144,7 @@ class PaymentEntry(AccountsController): # If there are referencers other than `allowed_types`, treat this as a normal payment entry if reference_types - allowed_types: self.book_advance_payments_in_separate_party_account = False + self.is_opening = "No" return liability_account = get_party_account( @@ -1249,13 +1253,16 @@ class PaymentEntry(AccountsController): "voucher_detail_no": invoice.name, } - date_field = "posting_date" - if invoice.reference_doctype in ["Sales Order", "Purchase Order"]: - date_field = "transaction_date" - posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field) - - if getdate(posting_date) < getdate(self.posting_date): + if self.reconcile_on_advance_payment_date: posting_date = self.posting_date + else: + date_field = "posting_date" + if invoice.reference_doctype in ["Sales Order", "Purchase Order"]: + date_field = "transaction_date" + posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field) + + if getdate(posting_date) < getdate(self.posting_date): + posting_date = self.posting_date dr_or_cr, account = self.get_dr_and_account_for_advances(invoice) args_dict["account"] = account diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index d22350d20fc..4631f8905e6 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1729,6 +1729,68 @@ class TestPaymentEntry(FrappeTestCase): self.check_gl_entries() self.check_pl_entries() + def test_opening_flag_for_advance_as_liability(self): + company = "_Test Company" + + advance_account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Received", + company=company, + account_type="Receivable", + ) + + # Enable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": advance_account, + }, + ) + # Advance Payment + adv = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + adv.is_opening = "Yes" + adv.save() # use save() to trigger set_liability_account() + adv.submit() + + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": adv.name, "is_opening": "Yes"} + ) + # 'Is Opening' can be 'Yes' for Advances in separate party account + self.assertNotEqual(gl_with_opening_set, []) + + # Disable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 0, + "default_advance_received_account": None, + }, + ) + payment = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + payment.is_opening = "Yes" + payment.save() + payment.submit() + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": payment.name, "is_opening": "Yes"} + ) + # 'Is Opening' should always be 'No' for normal advance payments + self.assertEqual(gl_with_opening_set, []) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 0a3a0678084..53f69a47e75 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1525,6 +1525,55 @@ class TestPaymentReconciliation(FrappeTestCase): ] self.assertEqual(pl_entries, expected_ple) + def test_advance_payment_reconciliation_date(self): + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": self.advance_payable_account, + "reconcile_on_advance_payment_date": 1, + }, + ) + + self.supplier = "_Test Supplier" + amount = 1500 + + pe = self.create_payment_entry(amount=amount) + pe.posting_date = add_days(nowdate(), -1) + pe.party_type = "Supplier" + pe.party = self.supplier + pe.payment_type = "Pay" + pe.paid_from = self.cash + pe.paid_to = self.advance_payable_account + pe.save().submit() + + pi = self.create_purchase_invoice(qty=10, rate=100) + self.assertNotEqual(pe.posting_date, pi.posting_date) + + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.default_advance_account = self.advance_payable_account + pr.from_payment_date = pe.posting_date + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # Assert Ledger Entries + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0, "posting_date": pe.posting_date}, + ) + self.assertEqual(len(gl_entries), 4) + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name, "delinked": 0, "posting_date": pe.posting_date}, + ) + self.assertEqual(len(pl_entries), 3) + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index b158edaed5f..9faf8693a8a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -70,7 +70,7 @@ class POSClosingEntry(StatusUpdater): for key, value in pos_occurences.items(): if len(value) > 1: error_list.append( - _(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}") + _("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value)) ) if error_list: diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 3350a160cea..34a31d52dd0 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -228,6 +228,7 @@ class POSInvoice(SalesInvoice): self.apply_loyalty_points() self.check_phone_payments() self.set_status(update=True) + self.make_bundle_for_sales_purchase_return() self.submit_serial_batch_bundle() if self.coupon_code: diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 4816cccb285..1fca0495098 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -318,29 +318,28 @@ class TestPOSInvoice(unittest.TestCase): pos.insert() pos.submit() + pos.reload() pos_return1 = make_sales_return(pos.name) # partial return 1 pos_return1.get("items")[0].qty = -1 + pos_return1.submit() + pos_return1.reload() bundle_id = frappe.get_doc( "Serial and Batch Bundle", pos_return1.get("items")[0].serial_and_batch_bundle ) - bundle_id.remove(bundle_id.entries[1]) - bundle_id.save() - bundle_id.load_from_db() serial_no = bundle_id.entries[0].serial_no self.assertEqual(serial_no, serial_nos[0]) - pos_return1.insert() - pos_return1.submit() - # partial return 2 pos_return2 = make_sales_return(pos.name) + pos_return2.submit() + self.assertEqual(pos_return2.get("items")[0].qty, -1) serial_no = get_serial_nos_from_bundle(pos_return2.get("items")[0].serial_and_batch_bundle)[0] self.assertEqual(serial_no, serial_nos[1]) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 7501df02c3f..e8f94b880e2 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -54,7 +54,7 @@ class POSInvoiceMergeLog(Document): for key, value in pos_occurences.items(): if len(value) > 1: error_list.append( - _(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}") + _("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value)) ) if error_list: @@ -481,7 +481,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): if closing_entry: closing_entry.set_status(update=True, status="Failed") if isinstance(error_message, list): - error_message = frappe.json.dumps(error_message) + error_message = json.dumps(error_message) closing_entry.db_set("error_message", error_message) raise diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index e8e80449292..6f191c106c9 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -74,15 +74,21 @@ "discount_amount", "discount_percentage", "for_price_list", - "section_break_13", - "threshold_percentage", - "priority", + "dynamic_condition_tab", "condition", - "column_break_66", + "section_break_13", "apply_multiple_pricing_rules", "apply_discount_on_rate", + "column_break_66", + "threshold_percentage", + "validate_pricing_rule_section", "validate_applied_rule", + "column_break_texp", "rule_description", + "priority_section", + "has_priority", + "column_break_sayg", + "priority", "help_section", "pricing_rule_help", "reference_section", @@ -477,7 +483,7 @@ { "collapsible": 1, "fieldname": "section_break_13", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Advanced Settings" }, { @@ -487,6 +493,7 @@ "label": "Threshold for Suggestion (In Percentage)" }, { + "depends_on": "has_priority", "description": "Higher the number, higher the priority", "fieldname": "priority", "fieldtype": "Select", @@ -513,6 +520,7 @@ { "default": "0", "depends_on": "eval:doc.price_or_product_discount == 'Price'", + "description": "If enabled, then system will only validate the pricing rule and not apply automatically. User has to manually set the discount percentage / margin / free items to validate the pricing rule", "fieldname": "validate_applied_rule", "fieldtype": "Check", "label": "Validate Applied Rule" @@ -525,7 +533,8 @@ }, { "fieldname": "help_section", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", + "label": "Help Article", "options": "Simple" }, { @@ -603,12 +612,42 @@ "fieldname": "apply_recursion_over", "fieldtype": "Float", "label": "Apply Recursion Over (As Per Transaction UOM)" + }, + { + "fieldname": "priority_section", + "fieldtype": "Section Break", + "label": "Priority" + }, + { + "fieldname": "dynamic_condition_tab", + "fieldtype": "Tab Break", + "label": "Dynamic Condition" + }, + { + "fieldname": "validate_pricing_rule_section", + "fieldtype": "Section Break", + "label": "Validate Pricing Rule" + }, + { + "fieldname": "column_break_texp", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_sayg", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Enable this checkbox even if you want to set the zero priority", + "fieldname": "has_priority", + "fieldtype": "Check", + "label": "Has Priority" } ], "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2023-02-14 04:53:34.887358", + "modified": "2024-05-17 13:16:34.496704", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index ee9a2137d55..420fd3bee9d 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -27,9 +27,7 @@ class PricingRule(Document): from frappe.types import DF from erpnext.accounts.doctype.pricing_rule_brand.pricing_rule_brand import PricingRuleBrand - from erpnext.accounts.doctype.pricing_rule_item_code.pricing_rule_item_code import ( - PricingRuleItemCode, - ) + from erpnext.accounts.doctype.pricing_rule_item_code.pricing_rule_item_code import PricingRuleItemCode from erpnext.accounts.doctype.pricing_rule_item_group.pricing_rule_item_group import ( PricingRuleItemGroup, ) @@ -67,6 +65,7 @@ class PricingRule(Document): free_item_rate: DF.Currency free_item_uom: DF.Link | None free_qty: DF.Float + has_priority: DF.Check is_cumulative: DF.Check is_recursive: DF.Check item_groups: DF.Table[PricingRuleItemGroup] @@ -156,6 +155,12 @@ class PricingRule(Document): frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on)) def validate_mandatory(self): + if self.has_priority and not self.priority: + throw(_("Priority is mandatory"), frappe.MandatoryError, _("Please Set Priority")) + + if self.priority and not self.has_priority: + self.has_priority = 1 + for apply_on, field in apply_on_dict.items(): if self.apply_on == apply_on and len(self.get(field) or []) < 1: throw(_("{0} is not added in the table").format(apply_on), frappe.MandatoryError) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 5df689e5a2b..7f9c55ff24f 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -929,6 +929,30 @@ class TestPricingRule(unittest.TestCase): for doc in [si, si1]: doc.delete() + def test_pricing_rule_for_transaction_with_condition(self): + make_item("PR Transaction Condition") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") + make_pricing_rule( + selling=1, + min_qty=0, + price_or_product_discount="Product", + apply_on="Transaction", + free_item="PR Transaction Condition", + free_qty=1, + free_item_rate=10, + condition="customer=='_Test Customer 1'", + ) + + si = create_sales_invoice(qty=5, customer="_Test Customer 1", do_not_submit=True) + self.assertEqual(len(si.items), 2) + self.assertEqual(si.items[1].rate, 10) + + si1 = create_sales_invoice(qty=5, customer="_Test Customer 2", do_not_submit=True) + self.assertEqual(len(si1.items), 1) + + for doc in [si, si1]: + doc.delete() + def test_remove_pricing_rule(self): item = make_item("Water Flask") make_item_price("Water Flask", "_Test Price List", 100) @@ -1157,6 +1181,62 @@ class TestPricingRule(unittest.TestCase): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + def test_priority_of_multiple_pricing_rules(self): + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + + test_record = { + "doctype": "Pricing Rule", + "title": "_Test Pricing Rule 1", + "name": "_Test Pricing Rule 1", + "apply_on": "Item Code", + "currency": "USD", + "items": [ + { + "item_code": "_Test Item", + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Percentage", + "discount_percentage": 10, + "has_priority": 1, + "priority": 1, + "company": "_Test Company", + } + + frappe.get_doc(test_record.copy()).insert() + + test_record = { + "doctype": "Pricing Rule", + "title": "_Test Pricing Rule 2", + "name": "_Test Pricing Rule 2", + "apply_on": "Item Code", + "currency": "USD", + "items": [ + { + "item_code": "_Test Item", + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Percentage", + "discount_percentage": 20, + "has_priority": 1, + "priority": 3, + "company": "_Test Company", + } + + frappe.get_doc(test_record.copy()).insert() + + so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True) + self.assertEqual(so.items[0].discount_percentage, 20) + self.assertEqual(so.items[0].rate, 800) + + frappe.delete_doc_if_exists("Sales Order", so.name) + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + test_dependencies = ["Campaign"] @@ -1185,6 +1265,7 @@ def make_pricing_rule(**args): "priority": args.priority or 1, "discount_amount": args.discount_amount or 0.0, "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0, + "has_priority": args.has_priority or 0, } ) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 44f7f33a319..60c9e26aabe 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -33,6 +33,9 @@ def get_pricing_rules(args, doc=None): for apply_on in ["Item Code", "Item Group", "Brand"]: pricing_rules.extend(_get_pricing_rules(apply_on, args, values)) + if pricing_rules and pricing_rules[0].has_priority: + continue + if pricing_rules and not apply_multiple_pricing_rules(pricing_rules): break @@ -561,6 +564,7 @@ def apply_pricing_rule_on_transaction(doc): if pricing_rules: pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, doc.total, pricing_rules) + pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc) if not pricing_rules: remove_free_item(doc) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index d4d66a51c34..5048fc5e25e 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -1,6 +1,8 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import json + import frappe from frappe import _, qb from frappe.model.document import Document @@ -504,7 +506,7 @@ def is_any_doc_running(for_filter: str | dict | None = None) -> str | None: running_doc = None if for_filter: if isinstance(for_filter, str): - for_filter = frappe.json.loads(for_filter) + for_filter = json.loads(for_filter) running_doc = frappe.db.get_value( "Process Payment Reconciliation", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index c73c13eb118..e0ec144e314 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -158,7 +158,7 @@ def set_ageing(doc, entry): ageing_filters = frappe._dict( { "company": doc.company, - "report_date": doc.to_date, + "report_date": doc.posting_date, "ageing_based_on": doc.ageing_based_on, "range1": 30, "range2": 60, diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html index 647600a9fea..bf8de073853 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html @@ -340,10 +340,11 @@ - - - - + + + + + @@ -352,6 +353,7 @@ +
30 Days60 Days90 Days120 Days0 - 30 Days30 - 60 Days60 - 90 Days90 - 120 DaysAbove 120 Days
{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }} {{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }} {{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 957611f7858..c0a43395216 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -485,10 +485,12 @@ function hide_fields(doc) { var item_fields_stock = ["warehouse_section", "received_qty", "rejected_qty"]; - cur_frm.fields_dict["items"].grid.set_column_disp( - item_fields_stock, - cint(doc.update_stock) == 1 || cint(doc.is_return) == 1 ? true : false - ); + if (cur_frm.fields_dict["items"]) { + cur_frm.fields_dict["items"].grid.set_column_disp( + item_fields_stock, + cint(doc.update_stock) == 1 || cint(doc.is_return) == 1 ? true : false + ); + } cur_frm.refresh_fields(); } @@ -675,7 +677,7 @@ frappe.ui.form.on("Purchase Invoice", { if (frm.doc.supplier) { frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; } - if (!frm.doc.__onload.supplier_tds) { + if (!frm.doc.__onload.enable_apply_tds) { frm.set_df_property("apply_tds", "read_only", 1); } } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 272a180ca8d..fba60cef632 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, throw +from frappe import _, qb, throw from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate @@ -347,6 +347,22 @@ class PurchaseInvoice(BuyingController): self.tax_withholding_category = tds_category self.set_onload("supplier_tds", tds_category) + # If Linked Purchase Order has TDS applied, enable 'apply_tds' checkbox + if purchase_orders := [x.purchase_order for x in self.items if x.purchase_order]: + po = qb.DocType("Purchase Order") + po_with_tds = ( + qb.from_(po) + .select(po.name) + .where( + po.docstatus.eq(1) + & (po.name.isin(purchase_orders)) + & (po.apply_tds.eq(1)) + & (po.tax_withholding_category.notnull()) + ) + .run() + ) + self.set_onload("enable_apply_tds", True if po_with_tds else False) + super().set_missing_values(for_validate) def validate_credit_to_acc(self): @@ -448,7 +464,7 @@ class PurchaseInvoice(BuyingController): stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") stock_items = self.get_stock_items() - asset_received_but_not_billed = None + self.asset_received_but_not_billed = None if self.update_stock: self.validate_item_code() @@ -531,26 +547,45 @@ class PurchaseInvoice(BuyingController): frappe.msgprint(msg, title=_("Expense Head Changed")) item.expense_account = stock_not_billed_account - elif item.is_fixed_asset and item.pr_detail: - if not asset_received_but_not_billed: - asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") - item.expense_account = asset_received_but_not_billed elif item.is_fixed_asset: - account_type = ( - "capital_work_in_progress_account" - if is_cwip_accounting_enabled(item.asset_category) - else "fixed_asset_account" - ) - asset_category_account = get_asset_category_account( - account_type, item=item.item_code, company=self.company - ) - if not asset_category_account: - form_link = get_link_to_form("Asset Category", item.asset_category) - throw( - _("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company), - title=_("Missing Account"), + account = None + if item.pr_detail: + if not self.asset_received_but_not_billed: + self.asset_received_but_not_billed = self.get_company_default( + "asset_received_but_not_billed" + ) + + # check if 'Asset Received But Not Billed' account is credited in Purchase receipt or not + arbnb_booked_in_pr = frappe.db.get_value( + "GL Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": item.purchase_receipt, + "account": self.asset_received_but_not_billed, + }, + "name", ) - item.expense_account = asset_category_account + if arbnb_booked_in_pr: + account = self.asset_received_but_not_billed + + if not account: + account_type = ( + "capital_work_in_progress_account" + if is_cwip_accounting_enabled(item.asset_category) + else "fixed_asset_account" + ) + account = get_asset_category_account( + account_type, item=item.item_code, company=self.company + ) + if not account: + form_link = get_link_to_form("Asset Category", item.asset_category) + throw( + _("Please set Fixed Asset Account in {} against {}.").format( + form_link, self.company + ), + title=_("Missing Account"), + ) + item.expense_account = account elif not item.expense_account and for_validate: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) @@ -648,6 +683,19 @@ class PurchaseInvoice(BuyingController): where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""", } ) + self.status_updater.append( + { + "source_dt": "Purchase Invoice Item", + "target_dt": "Material Request Item", + "join_field": "material_request_item", + "target_field": "received_qty", + "target_parent_dt": "Material Request", + "target_parent_field": "per_received", + "target_ref_field": "stock_qty", + "source_field": "stock_qty", + "percent_join_field": "material_request", + } + ) if cint(self.is_return): self.status_updater.append( { @@ -707,6 +755,7 @@ class PurchaseInvoice(BuyingController): # Updating stock ledger should always be called after updating prevdoc status, # because updating ordered qty in bin depends upon updated ordered qty in PO if self.update_stock == 1: + self.make_bundle_for_sales_purchase_return() self.make_bundle_using_old_serial_batch_fields() self.update_stock_ledger() @@ -1035,10 +1084,10 @@ class PurchaseInvoice(BuyingController): if provisional_accounting_for_non_stock_items: if item.purchase_receipt: - provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value( + provisional_account, pr_qty, pr_base_rate, pr_rate = frappe.get_cached_value( "Purchase Receipt Item", item.pr_detail, - ["provisional_expense_account", "qty", "base_rate"], + ["provisional_expense_account", "qty", "base_rate", "rate"], ) provisional_account = provisional_account or self.get_company_default( "default_provisional_account" @@ -1072,7 +1121,10 @@ class PurchaseInvoice(BuyingController): self.posting_date, provisional_account, reverse=1, - item_amount=(min(item.qty, pr_qty) * pr_base_rate), + item_amount=( + (min(item.qty, pr_qty) * pr_rate) + * purchase_receipt_doc.get("conversion_rate") + ), ) if not self.is_internal_transfer(): @@ -1182,7 +1234,7 @@ class PurchaseInvoice(BuyingController): asset.name, { "gross_purchase_amount": purchase_amount, - "purchase_receipt_amount": purchase_amount, + "purchase_amount": purchase_amount, }, ) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 3ea88f195d8..3c3b081fa2f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -17,6 +17,8 @@ from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.exceptions import InvalidCurrency from erpnext.projects.doctype.project.test_project import make_project from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.material_request.material_request import make_purchase_order +from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as create_purchase_invoice_from_receipt, ) @@ -72,6 +74,31 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): # teardown pi.delete() + def test_update_received_qty_in_material_request(self): + from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice + + """ + Test if the received_qty in Material Request is updated correctly when + a Purchase Invoice with update_stock=True is submitted. + """ + mr = make_material_request(item_code="_Test Item", qty=10) + mr.save() + mr.submit() + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.save() + po.submit() + + # Create a Purchase Invoice with update_stock=True + pi = make_purchase_invoice(po.name) + pi.update_stock = True + pi.insert() + pi.submit() + + # Check if the received quantity is updated in Material Request + mr.reload() + self.assertEqual(mr.items[0].received_qty, 10) + def test_gl_entries_without_perpetual_inventory(self): frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC") pi = frappe.copy_doc(test_records[0]) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 66df76a3af0..1d7b0c2f461 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -105,6 +105,8 @@ "purchase_receipt", "pr_detail", "sales_invoice_item", + "material_request", + "material_request_item", "item_weight_details", "weight_per_unit", "total_weight", @@ -934,12 +936,34 @@ { "fieldname": "column_break_vbbb", "fieldtype": "Column Break" + }, + { + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "no_copy": 1, + "options": "Material Request", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "material_request_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Material Request Item", + "no_copy": 1, + "oldfieldname": "pr_detail", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "search_index": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2024-03-19 19:09:47.210965", + "modified": "2024-06-14 11:57:07.171700", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -949,4 +973,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py index ccbc34749d7..baeece4815c 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py @@ -52,6 +52,8 @@ class PurchaseInvoiceItem(Document): manufacturer_part_no: DF.Data | None margin_rate_or_amount: DF.Float margin_type: DF.Literal["", "Percentage", "Amount"] + material_request: DF.Link | None + material_request_item: DF.Data | None net_amount: DF.Currency net_rate: DF.Currency page_break: DF.Check diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json index 5b7cd2b0b20..0d74b2150f7 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json @@ -1,7 +1,5 @@ { "actions": [], - "allow_rename": 1, - "autoname": "format:ACC-REPOST-{#####}", "creation": "2023-07-04 13:07:32.923675", "default_view": "List", "doctype": "DocType", @@ -55,14 +53,15 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-09-26 14:21:27.362567", + "modified": "2024-06-03 17:30:37.012593", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger", - "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -71,7 +70,9 @@ "read": 1, "report": 1, "role": "System Manager", + "select": 1, "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json index 8aa0a840c7e..dd314ca6601 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -17,7 +17,7 @@ "in_create": 1, "issingle": 1, "links": [], - "modified": "2023-11-07 14:24:13.321522", + "modified": "2024-06-06 13:56:37.908879", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger Settings", @@ -30,13 +30,17 @@ "print": 1, "read": 1, "role": "Administrator", + "select": 1, "share": 1, "write": 1 }, { + "create": 1, + "delete": 1, "read": 1, "role": "System Manager", - "select": 1 + "select": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json index ed8d395a0ec..2b4efead7f3 100644 --- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json +++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_rename": 1, "creation": "2022-10-19 21:59:33.553852", "doctype": "DocType", "editable_grid": 1, @@ -99,13 +98,15 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-09-26 14:21:35.719727", + "modified": "2024-06-03 17:31:04.472279", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Payment Ledger", "owner": "Administrator", "permissions": [ { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -114,7 +115,9 @@ "read": 1, "report": 1, "role": "System Manager", + "select": 1, "share": 1, + "submit": 1, "write": 1 }, { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 1b36bf50f86..90b6e768092 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -787,6 +787,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Time Sheets", + "no_copy": 1, "options": "Sales Invoice Timesheet", "print_hide": 1 }, @@ -2187,7 +2188,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-05-08 18:02:28.549041", + "modified": "2024-06-07 16:49:32.458402", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 63f13ae251f..cb0803db932 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -450,6 +450,7 @@ class SalesInvoice(SellingController): if not self.get(table_name): continue + self.make_bundle_for_sales_purchase_return(table_name) self.make_bundle_using_old_serial_batch_fields(table_name) self.update_stock_ledger() @@ -2677,6 +2678,10 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): target.closing_text = letter_text.get("closing_text") target.language = letter_text.get("language") + # update outstanding + if source.payment_schedule and len(source.payment_schedule) == 1: + target.overdue_payments[0].outstanding = source.get("outstanding_amount") + target.validate() return get_mapped_doc( diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ee8658a9414..5cffe11e995 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import copy +import json import frappe from frappe.model.dynamic_links import get_dynamic_link_map @@ -3720,9 +3721,9 @@ class TestSalesInvoice(FrappeTestCase): map_docs( method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", - source_names=frappe.json.dumps([dn1.name, dn2.name]), + source_names=json.dumps([dn1.name, dn2.name]), target_doc=si, - args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}), + args=json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}), ) si.save().submit() diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index e7536e9bb94..932bc8e49d4 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -870,7 +870,8 @@ "label": "Purchase Order", "options": "Purchase Order", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "column_break_92", @@ -926,7 +927,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-25 15:56:44.828634", + "modified": "2024-05-23 16:36:18.970862", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", @@ -936,4 +937,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index edbd78db611..7612294a85c 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -1,6 +1,8 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import json + import frappe from frappe import _, qb from frappe.model.document import Document @@ -161,7 +163,7 @@ def get_linked_payments_for_doc( @frappe.whitelist() def create_unreconcile_doc_for_selection(selections=None): if selections: - selections = frappe.json.loads(selections) + selections = json.loads(selections) # assuming each row is a unique voucher for row in selections: unrecon = frappe.new_doc("Unreconcile Payment") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 0ff9e973e59..3f01dee888d 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -577,6 +577,8 @@ def make_reverse_gl_entries( and make reverse gl entries by swapping debit and credit """ + immutable_ledger_enabled = is_immutable_ledger_enabled() + if not gl_entries: gl_entry = frappe.qb.DocType("GL Entry") gl_entries = ( @@ -608,7 +610,6 @@ def make_reverse_gl_entries( for x in gl_entries: query = ( frappe.qb.update(gle) - .set(gle.is_cancelled, True) .set(gle.modified, now()) .set(gle.modified_by, frappe.session.user) .where( @@ -623,9 +624,14 @@ def make_reverse_gl_entries( & (gle.voucher_detail_no == x.voucher_detail_no) ) ) + + if not immutable_ledger_enabled: + query = query.set(gle.is_cancelled, True) + query.run() else: - set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) + if not immutable_ledger_enabled: + set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) for entry in gl_entries: new_gle = copy.deepcopy(entry) @@ -644,6 +650,10 @@ def make_reverse_gl_entries( new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"] new_gle["is_cancelled"] = 1 + if immutable_ledger_enabled: + new_gle["is_cancelled"] = 0 + new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate() + if new_gle["debit"] or new_gle["credit"]: make_entry(new_gle, adv_adj, "Yes") @@ -736,3 +746,7 @@ def validate_allowed_dimensions(gl_entry, dimension_filter_map): ), InvalidAccountDimensionError, ) + + +def is_immutable_ledger_enabled(): + return frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger") diff --git a/erpnext/accounts/report/account_balance/account_balance.py b/erpnext/accounts/report/account_balance/account_balance.py index 6f9f7ebcb8d..f385c71a9f4 100644 --- a/erpnext/accounts/report/account_balance/account_balance.py +++ b/erpnext/accounts/report/account_balance/account_balance.py @@ -49,7 +49,6 @@ def get_conditions(filters): if filters.account_type: conditions["account_type"] = filters.account_type - return conditions if filters.company: conditions["company"] = filters.company diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 2bd493cd4d0..64dba0183dc 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1028,20 +1028,6 @@ class ReceivablePayableReport: fieldtype="Link", options="Contact", ) - if self.filters.party_type == "Customer": - self.add_column( - _("Customer Name"), - fieldname="customer_name", - fieldtype="Link", - options="Customer", - ) - elif self.filters.party_type == "Supplier": - self.add_column( - _("Supplier Name"), - fieldname="supplier_name", - fieldtype="Link", - options="Supplier", - ) self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data") self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data") diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py index d285f28d8e3..e1545bdcd87 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder import DocType from frappe.utils import cstr, flt @@ -75,11 +76,24 @@ def get_data(filters): asset_data = assets_details.get(d.against_voucher) if asset_data: if not asset_data.get("accumulated_depreciation_amount"): - asset_data.accumulated_depreciation_amount = d.debit + asset_data.get( - "opening_accumulated_depreciation" - ) + AssetDepreciationSchedule = DocType("Asset Depreciation Schedule") + DepreciationSchedule = DocType("Depreciation Schedule") + query = ( + frappe.qb.from_(DepreciationSchedule) + .join(AssetDepreciationSchedule) + .on(DepreciationSchedule.parent == AssetDepreciationSchedule.name) + .select(DepreciationSchedule.accumulated_depreciation_amount) + .where( + (AssetDepreciationSchedule.asset == d.against_voucher) + & (DepreciationSchedule.parenttype == "Asset Depreciation Schedule") + & (DepreciationSchedule.schedule_date == d.posting_date) + ) + ).run(as_dict=True) + asset_data.accumulated_depreciation_amount = query[0]["accumulated_depreciation_amount"] + else: asset_data.accumulated_depreciation_amount += d.debit + asset_data.opening_accumulated_depreciation = asset_data.accumulated_depreciation_amount - d.debit row = frappe._dict(asset_data) row.update( diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 4fa485f54b3..f9a008ade7f 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -69,48 +69,50 @@ def get_asset_categories_for_grouped_by_category(filters): condition = "" if filters.get("asset_category"): condition += " and asset_category = %(asset_category)s" + # nosemgrep return frappe.db.sql( f""" - SELECT asset_category, - ifnull(sum(case when purchase_date < %(from_date)s then - case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then - gross_purchase_amount + SELECT a.asset_category, + ifnull(sum(case when a.purchase_date < %(from_date)s then + case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_as_on_from_date, - ifnull(sum(case when purchase_date >= %(from_date)s then - gross_purchase_amount + ifnull(sum(case when a.purchase_date >= %(from_date)s then + a.gross_purchase_amount else 0 end), 0) as cost_of_new_purchase, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Sold" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Sold" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_sold_asset, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Scrapped" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Scrapped" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_scrapped_asset - from `tabAsset` + from `tabAsset` a where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition} - group by asset_category + and not exists(select name from `tabAsset Capitalization Asset Item` where asset = a.name) + group by a.asset_category """, { "to_date": filters.to_date, diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 02de6c3aee1..e884deb2a17 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -219,7 +219,8 @@ def get_conditions(filters): if filters.get("account"): filters.account = get_accounts_with_children(filters.account) - conditions.append("account in %(account)s") + if filters.account: + conditions.append("account in %(account)s") if filters.get("cost_center"): filters.cost_center = get_cost_centers_with_children(filters.cost_center) @@ -329,7 +330,7 @@ def get_accounts_with_children(accounts): else: frappe.throw(_("Account: {0} does not exist").format(d)) - return list(set(all_accounts)) + return list(set(all_accounts)) if all_accounts else None def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries): @@ -420,6 +421,8 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): if filters.get("show_net_values_in_party_account"): account_type_map = get_account_type_map(filters.get("company")) + immutable_ledger = frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger") + def update_value_in_dict(data, key, gle): data[key].debit += gle.debit data[key].credit += gle.credit @@ -481,12 +484,17 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): elif group_by_voucher_consolidated: keylist = [ + gle.get("posting_date"), gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account"), gle.get("party_type"), gle.get("party"), ] + + if immutable_ledger: + keylist.append(gle.get("creation")) + if filters.get("include_dimensions"): for dim in accounting_dimensions: keylist.append(gle.get(dim)) 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 ebdea0602d1..1cd9b872a9b 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 @@ -5,14 +5,15 @@ import frappe from frappe import _ from frappe.utils import flt +from pypika import Order import erpnext from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import ( add_sub_total_row, add_total_row, + apply_group_by_conditions, get_grand_total, get_group_by_and_display_fields, - get_group_by_conditions, get_tax_accounts, ) from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -29,7 +30,7 @@ def _execute(filters=None, additional_table_columns=None): company_currency = erpnext.get_company_currency(filters.company) - item_list = get_items(filters, get_query_columns(additional_table_columns)) + item_list = get_items(filters, additional_table_columns) aii_account_map = get_aii_accounts() if item_list: itemised_tax, tax_columns = get_tax_accounts( @@ -287,59 +288,90 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters): - conditions = "" +def apply_conditions(query, pi, pii, filters): + for opts in ("company", "supplier", "mode_of_payment"): + if filters.get(opts): + query = query.where(pi[opts] == filters[opts]) - for opts in ( - ("company", " and `tabPurchase Invoice`.company=%(company)s"), - ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"), - ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"), - ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"), - ("item_group", " and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(pi.posting_date >= filters.get("from_date")) + + if filters.get("to_date"): + query = query.where(pi.posting_date <= filters.get("to_date")) + + if filters.get("item_code"): + query = query.where(pii.item_code == filters.get("item_code")) + + if filters.get("item_group"): + query = query.where(pii.item_group == filters.get("item_group")) if not filters.get("group_by"): - conditions += ( - "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc" - ) + query = query.orderby(pi.posting_date, order=Order.desc) + query = query.orderby(pii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Purchase Invoice") + query = apply_group_by_conditions(filters, "Purchase Invoice") - return conditions + return query -def get_items(filters, additional_query_columns): - conditions = get_conditions(filters) - if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - f""" - select - `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, - `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_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`, - `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, - `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, - `tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`, - `tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {additional_query_columns} - from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem` - where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and - `tabItem`.name = `tabPurchase Invoice Item`.`item_code` and - `tabPurchase Invoice`.docstatus = 1 {conditions} - """, - filters, - as_dict=1, +def get_items(filters, additional_table_columns): + pi = frappe.qb.DocType("Purchase Invoice") + pii = frappe.qb.DocType("Purchase Invoice Item") + Item = frappe.qb.DocType("Item") + query = ( + frappe.qb.from_(pi) + .join(pii) + .on(pi.name == pii.parent) + .left_join(Item) + .on(pii.item_code == Item.name) + .select( + pii.name, + pii.parent, + pi.posting_date, + pi.credit_to, + pi.company, + pi.supplier, + pi.remarks, + pi.base_net_total, + pi.unrealized_profit_loss_account, + pii.item_code, + pii.description, + pii.item_group, + pii.item_name.as_("pi_item_name"), + pii.item_group.as_("pi_item_group"), + Item.item_name.as_("i_item_name"), + Item.item_group.as_("i_item_group"), + pii.project, + pii.purchase_order, + pii.purchase_receipt, + pii.po_detail, + pii.expense_account, + pii.stock_qty, + pii.stock_uom, + pii.base_net_amount, + pi.supplier_name, + pi.mode_of_payment, + ) + .where(pi.docstatus == 1) ) + if filters.get("supplier"): + query = query.where(pi.supplier == filters["supplier"]) + if filters.get("company"): + query = query.where(pi.company == filters["company"]) + + if additional_table_columns: + for column in additional_table_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(pi[column.get("fieldname")]) + + query = apply_conditions(query, pi, pii, filters) + + return query.run(as_dict=True) + def get_aii_accounts(): return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany")) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index 7ed2ebd89ab..16a97733393 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -41,6 +41,12 @@ frappe.query_reports["Item-wise Sales Register"] = { label: __("Warehouse"), fieldtype: "Link", options: "Warehouse", + get_query: function () { + const company = frappe.query_report.get_filter_value("company"); + return { + filters: { company: company }, + }; + }, }, { fieldname: "brand", @@ -48,6 +54,12 @@ frappe.query_reports["Item-wise Sales Register"] = { fieldtype: "Link", options: "Brand", }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + options: "Item", + }, { fieldname: "item_group", label: __("Item Group"), diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index c7819731930..cf08e45c537 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -7,6 +7,7 @@ from frappe import _ from frappe.model.meta import get_field_precision from frappe.utils import cstr, flt from frappe.utils.xlsxutils import handle_html +from pypika import Order from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -26,7 +27,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions) + item_list = get_items(filters, additional_table_columns, additional_conditions) if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) @@ -83,9 +84,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= "company": d.company, "sales_order": d.sales_order, "delivery_note": d.delivery_note, - "income_account": d.unrealized_profit_loss_account - if d.is_internal_customer == 1 - else d.income_account, + "income_account": get_income_account(d), "cost_center": d.cost_center, "stock_qty": d.stock_qty, "stock_uom": d.stock_uom, @@ -150,6 +149,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= return columns, data, None, None, None, skip_total_row +def get_income_account(row): + if row.enable_deferred_revenue: + return row.deferred_revenue_account + elif row.is_internal_customer == 1: + return row.unrealized_profit_loss_account + else: + return row.income_account + + def get_columns(additional_table_columns, filters): columns = [] @@ -333,93 +341,143 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters, additional_conditions=None): - conditions = "" +def apply_conditions(query, si, sii, filters, additional_conditions=None): + for opts in ("company", "customer"): + if filters.get(opts): + query = query.where(si[opts] == filters[opts]) - for opts in ( - ("company", " and `tabSales Invoice`.company=%(company)s"), - ("customer", " and `tabSales Invoice`.customer = %(customer)s"), - ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(si.posting_date >= filters.get("from_date")) - if additional_conditions: - conditions += additional_conditions + if filters.get("to_date"): + query = query.where(si.posting_date <= filters.get("to_date")) if filters.get("mode_of_payment"): - conditions += """ and exists(select name from `tabSales Invoice Payment` - where parent=`tabSales Invoice`.name - and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" + sales_invoice = frappe.db.get_all( + "Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent" + ) + query = query.where(si.name.isin(sales_invoice)) if filters.get("warehouse"): if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"): lft, rgt = frappe.db.get_all( "Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True )[0] - conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) " + warehouses = frappe.db.get_all("Warehouse", {"lft": (">", lft), "rgt": ("<", rgt)}, pluck="name") + query = query.where(sii.warehouse.isin(warehouses)) else: - conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s""" + query = query.where(sii.warehouse == filters.get("warehouse")) if filters.get("brand"): - conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s""" + query = query.where(sii.brand == filters.get("brand")) + + if filters.get("item_code"): + query = query.where(sii.item_code == filters.get("item_code")) if filters.get("item_group"): - conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s""" + query = query.where(sii.item_group == filters.get("item_group")) + + if filters.get("income_account"): + query = query.where( + (sii.income_account == filters.get("income_account")) + | (sii.deferred_revenue_account == filters.get("income_account")) + | (si.unrealized_profit_loss_account == filters.get("income_account")) + ) if not filters.get("group_by"): - conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc" + query = query.orderby(si.posting_date, order=Order.desc) + query = query.orderby(sii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Sales Invoice") + query = apply_group_by_conditions(query, si, sii, filters) - return conditions + for key, value in (additional_conditions or {}).items(): + query = query.where(si[key] == value) + + return query -def get_group_by_conditions(filters, doctype): +def apply_group_by_conditions(query, si, ii, filters): if filters.get("group_by") == "Invoice": - return f"ORDER BY `tab{doctype} Item`.parent desc" + query = query.orderby(ii.parent, order=Order.desc) elif filters.get("group_by") == "Item": - return f"ORDER BY `tab{doctype} Item`.`item_code`" + query = query.orderby(ii.item_code) elif filters.get("group_by") == "Item Group": - return "ORDER BY `tab{} Item`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(ii.item_group) elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"): - return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(si[frappe.scrub(filters.get("group_by"))]) + + return query def get_items(filters, additional_query_columns, additional_conditions=None): - conditions = get_conditions(filters, additional_conditions) + si = frappe.qb.DocType("Sales Invoice") + sii = frappe.qb.DocType("Sales Invoice Item") + item = frappe.qb.DocType("Item") + + query = ( + frappe.qb.from_(si) + .join(sii) + .on(si.name == sii.parent) + .left_join(item) + .on(sii.item_code == item.name) + .select( + sii.name, + sii.parent, + si.posting_date, + si.debit_to, + si.unrealized_profit_loss_account, + si.is_internal_customer, + si.customer, + si.remarks, + si.territory, + si.company, + si.base_net_total, + sii.project, + sii.item_code, + sii.description, + sii.item_name, + sii.item_group, + sii.item_name.as_("si_item_name"), + sii.item_group.as_("si_item_group"), + item.item_name.as_("i_item_name"), + item.item_group.as_("i_item_group"), + sii.sales_order, + sii.delivery_note, + sii.income_account, + sii.cost_center, + sii.enable_deferred_revenue, + sii.deferred_revenue_account, + sii.stock_qty, + sii.stock_uom, + sii.base_net_rate, + sii.base_net_amount, + si.customer_name, + si.customer_group, + sii.so_detail, + si.update_stock, + sii.uom, + sii.qty, + ) + .where(si.docstatus == 1) + ) + if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - """ - select - `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent, - `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, - `tabSales Invoice`.unrealized_profit_loss_account, - `tabSales Invoice`.is_internal_customer, - `tabSales Invoice`.customer, `tabSales Invoice`.remarks, - `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, - `tabSales Invoice Item`.project, - `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, - `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, - `tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group, - `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, - `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, - `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, - `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, - `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, - `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail, - `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {} - from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem` - where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and - `tabItem`.name = `tabSales Invoice Item`.`item_code` and - `tabSales Invoice`.docstatus = 1 {} - """.format(additional_query_columns, conditions), - filters, - as_dict=1, - ) # nosec + for column in additional_query_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(si[column.get("fieldname")]) + + if filters.get("customer"): + query = query.where(si.customer == filters["customer"]) + + if filters.get("customer_group"): + query = query.where(si.customer_group == filters["customer_group"]) + + query = apply_conditions(query, si, sii, filters, additional_conditions) + + return query.run(as_dict=True) def get_delivery_notes_against_sales_order(item_list): @@ -427,16 +485,14 @@ def get_delivery_notes_against_sales_order(item_list): so_item_rows = list(set([d.so_detail for d in item_list])) if so_item_rows: - delivery_notes = frappe.db.sql( - """ - select parent, so_detail - from `tabDelivery Note Item` - where docstatus=1 and so_detail in (%s) - group by so_detail, parent - """ - % (", ".join(["%s"] * len(so_item_rows))), - tuple(so_item_rows), - as_dict=1, + dn_item = frappe.qb.DocType("Delivery Note Item") + delivery_notes = ( + frappe.qb.from_(dn_item) + .select(dn_item.parent, dn_item.so_detail) + .where(dn_item.docstatus == 1) + .where(dn_item.so_detail.isin(so_item_rows)) + .groupby(dn_item.so_detail, dn_item.parent) + .run(as_dict=True) ) for dn in delivery_notes: @@ -446,15 +502,16 @@ def get_delivery_notes_against_sales_order(item_list): def get_grand_total(filters, doctype): - return frappe.db.sql( - f""" SELECT - SUM(`tab{doctype}`.base_grand_total) - FROM `tab{doctype}` - WHERE `tab{doctype}`.docstatus = 1 - and posting_date between %s and %s - """, - (filters.get("from_date"), filters.get("to_date")), - )[0][0] # nosec + return flt( + frappe.db.get_value( + doctype, + { + "docstatus": 1, + "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), + }, + "sum(base_grand_total)", + ) + ) def get_tax_accounts( diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 834eb5f519c..f3f30d38a04 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -3,7 +3,9 @@ import frappe -from frappe import _ +from frappe import _, qb +from frappe.query_builder import Criterion +from frappe.query_builder.functions import Abs from frappe.utils import flt, getdate from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport @@ -21,16 +23,12 @@ def execute(filters=None): data = [] for d in entries: - invoice = invoice_details.get(d.against_voucher) or frappe._dict() - - if d.reference_type == "Purchase Invoice": - payment_amount = flt(d.debit) or -1 * flt(d.credit) - else: - payment_amount = flt(d.credit) or -1 * flt(d.debit) + invoice = invoice_details.get(d.against_voucher_no) or frappe._dict() + payment_amount = d.amount d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount}) - if d.against_voucher: + if d.against_voucher_no: ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d) row = [ @@ -39,11 +37,10 @@ def execute(filters=None): d.party_type, d.party, d.posting_date, - d.against_voucher, + d.against_voucher_no, invoice.posting_date, invoice.due_date, - d.debit, - d.credit, + d.amount, d.remarks, d.age, d.range1, @@ -111,8 +108,7 @@ def get_columns(filters): "width": 100, }, {"fieldname": "due_date", "label": _("Payment Due Date"), "fieldtype": "Date", "width": 100}, - {"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 140}, - {"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140}, + {"fieldname": "amount", "label": _("Amount"), "fieldtype": "Currency", "width": 140}, {"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200}, {"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50}, {"fieldname": "range1", "label": _("0-30"), "fieldtype": "Currency", "width": 140}, @@ -129,51 +125,68 @@ def get_columns(filters): def get_conditions(filters): + ple = qb.DocType("Payment Ledger Entry") conditions = [] - if not filters.party_type: - if filters.payment_type == _("Outgoing"): - filters.party_type = "Supplier" - else: - filters.party_type = "Customer" - - if filters.party_type: - conditions.append("party_type=%(party_type)s") + conditions.append(ple.delinked.eq(0)) + if filters.payment_type == _("Outgoing"): + conditions.append(ple.party_type.eq("Supplier")) + conditions.append(ple.against_voucher_type.eq("Purchase Invoice")) + else: + conditions.append(ple.party_type.eq("Customer")) + conditions.append(ple.against_voucher_type.eq("Sales Invoice")) if filters.party: - conditions.append("party=%(party)s") - - if filters.party_type: - conditions.append("against_voucher_type=%(reference_type)s") - filters["reference_type"] = ( - "Sales Invoice" if filters.party_type == "Customer" else "Purchase Invoice" - ) + conditions.append(ple.party.eq(filters.party)) if filters.get("from_date"): - conditions.append("posting_date >= %(from_date)s") + conditions.append(ple.posting_date.gte(filters.get("from_date"))) if filters.get("to_date"): - conditions.append("posting_date <= %(to_date)s") + conditions.append(ple.posting_date.lte(filters.get("to_date"))) - return "and " + " and ".join(conditions) if conditions else "" + if filters.get("company"): + conditions.append(ple.company.eq(filters.get("company"))) + + return conditions def get_entries(filters): - return frappe.db.sql( - """select - voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher - from `tabGL Entry` - where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') and is_cancelled = 0 {} - """.format(get_conditions(filters)), - filters, - as_dict=1, + ple = qb.DocType("Payment Ledger Entry") + conditions = get_conditions(filters) + + query = ( + qb.from_(ple) + .select( + ple.voucher_type, + ple.voucher_no, + ple.party_type, + ple.party, + ple.posting_date, + Abs(ple.amount).as_("amount"), + ple.remarks, + ple.against_voucher_no, + ) + .where(Criterion.all(conditions)) ) + res = query.run(as_dict=True) + return res def get_invoice_posting_date_map(filters): invoice_details = {} - dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice" - for t in frappe.db.sql(f"select name, posting_date, due_date from `tab{dt}`", as_dict=1): + dt = ( + qb.DocType("Sales Invoice") + if filters.get("payment_type") == _("Incoming") + else qb.DocType("Purchase Invoice") + ) + res = ( + qb.from_(dt) + .select(dt.name, dt.posting_date, dt.due_date) + .where((dt.docstatus.eq(1)) & (dt.company.eq(filters.get("company")))) + .run(as_dict=1) + ) + for t in res: invoice_details[t.name] = t return invoice_details diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js index e3f90f29982..0e84f882b51 100644 --- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js +++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js @@ -1,8 +1,4 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/purchase_trends_filters.js", function () { - frappe.query_reports["Purchase Invoice Trends"] = { - filters: erpnext.get_purchase_trends_filters(), - }; -}); +frappe.query_reports["Purchase Invoice Trends"] = $.extend({}, erpnext.purchase_trends_filters); diff --git a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js index 292d827b163..bdc39f36a8e 100644 --- a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js +++ b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js @@ -1,8 +1,4 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/sales_trends_filters.js", function () { - frappe.query_reports["Sales Invoice Trends"] = { - filters: erpnext.get_sales_trends_filters(), - }; -}); +frappe.query_reports["Sales Invoice Trends"] = $.extend({}, erpnext.sales_trends_filters); diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f27569531b1..6c0bf91e3f8 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -80,6 +80,7 @@ def _execute(filters, additional_table_columns=None): delivery_note = list(set(invoice_so_dn_map.get(inv.name, {}).get("delivery_note", []))) cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", []))) warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", []))) + inv_customer_details = customer_details.get(inv.customer, {}) row = { "voucher_type": inv.doctype, @@ -88,9 +89,9 @@ def _execute(filters, additional_table_columns=None): "customer": inv.customer, "customer_name": inv.customer_name, **get_values_for_columns(additional_table_columns, inv), - "customer_group": customer_details.get(inv.customer).get("customer_group"), - "territory": customer_details.get(inv.customer).get("territory"), - "tax_id": customer_details.get(inv.customer).get("tax_id"), + "customer_group": inv_customer_details.get("customer_group"), + "territory": inv_customer_details.get("territory"), + "tax_id": inv_customer_details.get("tax_id"), "receivable_account": inv.debit_to, "mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])), "project": inv.project, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index bbb8900de39..1ad24c46603 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -56,7 +56,7 @@ def get_fiscal_year( date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False ): if isinstance(boolean, str): - boolean = frappe.json.loads(boolean) + boolean = loads(boolean) fiscal_years = get_fiscal_years( date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 0f71e5d6f60..5ed62cb132f 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -652,7 +652,7 @@ frappe.ui.form.on("Asset", { ); frm.set_value("gross_purchase_amount", purchase_amount); - frm.set_value("purchase_receipt_amount", purchase_amount); + frm.set_value("purchase_amount", purchase_amount); frm.set_value("asset_quantity", asset_quantity); frm.set_value("cost_center", item.cost_center || purchase_doc.cost_center); if (item.asset_location) { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 39a0867d984..3a2a942bdf2 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -72,7 +72,7 @@ "status", "booked_fixed_asset", "column_break_51", - "purchase_receipt_amount", + "purchase_amount", "default_finance_book", "depr_entry_posting_status", "amended_from", @@ -408,15 +408,6 @@ "options": "Purchase Receipt", "print_hide": 1 }, - { - "fieldname": "purchase_receipt_amount", - "fieldtype": "Currency", - "hidden": 1, - "label": "Purchase Receipt Amount", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, { "depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset", "fieldname": "purchase_invoice", @@ -546,6 +537,15 @@ "label": "Additional Asset Cost", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "purchase_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Purchase Amount", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 72, @@ -589,7 +589,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2024-01-15 17:35:49.226603", + "modified": "2024-04-18 16:45:47.306032", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -633,4 +633,4 @@ "states": [], "title_field": "asset_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 3a05a598b1c..063a5447ab5 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -92,10 +92,10 @@ class Asset(AccountsController): number_of_depreciations_booked: DF.Int opening_accumulated_depreciation: DF.Currency policy_number: DF.Data | None + purchase_amount: DF.Currency purchase_date: DF.Date | None purchase_invoice: DF.Link | None purchase_receipt: DF.Link | None - purchase_receipt_amount: DF.Currency split_from: DF.Link | None status: DF.Literal[ "Draft", @@ -354,7 +354,7 @@ class Asset(AccountsController): if self.is_existing_asset: return - if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: + if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_amount: error_message = _( "Gross Purchase Amount should be equal to purchase amount of one single Asset." ) @@ -696,7 +696,7 @@ class Asset(AccountsController): purchase_document = self.get_purchase_document() fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account() - if purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate(): + if purchase_document and self.purchase_amount and getdate(self.available_for_use_date) <= getdate(): gl_entries.append( self.get_gl_dict( { @@ -704,8 +704,8 @@ class Asset(AccountsController): "against": fixed_asset_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, - "credit": self.purchase_receipt_amount, - "credit_in_account_currency": self.purchase_receipt_amount, + "credit": self.purchase_amount, + "credit_in_account_currency": self.purchase_amount, "cost_center": self.cost_center, }, item=self, @@ -719,8 +719,8 @@ class Asset(AccountsController): "against": cwip_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, - "debit": self.purchase_receipt_amount, - "debit_in_account_currency": self.purchase_receipt_amount, + "debit": self.purchase_amount, + "debit_in_account_currency": self.purchase_amount, "cost_center": self.cost_center, }, item=self, @@ -1116,8 +1116,8 @@ def create_new_asset_after_split(asset, split_qty): ) new_asset.gross_purchase_amount = new_gross_purchase_amount - if asset.purchase_receipt_amount: - new_asset.purchase_receipt_amount = new_gross_purchase_amount + if asset.purchase_amount: + new_asset.purchase_amount = new_gross_purchase_amount new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation new_asset.asset_quantity = split_qty new_asset.split_from = asset.name @@ -1140,6 +1140,8 @@ def create_new_asset_after_split(asset, split_qty): for row in new_asset.get("finance_books"): current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) + if not current_asset_depr_schedule_doc: + continue new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc) new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 1b3951e86a6..7e0c3ad6888 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1000,7 +1000,7 @@ class TestDepreciationBasics(AssetSetup): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") - depreciation_amount = get_depreciation_amount( + depreciation_amount, prev_per_day_depr = get_depreciation_amount( asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0] ) self.assertEqual(depreciation_amount, 30000) @@ -1698,7 +1698,7 @@ def create_asset(**args): "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, "gross_purchase_amount": args.gross_purchase_amount or 100000, - "purchase_receipt_amount": args.purchase_receipt_amount or 100000, + "purchase_amount": args.purchase_amount or 100000, "maintenance_required": args.maintenance_required or 0, "warehouse": args.warehouse or "_Test Warehouse - _TC", "available_for_use_date": args.available_for_use_date or "2020-06-06", @@ -1723,6 +1723,7 @@ def create_asset(**args): "depreciation_start_date": args.depreciation_start_date, "daily_prorata_based": args.daily_prorata_based or 0, "shift_based": args.shift_based or 0, + "rate_of_depreciation": args.rate_of_depreciation or 0, }, ) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 8d3bcfc153d..573dd92c585 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -145,7 +145,7 @@ class AssetCapitalization(StockController): def on_trash(self): frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None) - super(AssetCapitalization, self).on_trash() + super().on_trash() def cancel_target_asset(self): if self.entry_type == "Capitalization" and self.target_asset: @@ -616,8 +616,7 @@ class AssetCapitalization(StockController): asset_doc.available_for_use_date = self.posting_date asset_doc.purchase_date = self.posting_date asset_doc.gross_purchase_amount = total_target_asset_value - asset_doc.purchase_receipt_amount = total_target_asset_value - asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.purchase_amount = total_target_asset_value asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.flags.asset_created_via_asset_capitalization = True @@ -653,7 +652,7 @@ class AssetCapitalization(StockController): asset_doc = frappe.get_doc("Asset", self.target_asset) asset_doc.gross_purchase_amount = total_target_asset_value - asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.purchase_amount = total_target_asset_value asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.save() diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 86a18c07d1f..31723ef3be3 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -89,7 +89,7 @@ class TestAssetCapitalization(unittest.TestCase): # Test Target Asset values target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) - self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + self.assertEqual(target_asset.purchase_amount, total_amount) # Test Consumed Asset values self.assertEqual(consumed_asset.db_get("status"), "Capitalized") @@ -179,7 +179,7 @@ class TestAssetCapitalization(unittest.TestCase): # Test Target Asset values target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) - self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + self.assertEqual(target_asset.purchase_amount, total_amount) # Test Consumed Asset values self.assertEqual(consumed_asset.db_get("status"), "Capitalized") @@ -256,7 +256,7 @@ class TestAssetCapitalization(unittest.TestCase): # Test Target Asset values target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) - self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + self.assertEqual(target_asset.purchase_amount, total_amount) # Test General Ledger Entries expected_gle = { @@ -526,7 +526,7 @@ def create_depreciation_asset(**args): asset.available_for_use_date = args.available_for_use_date or asset.purchase_date asset.gross_purchase_amount = args.asset_value or 100000 - asset.purchase_receipt_amount = asset.gross_purchase_amount + asset.purchase_amount = asset.gross_purchase_amount finance_book = asset.append("finance_books") finance_book.depreciation_start_date = args.depreciation_start_date or "2020-12-31" 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 abbca68fea0..f64e9123dc0 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -285,6 +285,7 @@ class AssetDepreciationSchedule(Document): number_of_pending_depreciations = final_number_of_depreciations - start yearly_opening_wdv = value_after_depreciation current_fiscal_year_end_date = None + prev_per_day_depr = True for n in range(start, final_number_of_depreciations): # If depreciation is already completed (for double declining balance) if skip_row: @@ -301,8 +302,7 @@ class AssetDepreciationSchedule(Document): prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount else: prev_depreciation_amount = 0 - - depreciation_amount = get_depreciation_amount( + depreciation_amount, prev_per_day_depr = get_depreciation_amount( self, asset_doc, value_after_depreciation, @@ -312,6 +312,7 @@ class AssetDepreciationSchedule(Document): prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, number_of_pending_depreciations, + prev_per_day_depr, ) if not has_pro_rata or ( n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2 @@ -362,6 +363,16 @@ class AssetDepreciationSchedule(Document): row.depreciation_start_date, has_wdv_or_dd_non_yearly_pro_rata, ) + if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) <= 0: + frappe.throw( + _( + "Gross Purchase Amount Too Low: {0} cannot be depreciated over {1} cycles with a frequency of {2} depreciations." + ).format( + frappe.bold(asset_doc.gross_purchase_amount), + frappe.bold(row.total_number_of_depreciations), + frappe.bold(row.frequency_of_depreciation), + ) + ) elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)): from_date = get_last_day( @@ -599,11 +610,12 @@ def get_depreciation_amount( prev_depreciation_amount=0, has_wdv_or_dd_non_yearly_pro_rata=False, number_of_pending_depreciations=0, + prev_per_day_depr=0, ): if fb_row.depreciation_method in ("Straight Line", "Manual"): return get_straight_line_or_manual_depr_amount( asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations - ) + ), None else: return get_wdv_or_dd_depr_amount( asset, @@ -614,6 +626,7 @@ def get_depreciation_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, ) @@ -637,49 +650,14 @@ def get_straight_line_or_manual_depr_amount( elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: if row.daily_prorata_based: amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - total_days = ( - date_diff( - get_last_day( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) - * row.frequency_of_depreciation, - ) - ), - add_days( - get_last_day( - add_months( - row.depreciation_start_date, - flt( - row.total_number_of_depreciations - - asset.number_of_depreciations_booked - - number_of_pending_depreciations - - 1 - ) - * row.frequency_of_depreciation, - ) - ), - 1, - ), - ) - + 1 - ) - daily_depr_amount = amount / total_days - - to_date = get_last_day( - add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) + return get_daily_prorata_based_straight_line_depr( + asset, + row, + schedule_idx, + number_of_pending_depreciations, + amount, ) - from_date = add_days( - get_last_day( - add_months( - row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation - ) - ), - 1, - ) - - return daily_depr_amount * (date_diff(to_date, from_date) + 1) else: return ( flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) @@ -692,40 +670,9 @@ def get_straight_line_or_manual_depr_amount( - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) ) - - total_days = ( - date_diff( - get_last_day( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) - * row.frequency_of_depreciation, - ) - ), - add_days( - get_last_day( - add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation) - ), - 1, - ), - ) - + 1 + return get_daily_prorata_based_straight_line_depr( + asset, row, schedule_idx, number_of_pending_depreciations, amount ) - - daily_depr_amount = amount / total_days - - to_date = get_last_day( - add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) - ) - from_date = add_days( - get_last_day( - add_months( - row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation - ) - ), - 1, - ) - return daily_depr_amount * (date_diff(to_date, from_date) + 1) else: return ( flt(asset.gross_purchase_amount) @@ -734,6 +681,23 @@ def get_straight_line_or_manual_depr_amount( ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) +def get_daily_prorata_based_straight_line_depr( + asset, row, schedule_idx, number_of_pending_depreciations, amount +): + total_years = flt(number_of_pending_depreciations * row.frequency_of_depreciation) / 12 + every_year_depr = amount / total_years + + year_start_date = add_years( + row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12 + ) + year_end_date = add_days(add_years(year_start_date, 1), -1) + daily_depr_amount = every_year_depr / (date_diff(year_end_date, year_start_date) + 1) + from_date, total_depreciable_days = _get_total_days( + row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation + ) + return daily_depr_amount * total_depreciable_days + + def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx): if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation: return ( @@ -779,6 +743,7 @@ def get_wdv_or_dd_depr_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, ): return get_default_wdv_or_dd_depr_amount( asset, @@ -788,6 +753,7 @@ def get_wdv_or_dd_depr_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, ) @@ -799,6 +765,39 @@ def get_default_wdv_or_dd_depr_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, +): + if not fb_row.daily_prorata_based or cint(fb_row.frequency_of_depreciation) == 12: + return _get_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, + ), None + else: + return _get_daily_prorata_based_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, + prev_per_day_depr, + ) + + +def _get_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, ): if cint(fb_row.frequency_of_depreciation) == 12: return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) @@ -825,6 +824,75 @@ def get_default_wdv_or_dd_depr_amount( return prev_depreciation_amount +def _get_daily_prorata_based_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, + prev_per_day_depr, +): + if has_wdv_or_dd_non_yearly_pro_rata: # If applicable days for ther first month is less than full month + if schedule_idx == 0: + return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100), None + + elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: # Year changes + return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value) + else: + return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr) + else: + if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: # year changes + return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value) + else: + return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr) + + +def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value): + """ " + Returns monthly depreciation amount when year changes + 1. Calculate per day depr based on new year + 2. Calculate monthly amount based on new per day amount + """ + from_date, days_in_month = _get_total_days( + fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation) + ) + per_day_depr = get_per_day_depr(fb_row, depreciable_value, from_date) + return (per_day_depr * days_in_month), per_day_depr + + +def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr): + """ " + Returns monthly depreciation amount based on prev per day depr + Calculate per day depr only for the first month + """ + from_date, days_in_month = _get_total_days( + fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation) + ) + return (prev_per_day_depr * days_in_month), prev_per_day_depr + + +def get_per_day_depr( + fb_row, + depreciable_value, + from_date, +): + to_date = add_days(add_years(from_date, 1), -1) + total_days = date_diff(to_date, from_date) + 1 + per_day_depr = (flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days + return per_day_depr + + +def _get_total_days(depreciation_start_date, schedule_idx, frequency_of_depreciation): + from_date = add_months(depreciation_start_date, (schedule_idx - 1) * frequency_of_depreciation) + to_date = add_months(from_date, frequency_of_depreciation) + if is_last_day_of_the_month(depreciation_start_date): + to_date = get_last_day(to_date) + from_date = add_days(get_last_day(from_date), 1) + return from_date, date_diff(to_date, from_date) + 1 + + def make_draft_asset_depr_schedules_if_not_present(asset_doc): asset_depr_schedules_names = [] diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index c55063f2ebf..6e4966ac6cf 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -3,10 +3,12 @@ import frappe from frappe.tests.utils import FrappeTestCase +from frappe.utils import cstr from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, + get_depr_schedule, ) @@ -25,3 +27,136 @@ class TestAssetDepreciationSchedule(FrappeTestCase): ) self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert) + + def test_daily_prorata_based_depr_on_sl_methond(self): + asset = create_asset( + calculate_depreciation=1, + depreciation_method="Straight Line", + daily_prorata_based=1, + available_for_use_date="2020-01-01", + depreciation_start_date="2020-01-31", + frequency_of_depreciation=1, + total_number_of_depreciations=24, + ) + + expected_schedules = [ + ["2020-01-31", 4234.97, 4234.97], + ["2020-02-29", 3961.75, 8196.72], + ["2020-03-31", 4234.97, 12431.69], + ["2020-04-30", 4098.36, 16530.05], + ["2020-05-31", 4234.97, 20765.02], + ["2020-06-30", 4098.36, 24863.38], + ["2020-07-31", 4234.97, 29098.35], + ["2020-08-31", 4234.97, 33333.32], + ["2020-09-30", 4098.36, 37431.68], + ["2020-10-31", 4234.97, 41666.65], + ["2020-11-30", 4098.36, 45765.01], + ["2020-12-31", 4234.97, 49999.98], + ["2021-01-31", 4246.58, 54246.56], + ["2021-02-28", 3835.62, 58082.18], + ["2021-03-31", 4246.58, 62328.76], + ["2021-04-30", 4109.59, 66438.35], + ["2021-05-31", 4246.58, 70684.93], + ["2021-06-30", 4109.59, 74794.52], + ["2021-07-31", 4246.58, 79041.1], + ["2021-08-31", 4246.58, 83287.68], + ["2021-09-30", 4109.59, 87397.27], + ["2021-10-31", 4246.58, 91643.85], + ["2021-11-30", 4109.59, 95753.44], + ["2021-12-31", 4246.56, 100000.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) + + # Test for Written Down Value Method + # Frequency of deprciation = 3 + def test_for_daily_prorata_based_depreciation_wdv_method_frequency_3_months(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + depreciation_method="Written Down Value", + daily_prorata_based=1, + available_for_use_date="2021-02-20", + depreciation_start_date="2021-03-31", + frequency_of_depreciation=3, + total_number_of_depreciations=6, + rate_of_depreciation=40, + ) + + expected_schedules = [ + ["2021-03-31", 4383.56, 4383.56], + ["2021-06-30", 9535.45, 13919.01], + ["2021-09-30", 9640.23, 23559.24], + ["2021-12-31", 9640.23, 33199.47], + ["2022-03-31", 9430.66, 42630.13], + ["2022-06-30", 5721.27, 48351.4], + ["2022-08-20", 51648.6, 100000.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) + + # Frequency of deprciation = 6 + def test_for_daily_prorata_based_depreciation_wdv_method_frequency_6_months(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + depreciation_method="Written Down Value", + daily_prorata_based=1, + available_for_use_date="2020-02-20", + depreciation_start_date="2020-02-29", + frequency_of_depreciation=6, + total_number_of_depreciations=6, + rate_of_depreciation=40, + ) + + expected_schedules = [ + ["2020-02-29", 1092.90, 1092.90], + ["2020-08-31", 19944.01, 21036.91], + ["2021-02-28", 19618.83, 40655.74], + ["2021-08-31", 11966.4, 52622.14], + ["2022-02-28", 11771.3, 64393.44], + ["2022-08-31", 7179.84, 71573.28], + ["2023-02-20", 28426.72, 100000.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) + + # Frequency of deprciation = 12 + def test_for_daily_prorata_based_depreciation_wdv_method_frequency_12_months(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + depreciation_method="Written Down Value", + daily_prorata_based=1, + available_for_use_date="2020-02-20", + depreciation_start_date="2020-03-31", + frequency_of_depreciation=12, + total_number_of_depreciations=4, + rate_of_depreciation=40, + ) + + expected_schedules = [ + ["2020-03-31", 4480.87, 4480.87], + ["2021-03-31", 38207.65, 42688.52], + ["2022-03-31", 22924.59, 65613.11], + ["2023-03-31", 13754.76, 79367.87], + ["2024-02-20", 20632.13, 100000], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 4ff04c80434..5d4ef4e3845 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -159,8 +159,9 @@ def prepare_chart_data(data, filters): if filters.filter_based_on not in ("Date Range", "Fiscal Year"): filters_filter_based_on = "Date Range" date_field = "purchase_date" - filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field) - filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field) + filtered_data = [d for d in data if d.get(date_field)] + filters_from_date = min(filtered_data, key=lambda a: a.get(date_field)).get(date_field) + filters_to_date = max(filtered_data, key=lambda a: a.get(date_field)).get(date_field) else: filters_filter_based_on = filters.filter_based_on date_field = frappe.scrub(filters.date_based_on) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 5351ff6d791..a280724193d 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -794,6 +794,8 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions "field_map": { "name": "po_detail", "parent": "purchase_order", + "material_request": "material_request", + "material_request_item": "material_request_item", "wip_composite_asset": "wip_composite_asset", }, "postprocess": update_item, @@ -902,12 +904,12 @@ def get_mapped_subcontracting_order(source_name, target_doc=None): ) target_doc.populate_items_table() + source_doc = frappe.get_doc("Purchase Order", source_name) if target_doc.set_warehouse: for item in target_doc.items: item.warehouse = target_doc.set_warehouse else: - source_doc = frappe.get_doc("Purchase Order", source_name) if source_doc.set_warehouse: for item in target_doc.items: item.warehouse = source_doc.set_warehouse diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 272d077b1e3..b8689d29a56 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -513,7 +513,7 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll method: "frappe.desk.doctype.tag.tag.get_tagged_docs", args: { doctype: "Supplier", - tag: args.tag, + tag: "%" + args.tag + "%", }, callback: load_suppliers, }); diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js index 366fff191a0..56684a8659b 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js @@ -1,8 +1,4 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/purchase_trends_filters.js", function () { - frappe.query_reports["Purchase Order Trends"] = { - filters: erpnext.get_purchase_trends_filters(), - }; -}); +frappe.query_reports["Purchase Order Trends"] = $.extend({}, erpnext.purchase_trends_filters); diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c527a02376c..40ce8b6bc57 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2183,10 +2183,10 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt( - grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount") + grand_total * flt(d.invoice_portion) / 100, d.precision("payment_amount") ) d.base_payment_amount = flt( - base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount") + base_grand_total * flt(d.invoice_portion) / 100, d.precision("base_payment_amount") ) d.outstanding = d.payment_amount elif not d.invoice_portion: diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 58bcc8c5fbe..bf6e3cd663a 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -8,6 +8,7 @@ from frappe.contacts.doctype.address.address import render_address from frappe.utils import cint, flt, getdate from frappe.utils.data import nowtime +import erpnext from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.party import get_party_details from erpnext.buying.utils import update_last_purchase_rate, validate_for_items @@ -332,6 +333,8 @@ class BuyingController(SubcontractingController): else: item.valuation_rate = 0.0 + update_regional_item_valuation_rate(self) + def set_incoming_rate(self): if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"): return @@ -787,7 +790,7 @@ class BuyingController(SubcontractingController): "supplier": self.supplier, "purchase_date": self.posting_date, "calculate_depreciation": 0, - "purchase_receipt_amount": purchase_amount, + "purchase_amount": purchase_amount, "gross_purchase_amount": purchase_amount, "asset_quantity": asset_quantity, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, @@ -935,3 +938,8 @@ def validate_item_type(doc, fieldname, message): ).format(items, message) frappe.throw(error_message) + + +@erpnext.allow_regional +def update_regional_item_valuation_rate(doc): + pass diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 854682bec66..a07a00dd03e 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -352,6 +352,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): doctype = "Batch" meta = frappe.get_meta(doctype, cached=True) searchfields = meta.get_search_fields() + page_len = 30 batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len) batches.extend(get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len)) @@ -422,6 +423,7 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, p & (stock_ledger_entry.batch_no.isnotnull()) ) .groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse) + .having(Sum(stock_ledger_entry.actual_qty) != 0) .offset(start) .limit(page_len) ) @@ -472,6 +474,7 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0 & (stock_ledger_entry.serial_and_batch_bundle.isnotnull()) ) .groupby(bundle.batch_no, bundle.warehouse) + .having(Sum(bundle.qty) != 0) .offset(start) .limit(page_len) ) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 913665dce2b..b61d1773a67 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +from collections import defaultdict import frappe from frappe import _ from frappe.model.meta import get_field_precision -from frappe.utils import flt, format_datetime, get_datetime +from frappe.utils import cint, flt, format_datetime, get_datetime import erpnext from erpnext.stock.serial_batch_bundle import get_batches_from_bundle @@ -513,6 +514,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai target_doc.rejected_warehouse = "" target_doc.warehouse = source_doc.rejected_warehouse target_doc.received_qty = target_doc.qty + target_doc.return_qty_from_rejected_warehouse = 1 elif doctype == "Purchase Invoice": returned_qty_map = get_returned_qty_map_for_row( @@ -570,7 +572,14 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return - if source_doc.item_code: + if ( + (source_doc.serial_no or source_doc.batch_no) + and not source_doc.serial_and_batch_bundle + and not source_doc.use_serial_batch_fields + ): + target_doc.set("use_serial_batch_fields", 1) + + if source_doc.item_code and target_doc.get("use_serial_batch_fields"): item_details = frappe.get_cached_value( "Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1 ) @@ -578,14 +587,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai if not item_details.has_batch_no and not item_details.has_serial_no: return - if not target_doc.get("use_serial_batch_fields"): - for qty_field in ["stock_qty", "rejected_qty"]: - if not target_doc.get(qty_field): - continue - - update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field) - elif target_doc.get("use_serial_batch_fields"): - update_non_bundled_serial_nos(source_doc, target_doc, source_parent) + update_non_bundled_serial_nos(source_doc, target_doc, source_parent) def update_non_bundled_serial_nos(source_doc, target_doc, source_parent): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -839,3 +841,246 @@ def get_returned_batches(child_doc, parent_doc, batch_no_field=None, ignore_vouc batches.update(get_batches_from_bundle(ids)) return batches + + +def available_serial_batch_for_return(field, doctype, reference_ids, is_rejected=False): + available_dict = get_available_serial_batches(field, doctype, reference_ids, is_rejected=is_rejected) + if not available_dict: + frappe.throw(_("No Serial / Batches are available for return")) + + return available_dict + + +def get_available_serial_batches(field, doctype, reference_ids, is_rejected=False): + _bundle_ids = get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=is_rejected) + if not _bundle_ids: + return frappe._dict({}) + + return get_serial_batches_based_on_bundle(field, _bundle_ids) + + +def get_serial_batches_based_on_bundle(field, _bundle_ids): + available_dict = frappe._dict({}) + batch_serial_nos = frappe.get_all( + "Serial and Batch Bundle", + fields=[ + "`tabSerial and Batch Entry`.`serial_no`", + "`tabSerial and Batch Entry`.`batch_no`", + "`tabSerial and Batch Entry`.`qty`", + "`tabSerial and Batch Bundle`.`voucher_detail_no`", + "`tabSerial and Batch Bundle`.`voucher_type`", + "`tabSerial and Batch Bundle`.`voucher_no`", + ], + filters=[ + ["Serial and Batch Bundle", "name", "in", _bundle_ids], + ["Serial and Batch Entry", "docstatus", "=", 1], + ], + order_by="`tabSerial and Batch Bundle`.`creation`, `tabSerial and Batch Entry`.`idx`", + ) + + for row in batch_serial_nos: + key = row.voucher_detail_no + if frappe.get_cached_value(row.voucher_type, row.voucher_no, "is_return"): + key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field) + + if row.voucher_type in ["Sales Invoice", "Delivery Note"]: + row.qty = -1 * row.qty + + if key not in available_dict: + available_dict[key] = frappe._dict( + {"qty": 0.0, "serial_nos": defaultdict(float), "batches": defaultdict(float)} + ) + + available_dict[key]["qty"] += row.qty + + if row.serial_no: + available_dict[key]["serial_nos"][row.serial_no] += row.qty + elif row.batch_no: + available_dict[key]["batches"][row.batch_no] += row.qty + + return available_dict + + +def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False): + filters = {"docstatus": 1, "name": ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")} + + pluck_field = "serial_and_batch_bundle" + if is_rejected: + del filters["serial_and_batch_bundle"] + filters["rejected_serial_and_batch_bundle"] = ("is", "set") + pluck_field = "rejected_serial_and_batch_bundle" + + _bundle_ids = frappe.get_all( + doctype, + filters=filters, + pluck=pluck_field, + ) + + if not _bundle_ids: + return {} + + del filters["name"] + + filters[field] = ("in", reference_ids) + + if not is_rejected: + _bundle_ids.extend( + frappe.get_all( + doctype, + filters=filters, + pluck="serial_and_batch_bundle", + ) + ) + else: + fields = [ + "serial_and_batch_bundle", + ] + + if is_rejected: + fields.extend(["rejected_serial_and_batch_bundle", "return_qty_from_rejected_warehouse"]) + + del filters["rejected_serial_and_batch_bundle"] + data = frappe.get_all( + doctype, + fields=fields, + filters=filters, + ) + + for d in data: + if not d.get("serial_and_batch_bundle") and not d.get("rejected_serial_and_batch_bundle"): + continue + + if is_rejected: + if d.get("return_qty_from_rejected_warehouse"): + _bundle_ids.append(d.get("serial_and_batch_bundle")) + else: + _bundle_ids.append(d.get("rejected_serial_and_batch_bundle")) + else: + _bundle_ids.append(d.get("serial_and_batch_bundle")) + + return _bundle_ids + + +def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field=None): + if not qty_field: + qty_field = "qty" + + if not warehouse_field: + warehouse_field = "warehouse" + + warehouse = row.get(warehouse_field) + qty = abs(row.get(qty_field)) + + filterd_serial_batch = frappe._dict({"serial_nos": [], "batches": defaultdict(float)}) + + if data.serial_nos: + available_serial_nos = [] + for serial_no, sn_qty in data.serial_nos.items(): + if sn_qty != 0: + available_serial_nos.append(serial_no) + + if available_serial_nos: + if parent_doc.doctype in ["Purchase Invoice", "Purchase Reecipt"]: + available_serial_nos = get_available_serial_nos(available_serial_nos) + + if len(available_serial_nos) > qty: + filterd_serial_batch["serial_nos"] = sorted(available_serial_nos[0 : cint(qty)]) + else: + filterd_serial_batch["serial_nos"] = available_serial_nos + + elif data.batches: + for batch_no, batch_qty in data.batches.items(): + if parent_doc.get("is_internal_customer"): + batch_qty = batch_qty * -1 + + if batch_qty <= 0: + continue + + if parent_doc.doctype in ["Purchase Invoice", "Purchase Reecipt"]: + batch_qty = get_available_batch_qty( + parent_doc, + batch_no, + warehouse, + ) + + if batch_qty <= 0: + frappe.throw( + _("Batch {0} is not available in warehouse {1}").format(batch_no, warehouse), + title=_("Batch Not Available for Return"), + ) + + if qty <= 0: + break + + if batch_qty > qty: + filterd_serial_batch["batches"][batch_no] = qty + qty = 0 + else: + filterd_serial_batch["batches"][batch_no] += batch_qty + qty -= batch_qty + + return filterd_serial_batch + + +def get_available_batch_qty(parent_doc, batch_no, warehouse): + from erpnext.stock.doctype.batch.batch import get_batch_qty + + return get_batch_qty( + batch_no, + warehouse, + posting_date=parent_doc.posting_date, + posting_time=parent_doc.posting_time, + for_stock_levels=True, + ) + + +def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_field=None, qty_field=None): + from erpnext.stock.serial_batch_bundle import SerialBatchCreation + + type_of_transaction = "Outward" + if parent_doc.doctype in ["Sales Invoice", "Delivery Note", "POS Invoice"]: + type_of_transaction = "Inward" + + if not warehouse_field: + warehouse_field = "warehouse" + + if not qty_field: + qty_field = "qty" + + warehouse = child_doc.get(warehouse_field) + if parent_doc.get("is_internal_customer"): + warehouse = child_doc.get("target_warehouse") + type_of_transaction = "Outward" + + if not child_doc.get(qty_field): + frappe.throw( + _("For the {0}, the quantity is required to make the return entry").format( + frappe.bold(child_doc.item_code) + ) + ) + + cls_obj = SerialBatchCreation( + { + "type_of_transaction": type_of_transaction, + "item_code": child_doc.item_code, + "warehouse": warehouse, + "serial_nos": data.get("serial_nos"), + "batches": data.get("batches"), + "posting_date": parent_doc.posting_date, + "posting_time": parent_doc.posting_time, + "voucher_type": parent_doc.doctype, + "voucher_no": parent_doc.name, + "voucher_detail_no": child_doc.name, + "qty": child_doc.get(qty_field), + "company": parent_doc.company, + "do_not_submit": True, + } + ).make_serial_and_batch_bundle() + + return cls_obj.name + + +def get_available_serial_nos(serial_nos, warehouse): + return frappe.get_all( + "Serial No", filters={"warehouse": warehouse, "name": ("in", serial_nos)}, pluck="name" + ) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 49252bd5b54..212c9adf4af 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -16,6 +16,11 @@ from erpnext.accounts.general_ledger import ( ) from erpnext.accounts.utils import cancel_exchange_gain_loss_journal, get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController +from erpnext.controllers.sales_and_purchase_return import ( + available_serial_batch_for_return, + filter_serial_batches, + make_serial_batch_bundle_for_return, +) from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.inventory_dimension.inventory_dimension import ( get_evaluated_inventory_dimension, @@ -92,7 +97,7 @@ class StockController(AccountsController): ) ) - def make_gl_entries(self, gl_entries=None, from_repost=False): + def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False): if self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) @@ -113,7 +118,11 @@ class StockController(AccountsController): if self.docstatus == 1: if not gl_entries: - gl_entries = self.get_gl_entries(warehouse_account) + gl_entries = ( + self.get_gl_entries(warehouse_account, via_landed_cost_voucher) + if self.doctype == "Purchase Receipt" + else self.get_gl_entries(warehouse_account) + ) make_gl_entries(gl_entries, from_repost=from_repost) def validate_serialized_batch(self): @@ -205,6 +214,7 @@ class StockController(AccountsController): "company": self.company, "is_rejected": 1 if row.get("rejected_warehouse") else 0, "use_serial_batch_fields": row.use_serial_batch_fields, + "via_landed_cost_voucher": via_landed_cost_voucher, "do_not_submit": True if not via_landed_cost_voucher else False, } @@ -216,6 +226,132 @@ class StockController(AccountsController): self.update_bundle_details(bundle_details, table_name, row, is_rejected=True) self.create_serial_batch_bundle(bundle_details, row) + def make_bundle_for_sales_purchase_return(self, table_name=None): + if not self.get("is_return"): + return + + if not table_name: + table_name = "items" + + self.make_bundle_for_non_rejected_qty(table_name) + + if self.doctype in ["Purchase Invoice", "Purchase Receipt"]: + self.make_bundle_for_rejected_qty(table_name) + + def make_bundle_for_rejected_qty(self, table_name=None): + field, reference_ids = self.get_reference_ids( + table_name, "rejected_qty", "rejected_serial_and_batch_bundle" + ) + + if not reference_ids: + return + + child_doctype = self.doctype + " Item" + available_dict = available_serial_batch_for_return( + field, child_doctype, reference_ids, is_rejected=True + ) + + for row in self.get(table_name): + if data := available_dict.get(row.get(field)): + qty_field = "rejected_qty" + warehouse_field = "rejected_warehouse" + if row.get("return_qty_from_rejected_warehouse"): + qty_field = "qty" + warehouse_field = "warehouse" + + if not data.get("qty"): + frappe.throw( + _("For the {0}, no stock is available for the return in the warehouse {1}.").format( + frappe.bold(row.item_code), row.get(warehouse_field) + ) + ) + + data = filter_serial_batches( + self, data, row, warehouse_field=warehouse_field, qty_field=qty_field + ) + bundle = make_serial_batch_bundle_for_return(data, row, self, warehouse_field, qty_field) + if row.get("return_qty_from_rejected_warehouse"): + row.db_set( + { + "serial_and_batch_bundle": bundle, + "batch_no": "", + "serial_no": "", + } + ) + else: + row.db_set( + { + "rejected_serial_and_batch_bundle": bundle, + "batch_no": "", + "rejected_serial_no": "", + } + ) + + def make_bundle_for_non_rejected_qty(self, table_name): + field, reference_ids = self.get_reference_ids(table_name) + if not reference_ids: + return + + child_doctype = self.doctype + " Item" + available_dict = available_serial_batch_for_return(field, child_doctype, reference_ids) + + for row in self.get(table_name): + if data := available_dict.get(row.get(field)): + data = filter_serial_batches(self, data, row) + bundle = make_serial_batch_bundle_for_return(data, row, self) + row.db_set( + { + "serial_and_batch_bundle": bundle, + "batch_no": "", + "serial_no": "", + } + ) + + def get_reference_ids(self, table_name, qty_field=None, bundle_field=None) -> tuple[str, list[str]]: + field = { + "Sales Invoice": "sales_invoice_item", + "Delivery Note": "dn_detail", + "Purchase Receipt": "purchase_receipt_item", + "Purchase Invoice": "purchase_invoice_item", + "POS Invoice": "pos_invoice_item", + }.get(self.doctype) + + if not bundle_field: + bundle_field = "serial_and_batch_bundle" + + if not qty_field: + qty_field = "qty" + + reference_ids = [] + + for row in self.get(table_name): + if not self.is_serial_batch_item(row.item_code): + continue + + if ( + row.get(field) + and ( + qty_field == "qty" + and not row.get("return_qty_from_rejected_warehouse") + or qty_field == "rejected_qty" + and (row.get("return_qty_from_rejected_warehouse") or row.get("rejected_warehouse")) + ) + and not row.get("use_serial_batch_fields") + and not row.get(bundle_field) + ): + reference_ids.append(row.get(field)) + + return field, reference_ids + + @frappe.request_cache + def is_serial_batch_item(self, item_code) -> bool: + item_details = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) + + if item_details.has_serial_no or item_details.has_batch_no: + return True + + return False + def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -590,6 +726,9 @@ class StockController(AccountsController): row.db_set("rejected_serial_and_batch_bundle", None) + if row.get("current_serial_and_batch_bundle"): + row.db_set("current_serial_and_batch_bundle", None) + def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False): if not table_name: table_name = "items" @@ -610,35 +749,16 @@ class StockController(AccountsController): def make_package_for_transfer( self, serial_and_batch_bundle, warehouse, type_of_transaction=None, do_not_submit=None ): - bundle_doc = frappe.get_doc("Serial and Batch Bundle", serial_and_batch_bundle) - - if not type_of_transaction: - type_of_transaction = "Inward" - - bundle_doc = frappe.copy_doc(bundle_doc) - bundle_doc.warehouse = warehouse - bundle_doc.type_of_transaction = type_of_transaction - bundle_doc.voucher_type = self.doctype - bundle_doc.voucher_no = "" if self.is_new() or self.docstatus == 2 else self.name - bundle_doc.is_cancelled = 0 - - for row in bundle_doc.entries: - row.is_outward = 0 - row.qty = abs(row.qty) - row.stock_value_difference = abs(row.stock_value_difference) - if type_of_transaction == "Outward": - row.qty *= -1 - row.stock_value_difference *= row.stock_value_difference - row.is_outward = 1 - - row.warehouse = warehouse - - bundle_doc.calculate_qty_and_amount() - bundle_doc.flags.ignore_permissions = True - bundle_doc.flags.ignore_validate = True - bundle_doc.save(ignore_permissions=True) - - return bundle_doc.name + return make_bundle_for_material_transfer( + is_new=self.is_new(), + docstatus=self.docstatus, + voucher_type=self.doctype, + voucher_no=self.name, + serial_and_batch_bundle=serial_and_batch_bundle, + warehouse=warehouse, + type_of_transaction=type_of_transaction, + do_not_submit=do_not_submit, + ) def get_sl_entries(self, d, args): sl_dict = frappe._dict( @@ -1556,3 +1676,38 @@ def create_item_wise_repost_entries( repost_entries.append(repost_entry) return repost_entries + + +def make_bundle_for_material_transfer(**kwargs): + if isinstance(kwargs, dict): + kwargs = frappe._dict(kwargs) + + bundle_doc = frappe.get_doc("Serial and Batch Bundle", kwargs.serial_and_batch_bundle) + + if not kwargs.type_of_transaction: + kwargs.type_of_transaction = "Inward" + + bundle_doc = frappe.copy_doc(bundle_doc) + bundle_doc.warehouse = kwargs.warehouse + bundle_doc.type_of_transaction = kwargs.type_of_transaction + bundle_doc.voucher_type = kwargs.voucher_type + bundle_doc.voucher_no = "" if kwargs.is_new or kwargs.docstatus == 2 else kwargs.voucher_no + bundle_doc.is_cancelled = 0 + + for row in bundle_doc.entries: + row.is_outward = 0 + row.qty = abs(row.qty) + row.stock_value_difference = abs(row.stock_value_difference) + if kwargs.type_of_transaction == "Outward": + row.qty *= -1 + row.stock_value_difference *= row.stock_value_difference + row.is_outward = 1 + + row.warehouse = kwargs.warehouse + + bundle_doc.calculate_qty_and_amount() + bundle_doc.flags.ignore_permissions = True + bundle_doc.flags.ignore_validate = True + bundle_doc.save(ignore_permissions=True) + + return bundle_doc.name diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index fc66345aeee..e0f1ed77140 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -327,13 +327,13 @@ class SubcontractingController(StockController): consumed_bundles.batch_nos[batch_no] += abs(qty) # Will be deprecated in v16 - if row.serial_no: + if row.serial_no and not consumed_bundles.serial_nos: self.available_materials[key]["serial_no"] = list( set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no)) ) # Will be deprecated in v16 - if row.batch_no: + if row.batch_no and not consumed_bundles.batch_nos: self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty def get_available_materials(self): diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index aeaebafabe9..db9d31b53bb 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -118,6 +118,8 @@ class Opportunity(TransactionBase, CRMNote): self.title = self.customer_name self.calculate_totals() + + def on_update(self): self.update_prospect() def map_fields(self): diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 4ce8342af6e..58ce7c50583 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -591,13 +591,13 @@ "default": "0", "fieldname": "fg_based_operating_cost", "fieldtype": "Check", - "label": "FG based Operating Cost" + "label": "Finished Goods based Operating Cost" }, { "depends_on": "fg_based_operating_cost", "fieldname": "fg_based_section_section", "fieldtype": "Section Break", - "label": "FG Based Operating Cost Section" + "label": "Finished Goods Based Operating Cost" }, { "depends_on": "fg_based_operating_cost", @@ -637,7 +637,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2024-04-02 16:22:47.518411", + "modified": "2024-06-03 16:24:47.518411", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index 0158f7c5b97..e236e7a6345 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -80,6 +80,18 @@ class BOMCreator(Document): if row.is_expandable and row.item_code == self.item_code: frappe.throw(_("Item {0} cannot be added as a sub-assembly of itself").format(row.item_code)) + if not row.parent_row_no and row.fg_item and row.fg_item != self.item_code: + frappe.throw( + _("At row {0}: set Parent Row No for item {1}").format(row.idx, row.item_code), + title=_("Set Parent Row No in Items Table"), + ) + + elif row.parent_row_no and row.fg_item == self.item_code: + frappe.throw( + _("At row {0}: Parent Row No cannot be set for item {1}").format(row.idx, row.item_code), + title=_("Remove Parent Row No in Items Table"), + ) + def set_status(self, save=False): self.status = { 0: "Draft", @@ -410,6 +422,10 @@ def add_sub_assembly(**kwargs): parent_row_no = item_row.idx name = "" + else: + parent_row_no = [row.idx for row in doc.items if row.name == kwargs.fg_reference_id] + if parent_row_no: + parent_row_no = parent_row_no[0] for row in bom_item.get("items"): row = frappe._dict(row) diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json index 56acd8a1a67..1726f898751 100644 --- a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json +++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json @@ -70,7 +70,7 @@ "fieldname": "fg_item", "fieldtype": "Link", "in_list_view": 1, - "label": "FG Item", + "label": "Finished Goods Item", "options": "Item", "reqd": 1 }, @@ -203,7 +203,7 @@ { "fieldname": "fg_reference_id", "fieldtype": "Data", - "label": "FG Reference", + "label": "Finished Goods Reference", "no_copy": 1, "read_only": 1 }, @@ -230,7 +230,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-16 13:34:06.321061", + "modified": "2024-06-03 18:45:24.339532", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator Item", diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 5a734d8684f..037a3fe0e81 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -77,7 +77,7 @@ "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, - "label": "Operation Time ", + "label": "Operation Time", "oldfieldname": "time_in_mins", "oldfieldtype": "Currency", "reqd": 1 @@ -203,4 +203,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 4fd628bbecb..abea4c86279 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -214,7 +214,11 @@ class JobCard(Document): if d.to_time and get_datetime(d.from_time) > get_datetime(d.to_time): frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx)) - data = self.get_overlap_for(d) + open_job_cards = [] + if d.get("employee"): + open_job_cards = self.get_open_job_cards(d.get("employee")) + + data = self.get_overlap_for(d, open_job_cards=open_job_cards) if data: frappe.throw( _("Row {0}: From Time and To Time of {1} is overlapping with {2}").format( @@ -235,12 +239,12 @@ class JobCard(Document): for row in self.sub_operations: self.total_completed_qty += row.completed_qty - def get_overlap_for(self, args): + def get_overlap_for(self, args, open_job_cards=None): time_logs = [] time_logs.extend(self.get_time_logs(args, "Job Card Time Log")) - time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time")) + time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time", open_job_cards=open_job_cards)) if not time_logs: return {} @@ -304,7 +308,7 @@ class JobCard(Document): return True return overlap - def get_time_logs(self, args, doctype): + def get_time_logs(self, args, doctype, open_job_cards=None): jc = frappe.qb.DocType("Job Card") jctl = frappe.qb.DocType(doctype) @@ -341,8 +345,14 @@ class JobCard(Document): if self.workstation: query = query.where(jc.workstation == self.workstation) - if args.get("employee") and doctype == "Job Card Time Log": - query = query.where(jctl.employee == args.get("employee")) + if args.get("employee"): + if not open_job_cards and doctype == "Job Card Scheduled Time": + return [] + + if doctype == "Job Card Time Log": + query = query.where(jctl.employee == args.get("employee")) + else: + query = query.where(jc.name.isin(open_job_cards)) if doctype != "Job Card Time Log": query = query.where(jc.total_time_in_mins == 0) @@ -351,6 +361,27 @@ class JobCard(Document): return time_logs + def get_open_job_cards(self, employee): + jc = frappe.qb.DocType("Job Card") + jctl = frappe.qb.DocType("Job Card Time Log") + + query = ( + frappe.qb.from_(jc) + .left_join(jctl) + .on(jc.name == jctl.parent) + .select(jc.name) + .where( + (jctl.parent == jc.name) + & (jc.workstation == self.workstation) + & (jctl.employee == employee) + & (jc.docstatus < 1) + & (jc.name != self.name) + ) + ) + + jobs = query.run(as_dict=True) + return [job.get("name") for job in jobs] if jobs else [] + def get_workstation_based_on_available_slot(self, existing_time_logs) -> dict: workstations = get_workstations(self.workstation_type) if workstations: 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 a7102d7d237..6a86214986a 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 @@ -42,8 +42,7 @@ "fieldname": "completed_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Completed Qty", - "reqd": 1 + "label": "Completed Qty" }, { "fieldname": "employee", @@ -64,7 +63,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-23 14:30:00.970916", + "modified": "2024-05-21 12:40:55.765860", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Time Log", @@ -74,4 +73,4 @@ "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 6db901c71a4..5b4ef233926 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -400,13 +400,20 @@ frappe.ui.form.on("Production Plan", { }, download_materials_required(frm) { + const warehouses_data = []; + + if (frm.doc.for_warehouse) { + warehouses_data.push({ warehouse: frm.doc.for_warehouse }); + } + const fields = [ { fieldname: "warehouses", fieldtype: "Table MultiSelect", label: __("Warehouses"), - default: frm.doc.from_warehouse, + default: warehouses_data, options: "Production Plan Material Request Warehouse", + reqd: 1, get_query: function () { return { filters: { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a378d8ae606..7d3aa000c87 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -452,6 +452,10 @@ class ProductionPlan(Document): {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty} ) + bom_no = data.bom_no or item_details and item_details.bom_no or "" + if not bom_no: + continue + pi = self.append( "po_items", { @@ -459,7 +463,7 @@ class ProductionPlan(Document): "item_code": data.item_code, "description": data.description or item_details.description, "stock_uom": item_details and item_details.stock_uom or "", - "bom_no": data.bom_no or item_details and item_details.bom_no or "", + "bom_no": bom_no, "planned_qty": data.pending_qty, "pending_qty": data.pending_qty, "planned_start_date": now_datetime(), diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 8957c11d3f0..71d9b59c677 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -328,6 +328,28 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(pln2.po_items[0].bom_no, bom2.name) + def test_production_plan_with_non_active_bom_item(self): + item = make_item("Test Production Item 1 for Non Active BOM", {"is_stock_item": 1}).name + + so1 = make_sales_order(item_code=item, qty=1) + + pln = frappe.new_doc("Production Plan") + pln.company = so1.company + pln.get_items_from = "Sales Order" + pln.append( + "sales_orders", + { + "sales_order": so1.name, + "sales_order_date": so1.transaction_date, + "customer": so1.customer, + "grand_total": so1.grand_total, + }, + ) + + pln.get_items() + + self.assertFalse(pln.po_items) + def test_production_plan_combine_items(self): "Test combining FG items in Production Plan." item = "Test Production Item 1" diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 78a389760a7..089d7396b60 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -85,7 +85,7 @@ "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, - "label": "FG Warehouse", + "label": "Finished Goods Warehouse", "options": "Warehouse" }, { @@ -220,7 +220,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-27 13:24:43.571844", + "modified": "2024-06-03 13:10:20.252166", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index 2aa31be0f0e..97c85502c98 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -21,7 +21,8 @@ def get_exploded_items(bom, data, indent=0, qty=1): exploded_items = frappe.get_all( "BOM Item", filters={"parent": bom}, - fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"], + fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom", "idx"], + order_by="idx ASC", ) for item in exploded_items: diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js index 23fa9ab41b0..4a34d126f88 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js @@ -93,4 +93,11 @@ frappe.query_reports["Exponential Smoothing Forecasting"] = { }, }, ], + formatter: function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (column.fieldname === "item_code" && value.includes("Total Quantity")) { + value = "" + value + ""; + } + return value; + }, }; diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 85648d6b326..0f5fa959dc5 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -144,7 +144,7 @@ class ForecastingReport(ExponentialSmoothingForecast): if not self.data: return - total_row = {"item_code": _(frappe.bold("Total Quantity"))} + total_row = {"item_code": _("Total Quantity")} for value in self.data: for period in self.period_list: diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py index 51efc6e655f..73560dd939b 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -102,7 +102,12 @@ def get_columns() -> Columns: "fieldtype": "Float", "width": "150", }, - {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"}, + { + "label": _("Finished Goods Value"), + "fieldname": "total_fg_value", + "fieldtype": "Float", + "width": "150", + }, { "label": _("Raw Material Value"), "fieldname": "total_rm_value", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0465f2ca53e..2522077e9c3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -363,3 +363,5 @@ erpnext.patches.v14_0.set_maintain_stock_for_bom_item erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset +erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount +erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py b/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py index 61a5c86386c..a1719fb41bb 100644 --- a/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py +++ b/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py @@ -13,8 +13,9 @@ def execute(): for d in accounting_dimensions: doctype = "Asset Repair" field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname}) + docfield = frappe.db.get_value("DocField", {"parent": doctype, "fieldname": d.fieldname}) - if field: + if field or docfield: continue df = { diff --git a/erpnext/patches/v14_0/enable_set_priority_for_pricing_rules.py b/erpnext/patches/v14_0/enable_set_priority_for_pricing_rules.py new file mode 100644 index 00000000000..af87eeb2727 --- /dev/null +++ b/erpnext/patches/v14_0/enable_set_priority_for_pricing_rules.py @@ -0,0 +1,10 @@ +import frappe + + +def execute(): + pr_table = frappe.qb.DocType("Pricing Rule") + ( + frappe.qb.update(pr_table) + .set(pr_table.has_priority, 1) + .where((pr_table.priority.isnotnull()) & (pr_table.priority != "")) + ).run() diff --git a/erpnext/patches/v15_0/rename_purchase_receipt_amount_to_purchase_amount.py b/erpnext/patches/v15_0/rename_purchase_receipt_amount_to_purchase_amount.py new file mode 100644 index 00000000000..8af3ed26323 --- /dev/null +++ b/erpnext/patches/v15_0/rename_purchase_receipt_amount_to_purchase_amount.py @@ -0,0 +1,8 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + frappe.reload_doc("assets", "doctype", "asset") + if frappe.db.has_column("Asset", "purchase_receipt_amount"): + rename_field("Asset", "purchase_receipt_amount", "purchase_amount") diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 49e8d8486a5..d03ab786cc1 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -55,6 +55,14 @@ frappe.ui.form.on("Project", { filters: filters, }; }); + + frm.set_query("cost_center", () => { + return { + filters: { + company: frm.doc.company, + }, + }; + }); }, refresh: function (frm) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a0685d80985..524d3033579 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1246,8 +1246,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } qty(doc, cdt, cdn) { - if (!this.frm.doc.__onload?.load_after_mapping) { - let item = frappe.get_doc(cdt, cdn); + let item = frappe.get_doc(cdt, cdn); + if (!this.is_a_mapped_document(item)) { // item.pricing_rules = '' frappe.run_serially([ () => this.remove_pricing_rule_for_item(item), @@ -2295,6 +2295,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (doc.is_return) { filters["is_return"] = 1; + if (["Sales Invoice", "Delivery Note"].includes(doc.doctype)) { + filters["is_inward"] = 1; + } } if (item.warehouse) filters["warehouse"] = item.warehouse; diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 527d452a450..6e1097072fa 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -34,5 +34,7 @@ import "./utils/sales_common.js"; import "./controllers/buying.js"; import "./utils/demo.js"; import "./financial_statements.js"; +import "./sales_trends_filters.js"; +import "./purchase_trends_filters.js"; // import { sum } from 'frappe/public/utils/util.js' diff --git a/erpnext/public/js/purchase_trends_filters.js b/erpnext/public/js/purchase_trends_filters.js index 14ffaf82162..75428d3be25 100644 --- a/erpnext/public/js/purchase_trends_filters.js +++ b/erpnext/public/js/purchase_trends_filters.js @@ -1,8 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -erpnext.get_purchase_trends_filters = function () { - return [ +erpnext.purchase_trends_filters = { + filters: [ { fieldname: "company", label: __("Company"), @@ -63,5 +63,5 @@ erpnext.get_purchase_trends_filters = function () { options: ["", { value: "Item", label: __("Item") }, { value: "Supplier", label: __("Supplier") }], default: "", }, - ]; + ], }; diff --git a/erpnext/public/js/sales_trends_filters.js b/erpnext/public/js/sales_trends_filters.js index 85daa01ff67..2f8e6f93c61 100644 --- a/erpnext/public/js/sales_trends_filters.js +++ b/erpnext/public/js/sales_trends_filters.js @@ -1,8 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -erpnext.get_sales_trends_filters = function () { - return [ +erpnext.sales_trends_filters = { + filters: [ { fieldname: "period", label: __("Period"), @@ -53,5 +53,5 @@ erpnext.get_sales_trends_filters = function () { options: "Company", default: frappe.defaults.get_user_default("Company"), }, - ]; + ], }; diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 288b2f6932d..a7d88edcafa 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -933,7 +933,13 @@ erpnext.utils.map_current_doc = function (opts) { frappe.msgprint(__("Please select {0}", [opts.source_doctype])); return; } - opts.source_name = values; + + if (values.constructor === Array) { + opts.source_name = [...new Set(values)]; + } else { + opts.source_name = values; + } + if ( opts.allow_child_item_selection || ["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype) @@ -1183,4 +1189,39 @@ $.extend(erpnext.stock.utils, { const barcode_scanner = new erpnext.utils.BarcodeScanner({ frm: frm }); barcode_scanner.scan_api_call(child_row.barcode, callback); }, + + get_serial_range(range_string, separator) { + /* Return an array of serial numbers generated from a range string. + + Examples (using separator "::"): + - "1::5" => ["1", "2", "3", "4", "5"] + - "SN0009::12" => ["SN0009", "SN0010", "SN0011", "SN0012"] + - "ABC//05::8" => ["ABC//05", "ABC//06", "ABC//07", "ABC//08"] + */ + if (!range_string) { + return; + } + + const [start_str, end_str] = range_string.trim().split(separator); + + if (!start_str || !end_str) { + return; + } + + const end_int = parseInt(end_str); + const length_difference = start_str.length - end_str.length; + const start_int = parseInt(start_str.substring(length_difference)); + + if (isNaN(start_int) || isNaN(end_int)) { + return; + } + + const serial_numbers = Array(end_int - start_int + 1) + .fill(1) + .map((x, y) => x + y) + .map((x) => x + start_int - 1); + return serial_numbers.map((val) => { + return start_str.substring(0, length_difference) + val.toString().padStart(end_str.length, "0"); + }); + }, }); diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 1edeca95018..4928f2dc1a5 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -206,6 +206,16 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { label: __("{0} {1} Manually", [primary_label, label]), depends_on: "eval:doc.import_using_csv_file === 0", }, + { + fieldtype: "Data", + label: __("Enter Serial No Range"), + fieldname: "serial_no_range", + depends_on: "eval:doc.import_using_csv_file === 0", + description: __('Enter "ABC-001::100" for serial nos "ABC-001" to "ABC-100".'), + onchange: () => { + this.set_serial_nos_from_range(); + }, + }, { fieldtype: "Small Text", label: __("Enter Serial Nos"), @@ -255,6 +265,20 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { return fields; } + set_serial_nos_from_range() { + const serial_no_range = this.dialog.get_value("serial_no_range"); + + if (!serial_no_range) { + return; + } + + const serial_nos = erpnext.stock.utils.get_serial_range(serial_no_range, "::"); + + if (serial_nos) { + this.dialog.set_value("upload_serial_nos", serial_nos.join("\n")); + } + } + create_serial_nos() { let { upload_serial_nos } = this.dialog.get_values(); @@ -589,7 +613,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } render_data() { - if (this.bundle) { + if (this.bundle || this.frm.doc.is_return) { frappe .call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers", @@ -597,6 +621,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { item_code: this.item.item_code, name: this.bundle, voucher_no: !this.frm.is_new() ? this.item.parent : "", + child_row: this.frm.doc.is_return ? this.item : "", }, }) .then((r) => { diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 63a44cc19fb..90051673e70 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -2,6 +2,8 @@ # License: GNU General Public License v3. See license.txt +import json + import frappe from frappe.test_runner import make_test_records from frappe.tests.utils import FrappeTestCase @@ -321,7 +323,7 @@ class TestCustomer(FrappeTestCase): frappe.ValidationError, update_child_qty_rate, so.doctype, - frappe.json.dumps([modified_item]), + json.dumps([modified_item]), so.name, ) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.js b/erpnext/selling/doctype/product_bundle/product_bundle.js index 3096b692a7e..67b9ae5ba31 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.js +++ b/erpnext/selling/doctype/product_bundle/product_bundle.js @@ -9,5 +9,13 @@ frappe.ui.form.on("Product Bundle", { query: "erpnext.selling.doctype.product_bundle.product_bundle.get_new_item_code", }; }); + + frm.set_query("item_code", "items", () => { + return { + filters: { + has_variants: 0, + }, + }; + }); }, }); diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 8c37e2f088e..d7aff8632b9 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -903,6 +903,9 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex fields: fields, primary_action: function () { var data = { items: d.fields_dict.items.grid.get_selected_children() }; + if (!data) { + frappe.throw(__("Please select items")); + } me.frm.call({ method: "make_work_orders", args: { diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 3a045d5ff2d..0695c3fd9c4 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1139,7 +1139,8 @@ "hide_seconds": 1, "label": "Inter Company Order Reference", "options": "Purchase Order", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "project", @@ -1645,7 +1646,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2024-03-29 16:27:41.539613", + "modified": "2024-05-23 16:35:54.905804", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index bc8991486db..e5e67ed38b7 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -794,6 +794,11 @@ def get_requested_item_qty(sales_order): def make_material_request(source_name, target_doc=None): requested_item_qty = get_requested_item_qty(source_name) + def postprocess(source, target): + if source.tc_name and frappe.db.get_value("Terms and Conditions", source.tc_name, "buying") != 1: + target.tc_name = None + target.terms = None + def get_remaining_qty(so_item): return flt( flt(so_item.qty) @@ -849,6 +854,7 @@ def make_material_request(source_name, target_doc=None): }, }, target_doc, + postprocess, ) return doc @@ -1224,11 +1230,19 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t target.discount_amount = 0.0 target.inter_company_order_reference = "" target.shipping_rule = "" + target.tc_name = "" + target.terms = "" + target.payment_terms_template = "" + target.payment_schedule = [] default_price_list = frappe.get_value("Supplier", supplier, "default_price_list") if default_price_list: target.buying_price_list = default_price_list + default_payment_terms = frappe.get_value("Supplier", supplier, "payment_terms") + if default_payment_terms: + target.payment_terms_template = default_payment_terms + if any(item.delivered_by_supplier == 1 for item in source.items): if source.shipping_address_name: target.shipping_address = source.shipping_address_name @@ -1280,7 +1294,6 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t "contact_person", "taxes_and_charges", "shipping_address", - "terms", ], "validation": {"docstatus": ["=", 1]}, }, @@ -1348,6 +1361,10 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): target.discount_amount = 0.0 target.inter_company_order_reference = "" target.shipping_rule = "" + target.tc_name = "" + target.terms = "" + target.payment_terms_template = "" + target.payment_schedule = [] if is_drop_ship_order(target): target.customer = source.customer @@ -1383,7 +1400,6 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): "contact_person", "taxes_and_charges", "shipping_address", - "terms", ], "validation": {"docstatus": ["=", 1]}, }, diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 452019ebf4b..864ceffa8b1 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -684,7 +684,7 @@ erpnext.PointOfSale.Controller = class { const is_stock_item = resp[1]; frappe.dom.unfreeze(); - const bold_uom = item_row.stock_uom.bold(); + const bold_uom = item_row.uom.bold(); const bold_item_code = item_row.item_code.bold(); const bold_warehouse = warehouse.bold(); const bold_available_qty = available_qty.toString().bold(); diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index fbee9c16267..694f70d4db5 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -857,7 +857,7 @@ erpnext.PointOfSale.ItemCart = class { }); this.$customer_section.find(".customer-details").html( `
-
Contact Details
+
${__("Contact Details")}
@@ -877,7 +877,7 @@ erpnext.PointOfSale.ItemCart = class {
-
Recent Transactions
` +
${__("Recent Transactions")}
` ); // transactions need to be in diff div from sticky elem for scrolling this.$customer_section.append(`
`); diff --git a/erpnext/selling/report/quotation_trends/quotation_trends.js b/erpnext/selling/report/quotation_trends/quotation_trends.js index 8ffeda47b64..ff0b30847de 100644 --- a/erpnext/selling/report/quotation_trends/quotation_trends.js +++ b/erpnext/selling/report/quotation_trends/quotation_trends.js @@ -1,8 +1,4 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/sales_trends_filters.js", function () { - frappe.query_reports["Quotation Trends"] = { - filters: erpnext.get_sales_trends_filters(), - }; -}); +frappe.query_reports["Quotation Trends"] = $.extend({}, erpnext.sales_trends_filters); diff --git a/erpnext/selling/report/sales_order_trends/sales_order_trends.js b/erpnext/selling/report/sales_order_trends/sales_order_trends.js index fe38804ed45..28bd5504930 100644 --- a/erpnext/selling/report/sales_order_trends/sales_order_trends.js +++ b/erpnext/selling/report/sales_order_trends/sales_order_trends.js @@ -1,8 +1,4 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/sales_trends_filters.js", function () { - frappe.query_reports["Sales Order Trends"] = { - filters: erpnext.get_sales_trends_filters(), - }; -}); +frappe.query_reports["Sales Order Trends"] = $.extend({}, erpnext.sales_trends_filters); diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index 5046ec52c95..b837d67e1c0 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -164,9 +164,10 @@ def prepare_data( rows = {} target_qty_amt_field = "target_qty" if filters.get("target_on") == "Quantity" else "target_amount" - qty_or_amount_field = "stock_qty" if filters.get("target_on") == "Quantity" else "base_net_amount" + item_group_parent_child_map = get_item_group_parent_child_map() + for d in sales_users_data: key = (d.parent, d.item_group) dist_data = get_periodwise_distribution_data(d.distribution_id, period_list, filters.get("period")) @@ -191,7 +192,11 @@ def prepare_data( r.get(sales_field) == d.parent and period.from_date <= r.get(date_field) and r.get(date_field) <= period.to_date - and (not sales_user_wise_item_groups.get(d.parent) or r.item_group == d.item_group) + and ( + not sales_user_wise_item_groups.get(d.parent) + or r.item_group == d.item_group + or r.item_group in item_group_parent_child_map.get(d.item_group, []) + ) ): details[p_key] += r.get(qty_or_amount_field, 0) details[variance_key] = details.get(p_key) - details.get(target_key) @@ -204,6 +209,25 @@ def prepare_data( return rows +def get_item_group_parent_child_map(): + """ + Returns a dict of all item group parents and leaf children associated with them. + """ + + item_groups = frappe.get_all( + "Item Group", fields=["name", "parent_item_group"], order_by="lft desc, rgt desc" + ) + item_group_parent_child_map = {} + + for item_group in item_groups: + children = item_group_parent_child_map.get(item_group.name, []) + if not children: + children = [item_group.name] + item_group_parent_child_map.setdefault(item_group.parent_item_group, []).extend(children) + + return item_group_parent_child_map + + def get_actual_data(filters, sales_users_or_territory_data, date_field, sales_field): fiscal_year = get_fiscal_year(fiscal_year=filters.get("fiscal_year"), as_dict=1) diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index f0253529c78..68d9fdfec5c 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -205,8 +205,11 @@ def clear_demo_record(document): if key not in valid_columns: filters.pop(key, None) - doc = frappe.get_doc(document_type, filters) - doc.delete(ignore_permissions=True) + try: + doc = frappe.get_doc(document_type, filters) + doc.delete(ignore_permissions=True) + except frappe.exceptions.DoesNotExistError: + pass def delete_company(company): diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 670d898b8df..4712a10cc0a 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -12,10 +12,11 @@ frappe.ui.form.on("Company", { } }); } - - frm.call("check_if_transactions_exist").then((r) => { - frm.toggle_enable("default_currency", !r.message); - }); + if (!frm.doc.__islocal) { + frm.call("check_if_transactions_exist").then((r) => { + frm.toggle_enable("default_currency", !r.message); + }); + } }, setup: function (frm) { frm.__rename_queue = "long"; @@ -251,7 +252,10 @@ erpnext.company.setup_queries = function (frm) { ["discount_allowed_account", { root_type: "Expense" }], ["discount_received_account", { root_type: "Income" }], ["exchange_gain_loss_account", { root_type: ["in", ["Expense", "Income"]] }], - ["unrealized_exchange_gain_loss_account", { root_type: ["in", ["Expense", "Income"]] }], + [ + "unrealized_exchange_gain_loss_account", + { root_type: ["in", ["Expense", "Income", "Equity", "Liability"]] }, + ], [ "accumulated_depreciation_account", { root_type: "Asset", account_type: "Accumulated Depreciation" }, diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index c222d6b96a7..b2d6af03a10 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -67,6 +67,7 @@ "default_finance_book", "advance_payments_section", "book_advance_payments_in_separate_party_account", + "reconcile_on_advance_payment_date", "column_break_fwcf", "default_advance_received_account", "default_advance_paid_account", @@ -779,6 +780,14 @@ "fieldtype": "Tab Break", "label": "Dashboard", "show_dashboard": 1 + }, + { + "default": "0", + "depends_on": "eval: doc.book_advance_payments_in_separate_party_account", + "description": "If Enabled - Reconciliation happens on the Advance Payment posting date
\nIf Disabled - Reconciliation happens on oldest of 2 Dates: Invoice Date or the Advance Payment posting date
\n", + "fieldname": "reconcile_on_advance_payment_date", + "fieldtype": "Check", + "label": "Reconcile on Advance Payment Date" } ], "icon": "fa fa-building", @@ -786,7 +795,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2024-04-23 12:38:33.173938", + "modified": "2024-05-27 17:32:49.057386", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -842,6 +851,10 @@ "role": "Accounts Manager", "share": 1, "write": 1 + }, + { + "role": "Auditor", + "select": 1 } ], "show_name_in_global_search": 1, diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index e330fe95cde..2a0b32ed568 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -85,6 +85,7 @@ class Company(NestedSet): parent_company: DF.Link | None payment_terms: DF.Link | None phone_no: DF.Data | None + reconcile_on_advance_payment_date: DF.Check registration_details: DF.Code | None rgt: DF.Int round_off_account: DF.Link | None diff --git a/erpnext/setup/doctype/department/department.json b/erpnext/setup/doctype/department/department.json index 99deca5c19d..fa6b9ad4a55 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": "2024-06-12 16:10:31.451257", "modified_by": "Administrator", "module": "Setup", "name": "Department", @@ -132,6 +132,10 @@ "role": "HR Manager", "share": 1, "write": 1 + }, + { + "role": "Employee", + "select": 1 } ], "show_name_in_global_search": 1, diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py index 9d854c7c412..6932f7be825 100644 --- a/erpnext/setup/doctype/holiday_list/holiday_list.py +++ b/erpnext/setup/doctype/holiday_list/holiday_list.py @@ -42,7 +42,7 @@ class HolidayList(Document): def validate(self): self.validate_days() self.total_holidays = len(self.holidays) - self.validate_dupliacte_date() + self.validate_duplicate_date() @frappe.whitelist() def get_weekly_off_dates(self): @@ -148,7 +148,7 @@ class HolidayList(Document): def clear_table(self): self.set("holidays", []) - def validate_dupliacte_date(self): + def validate_duplicate_date(self): unique_dates = [] for row in self.holidays: if row.holiday_date in unique_dates: diff --git a/erpnext/setup/doctype/vehicle/test_vehicle.py b/erpnext/setup/doctype/vehicle/test_vehicle.py index 97fe651122c..3cd52386284 100644 --- a/erpnext/setup/doctype/vehicle/test_vehicle.py +++ b/erpnext/setup/doctype/vehicle/test_vehicle.py @@ -26,3 +26,29 @@ class TestVehicle(unittest.TestCase): } ) vehicle.insert() + + def test_renaming_vehicle(self): + license_plate = random_string(10).upper() + + vehicle = frappe.get_doc( + { + "doctype": "Vehicle", + "license_plate": license_plate, + "make": "Skoda", + "model": "Slavia", + "last_odometer": 5000, + "acquisition_date": frappe.utils.nowdate(), + "location": "Mumbai", + "chassis_no": "1234EFGH", + "uom": "Litre", + "vehicle_value": frappe.utils.flt(500000), + } + ) + vehicle.insert() + + new_license_plate = random_string(10).upper() + frappe.rename_doc("Vehicle", license_plate, new_license_plate) + + self.assertEqual( + new_license_plate, frappe.db.get_value("Vehicle", new_license_plate, "license_plate") + ) diff --git a/erpnext/setup/doctype/vehicle/vehicle.json b/erpnext/setup/doctype/vehicle/vehicle.json index b19d45924fb..c76719e4b92 100644 --- a/erpnext/setup/doctype/vehicle/vehicle.json +++ b/erpnext/setup/doctype/vehicle/vehicle.json @@ -2,7 +2,8 @@ "allow_copy": 0, "allow_guest_to_view": 0, "allow_import": 0, - "allow_rename": 0, + "actions": [], + "allow_rename": 1, "autoname": "field:license_plate", "beta": 0, "creation": "2016-09-03 03:33:27.680331", @@ -834,7 +835,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2022-06-27 14:48:30.813359", + "links": [], + "modified": "2024-05-31 06:38:15.399283", "modified_by": "Administrator", "module": "Setup", "name": "Vehicle", diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 81daa924b75..e43e6f21c92 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -8,8 +8,11 @@ from pypika import Order class DeprecatedSerialNoValuation: @deprecated def calculate_stock_value_from_deprecarated_ledgers(self): - if not frappe.db.get_value( - "Stock Ledger Entry", {"serial_no": ("is", "set"), "is_cancelled": 0}, "name" + if not frappe.db.get_all( + "Stock Ledger Entry", + fields=["name"], + filters={"serial_no": ("is", "set"), "is_cancelled": 0, "item_code": self.sle.item_code}, + limit=1, ): return @@ -17,15 +20,11 @@ class DeprecatedSerialNoValuation: if not serial_nos: return - actual_qty = flt(self.sle.actual_qty) - stock_value_change = 0 - if actual_qty < 0: - if not self.sle.is_cancelled: - outgoing_value = self.get_incoming_value_for_serial_nos(serial_nos) - stock_value_change = -1 * outgoing_value + if not self.sle.is_cancelled: + stock_value_change = self.get_incoming_value_for_serial_nos(serial_nos) - self.stock_value_change += stock_value_change + self.stock_value_change += flt(stock_value_change) def get_filterd_serial_nos(self): serial_nos = [] @@ -45,6 +44,12 @@ class DeprecatedSerialNoValuation: # get rate from serial nos within same company incoming_values = 0.0 for serial_no in serial_nos: + sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1) + if sn_details and sn_details.purchase_rate and sn_details.company == self.sle.company: + self.serial_no_incoming_rate[serial_no] += flt(sn_details.purchase_rate) + incoming_values += self.serial_no_incoming_rate[serial_no] + continue + table = frappe.qb.DocType("Stock Ledger Entry") stock_ledgers = ( frappe.qb.from_(table) @@ -141,7 +146,14 @@ class DeprecatedBatchNoValuation: if not self.non_batchwise_balance_qty: continue - self.batch_avg_rate[batch_no] = self.non_batchwise_balance_value / self.non_batchwise_balance_qty + if self.non_batchwise_balance_value == 0: + self.batch_avg_rate[batch_no] = 0.0 + self.stock_value_differece[batch_no] = 0.0 + else: + self.batch_avg_rate[batch_no] = ( + self.non_batchwise_balance_value / self.non_batchwise_balance_qty + ) + self.stock_value_differece[batch_no] = self.non_batchwise_balance_value stock_value_change = self.batch_avg_rate[batch_no] * ledger.qty self.stock_value_change += stock_value_change diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js index 3719c96c6e7..4ed428421ca 100644 --- a/erpnext/stock/doctype/batch/batch.js +++ b/erpnext/stock/doctype/batch/batch.js @@ -47,9 +47,14 @@ frappe.ui.form.on("Batch", { }, make_dashboard: (frm) => { if (!frm.is_new()) { + let for_stock_levels = 0; + if (!frm.doc.batch_qty && frm.doc.expiry_date) { + for_stock_levels = 1; + } + frappe.call({ method: "erpnext.stock.doctype.batch.batch.get_batch_qty", - args: { batch_no: frm.doc.name, item_code: frm.doc.item }, + args: { batch_no: frm.doc.name, item_code: frm.doc.item, for_stock_levels: for_stock_levels }, callback: (r) => { if (!r.message) { return; diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 8726642cb43..0be85e46015 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -199,6 +199,7 @@ def get_batch_qty( posting_date=None, posting_time=None, ignore_voucher_nos=None, + for_stock_levels=False, ): """Returns batch actual qty if warehouse is passed, or returns dict of qty by warehouse if warehouse is None @@ -207,7 +208,8 @@ def get_batch_qty( :param batch_no: Optional - give qty for this batch no :param warehouse: Optional - give qty for this warehouse - :param item_code: Optional - give qty for this item""" + :param item_code: Optional - give qty for this item + :param for_stock_levels: True consider expired batches""" from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( get_auto_batch_nos, @@ -222,6 +224,7 @@ def get_batch_qty( "posting_time": posting_time, "batch_no": batch_no, "ignore_voucher_nos": ignore_voucher_nos, + "for_stock_levels": for_stock_levels, } ) diff --git a/erpnext/stock/doctype/batch/batch_list.js b/erpnext/stock/doctype/batch/batch_list.js index 2060d6e8763..644ef131399 100644 --- a/erpnext/stock/doctype/batch/batch_list.js +++ b/erpnext/stock/doctype/batch/batch_list.js @@ -3,8 +3,6 @@ frappe.listview_settings["Batch"] = { get_indicator: (doc) => { if (doc.disabled) { return [__("Disabled"), "gray", "disabled,=,1"]; - } else if (!doc.batch_qty) { - return [__("Empty"), "gray", "batch_qty,=,0|disabled,=,0"]; } else if ( doc.expiry_date && frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0 @@ -14,6 +12,8 @@ frappe.listview_settings["Batch"] = { "red", "expiry_date,not in,|expiry_date,<=,Today|batch_qty,>,0|disabled,=,0", ]; + } else if (!doc.batch_qty) { + return [__("Empty"), "gray", "batch_qty,=,0|disabled,=,0"]; } else { return [__("Active"), "green", "batch_qty,>,0|disabled,=,0"]; } diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index ec05738c68a..c971c8da4f0 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -467,6 +467,7 @@ class DeliveryNote(SellingController): if not self.get(table_name): continue + self.make_bundle_for_sales_purchase_return(table_name) self.make_bundle_using_old_serial_batch_fields(table_name) # Updating stock ledger should always be called after updating prevdoc status, @@ -1371,6 +1372,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): if source_parent.doctype == "Delivery Note" and source.received_qty: target.qty = flt(source.qty) + flt(source.returned_qty) - flt(source.received_qty) + if source.get("use_serial_batch_fields"): + target.set("use_serial_batch_fields", 1) + doclist = get_mapped_doc( doctype, source_name, diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 791a9730b50..f24c912e362 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -249,18 +249,15 @@ class TestDeliveryNote(FrappeTestCase): self.assertTrue(dn.items[0].serial_no) frappe.flags.ignore_serial_batch_bundle_validation = False + frappe.flags.use_serial_and_batch_fields = False # return entry dn1 = make_sales_return(dn.name) dn1.items[0].qty = -2 - - bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle) - bundle_doc.set("entries", bundle_doc.entries[:2]) - bundle_doc.save() - - dn1.save() + dn1.items[0].serial_no = "\n".join(get_serial_nos(serial_nos)[0:2]) dn1.submit() + dn1.reload() returned_serial_nos1 = get_serial_nos_from_bundle(dn1.items[0].serial_and_batch_bundle) for serial_no in returned_serial_nos1: @@ -269,21 +266,15 @@ class TestDeliveryNote(FrappeTestCase): dn2 = make_sales_return(dn.name) dn2.items[0].qty = -2 - - bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn2.items[0].serial_and_batch_bundle) - bundle_doc.set("entries", bundle_doc.entries[:2]) - bundle_doc.save() - - dn2.save() + dn2.items[0].serial_no = "\n".join(get_serial_nos(serial_nos)[2:4]) dn2.submit() + dn2.reload() returned_serial_nos2 = get_serial_nos_from_bundle(dn2.items[0].serial_and_batch_bundle) for serial_no in returned_serial_nos2: self.assertTrue(serial_no in serial_nos) self.assertFalse(serial_no in returned_serial_nos1) - frappe.flags.use_serial_and_batch_fields = False - def test_sales_return_for_non_bundled_items_partial(self): company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") @@ -428,7 +419,7 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(dn.per_returned, 100) self.assertEqual(dn.status, "Return Issued") - def test_delivery_note_return_valuation_on_different_warehuose(self): + def test_delivery_note_return_valuation_on_different_warehouse(self): from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") @@ -1697,6 +1688,236 @@ class TestDeliveryNote(FrappeTestCase): if row.serial_no: self.assertEqual(row.serial_no, serial_no) + def test_delivery_note_legacy_serial_no_valuation(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + frappe.flags.ignore_serial_batch_bundle_validation = True + sn_item = "Old Serial NO Item Valuation Test - 2" + make_item( + sn_item, + { + "has_serial_no": 1, + "serial_no_series": "SN-SOVOSN-.####", + "is_stock_item": 1, + }, + ) + + serial_nos = [ + "SN-SOVOSN-1234", + "SN-SOVOSN-2234", + ] + + for sn in serial_nos: + if not frappe.db.exists("Serial No", sn): + sn_doc = frappe.get_doc( + { + "doctype": "Serial No", + "item_code": sn_item, + "serial_no": sn, + } + ) + sn_doc.insert() + + warehouse = "_Test Warehouse - _TC" + company = frappe.db.get_value("Warehouse", warehouse, "company") + se_doc = make_stock_entry( + item_code=sn_item, + company=company, + target="_Test Warehouse - _TC", + qty=2, + basic_rate=150, + do_not_submit=1, + use_serial_batch_fields=0, + ) + se_doc.submit() + + se_doc.items[0].db_set("serial_no", "\n".join(serial_nos)) + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": se_doc.name, "voucher_type": "Stock Entry"}, + )[0] + + sle_doc = frappe.get_doc("Stock Ledger Entry", sle_data.name) + self.assertFalse(sle_doc.serial_no) + sle_doc.db_set("serial_no", "\n".join(serial_nos)) + sle_doc.reload() + self.assertTrue(sle_doc.serial_no) + self.assertFalse(sle_doc.is_cancelled) + + for sn in serial_nos: + sn_doc = frappe.get_doc("Serial No", sn) + sn_doc.db_set( + { + "status": "Active", + "warehouse": warehouse, + } + ) + + self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos)) + frappe.flags.ignore_serial_batch_bundle_validation = False + + se_doc = make_stock_entry( + item_code=sn_item, + company=company, + target="_Test Warehouse - _TC", + qty=2, + basic_rate=200, + ) + + serial_nos.extend(get_serial_nos_from_bundle(se_doc.items[0].serial_and_batch_bundle)) + + dn = create_delivery_note( + item_code=sn_item, + qty=3, + rate=500, + warehouse=warehouse, + company=company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos[0:3]), + ) + + dn.reload() + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, + fields=["stock_value_difference", "actual_qty"], + )[0] + + self.assertEqual(sle_data.actual_qty, 3 * -1) + self.assertEqual(sle_data.stock_value_difference, 500.0 * -1) + + dn = create_delivery_note( + item_code=sn_item, + qty=1, + rate=500, + warehouse=warehouse, + company=company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + use_serial_batch_fields=1, + serial_no=serial_nos[-1], + ) + + dn.reload() + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, + fields=["stock_value_difference", "actual_qty"], + )[0] + + self.assertEqual(sle_data.actual_qty, 1 * -1) + self.assertEqual(sle_data.stock_value_difference, 200.0 * -1) + + def test_sales_return_batch_no_for_batched_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Batched Item for Sales Return 11", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + def test_partial_sales_return_batch_no_for_batched_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Partial Batched Item for Sales Return 11", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BPART11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.items[0].qty = 3 * -1 + dn_return.save().submit() + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + sabb_qty = frappe.db.get_value( + "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" + ) + self.assertEqual(sabb_qty, 3) + + dn_return = make_sales_return(dn.name) + dn_return.items[0].qty = 2 * -1 + dn_return.save().submit() + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + sabb_qty = frappe.db.get_value( + "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" + ) + self.assertEqual(sabb_qty, 2) + + def test_sales_return_serial_no_for_serial_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Serial Item for Sales Return 11", + properties={ + "has_serial_no": 1, + "serial_no_series": "SNN11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + serial_no=serial_nos, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + returned_serial_nos = get_serial_nos_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(serial_nos, returned_serial_nos) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 1c43233d7c2..1ceb949d691 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -5,7 +5,7 @@ import copy import json import frappe -from frappe import _ +from frappe import _, bold from frappe.model.document import Document from frappe.query_builder import Interval from frappe.query_builder.functions import Count, CurDate, UnixTimestamp @@ -469,6 +469,13 @@ class Item(Document): def validate_warehouse_for_reorder(self): """Validate Reorder level table for duplicate and conditional mandatory""" warehouse_material_request_type: list[tuple[str, str]] = [] + + _warehouse_before_save = frappe._dict() + if not self.is_new() and self._doc_before_save: + _warehouse_before_save = { + d.name: d.warehouse for d in self._doc_before_save.get("reorder_levels") or [] + } + for d in self.get("reorder_levels"): if not d.warehouse_group: d.warehouse_group = d.warehouse @@ -485,6 +492,19 @@ class Item(Document): if d.warehouse_reorder_level and not d.warehouse_reorder_qty: frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx)) + if d.warehouse_group and d.warehouse: + if _warehouse_before_save.get(d.name) == d.warehouse: + continue + + child_warehouses = get_child_warehouses(d.warehouse_group) + if d.warehouse not in child_warehouses: + frappe.throw( + _( + "Row #{0}: The warehouse {1} is not a child warehouse of a group warehouse {2}" + ).format(d.idx, bold(d.warehouse), bold(d.warehouse_group)), + title=_("Incorrect Check in (group) Warehouse for Reorder"), + ) + def stock_ledger_created(self): if not hasattr(self, "_stock_ledger_created"): self._stock_ledger_created = len( @@ -1360,3 +1380,10 @@ def get_asset_naming_series(): from erpnext.assets.doctype.asset.asset import get_asset_naming_series return get_asset_naming_series() + + +@frappe.request_cache +def get_child_warehouses(warehouse): + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + + return get_child_warehouses(warehouse) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 2b3d3b72a02..d5f13e62a5c 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -862,6 +862,27 @@ class TestItem(FrappeTestCase): self.assertEqual(data[0].description, item.description) self.assertTrue("description" in data[0]) + def test_group_warehouse_for_reorder_item(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + item_doc = make_item("_Test Group Warehouse For Reorder Item", {"is_stock_item": 1}) + warehouse = create_warehouse("_Test Warehouse - _TC") + warehouse_doc = frappe.get_doc("Warehouse", warehouse) + warehouse_doc.db_set("parent_warehouse", "") + + item_doc.append( + "reorder_levels", + { + "warehouse": warehouse, + "warehouse_reorder_level": 10, + "warehouse_reorder_qty": 100, + "material_request_type": "Purchase", + "warehouse_group": "_Test Warehouse Group - _TC", + }, + ) + + self.assertRaises(frappe.ValidationError, item_doc.save) + def set_item_variant_settings(fields): doc = frappe.get_doc("Item Variant Settings") diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 683e946298a..eb84fdbc7c0 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -252,7 +252,10 @@ class LandedCostVoucher(Document): doc.docstatus = 1 doc.make_bundle_using_old_serial_batch_fields(via_landed_cost_voucher=True) doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) - doc.make_gl_entries() + if d.receipt_document_type == "Purchase Receipt": + doc.make_gl_entries(via_landed_cost_voucher=True) + else: + doc.make_gl_entries() doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 13b7f97b7c4..39f9ecb915d 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -946,6 +946,128 @@ class TestLandedCostVoucher(FrappeTestCase): frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), ) + def test_do_not_validate_against_landed_cost_voucher_for_serial_for_legacy_pr(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import get_auto_batch_nos + + frappe.flags.ignore_serial_batch_bundle_validation = True + frappe.flags.use_serial_and_batch_fields = True + sn_item = "Test Don't Validate Against LCV For Serial NO for Legacy PR" + sn_item_doc = make_item( + sn_item, + { + "has_serial_no": 1, + "serial_no_series": "SN-ALCVTDVLCVSNO-.####", + "is_stock_item": 1, + }, + ) + + serial_nos = [ + "SN-ALCVTDVLCVSNO-0001", + "SN-ALCVTDVLCVSNO-0002", + "SN-ALCVTDVLCVSNO-0003", + "SN-ALCVTDVLCVSNO-0004", + "SN-ALCVTDVLCVSNO-0005", + ] + + for sn in serial_nos: + if not frappe.db.exists("Serial No", sn): + sn_doc = frappe.get_doc( + { + "doctype": "Serial No", + "item_code": sn_item, + "serial_no": sn, + } + ) + sn_doc.insert() + + warehouse = "_Test Warehouse - _TC" + company = frappe.db.get_value("Warehouse", warehouse, "company") + + pr = make_purchase_receipt( + company=company, + warehouse=warehouse, + item_code=sn_item, + qty=5, + rate=100, + uom=sn_item_doc.stock_uom, + stock_uom=sn_item_doc.stock_uom, + ) + + pr.reload() + + for sn in serial_nos: + sn_doc = frappe.get_doc("Serial No", sn) + sn_doc.db_set( + { + "warehouse": warehouse, + "status": "Active", + } + ) + + for row in pr.items: + if row.item_code == sn_item: + row.db_set("serial_no", ", ".join(serial_nos)) + + stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": pr.name}) + for sle in stock_ledger_entries: + doc = frappe.get_doc("Stock Ledger Entry", sle.name) + if doc.item_code == sn_item: + doc.db_set("serial_no", ", ".join(serial_nos)) + + dn = create_delivery_note( + company=company, + warehouse=warehouse, + item_code=sn_item, + qty=5, + rate=100, + uom=sn_item_doc.stock_uom, + stock_uom=sn_item_doc.stock_uom, + ) + + stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": dn.name}) + for sle in stock_ledger_entries: + doc = frappe.get_doc("Stock Ledger Entry", sle.name) + if doc.item_code == sn_item: + doc.db_set("serial_no", ", ".join(serial_nos)) + + frappe.flags.ignore_serial_batch_bundle_validation = False + frappe.flags.use_serial_and_batch_fields = False + + lcv = make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=20, + distribute_charges_based_on="Qty", + do_not_save=True, + ) + + lcv.get_items_from_purchase_receipts() + lcv.save() + lcv.submit() + + pr.reload() + + for row in pr.items: + self.assertEqual(row.valuation_rate, 104) + self.assertTrue(row.serial_and_batch_bundle) + self.assertEqual( + row.valuation_rate, + frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), + ) + + lcv.cancel() + pr.reload() + + for row in pr.items: + self.assertEqual(row.valuation_rate, 100) + self.assertTrue(row.serial_and_batch_bundle) + self.assertEqual( + row.valuation_rate, + frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), + ) + def make_landed_cost_voucher(**args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 65fe853ec8d..116a0bd833d 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -167,6 +167,7 @@ class TestPickList(FrappeTestCase): "item_code": "_Test Serialized Item", "warehouse": "_Test Warehouse - _TC", "valuation_rate": 100, + "reconcile_all_serial_batch": 1, "qty": 5, "serial_and_batch_bundle": make_serial_batch_bundle( frappe._dict( diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 2e751ad5251..6a00fed5588 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -370,6 +370,7 @@ class PurchaseReceipt(BuyingController): else: self.db_set("status", "Completed") + self.make_bundle_for_sales_purchase_return() self.make_bundle_using_old_serial_batch_fields() # Updating stock ledger should always be called after updating prevdoc status, # because updating ordered qty, reserved_qty_for_subcontract in bin @@ -421,13 +422,13 @@ class PurchaseReceipt(BuyingController): self.delete_auto_created_batches() self.set_consumed_qty_in_subcontract_order() - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False): from erpnext.accounts.general_ledger import process_gl_map gl_entries = [] self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) - self.make_tax_gl_entries(gl_entries) + self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher) update_regional_gl_entries(gl_entries, self) return process_gl_map(gl_entries) @@ -775,7 +776,7 @@ class PurchaseReceipt(BuyingController): posting_date=posting_date, ) - def make_tax_gl_entries(self, gl_entries): + def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) # Cost center-wise amount breakup for other charges included for valuation @@ -810,18 +811,17 @@ class PurchaseReceipt(BuyingController): i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): - negative_expense_booked_in_pi = frappe.db.sql( - """select name from `tabPurchase Invoice Item` pi - where docstatus = 1 and purchase_receipt=%s - and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' - and voucher_no=pi.parent and account=%s)""", - (self.name, tax.account_head), - ) - - if negative_expense_booked_in_pi: - account = stock_rbnb - else: + if via_landed_cost_voucher: account = tax.account_head + else: + negative_expense_booked_in_pi = frappe.db.sql( + """select name from `tabPurchase Invoice Item` pi + where docstatus = 1 and purchase_receipt=%s + and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' + and voucher_no=pi.parent and account=%s)""", + (self.name, tax.account_head), + ) + account = stock_rbnb if negative_expense_booked_in_pi else tax.account_head if i == len(valuation_tax): applicable_amount = amount_including_divisional_loss @@ -858,7 +858,7 @@ class PurchaseReceipt(BuyingController): asset.name, { "gross_purchase_amount": purchase_amount, - "purchase_receipt_amount": purchase_amount, + "purchase_amount": purchase_amount, }, ) @@ -923,6 +923,15 @@ class PurchaseReceipt(BuyingController): notify=True, ) + def enable_recalculate_rate_in_sles(self): + sle_table = frappe.qb.DocType("Stock Ledger Entry") + ( + frappe.qb.update(sle_table) + .set(sle_table.recalculate_rate, 1) + .where(sle_table.voucher_no == self.name) + .where(sle_table.voucher_type == "Purchase Receipt") + ).run() + def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse): return frappe.db.get_value( @@ -1095,15 +1104,10 @@ def adjust_incoming_rate_for_pr(doc): for item in doc.get("items"): item.db_update() - doc.docstatus = 2 - doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) - doc.make_gl_entries_on_cancel() + if doc.doctype == "Purchase Receipt": + doc.enable_recalculate_rate_in_sles() - # update stock & gl entries for submit state of PR - doc.docstatus = 1 - doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) - doc.make_gl_entries() - doc.repost_future_sle_and_gle() + doc.repost_future_sle_and_gle(force=True) def get_item_wise_returned_qty(pr_doc): @@ -1163,7 +1167,12 @@ def make_purchase_invoice(source_name, target_doc=None, args=None): qty = item_row.qty if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"): qty = item_row.received_qty + pending_qty = qty - invoiced_qty_map.get(item_row.name, 0) + + if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"): + return pending_qty, 0 + returned_qty = flt(returned_qty_map.get(item_row.name, 0)) if returned_qty: if returned_qty >= pending_qty: @@ -1172,6 +1181,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=None): else: pending_qty -= returned_qty returned_qty = 0 + return pending_qty, returned_qty doclist = get_mapped_doc( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 54a695126c7..42747818d2b 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -9,6 +9,7 @@ from pypika import functions as fn import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.controllers.buying_controller import QtyMismatchError +from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( @@ -895,6 +896,8 @@ class TestPurchaseReceipt(FrappeTestCase): create_purchase_order, ) + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) + po = create_purchase_order() pr = create_pr_against_po(po.name) @@ -914,6 +917,7 @@ class TestPurchaseReceipt(FrappeTestCase): po.cancel() def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self): + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) pr1 = make_purchase_receipt(qty=8, do_not_submit=True) pr1.append( "items", @@ -1678,7 +1682,6 @@ class TestPurchaseReceipt(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0) def test_internal_pr_gl_entries(self): - from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -2642,7 +2645,7 @@ class TestPurchaseReceipt(FrappeTestCase): for row in inter_transfer_dn_return.items: self.assertTrue(row.serial_and_batch_bundle) - def test_internal_transfer_with_serial_batch_items_without_user_serial_batch_fields(self): + def test_internal_transfer_with_serial_batch_items_without_use_serial_batch_fields(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note @@ -2783,6 +2786,181 @@ class TestPurchaseReceipt(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def test_purchase_receipt_bill_for_rejected_quantity_in_purchase_invoice(self): + item_code = make_item( + "_Test Purchase Receipt Bill For Rejected Quantity", + properties={"is_stock_item": 1}, + ).name + + pr = make_purchase_receipt(item_code=item_code, qty=5, rate=100) + + return_pr = make_purchase_receipt( + item_code=item_code, + is_return=1, + return_against=pr.name, + qty=-2, + do_not_submit=1, + ) + return_pr.items[0].purchase_receipt_item = pr.items[0].name + return_pr.submit() + old_value = frappe.db.get_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" + ) + + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) + pi = make_purchase_invoice(pr.name) + self.assertEqual(pi.items[0].qty, 3) + + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1) + pi = make_purchase_invoice(pr.name) + pi.submit() + self.assertEqual(pi.items[0].qty, 5) + + frappe.db.set_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", old_value + ) + + def test_zero_valuation_rate_for_batched_item(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = make_item( + "_Test Zero Valuation Rate For the Batch Item", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TZVRFORBATCH.#####", + "valuation_rate": 200, + }, + ) + + pi = make_purchase_receipt( + qty=10, + rate=0, + item_code=item.name, + ) + + pi.reload() + batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle) + + se = make_stock_entry( + purpose="Material Issue", + item_code=item.name, + source=pi.items[0].warehouse, + qty=10, + batch_no=batch_no, + use_serial_batch_fields=0, + ) + + se.submit() + + se.reload() + + self.assertEqual(se.items[0].valuation_rate, 0) + self.assertEqual(se.items[0].basic_rate, 0) + + sabb_doc = frappe.get_doc("Serial and Batch Bundle", se.items[0].serial_and_batch_bundle) + for row in sabb_doc.entries: + self.assertEqual(row.incoming_rate, 0) + + def test_purchase_return_from_accepted_and_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return, + ) + + item = make_item( + "_Test PR Item With Return From Accepted and Rejected WH", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SD-TZVRFORBATCH.#####", + "valuation_rate": 200, + }, + ) + + pr = make_purchase_receipt( + qty=10, + rejected_qty=5, + rate=100, + item_code=item.name, + ) + + pr.reload() + self.assertTrue(pr.items[0].serial_and_batch_bundle) + self.assertTrue(pr.items[0].rejected_serial_and_batch_bundle) + + return_pr = make_purchase_return(pr.name) + return_pr.submit() + + return_pr.reload() + self.assertTrue(return_pr.items[0].serial_and_batch_bundle) + self.assertTrue(return_pr.items[0].rejected_serial_and_batch_bundle) + + self.assertEqual( + return_pr.items[0].qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].serial_and_batch_bundle, "total_qty" + ), + ) + + self.assertEqual( + return_pr.items[0].rejected_qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty" + ), + ) + + def test_valuation_taxes_lcv_repost_after_billing(self): + from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( + make_landed_cost_voucher, + ) + + old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company") + frappe.local.enable_perpetual_inventory["_Test Company"] = 1 + frappe.db.set_value( + "Company", + "_Test Company", + "stock_received_but_not_billed", + "Stock Received But Not Billed - _TC", + ) + + pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1) + pr.append( + "taxes", + { + "category": "Valuation and Total", + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "tax_amount": 2000, + "description": "Test", + }, + ) + pr.submit() + pi = make_purchase_invoice(pr.name) + pi.submit() + make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=2000, + distribute_charges_based_on="Qty", + expense_account="Expenses Included In Valuation - _TC", + ) + + gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False) + warehouse_account = get_warehouse_account_map("_Test Company") + expected_gle = ( + ("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"), + ("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"), + ("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"), + (warehouse_account[pr.items[0].warehouse]["account"], 14000, 0, "Main - _TC"), + ) + self.assertSequenceEqual(expected_gle, gl_entries) + frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier @@ -2829,14 +3007,24 @@ def get_sl_entries(voucher_type, voucher_no): ) -def get_gl_entries(voucher_type, voucher_no): - return frappe.db.sql( - """select account, debit, credit, cost_center, is_cancelled - from `tabGL Entry` where voucher_type=%s and voucher_no=%s - order by account desc""", - (voucher_type, voucher_no), - as_dict=1, +def get_gl_entries(voucher_type, voucher_no, skip_cancelled=False, as_dict=True): + gl = frappe.qb.DocType("GL Entry") + gl_query = ( + frappe.qb.from_(gl) + .select( + gl.account, + gl.debit, + gl.credit, + gl.cost_center, + ) + .where((gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no)) + .orderby(gl.account, order=frappe.qb.desc) ) + if skip_cancelled: + gl_query = gl_query.where(gl.is_cancelled == 0) + else: + gl_query = gl_query.select(gl.is_cancelled) + return gl_query.run(as_dict=as_dict) def get_taxes(**args): diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index a95f99ffd43..fd26b611f45 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -81,6 +81,7 @@ "purchase_invoice", "column_break_40", "allow_zero_valuation_rate", + "return_qty_from_rejected_warehouse", "is_fixed_asset", "asset_location", "asset_category", @@ -1116,12 +1117,19 @@ "hidden": 1, "label": "Apply TDS", "read_only": 1 + }, + { + "default": "0", + "fieldname": "return_qty_from_rejected_warehouse", + "fieldtype": "Check", + "label": "Return Qty from Rejected Warehouse", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2024-04-08 20:00:16.278292", + "modified": "2024-05-28 09:48:24.448815", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", @@ -1132,4 +1140,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py index 908c0a7a0f4..393b6a25691 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py @@ -85,6 +85,7 @@ class PurchaseReceiptItem(Document): rejected_serial_no: DF.Text | None rejected_warehouse: DF.Link | None retain_sample: DF.Check + return_qty_from_rejected_warehouse: DF.Check returned_qty: DF.Float rm_supp_cost: DF.Currency sales_order: DF.Link | None diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py index 9f7fdeccd05..9178229c018 100644 --- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py @@ -377,7 +377,7 @@ class TestPutawayRule(FrappeTestCase): apply_putaway_rule=1, do_not_save=1, ) - stock_entry.save() + stock_entry.submit() stock_entry.load_from_db() self.assertEqual(stock_entry.items[0].t_warehouse, self.warehouse_1) @@ -398,11 +398,17 @@ class TestPutawayRule(FrappeTestCase): self.assertUnchangedItemsOnResave(stock_entry) - for row in stock_entry.items: - if row.serial_and_batch_bundle: - frappe.delete_doc("Serial and Batch Bundle", row.serial_and_batch_bundle) - stock_entry.load_from_db() + stock_entry.cancel() + + rivs = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}) + for row in rivs: + riv_doc = frappe.get_doc("Repost Item Valuation", row.name) + riv_doc.cancel() + riv_doc.delete() + + frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 1) + stock_entry.delete() pr.cancel() rule_1.delete() 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 67f946eb7c8..87bf2df5f61 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 @@ -156,6 +156,8 @@ class SerialandBatchBundle(Document): def validate_serial_nos_duplicate(self): # Don't inward same serial number multiple times + if self.voucher_type in ["POS Invoice", "Pick List"]: + return if not self.warehouse: return @@ -249,8 +251,7 @@ class SerialandBatchBundle(Document): if self.has_serial_no: d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0)) else: - if sn_obj.batch_avg_rate.get(d.batch_no): - d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no)) + d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no))) available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty")) if self.docstatus == 1: @@ -429,6 +430,9 @@ class SerialandBatchBundle(Document): self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} should be submit first.") def check_future_entries_exists(self): + if self.flags and self.flags.via_landed_cost_voucher: + return + if not self.has_serial_no: return @@ -1205,31 +1209,44 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals @frappe.whitelist() -def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None): +def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None, child_row=None): filters = get_filters_for_bundle( - item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name + item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name, child_row=child_row ) + fields = [ + "`tabSerial and Batch Bundle`.`item_code`", + "`tabSerial and Batch Entry`.`qty`", + "`tabSerial and Batch Entry`.`warehouse`", + "`tabSerial and Batch Entry`.`batch_no`", + "`tabSerial and Batch Entry`.`serial_no`", + ] + + if not child_row: + fields.append("`tabSerial and Batch Bundle`.`name`") + return frappe.get_all( "Serial and Batch Bundle", - fields=[ - "`tabSerial and Batch Bundle`.`name`", - "`tabSerial and Batch Bundle`.`item_code`", - "`tabSerial and Batch Entry`.`qty`", - "`tabSerial and Batch Entry`.`warehouse`", - "`tabSerial and Batch Entry`.`batch_no`", - "`tabSerial and Batch Entry`.`serial_no`", - ], + fields=fields, filters=filters, order_by="`tabSerial and Batch Entry`.`idx`", ) -def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None): +def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None, child_row=None): filters = [ ["Serial and Batch Bundle", "is_cancelled", "=", 0], ] + if child_row and isinstance(child_row, str): + child_row = parse_json(child_row) + + if not name and child_row and child_row.get("qty") < 0: + bundle = get_reference_serial_and_batch_bundle(child_row) + if bundle: + voucher_no = None + filters.append(["Serial and Batch Bundle", "name", "=", bundle]) + if item_code: filters.append(["Serial and Batch Bundle", "item_code", "=", item_code]) @@ -1253,6 +1270,19 @@ def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name return filters +def get_reference_serial_and_batch_bundle(child_row): + field = { + "Sales Invoice Item": "sales_invoice_item", + "Delivery Note Item": "dn_detail", + "Purchase Receipt Item": "purchase_receipt_item", + "Purchase Invoice Item": "purchase_invoice_item", + "POS Invoice Item": "pos_invoice_item", + }.get(child_row.doctype) + + if field: + return frappe.get_cached_value(child_row.doctype, child_row.get(field), "serial_and_batch_bundle") + + @frappe.whitelist() def add_serial_batch_ledgers(entries, child_row, doc, warehouse, do_not_save=False) -> object: if isinstance(child_row, str): @@ -1330,9 +1360,6 @@ def get_type_of_transaction(parent_doc, child_row): if parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]: type_of_transaction = "Inward" - if parent_doc.get("is_return"): - type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward" - if parent_doc.get("doctype") == "Subcontracting Receipt": type_of_transaction = "Outward" if child_row.get("doctype") == "Subcontracting Receipt Item": @@ -1340,6 +1367,14 @@ def get_type_of_transaction(parent_doc, child_row): elif parent_doc.get("doctype") == "Stock Reconciliation": type_of_transaction = "Inward" + if parent_doc.get("is_return"): + type_of_transaction = "Inward" + if ( + parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"] + or child_row.get("doctype") == "Subcontracting Receipt Item" + ): + type_of_transaction = "Outward" + return type_of_transaction @@ -1863,14 +1898,14 @@ def get_available_batches(kwargs): batch_ledger.warehouse, Sum(batch_ledger.qty).as_("qty"), ) - .where( - (batch_table.disabled == 0) - & ((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())) - ) + .where(batch_table.disabled == 0) .where(stock_ledger_entry.is_cancelled == 0) .groupby(batch_ledger.batch_no, batch_ledger.warehouse) ) + if not kwargs.get("for_stock_levels"): + query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())) + if kwargs.get("posting_date"): if kwargs.get("posting_time") is None: kwargs.posting_time = nowtime() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index fb63f1c23c6..c54876713c3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -113,6 +113,10 @@ frappe.ui.form.on("Stock Entry", { filters["warehouse"] = item.s_warehouse || item.t_warehouse; } + if (!item.s_warehouse && item.t_warehouse) { + filters["is_inward"] = 1; + } + return { query: "erpnext.controllers.queries.get_batch_no", filters: filters, @@ -1110,6 +1114,13 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle on_submit() { this.clean_up(); + this.refresh_serial_batch_bundle_field(); + } + + refresh_serial_batch_bundle_field() { + frappe.route_hooks.after_submit = (frm_obj) => { + frm_obj.reload_doc(); + }; } after_cancel() { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 569d4e9c0a0..266d74afbed 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -226,6 +226,7 @@ class StockEntry(StockController): if not self.from_bom: self.fg_completed_qty = 0.0 + self.make_serial_and_batch_bundle_for_outward() self.validate_serialized_batch() self.calculate_rate_and_amount() self.validate_putaway_capacity() @@ -289,9 +290,6 @@ class StockEntry(StockController): if self.purpose == "Material Transfer" and self.outgoing_stock_entry: self.set_material_request_transfer_status("In Transit") - def before_save(self): - self.make_serial_and_batch_bundle_for_outward() - def on_update(self): self.set_serial_and_batch_bundle() @@ -992,7 +990,7 @@ class StockEntry(StockController): self.purpose = frappe.get_cached_value("Stock Entry Type", self.stock_entry_type, "purpose") def make_serial_and_batch_bundle_for_outward(self): - if self.docstatus == 1: + if self.docstatus == 0: return serial_or_batch_items = get_serial_or_batch_items(self.items) @@ -1045,12 +1043,11 @@ class StockEntry(StockController): if not bundle_doc: continue - if self.docstatus == 0: - for entry in bundle_doc.entries: - if not entry.serial_no: - continue + for entry in bundle_doc.entries: + if not entry.serial_no: + continue - already_picked_serial_nos.append(entry.serial_no) + already_picked_serial_nos.append(entry.serial_no) row.serial_and_batch_bundle = bundle_doc.name 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 8585f7b1d87..319303dbbb0 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -93,11 +93,15 @@ class StockLedgerEntry(Document): self.validate_with_last_transaction_posting_time() self.validate_inventory_dimension_negative_stock() - def set_posting_datetime(self): + def set_posting_datetime(self, save=False): from erpnext.stock.utils import get_combine_datetime - self.posting_datetime = get_combine_datetime(self.posting_date, self.posting_time) - self.db_set("posting_datetime", self.posting_datetime) + if save: + posting_datetime = get_combine_datetime(self.posting_date, self.posting_time) + if not self.posting_datetime or self.posting_datetime != posting_datetime: + self.db_set("posting_datetime", posting_datetime) + else: + self.posting_datetime = get_combine_datetime(self.posting_date, self.posting_time) def validate_inventory_dimension_negative_stock(self): if self.is_cancelled: @@ -169,7 +173,7 @@ class StockLedgerEntry(Document): return inv_dimension_dict def on_submit(self): - self.set_posting_datetime() + self.set_posting_datetime(save=True) self.check_stock_frozen_date() # Added to handle few test cases where serial_and_batch_bundles are not required diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 8532b60d59c..31985678009 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -206,6 +206,7 @@ frappe.ui.form.on("Stock Reconciliation", { posting_date: frm.doc.posting_date, posting_time: frm.doc.posting_time, batch_no: d.batch_no, + row: d, }, callback: function (r) { const row = frappe.model.get_doc(cdt, cdn); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index f92d7361f41..8301a706183 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, bold, msgprint +from frappe import _, bold, json, msgprint from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import add_to_date, cint, cstr, flt @@ -162,6 +162,15 @@ class StockReconciliation(StockController): def set_current_serial_and_batch_bundle(self, voucher_detail_no=None, save=False) -> None: """Set Serial and Batch Bundle for each item""" for item in self.items: + if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle: + bundle = self.get_bundle_for_specific_serial_batch(item) + item.current_serial_and_batch_bundle = bundle.name + item.current_valuation_rate = abs(bundle.avg_rate) + + if not item.valuation_rate: + item.valuation_rate = item.current_valuation_rate + continue + if not save and item.use_serial_batch_fields: continue @@ -273,6 +282,79 @@ class StockReconciliation(StockController): } ) + def get_bundle_for_specific_serial_batch(self, row) -> str: + from erpnext.stock.serial_batch_bundle import SerialBatchCreation + + if row.current_serial_and_batch_bundle and not self.has_change_in_serial_batch(row): + return frappe._dict( + { + "name": row.current_serial_and_batch_bundle, + "avg_rate": row.current_valuation_rate, + } + ) + + cls_obj = SerialBatchCreation( + { + "type_of_transaction": "Outward", + "serial_and_batch_bundle": row.serial_and_batch_bundle, + "item_code": row.get("item_code"), + "warehouse": row.get("warehouse"), + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "do_not_save": True, + } + ) + + reco_obj = cls_obj.duplicate_package() + + total_current_qty = 0.0 + for entry in reco_obj.entries: + if not entry.batch_no or entry.serial_no: + total_current_qty += entry.qty + entry.qty *= -1 + continue + + current_qty = get_batch_qty( + entry.batch_no, + row.warehouse, + row.item_code, + posting_date=self.posting_date, + posting_time=self.posting_time, + ) + + total_current_qty += current_qty + entry.qty = current_qty * -1 + + reco_obj.save() + + row.current_qty = total_current_qty + + return reco_obj + + def has_change_in_serial_batch(self, row) -> bool: + bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []} + + data = frappe.get_all( + "Serial and Batch Entry", + fields=["serial_no", "batch_no", "parent"], + filters={"parent": ("in", [row.serial_and_batch_bundle, row.current_serial_and_batch_bundle])}, + order_by="idx", + ) + + for d in data: + bundles[d.parent].append(d.serial_no or d.batch_no) + + diff = set(bundles[row.serial_and_batch_bundle]) - set(bundles[row.current_serial_and_batch_bundle]) + + if diff: + bundle = row.current_serial_and_batch_bundle + row.current_serial_and_batch_bundle = None + frappe.delete_doc("Serial and Batch Bundle", bundle) + + return True + + return False + def set_new_serial_and_batch_bundle(self): for item in self.items: if item.use_serial_batch_fields: @@ -340,6 +422,7 @@ class StockReconciliation(StockController): self.posting_time, batch_no=item.batch_no, inventory_dimensions_dict=inventory_dimensions_dict, + row=item, ) if ( @@ -646,7 +729,7 @@ class StockReconciliation(StockController): for d in serial_nos: frappe.db.set_value("Serial No", d, "purchase_rate", valuation_rate) - def get_sle_for_items(self, row, serial_nos=None): + def get_sle_for_items(self, row, serial_nos=None, current_bundle=True): """Insert Stock Ledger Entries""" if not serial_nos and row.serial_no: @@ -680,7 +763,7 @@ class StockReconciliation(StockController): has_dimensions = True if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle): - if row.current_qty: + if row.current_qty and current_bundle: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) data.previous_qty_after_transaction = flt(row.qty) @@ -710,6 +793,8 @@ class StockReconciliation(StockController): has_serial_no = False for row in self.items: sl_entries.append(self.get_sle_for_items(row)) + if row.serial_and_batch_bundle and row.current_serial_and_batch_bundle: + sl_entries.append(self.get_sle_for_items(row, current_bundle=False)) if sl_entries: if has_serial_no: @@ -840,6 +925,7 @@ class StockReconciliation(StockController): row.warehouse, self.posting_date, self.posting_time, + row=row, ) current_qty = item_dict.get("qty") @@ -1166,11 +1252,18 @@ def get_stock_balance_for( batch_no: str | None = None, with_valuation_rate: bool = True, inventory_dimensions_dict=None, + row=None, ): frappe.has_permission("Stock Reconciliation", "write", throw=True) item_dict = frappe.get_cached_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) + if isinstance(row, str): + row = json.loads(row) + + if isinstance(row, dict): + row = frappe._dict(row) + if not item_dict: # In cases of data upload to Items table msg = _("Item {} does not exist.").format(item_code) @@ -1188,7 +1281,7 @@ def get_stock_balance_for( "qty": 0, "rate": 0, "serial_nos": None, - "use_serial_batch_fields": use_serial_batch_fields, + "use_serial_batch_fields": row.use_serial_batch_fields if row else use_serial_batch_fields, } # TODO: fetch only selected batch's values @@ -1214,7 +1307,7 @@ def get_stock_balance_for( "qty": qty, "rate": rate, "serial_nos": serial_nos, - "use_serial_batch_fields": use_serial_batch_fields, + "use_serial_batch_fields": row.use_serial_batch_fields if row else use_serial_batch_fields, } diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 92a931036e9..8845bdbb753 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1070,6 +1070,117 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertTrue(sr.items[0].serial_and_batch_bundle) self.assertFalse(sr.items[0].current_serial_and_batch_bundle) + def test_not_reconcile_all_batch(self): + from erpnext.stock.doctype.batch.batch import get_batch_qty + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = self.make_item( + "Test Batch Item Not Reconcile All Serial Batch", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-NRALL-SRCOSRWFEE-.###", + }, + ) + + warehouse = "_Test Warehouse - _TC" + + batches = [] + for qty in [10, 20, 30]: + se = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=qty, + basic_rate=100 + qty, + posting_date=nowdate(), + ) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + batches.append(frappe._dict({"batch_no": batch_no, "qty": qty})) + + sr = create_stock_reconciliation( + item_code=item.name, + warehouse=warehouse, + qty=100, + rate=1000, + reconcile_all_serial_batch=0, + batch_no=batches[0].batch_no, + ) + + sr.reload() + + self.assertTrue(sr.items[0].current_valuation_rate) + current_sabb = sr.items[0].current_serial_and_batch_bundle + doc = frappe.get_doc("Serial and Batch Bundle", current_sabb) + for row in doc.entries: + self.assertEqual(row.batch_no, batches[0].batch_no) + self.assertEqual(row.qty, batches[0].qty * -1) + + batch_qty = get_batch_qty(batches[0].batch_no, warehouse, item.name) + self.assertEqual(batch_qty, 100) + + for row in frappe.get_all("Repost Item Valuation", filters={"voucher_no": sr.name}): + rdoc = frappe.get_doc("Repost Item Valuation", row.name) + rdoc.cancel() + rdoc.delete() + + sr.cancel() + + for row in frappe.get_all( + "Serial and Batch Bundle", fields=["docstatus"], filters={"voucher_no": sr.name} + ): + self.assertEqual(row.docstatus, 2) + + def test_not_reconcile_all_serial_nos(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.utils import get_incoming_rate + + item = self.make_item( + "Test Serial NO Item Not Reconcile All Serial Batch", + { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SNN-TEST-BATCH-NRALL-S-.###", + }, + ) + + warehouse = "_Test Warehouse - _TC" + + serial_nos = [] + for qty in [5, 5, 5]: + se = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=qty, + basic_rate=100 + qty, + posting_date=nowdate(), + ) + + serial_nos.extend(get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)) + + sr = create_stock_reconciliation( + item_code=item.name, + warehouse=warehouse, + qty=5, + rate=1000, + reconcile_all_serial_batch=0, + serial_no=serial_nos[0:5], + ) + + sr.reload() + current_sabb = sr.items[0].current_serial_and_batch_bundle + doc = frappe.get_doc("Serial and Batch Bundle", current_sabb) + for row in doc.entries: + self.assertEqual(row.serial_no, serial_nos[row.idx - 1]) + + sabb = sr.items[0].serial_and_batch_bundle + doc = frappe.get_doc("Serial and Batch Bundle", sabb) + for row in doc.entries: + self.assertEqual(row.qty, 1) + self.assertAlmostEqual(row.incoming_rate, 1000.00) + self.assertEqual(row.serial_no, serial_nos[row.idx - 1]) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) @@ -1193,12 +1304,16 @@ def create_stock_reconciliation(**args): ) ).name + if args.reconcile_all_serial_batch is None: + args.reconcile_all_serial_batch = 1 + sr.append( "items", { "item_code": args.item_code or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", "qty": args.qty, + "reconcile_all_serial_batch": args.reconcile_all_serial_batch, "valuation_rate": args.rate, "serial_no": args.serial_no if args.use_serial_batch_fields else None, "batch_no": args.batch_no if args.use_serial_batch_fields else None, diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 734225972c7..bce819fe231 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -20,6 +20,7 @@ "serial_no_and_batch_section", "add_serial_batch_bundle", "use_serial_batch_fields", + "reconcile_all_serial_batch", "column_break_11", "serial_and_batch_bundle", "current_serial_and_batch_bundle", @@ -243,11 +244,18 @@ { "fieldname": "column_break_eefq", "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:!doc.use_serial_batch_fields", + "fieldname": "reconcile_all_serial_batch", + "fieldtype": "Check", + "label": "Reconcile All Serial Nos / Batches" } ], "istable": 1, "links": [], - "modified": "2024-02-04 16:19:44.576022", + "modified": "2024-05-30 23:20:00.947243", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", @@ -258,4 +266,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py index 1938fec32b0..f2a9aeba8f4 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.py @@ -33,6 +33,7 @@ class StockReconciliationItem(Document): parenttype: DF.Data qty: DF.Float quantity_difference: DF.ReadOnly | None + reconcile_all_serial_batch: DF.Check serial_and_batch_bundle: DF.Link | None serial_no: DF.LongText | None use_serial_batch_fields: DF.Check diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js index 1972b193732..0443f3f1ece 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.js +++ b/erpnext/stock/doctype/stock_settings/stock_settings.js @@ -14,6 +14,22 @@ frappe.ui.form.on("Stock Settings", { frm.set_query("default_warehouse", filters); frm.set_query("sample_retention_warehouse", filters); }, + + use_serial_batch_fields(frm) { + if (frm.doc.use_serial_batch_fields && !frm.doc.disable_serial_no_and_batch_selector) { + frm.set_value("disable_serial_no_and_batch_selector", 1); + } + }, + + disable_serial_no_and_batch_selector(frm) { + if (!frm.doc.disable_serial_no_and_batch_selector && frm.doc.use_serial_batch_fields) { + frm.set_value("disable_serial_no_and_batch_selector", 1); + frappe.msgprint( + __("Serial No and Batch Selector cannot be use when Use Serial / Batch Fields is enabled.") + ); + } + }, + allow_negative_stock: function (frm) { if (!frm.doc.allow_negative_stock) { return; diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index b5df094eee7..24e8dccd17c 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -84,14 +84,6 @@ class StockSettings(Document): make_mandatory=0, ) - stock_frozen_limit = 356 - submitted_stock_frozen = self.stock_frozen_upto_days or 0 - if submitted_stock_frozen > stock_frozen_limit: - self.stock_frozen_upto_days = stock_frozen_limit - frappe.msgprint( - _("`Freeze Stocks Older Than` should be smaller than %d days.") % stock_frozen_limit - ) - # show/hide barcode field for name in ["barcode", "barcodes", "scan_barcode"]: frappe.make_property_setter( diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 6dcd3b6f484..aa9dfd2048e 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - import frappe from frappe import _, throw from frappe.contacts.address_and_contact import load_address_and_contact diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 16a0de57a5d..822da13cc72 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, get_table_name, getdate +from frappe.utils import add_to_date, cint, flt, get_datetime, get_table_name, getdate from frappe.utils.deprecations import deprecated from pypika import functions as fn @@ -107,6 +107,8 @@ def get_stock_ledger_entries_for_batch_no(filters): if not filters.get("to_date"): frappe.throw(_("'To Date' is required")) + posting_datetime = get_datetime(add_to_date(filters["to_date"], days=1)) + sle = frappe.qb.DocType("Stock Ledger Entry") query = ( frappe.qb.from_(sle) @@ -121,7 +123,7 @@ def get_stock_ledger_entries_for_batch_no(filters): (sle.docstatus < 2) & (sle.is_cancelled == 0) & (sle.batch_no != "") - & (sle.posting_date <= filters["to_date"]) + & (sle.posting_datetime < posting_datetime) ) .groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse) .orderby(sle.item_code, sle.warehouse) diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js index 5e7dc8b2a63..67cf0ca9c3f 100644 --- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js +++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js @@ -1,8 +1,4 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/sales_trends_filters.js", function () { - frappe.query_reports["Delivery Note Trends"] = { - filters: erpnext.get_sales_trends_filters(), - }; -}); +frappe.query_reports["Delivery Note Trends"] = $.extend({}, erpnext.sales_trends_filters); diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js index bddfe5d7705..8a293e659fd 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js @@ -1,8 +1,4 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/purchase_trends_filters.js", function () { - frappe.query_reports["Purchase Receipt Trends"] = { - filters: erpnext.get_purchase_trends_filters(), - }; -}); +frappe.query_reports["Purchase Receipt Trends"] = $.extend({}, erpnext.purchase_trends_filters); diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py index 7bd8d704fda..15d9a12bc65 100644 --- a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py +++ b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py @@ -220,7 +220,7 @@ def get_serial_nos(doctype, txt, searchfield, start, page_len, filters): def get_batch_nos(doctype, txt, searchfield, start, page_len, filters): query_filters = {} - if txt: + if filters.get("voucher_no") and txt: query_filters["batch_no"] = ["like", f"%{txt}%"] if filters.get("voucher_no"): @@ -239,5 +239,8 @@ def get_batch_nos(doctype, txt, searchfield, start, page_len, filters): ) else: + if txt: + query_filters["name"] = ["like", f"%{txt}%"] + query_filters["item"] = filters.get("item_code") return frappe.get_all("Batch", filters=query_filters, as_list=True) 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 f229f73e683..6ef02724f65 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -9,6 +9,9 @@ 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 +BUYING_VOUCHER_TYPES = ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"] +SELLING_VOUCHER_TYPES = ["Sales Invoice", "Delivery Note"] + def execute(filters=None): columns = get_columns(filters) @@ -72,6 +75,20 @@ def get_columns(filters): "fieldname": "qty", "width": 150, }, + { + "label": _("Party Type"), + "fieldtype": "Link", + "fieldname": "party_type", + "options": "DocType", + "width": 90, + }, + { + "label": _("Party"), + "fieldtype": "Dynamic Link", + "fieldname": "party", + "options": "party_type", + "width": 120, + }, ] return columns @@ -102,6 +119,17 @@ def get_data(filters): } ) + # get party details depending on the voucher type + party_field = ( + "supplier" + if row.voucher_type in BUYING_VOUCHER_TYPES + else ("customer" if row.voucher_type in SELLING_VOUCHER_TYPES else None) + ) + args.party_type = party_field.title() if party_field else None + args.party = ( + frappe.db.get_value(row.voucher_type, row.voucher_no, party_field) if party_field else None + ) + serial_nos = [] if row.serial_no: parsed_serial_nos = get_serial_nos_from_sle(row.serial_no) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index b57333f9f35..e56c499d767 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -7,7 +7,7 @@ from collections import defaultdict import frappe from frappe import _ -from frappe.query_builder.functions import CombineDatetime +from frappe.query_builder.functions import CombineDatetime, Sum from frappe.utils import cint, flt from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions @@ -27,7 +27,11 @@ def execute(filters=None): items = get_items(filters) sl_entries = get_stock_ledger_entries(filters, items) item_details = get_item_details(items, sl_entries, include_uom) - opening_row = get_opening_balance(filters, columns, sl_entries) + if filters.get("batch_no"): + opening_row = get_opening_balance_from_batch(filters, columns, sl_entries) + else: + opening_row = get_opening_balance(filters, columns, sl_entries) + precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) bundle_details = {} @@ -48,21 +52,29 @@ def execute(filters=None): available_serial_nos = {} inventory_dimension_filters_applied = check_inventory_dimension_filters_applied(filters) - batch_balance_dict = defaultdict(float) + batch_balance_dict = frappe._dict({}) + if actual_qty and filters.get("batch_no"): + batch_balance_dict[filters.batch_no] = [actual_qty, stock_value] + for sle in sl_entries: item_detail = item_details[sle.item_code] sle.update(item_detail) if bundle_info := bundle_details.get(sle.serial_and_batch_bundle): - data.extend(get_segregated_bundle_entries(sle, bundle_info, batch_balance_dict)) + data.extend(get_segregated_bundle_entries(sle, bundle_info, batch_balance_dict, filters)) continue if filters.get("batch_no") or inventory_dimension_filters_applied: actual_qty += flt(sle.actual_qty, precision) stock_value += sle.stock_value_difference - batch_balance_dict[sle.batch_no] += sle.actual_qty + if sle.batch_no: + if not batch_balance_dict.get(sle.batch_no): + batch_balance_dict[sle.batch_no] = [0, 0] + + batch_balance_dict[sle.batch_no][0] += sle.actual_qty + if filters.get("segregate_serial_batch_bundle"): - actual_qty = batch_balance_dict[sle.batch_no] + actual_qty = batch_balance_dict[sle.batch_no][0] if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty: actual_qty = sle.qty_after_transaction @@ -90,7 +102,7 @@ def execute(filters=None): return columns, data -def get_segregated_bundle_entries(sle, bundle_details, batch_balance_dict): +def get_segregated_bundle_entries(sle, bundle_details, batch_balance_dict, filters): segregated_entries = [] qty_before_transaction = sle.qty_after_transaction - sle.actual_qty stock_value_before_transaction = sle.stock_value - sle.stock_value_difference @@ -109,9 +121,19 @@ def get_segregated_bundle_entries(sle, bundle_details, batch_balance_dict): } ) - if row.batch_no: - batch_balance_dict[row.batch_no] += row.qty - new_sle.update({"qty_after_transaction": batch_balance_dict[row.batch_no]}) + if filters.get("batch_no") and row.batch_no: + if not batch_balance_dict.get(row.batch_no): + batch_balance_dict[row.batch_no] = [0, 0] + + batch_balance_dict[row.batch_no][0] += row.qty + batch_balance_dict[row.batch_no][1] += row.stock_value_difference + + new_sle.update( + { + "qty_after_transaction": batch_balance_dict[row.batch_no][0], + "stock_value": batch_balance_dict[row.batch_no][1], + } + ) qty_before_transaction += row.qty stock_value_before_transaction += new_sle.stock_value_difference @@ -504,6 +526,69 @@ def get_sle_conditions(filters): return "and {}".format(" and ".join(conditions)) if conditions else "" +def get_opening_balance_from_batch(filters, columns, sl_entries): + query_filters = { + "batch_no": filters.batch_no, + "docstatus": 1, + "is_cancelled": 0, + "posting_date": ("<", filters.from_date), + "company": filters.company, + } + + for fields in ["item_code", "warehouse"]: + if filters.get(fields): + query_filters[fields] = filters.get(fields) + + opening_data = frappe.get_all( + "Stock Ledger Entry", + fields=["sum(actual_qty) as qty_after_transaction", "sum(stock_value_difference) as stock_value"], + filters=query_filters, + )[0] + + for field in ["qty_after_transaction", "stock_value", "valuation_rate"]: + if opening_data.get(field) is None: + opening_data[field] = 0.0 + + table = frappe.qb.DocType("Stock Ledger Entry") + sabb_table = frappe.qb.DocType("Serial and Batch Entry") + query = ( + frappe.qb.from_(table) + .inner_join(sabb_table) + .on(table.serial_and_batch_bundle == sabb_table.parent) + .select( + Sum(sabb_table.qty).as_("qty"), + Sum(sabb_table.stock_value_difference).as_("stock_value"), + ) + .where( + (sabb_table.batch_no == filters.batch_no) + & (sabb_table.docstatus == 1) + & (table.posting_date < filters.from_date) + & (table.is_cancelled == 0) + ) + ) + + for field in ["item_code", "warehouse", "company"]: + if filters.get(field): + query = query.where(table[field] == filters.get(field)) + + bundle_data = query.run(as_dict=True) + + if bundle_data: + opening_data.qty_after_transaction += flt(bundle_data[0].qty) + opening_data.stock_value += flt(bundle_data[0].stock_value) + if opening_data.qty_after_transaction: + opening_data.valuation_rate = flt(opening_data.stock_value) / flt( + opening_data.qty_after_transaction + ) + + return { + "item_code": _("'Opening'"), + "qty_after_transaction": opening_data.qty_after_transaction, + "valuation_rate": opening_data.valuation_rate, + "stock_value": opening_data.stock_value, + } + + def get_opening_balance(filters, columns, sl_entries): if not (filters.item_code and filters.warehouse and filters.from_date): return diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 573d7280ca1..1fa5665c141 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -55,8 +55,45 @@ class SerialBatchBundle: elif not self.sle.is_cancelled: self.validate_item_and_warehouse() + def is_material_transfer(self): + allowed_types = [ + "Material Transfer", + "Send to Subcontractor", + "Material Transfer for Manufacture", + ] + + if ( + self.sle.voucher_type == "Stock Entry" + and not self.sle.is_cancelled + and frappe.get_cached_value("Stock Entry", self.sle.voucher_no, "purpose") in allowed_types + ): + return True + + def make_serial_batch_no_bundle_for_material_transfer(self): + from erpnext.controllers.stock_controller import make_bundle_for_material_transfer + + bundle = frappe.db.get_value( + "Stock Entry Detail", self.sle.voucher_detail_no, "serial_and_batch_bundle" + ) + + if bundle: + new_bundle_id = make_bundle_for_material_transfer( + is_new=False, + docstatus=1, + voucher_type=self.sle.voucher_type, + voucher_no=self.sle.voucher_no, + serial_and_batch_bundle=bundle, + warehouse=self.sle.warehouse, + type_of_transaction="Inward" if self.sle.actual_qty > 0 else "Outward", + do_not_submit=0, + ) + self.sle.db_set({"serial_and_batch_bundle": new_bundle_id}) + def make_serial_batch_no_bundle(self): self.validate_item() + if self.sle.actual_qty > 0 and self.is_material_transfer(): + self.make_serial_batch_no_bundle_for_material_transfer() + return sn_doc = SerialBatchCreation( { @@ -143,6 +180,9 @@ class SerialBatchBundle: "serial_and_batch_bundle": sn_doc.name, } + if self.sle.actual_qty < 0 and self.is_material_transfer(): + values_to_update["valuation_rate"] = sn_doc.avg_rate + if not frappe.db.get_single_value( "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle" ): @@ -341,11 +381,9 @@ def get_serial_nos(serial_and_batch_bundle, serial_nos=None): if serial_nos: filters["serial_no"] = ("in", serial_nos) - entries = frappe.get_all("Serial and Batch Entry", fields=["serial_no"], filters=filters, order_by="idx") - if not entries: - return [] + serial_nos = frappe.get_all("Serial and Batch Entry", filters=filters, order_by="idx", pluck="serial_no") - return [d.serial_no for d in entries if d.serial_no] + return serial_nos def get_batches_from_bundle(serial_and_batch_bundle, batches=None): @@ -553,7 +591,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation): self.set_stock_value_difference() def get_batch_no_ledgers(self) -> list[dict]: - if not self.batches: + if not self.batchwise_valuation_batches: return [] parent = frappe.qb.DocType("Serial and Batch Bundle") @@ -575,7 +613,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation): Sum(child.qty).as_("qty"), ) .where( - (child.batch_no.isin(self.batches)) + (child.batch_no.isin(self.batchwise_valuation_batches)) & (parent.warehouse == self.sle.warehouse) & (parent.item_code == self.sle.item_code) & (parent.docstatus == 1) @@ -810,10 +848,14 @@ class SerialBatchCreation: new_package.docstatus = 0 new_package.warehouse = self.warehouse new_package.voucher_no = "" - new_package.posting_date = today() - new_package.posting_time = nowtime() + new_package.posting_date = self.posting_date if hasattr(self, "posting_date") else today() + new_package.posting_time = self.posting_time if hasattr(self, "posting_time") else nowtime() new_package.type_of_transaction = self.type_of_transaction new_package.returned_against = self.get("returned_against") + + if self.get("do_not_save"): + return new_package + new_package.save() self.serial_and_batch_bundle = new_package.name @@ -840,6 +882,9 @@ class SerialBatchCreation: self.set_auto_serial_batch_entries_for_inward() self.add_serial_nos_for_batch_item() + if hasattr(self, "via_landed_cost_voucher") and self.via_landed_cost_voucher: + doc.flags.via_landed_cost_voucher = self.via_landed_cost_voucher + self.set_serial_batch_entries(doc) if not doc.get("entries"): return frappe._dict({}) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 5c5fd83af2f..e804ae18016 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -36,6 +36,7 @@ from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, get_incoming_rate, get_or_make_bin, + get_serial_nos_data, get_stock_balance, get_valuation_method, ) @@ -220,6 +221,7 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): sle.flags.ignore_permissions = 1 sle.allow_negative_stock = allow_negative_stock sle.via_landed_cost_voucher = via_landed_cost_voucher + sle.set_posting_datetime() sle.submit() # Added to handle the case when the stock ledger entry is created from the repostig @@ -307,7 +309,15 @@ def get_reposting_data(file_path) -> dict: attached_file = frappe.get_doc("File", file_name) - data = gzip.decompress(attached_file.get_content()) + content = attached_file.get_content() + if isinstance(content, str): + content = content.encode("utf-8") + + try: + data = gzip.decompress(content) + except Exception: + return frappe._dict() + if data := json.loads(data.decode("utf-8")): data = data @@ -802,9 +812,10 @@ class update_entries_after: self.update_outgoing_rate_on_transaction(sle) def get_serialized_values(self, sle): + from erpnext.stock.serial_batch_bundle import SerialNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) - serial_nos = cstr(sle.serial_no).split("\n") if incoming_rate < 0: # wrong incoming rate @@ -817,8 +828,16 @@ class update_entries_after: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry if not sle.is_cancelled: - outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) - stock_value_change = -1 * outgoing_value + new_sle = copy.deepcopy(sle) + new_sle.qty = new_sle.actual_qty + new_sle.serial_nos = get_serial_nos_data(new_sle.get("serial_no")) + + sn_obj = SerialNoValuation( + sle=new_sle, warehouse=new_sle.get("warehouse"), item_code=new_sle.get("item_code") + ) + + outgoing_value = sn_obj.get_incoming_rate() + stock_value_change = actual_qty * outgoing_value else: stock_value_change = actual_qty * sle.outgoing_rate @@ -1263,6 +1282,8 @@ class update_entries_after: self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction def update_batched_values(self, sle): + from erpnext.stock.serial_batch_bundle import BatchNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) @@ -1273,21 +1294,25 @@ class update_entries_after: if actual_qty > 0: stock_value_difference = incoming_rate * actual_qty else: - outgoing_rate = get_batch_incoming_rate( - item_code=sle.item_code, - warehouse=sle.warehouse, - batch_no=sle.batch_no, - posting_date=sle.posting_date, - posting_time=sle.posting_time, - creation=sle.creation, + new_sle = copy.deepcopy(sle) + + new_sle.qty = new_sle.actual_qty + new_sle.batch_nos = frappe._dict({new_sle.batch_no: new_sle}) + batch_obj = BatchNoValuation( + sle=new_sle, + warehouse=new_sle.get("warehouse"), + item_code=new_sle.get("item_code"), ) + outgoing_rate = batch_obj.get_incoming_rate() + if outgoing_rate is None: # This can *only* happen if qty available for the batch is zero. # in such case fall back various other rates. # future entries will correct the overall accounting as each # batch individually uses moving average rates. outgoing_rate = self.get_fallback_rate(sle) + stock_value_difference = outgoing_rate * actual_qty self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference) @@ -1427,7 +1452,11 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc order by posting_datetime desc, creation desc limit 1 for update""", - args, + { + "item_code": args.get("item_code"), + "warehouse": args.get("warehouse"), + "posting_datetime": args.get("posting_datetime"), + }, as_dict=1, ) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 9d788f0809d..4ed73805314 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -235,9 +235,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll } has_unsupplied_items() { - return this.frm.doc["supplied_items"].some( - (item) => item.required_qty > item.supplied_qty - item.returned_qty - ); + let over_transfer_allowance = this.frm.doc.__onload.over_transfer_allowance; + return this.frm.doc["supplied_items"].some((item) => { + let required_qty = item.required_qty + (item.required_qty * over_transfer_allowance) / 100; + return required_qty > item.supplied_qty - item.returned_qty; + }); } make_subcontracting_receipt() { diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 8e146c9d49c..8419c8e9093 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -99,6 +99,12 @@ class SubcontractingOrder(SubcontractingController): } ] + def onload(self): + self.set_onload( + "over_transfer_allowance", + frappe.db.get_single_value("Buying Settings", "over_transfer_allowance"), + ) + def before_validate(self): super().before_validate() diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index d407d9c82d7..8dfd9bd486d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -302,6 +302,21 @@ frappe.ui.form.on("Subcontracting Receipt", { }; } }, + + reset_raw_materials_table: (frm) => { + frm.clear_table("supplied_items"); + + frm.call({ + method: "reset_raw_materials", + doc: frm.doc, + freeze: true, + callback: (r) => { + if (!r.exc) { + frm.save(); + } + }, + }); + }, }); frappe.ui.form.on("Landed Cost Taxes and Charges", { @@ -332,7 +347,7 @@ frappe.ui.form.on("Subcontracting Receipt Item", { set_missing_values(frm); }, - items_remove: (frm) => { + items_delete: (frm) => { set_missing_values(frm); }, diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 383a83b3fcd..8bcf97da40d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -47,8 +47,11 @@ "total_qty", "column_break_27", "total", - "raw_material_details", + "raw_materials_consumed_section", + "reset_raw_materials_table", + "column_break_uinr", "get_current_stock", + "raw_material_details", "supplied_items", "additional_costs_section", "distribute_additional_costs_based_on", @@ -300,6 +303,7 @@ "depends_on": "supplied_items", "fieldname": "raw_material_details", "fieldtype": "Section Break", + "hide_border": 1, "label": "Raw Materials Consumed", "options": "fa fa-table", "print_hide": 1, @@ -640,12 +644,26 @@ "fieldname": "supplier_delivery_note", "fieldtype": "Data", "label": "Supplier Delivery Note" + }, + { + "fieldname": "raw_materials_consumed_section", + "fieldtype": "Section Break", + "label": "Raw Materials Actions" + }, + { + "fieldname": "reset_raw_materials_table", + "fieldtype": "Button", + "label": "Reset Raw Materials Table" + }, + { + "fieldname": "column_break_uinr", + "fieldtype": "Column Break" } ], "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2023-11-16 13:04:00.710534", + "modified": "2024-05-28 15:02:13.517969", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", @@ -714,4 +732,4 @@ "timeline_field": "supplier", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 19e8dfd5431..5e717e1f22a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -179,6 +179,11 @@ class SubcontractingReceipt(SubcontractingController): self.update_status() self.delete_auto_created_batches() + @frappe.whitelist() + def reset_raw_materials(self): + self.supplied_items = [] + self.create_raw_materials_supplied() + def validate_closed_subcontracting_order(self): for item in self.items: if item.subcontracting_order: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 996a99065bb..81662a6257b 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -1235,6 +1235,116 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertTrue(scr.items[0].serial_and_batch_bundle) self.assertTrue(scr.items[0].rejected_serial_and_batch_bundle) + def test_subcontracting_receipt_for_batch_materials_without_use_serial_batch_fields(self): + from erpnext.controllers.subcontracting_controller import make_rm_stock_entry + + set_backflush_based_on("Material Transferred for Subcontract") + + fg_item = make_item( + "Test Subcontracted FG Item With Batch No and Without Use Serial Batch Fields", + properties={"is_stock_item": 1, "is_sub_contracted_item": 1}, + ).name + + rm_item1 = make_item( + "Test Subcontracted RM Item With Batch No and Without Use Serial Batch Fields", + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BATCH-RM-BNGS-.####", + }, + ).name + + make_item( + "Subcontracted Service Item 21", + properties={ + "is_stock_item": 0, + }, + ) + + bom = make_bom(item=fg_item, raw_materials=[rm_item1]) + + rm_batch_no = None + for row in bom.items: + se = make_stock_entry( + item_code=row.item_code, + qty=10, + target="_Test Warehouse - _TC", + rate=300, + ) + + se.reload() + rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 21", + "qty": 10, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + self.assertTrue(sco.docstatus) + rm_items = [ + { + "name": sco.supplied_items[0].name, + "item_code": rm_item1, + "rm_item_code": rm_item1, + "item_name": rm_item1, + "qty": 10, + "warehouse": "_Test Warehouse - _TC", + "rate": 100, + "stock_uom": frappe.get_cached_value("Item", rm_item1, "stock_uom"), + "use_serial_batch_fields": 1, + }, + ] + se = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) + se.items[0].subcontracted_item = fg_item + se.items[0].s_warehouse = "_Test Warehouse - _TC" + se.items[0].t_warehouse = "_Test Warehouse 1 - _TC" + se.items[0].use_serial_batch_fields = 1 + se.items[0].batch_no = rm_batch_no + se.submit() + + self.assertEqual(se.items[0].batch_no, rm_batch_no) + self.assertEqual(se.items[0].use_serial_batch_fields, 1) + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty = 2 + scr.save() + scr.submit() + + self.assertEqual(scr.supplied_items[0].consumed_qty, 2) + self.assertEqual(scr.supplied_items[0].batch_no, rm_batch_no) + self.assertEqual(get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle), rm_batch_no) + + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty = 2 + scr.save() + scr.submit() + + self.assertEqual(scr.supplied_items[0].consumed_qty, 2) + self.assertEqual(scr.supplied_items[0].batch_no, rm_batch_no) + self.assertEqual(get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle), rm_batch_no) + + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty = 6 + scr.save() + scr.submit() + + self.assertEqual(scr.supplied_items[0].consumed_qty, 6) + self.assertEqual(scr.supplied_items[0].batch_no, rm_batch_no) + self.assertEqual(get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle), rm_batch_no) + + sco.reload() + self.assertEqual(sco.status, "Completed") + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def make_return_subcontracting_receipt(**args): args = frappe._dict(args) diff --git a/erpnext/templates/print_formats/includes/total.html b/erpnext/templates/print_formats/includes/total.html index 879203bbf25..f964047bd08 100644 --- a/erpnext/templates/print_formats/includes/total.html +++ b/erpnext/templates/print_formats/includes/total.html @@ -1,14 +1,14 @@ -
+
{% if doc.flags.show_inclusive_tax_in_print %}
-
+
{{ doc.get_formatted("net_total", doc) }}
{% else %}
-
+
{{ doc.get_formatted("total", doc) }}
{% endif %} diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv index e030efd6e86..f79acdbb6c6 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -46,27 +46,27 @@ Account Type,Hesap Türü, Account Type for {0} must be {1},{0} için hesap türü {1} olmalı, "Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'",Bakiye alacaklı durumdaysa borçlu duruma çevrilemez., "Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'",Bakiye borçlu durumdaysa alacaklı durumuna çevrilemez., -Account number for account {0} is not available.
Please setup your Chart of Accounts correctly.,Hesap {0} için hesap numarası mevcut değil.
Lütfen Hesap Tablonuzu doğru ayarlayın., -Account with child nodes cannot be converted to ledger,Alt hesapları bulunan hesaplar muhasebe defterine dönüştürülemez., -Account with child nodes cannot be set as ledger,Alt düğümleri olan hesaplar Hesap Defteri olarak ayarlanamaz, -Account with existing transaction can not be converted to group.,İşlem görmüş hesap kartları dönüştürülemez., -Account with existing transaction can not be deleted,İşlem görmüş hesaplar silinemez., -Account with existing transaction cannot be converted to ledger,İşlem görmüş hesaplar muhasebe defterine dönüştürülemez., -Account {0} does not belong to company: {1},Hesap {0} Şirkete ait değil: {1}, -Account {0} does not belongs to company {1},Hesap {0} yapan şirkete ait değil {1}, -Account {0} does not exist,Hesap {0} yok, -Account {0} does not exists,Hesap {0} yok, -Account {0} does not match with Company {1} in Mode of Account: {2},"Hesap {0}, hesap modunda {1} şirketi ile eşleşmez: {2}", -Account {0} has been entered multiple times,Hesap {0} birden çok kez girilmiş, -Account {0} is added in the child company {1},{1} alt barındırma {0} hesabı eklendi, -Account {0} is frozen,Hesap {0} donduruldu, -Account {0} is invalid. Account Currency must be {1},Hesap {0} geçersiz. Hesap Para Birimi olmalıdır {1}, -Account {0}: Parent account {1} can not be a ledger,Hesap {0}: Ana hesap {1} bir defter olamaz, -Account {0}: Parent account {1} does not belong to company: {2},Hesap {0}: Ana hesap {1} şirkete ait değil: {2}, -Account {0}: Parent account {1} does not exist,Hesap {0}: Ana hesap {1} yok, -Account {0}: You can not assign itself as parent account,Hesap {0}: üretken bir ana hesap olarak atayamazsınız, -Account: {0} can only be updated via Stock Transactions,Hesap: {0} sadece Stok İşlemleri üzerinden güncellenebilir, -Account: {0} with currency: {1} can not be selected,Hesap: {0} para ile: {1} seçilemez, +Account number for account {0} is not available.
Please setup your Chart of Accounts correctly.,{0} hesabına ait hesap numarası mevcut değil.
Lütfen Hesap Planınızı doğru şekilde ayarlayın., +Account with child nodes cannot be converted to ledger,Alt düğümleri olan hesap genel muhasebeye dönüştürülemez, +Account with child nodes cannot be set as ledger,Alt düğümlere sahip hesap genel muhasebe olarak ayarlanamaz, +Account with existing transaction can not be converted to group.,İşlem görmüş hesap gruba dönüştürülemez., +Account with existing transaction can not be deleted,İşlem görmüş hesap silinemez., +Account with existing transaction cannot be converted to ledger,İşlem görmüş hesap muhasebe defterine dönüştürülemez., +Account {0} does not belong to company: {1},Hesap {0} şu şirkete ait değil: {1}, +Account {0} does not belongs to company {1},"Hesap {0}, {1} şirketine ait değil", +Account {0} does not exist,{0} hesabı mevcut değil, +Account {0} does not exists,{0} hesabı mevcut değil, +Account {0} does not match with Company {1} in Mode of Account: {2},"Hesap {0}, Hesap Modunda {1} Şirketi ile eşleşmiyor: {2}", +Account {0} has been entered multiple times,{0} hesabına birden çok kez girildi, +Account {0} is added in the child company {1},"{0} hesabı, {1} alt şirketine eklendi", +Account {0} is frozen,{0} hesabı donduruldu, +Account {0} is invalid. Account Currency must be {1},{0} hesabı geçersiz. Hesabın Para Birimi {1} olmalıdır, +Account {0}: Parent account {1} can not be a ledger,"Hesap {0}: Ana hesap {1}, genel muhasebe olamaz", +Account {0}: Parent account {1} does not belong to company: {2},Hesap {0}: Ana hesap {1} şu şirkete ait değil: {2}, +Account {0}: Parent account {1} does not exist,Hesap {0}: Ebeveyn hesabı {1} mevcut değil, +Account {0}: You can not assign itself as parent account,Hesap {0}: Kendini ana hesap olarak atayamazsınız, +Account: {0} can only be updated via Stock Transactions,Hesap: {0} yalnızca Hisse Senedi İşlemleri yoluyla güncellenebilir, +Account: {0} with currency: {1} can not be selected,Hesap: {0} ve para birimi: {1} seçilemez, Accountant,Muhasebeci, Accounting,Muhasebe, Accounting Entry for Asset,Varlık Muhasebe Kaydı, @@ -120,19 +120,19 @@ Add Suppliers,Tedarikçi Ekle, Add Time Slots,Zaman Dilimi Ekle, Add Timesheets,Zaman Çizelgesi Ekle, Add Timeslots,Zaman Dilimi Ekle, -Add Users to Marketplace,Kullanıcıları Pazaryerine Ekle, -Add a new address,yeni bir adres ekleyin, -Add cards or custom sections on homepage,Ana sayfaya kart veya özel bölüm ekleme, -Add more items or open full form,Daha fazla ürün ekle veya Tüm Formu aç, +Add Users to Marketplace,Marketplace Kullanıcıları Ekle, +Add a new address,Yeni bir adres ekle, +Add cards or custom sections on homepage,Ana sayfaya kart veya özel bölüm ekle, +Add more items or open full form,Daha fazla ürün ekle veya tüm formu aç, Add notes,Not Ekle, Add the rest of your organization as your users. You can also add invite Customers to your portal by adding them from Contacts,"Kuruluşunuzun geri kalanını kullanıcı olarak ekleyin. Ayrıca, müşterileri portalınıza ilave ederek, bunları kişilerden ekleyerek de ekleyebilirsiniz.", Add/Remove Recipients,Alıcıları Ekle/Kaldır, Added,Eklendi, Added {0} users,{0} kullanıcı eklendi, -Additional Salary Component Exists.,Ek Maaş Bileşeni Vardır., +Additional Salary Component Exists.,Ek Maaş Bileşeni Var., Address,Adres, Address Line 2,Adres Satırı 2, -Address Name,Adres adı, +Address Name,Adres Adı, Address Title,Adres Başlığı, Address Type,Adres Tipi, Administrative Expenses,Yönetim Giderleri, @@ -151,17 +151,17 @@ Advertising,Reklamcılık, Aerospace,Havacılık ve Uzay;, Against,Karşı, Against Account,Hesap Karşılığı, -Against Journal Entry {0} does not have any unmatched {1} entry,Journal Karşı giriş {0} herhangi eşsiz {1} girişi yok, -Against Journal Entry {0} is already adjusted against some other voucher,Journal Karşı giriş {0} zaten başka çeki karşı ayarlanır, -Against Supplier Invoice {0} dated {1},{1} tarihli {0} Tedarikçi Faturası karşılığı, +Against Journal Entry {0} does not have any unmatched {1} entry,Yevmiye Kaydına Karşı {0}'da eşleşmeyen {1} girişi yok, +Against Journal Entry {0} is already adjusted against some other voucher,Yevmiye Karşılığı {0} zaten başka bir fişe göre ayarlanmıştır, +Against Supplier Invoice {0} dated {1},{1} tarihli {0} Tedarikçi Faturasına Karşı, Against Voucher,Fiş Karşılığı, -Against Voucher Type,Fiş Tipi Karşılığı, +Against Voucher Type,Fiş Türü Karşılığı, Age,Yaş, Age (Days),Yaş (Gün), -Ageing Based On,Yaşlandırma Temeli, +Ageing Based On,Yaşlanma Dayanımı, Ageing Range 1,Yaşlanma Aralığı 1, -Ageing Range 2,Yaşlanma aralığı 2, -Ageing Range 3,Yaşlanma aralığı 3, +Ageing Range 2,Yaşlanma Aralığı 2, +Ageing Range 3,Yaşlanma Aralığı 3, Agriculture,Tarım, Agriculture (beta),Tarım (beta), Airline,Havayolu, @@ -182,57 +182,57 @@ All Supplier Groups,Tüm Tedarikçi Grupları, All Supplier scorecards.,Tüm Tedarikçi puan kartları., All Territories,Tüm Bölgeler, All Warehouses,Tüm Depolar, -All communications including and above this shall be moved into the new Issue,"Bunları içeren ve bunun üstündeki tüm iletişim, yeni sayıya taşınacaktır.", +All communications including and above this shall be moved into the new Issue,Bu dahil ve bunun üzerindeki tüm iletişimler yeni Sayıya taşınacaktır., All items have already been transferred for this Work Order.,Bu İş Emri için tüm öğeler zaten aktarıldı., -All other ITC,Diğer Tüm ITC, -All the mandatory Task for employee creation hasn't been done yet.,Çalışan yaratmak için tüm zorunlu görev henüz yapılmamış., -Allocate Payment Amount,Ödeme Tutarı Ayır, -Allocated Amount,Ayrılan Tutar, -Allocating leaves...,İzinler tahsis ediliyor ..., -Already record exists for the item {0},Zaten {0} öğesi için kayıt var, -"Already set default in pos profile {0} for user {1}, kindly disabled default","{1} kullanıcısı için {0} pos profilinde varsayılan olarak varsayılan değer ayarladınız, varsayılan olarak lütfen devre dışı bırakıldı", -Alternate Item,Alternatif Öğe, -Alternative item must not be same as item code,"Alternatif öğe, ürün koduyla aynı olmamalıdır", -Amended From,İtibaren değiştirilmiş, -Amount,Tutar, -Amount After Depreciation,Değer kaybı sonrası tutar, -Amount of Integrated Tax,Entegre Vergi Miktarı, -Amount of TDS Deducted,TDS'den Düşülen Tutar, -Amount should not be less than zero.,Miktar sıfırdan daha az olmamalıdır., -Amount to Bill,Faturalanacak Tutar, -Amount {0} {1} against {2} {3},Miktar {0} {2} yani {1} {3}, -Amount {0} {1} deducted against {2},{2}'ye karşılık düşülecek miktar {0} {1}, -Amount {0} {1} transferred from {2} to {3},{0} {1} miktarı {2}'den {3}'e aktarılacak, -Amount {0} {1} {2} {3},Miktar {0} {1} {2} {3}, +All other ITC,Diğer tüm ITC, +All the mandatory Task for employee creation hasn't been done yet.,Çalışan oluşturmaya yönelik zorunlu Görevlerin tümü henüz yapılmadı., +Allocate Payment Amount,Ödeme Tutarını Tahsis Et, +Allocated Amount,Tahsis Edilen Tutar, +Allocating leaves...,İzinler tahsis ediliyor..., +Already record exists for the item {0},{0} öğesi için zaten kayıt mevcut, +"Already set default in pos profile {0} for user {1}, kindly disabled default","{1} kullanıcısı için {0} konum profilinde zaten varsayılan ayarlandı, varsayılanı devre dışı bırakmanızı rica ederiz", +Alternate Item,Alternatif Ürün, +Alternative item must not be same as item code,"Alternatif ürün, ürün koduyla aynı olmamalıdır", +Amended From,Şu tarihten itibaren değiştirildi:, +Amount,Miktar, +Amount After Depreciation,Amortisman Sonrası Tutar, +Amount of Integrated Tax,Entegre Vergi Tutarı, +Amount of TDS Deducted,Kesilen TDS Tutarı, +Amount should not be less than zero.,Tutar sıfırdan az olmamalıdır., +Amount to Bill,Fatura Tutarı, +Amount {0} {1} against {2} {3},{2} {3}'a karşı {0} {1} tutarı, +Amount {0} {1} deducted against {2},{2} karşılığından düşülen {0} {1} tutarı, +Amount {0} {1} transferred from {2} to {3},{2}'den {3}'a aktarılan {0} {1} tutarı, +Amount {0} {1} {2} {3},Tutar {0} {1} {2} {3}, Amt,Tutar, -"An Item Group exists with same name, please change the item name or rename the item group","Bir Ürün grubu aynı isimle bulunuyorsa, lütfen Ürün veya Ürün grubu etiketine bakın", -An academic term with this 'Academic Year' {0} and 'Term Name' {1} already exists. Please modify these entries and try again.,Bu 'Akademik Yılı' ile akademik bir terim {0} ve 'Vadeli Adı' {1} zaten var. Bu girişleri değiştirin ve tekrar deneyin., +"An Item Group exists with same name, please change the item name or rename the item group","Aynı adda bir Öğe Grubu mevcut, lütfen öğe adını değiştirin veya öğe grubunu yeniden adlandırın", +An academic term with this 'Academic Year' {0} and 'Term Name' {1} already exists. Please modify these entries and try again.,Bu 'Akademik Yılı' {0} ve 'Dönem Adı' {1}'nı içeren bir akademik dönem zaten mevcut. Lütfen bu girişleri değiştirin ve tekrar deneyin., An error occurred during the update process,Güncelleme işlemi sırasında bir hata oluştu, -"An item exists with same name ({0}), please change the item group name or rename the item","Bir Ürün aynı isimle bulunuyorsa ({0}), lütfen madde grubunu veya çıldırtıcı etiketini", +"An item exists with same name ({0}), please change the item group name or rename the item","Aynı adda ({0}) bir öğe mevcut, lütfen öğe grubu adını değiştirin veya öğeyi yeniden adlandırın", Analyst,Analist, -Annual Billing: {0},Yıllık Fatura: {0}, -Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4},{1} '{2}' karşı bir başka bütçe kitabı '{0}' zaten var ve {4} mali yılı için '{3}' hesap var, -Another Period Closing Entry {0} has been made after {1},{1} den sonra başka bir dönem kapatma girdisi {0} yapılmıştır, -Another Sales Person {0} exists with the same Employee id,Başka Satış Kişi {0} aynı çalışan pozisyonu ile var, +Annual Billing: {0},Yıllık Faturalandırma: {0}, +Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4},{4} mali yılı için {1} '{2}' ve '{3}' hesabına karşı başka bir '{0}' Bütçe kaydı zaten mevcut, +Another Period Closing Entry {0} has been made after {1},{1} tarihinden sonra başka bir Dönem Kapanış Girişi {0} yapıldı, +Another Sales Person {0} exists with the same Employee id,Aynı Çalışan kimliğine sahip başka bir Satış Görevlisi {0} mevcut, Antibiotic,Antibiyotik, Apparel & Accessories,Giyim ve Aksesuar, -Applicable For,Uygulanabilir:, -"Applicable if the company is SpA, SApA or SRL","Şirket SpA, SApA veya SRL ise uygulanabilir", -Applicable if the company is a limited liability company,Şirket limited şirketi ise uygulanabilir, -Applicable if the company is an Individual or a Proprietorship,Şirket Birey veya Mülkiyet ise uygulanabilir, -Application of Funds (Assets),fon (varlık) çalışması, +Applicable For,Uygulanabilirlik, +"Applicable if the company is SpA, SApA or SRL","Şirketin SpA, SAPA veya SRL olması durumunda geçerlidir", +Applicable if the company is a limited liability company,Şirketin limited şirket olması halinde geçerlidir, +Applicable if the company is an Individual or a Proprietorship,Şirketin Şahıs veya Mülkiyet olması halinde geçerlidir, +Application of Funds (Assets),Fon Başvurusu (Varlıklar), Applied,Başvuruldu, Appointment Confirmation,Randevu onayı, -Appointment Duration (mins),Randevu Süresi (dk.), +Appointment Duration (mins),Randevu Süresi (dakika), Appointment Type,Randevu Türü, Appointment {0} and Sales Invoice {1} cancelled,Randevu {0} ve Satış Faturası {1} iptal edildi, Appointments and Encounters,Randevular ve Muayeneleri, Appointments and Patient Encounters,Randevular ve Hasta Muayeneleri, -Appraisal {0} created for Employee {1} in the given date range,Verilen aralıkta çalışan {1} için çalıştırılan değerlendirme {0}, -Approving Role cannot be same as role the rule is Applicable To,Onaylayan Rol kuralın geçerli olduğu rolle aynı olamaz, -Approving User cannot be same as user the rule is Applicable To,Onaylayan Kullanıcı kuralın Uygulandığı Kullanıcı ile aynı olamaz, -"Apps using current key won't be able to access, are you sure?","Geçerli anahtarı kullanan uygulamalar erişemeyecek, emin misiniz??", -Are you sure you want to cancel this appointment?,Bu randevuyu iptal etmek istediğinize emin misiniz?, +Appraisal {0} created for Employee {1} in the given date range,Belirtilen tarih aralığında Çalışan {1} için {0} değerlendirmesi oluşturuldu, +Approving Role cannot be same as role the rule is Applicable To,"Onaylanan Rol, kuralın Uygulanabileceği rolle aynı olamaz", +Approving User cannot be same as user the rule is Applicable To,"Onaylayan Kullanıcı, kuralın Uygulanacağı kullanıcıyla aynı olamaz", +"Apps using current key won't be able to access, are you sure?",Geçerli anahtarı kullanan uygulamaların erişemeyeceğinden emin misiniz?, +Are you sure you want to cancel this appointment?,Bu randevuyu iptal etmek istediğinizden emin misiniz?, Arrear,Borç/Bakiye, As Examiner,Denetmen olarak, As On Date,Tarihinde gibi, @@ -242,10 +242,10 @@ As per section 17(5),Bölüm 17'ye göre (5), Assessment,Değerlendirme, Assessment Criteria,Değerlendirme Kriterleri, Assessment Group,Değerlendirme Grubu, -Assessment Group: ,Değerlendirme Grubu:, +Assessment Group: ,Değerlendirme Grubu: , Assessment Plan,Değerlendirme Planı, Assessment Plan Name,Değerlendirme Planı Adı, -Assessment Report,değerlendirme raporu, +Assessment Report,Değerlendirme Raporu, Assessment Reports,Değerlendirme Raporları, Assessment Result,Değerlendirme Sonucu, Assessment Result record {0} already exists.,Değerlendirme Sonuç kaydı {0} zaten var., @@ -256,39 +256,39 @@ Asset Maintenance,Varlık Bakımı, Asset Movement,Varlık Hareketi, Asset Movement record {0} created,Varlık Hareket kaydı {0} oluşturuldu, Asset Name,Varlık Adı, -Asset Received But Not Billed,Alınan ancak Faturalandırılmayan Öğe, -Asset Value Adjustment,Varlık Değeri Ayarlaması, -"Asset cannot be cancelled, as it is already {0}","Varlık iptal edilemez, hala {0}", -Asset scrapped via Journal Entry {0},"Varlık, Yevmiye Kaydı {0} ile hurda edildi", -"Asset {0} cannot be scrapped, as it is already {1}","{0} varlığı hurda edilemez, {1} da var olarak gözüküyor", -Asset {0} does not belong to company {1},"Varlık {0}, {1} firmasına ait değil", -Asset {0} must be submitted,{0} ın varlığı onaylanmalı, -Assets,Aktifler, -Assign To,Ata, -Associate,Ortak, -At least one mode of payment is required for POS invoice.,Ödeme en az bir mod POS fatura için gereklidir., -Atleast one item should be entered with negative quantity in return document,En az bir öğe dönüş belgesinde negatif miktar ile girilmelidir, -Atleast one of the Selling or Buying must be selected,Satış veya Alıştan en az biri seçilmelidir, +Asset Received But Not Billed,Varlık Alındı Ancak Faturalandırılmadı, +Asset Value Adjustment,Varlık Değer Ayarlaması, +"Asset cannot be cancelled, as it is already {0}",Öğe zaten {0} olduğundan iptal edilemez, +Asset scrapped via Journal Entry {0},Varlık {0} Yevmiye Girişi yoluyla hurdaya çıkarıldı, +"Asset {0} cannot be scrapped, as it is already {1}",{0} numaralı varlık zaten {1} olduğundan hurdaya çıkarılamaz, +Asset {0} does not belong to company {1},{0} varlığı {1} şirketine ait değil, +Asset {0} must be submitted,{0} numaralı varlık gönderilmelidir, +Assets,Varlıklar, +Assign To,Atamak, +Associate,İş arkadaşı, +At least one mode of payment is required for POS invoice.,POS faturası için en az bir ödeme şekli gereklidir., +Atleast one item should be entered with negative quantity in return document,İade belgesinde en az bir ürün negatif miktarla girilmelidir, +Atleast one of the Selling or Buying must be selected,Satış veya Alış seçeneklerinden en az biri seçilmelidir, Atleast one warehouse is mandatory,En az bir depo zorunludur, Attach Logo,Logo Ekle, -Attachment,Belge Eki, -Attachments,Belge Ekleri, -Attendance can not be marked for future dates,İlerideki tarihler için katılım işaretlenemez, -Attendance date can not be less than employee's joining date,Katılım tarihi çalışanın işe giriş tarihinden önce olamaz, -Attendance for employee {0} is already marked,Çalışan {0} için devam zaten işaretlenmiştir, -Attendance has been marked successfully.,Mevcudiyet başarıyla işaretlendi, -Attendance not submitted for {0} as {1} on leave.,"Katılım, {0} için ayrılmadan önce {1} olarak gönderilmedi.", +Attachment,EK, +Attachments,Ekler, +Attendance can not be marked for future dates,Gelecek tarihler için katılım işaretlenemez, +Attendance date can not be less than employee's joining date,Devam tarihi çalışanın işe giriş tarihinden az olamaz, +Attendance for employee {0} is already marked,{0} adlı çalışanın katılımı zaten işaretlendi, +Attendance has been marked successfully.,Katılım başarıyla işaretlendi., +Attendance not submitted for {0} as {1} on leave.,{1} izinli olduğundan {0} için katılım bilgisi gönderilmedi., Attribute table is mandatory,Özellik tablosu zorunludur, -Attribute {0} selected multiple times in Attributes Table,Özellik {0} Nitelikler Tablo birden çok kez seçilmiş, -Authorized Signatory,Yetkili imza, -Auto Material Requests Generated,Otomatik Malzeme İstekler Oluşturulmuş, +Attribute {0} selected multiple times in Attributes Table,Nitelikler Tablosunda {0} özelliği birden çok kez seçildi, +Authorized Signatory,Yetkili İmza, +Auto Material Requests Generated,Otomatik Malzeme Talepleri Oluşturuldu, Auto Repeat,Otomatik Tekrarla, -Auto repeat document updated,Otomatik tekrar dokümanı güncellendi, +Auto repeat document updated,Otomatik tekrarlanan belge güncellendi, Automotive,Otomotiv, Available,Mevcut, Available Qty,Mevcut Miktar, Available Selling,Mevcut Satış, -Available for use date is required,Kullanılabilir olacağı tarih gereklidir, +Available for use date is required,Kullanıma hazır olma tarihi gerekli, Available slots,Kullanılabilir alanlar, Available {0},Mevcut {0}, Available-for-use Date should be after purchase date,"Kullanıma hazır tarih, Satınalma tarihinden sonra olmalıdır.", @@ -309,12 +309,12 @@ BOM {0} does not belong to Item {1},Ürün Ağacı {0} {1} Kalemine ait değil, BOM {0} must be active,Ürün Ağacı {0} aktif olmalıdır, BOM {0} must be submitted,Ürün Ağacı {0} devreye alınmalıdır, Balance,Bakiye, -Balance (Dr - Cr),Denge (Dr - Cr), +Balance (Dr - Cr),Bakiye (Borç - Alacak), Balance ({0}),Bakiye ({0}), -Balance Qty,Denge Adet, +Balance Qty,Bakiye Miktar, Balance Sheet,Bilanço, -Balance Value,Mevcut Maliyet, -Balance for Account {0} must always be {1},Hesap {0} her zaman dengede olmalı {1}, +Balance Value,Bakiye Değeri, +Balance for Account {0} must always be {1},{0} Hesabının Bakiyesi her zaman {1} olmalıdır, Bank,Banka, Bank Account,Banka Hesabı, Bank Accounts,Banka Hesapları, @@ -325,7 +325,7 @@ Bank Reconciliation,Banka Mutabakatı, Bank Reconciliation Statement,Banka Mutabakat Kaydı, Bank Statement,Banka Ekstresi, Bank Statement Settings,Banka Ekstre Ayarları, -Bank Statement balance as per General Ledger,Genel Muhasebe uyarınca Banka Hesap bakiyesi, +Bank Statement balance as per General Ledger,Defteri Kebire göre Hesap Ekstresi Bakiyesi, Bank account cannot be named as {0},Banka hesabı adı {0} olamaz, Bank/Cash transactions against party or for internal transfer,Cariye karşı veya iç transfer için Banka / Kasa işlemleri, Banking,Banka İşlemleri, @@ -359,7 +359,7 @@ Billing Address,Fatura Adresi, Billing Address is same as Shipping Address,"Fatura Adresi, Teslimat Adresiyle aynı", Billing Amount,Fatura Tutarı, Billing Status,Fatura Durumu, -Billing currency must be equal to either default company's currency or party account currency,"Faturalandırma para birimi, varsayılan şirketin para birimi veya Cari hesabı para birimine eşit olmalıdır", +Billing currency must be equal to either default company's currency or party account currency,"Faturalandırma para birimi, varsayılan Şirketin para birimine veya Cari hesabın para birimine eşit olmalıdır", Bills raised by Suppliers.,Tedarikçiler tarafından artırılan faturalar, Bills raised to Customers.,Müşterilere artırılan faturalar, Biotechnology,Biyoteknoloji, @@ -399,52 +399,52 @@ CWIP Account,CWIP Hesabı, Calculated Bank Statement balance,Hesaplanan Banka Hesap Bakiyesi, Campaign,Kampanya, Can be approved by {0},{0} tarafından onaylandı, -"Can not filter based on Account, if grouped by Account","Hesap, olarak gruplandırıldı ise Hesaba tabanlı yönetim yönetimi", -"Can not filter based on Voucher No, if grouped by Voucher","Dekont, olarak gruplandırıldıysa, Makbuz numarasına dayalı yönetim yönetimi", +"Can not filter based on Account, if grouped by Account",Hesaba göre gruplandırılmışsa Hesaba göre filtreleme yapılamaz, +"Can not filter based on Voucher No, if grouped by Voucher",Fişe göre gruplandırılmışsa Fiş Numarasına göre filtreleme yapılamaz, "Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}","Yatan Hasta Kaydı Taburcu Edildi olarak işaretlenemiyor, Faturalanmamış Faturalar Var {0}", -Can only make payment against unbilled {0},Sadece karşı ödeme yapamazsınız faturalanmamış {0}, -Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total',Eğer ücret tipi 'Önceki Satır Tutarında' veya 'Önceki Satır Toplamı' ise referans verebilir, -"Can't change valuation method, as there are transactions against some items which does not have it's own valuation method",Kendi değerleme yöntemine sahip olmayan bazı ürünlere karşı işlemler olduğu için değerleme kullanımı değiştiremezsiniz, -Can't create standard criteria. Please rename the criteria,Standart ölçüler oluşturulamıyor. Lütfen ölçütleri yeniden tanımlayanın, -Cancel,İptal, -Cancel Material Visit {0} before cancelling this Warranty Claim,Malzeme ziyareti {0} Bu Garanti Talebi iptal edilmeden önce iptal, -Cancel Material Visits {0} before cancelling this Maintenance Visit,Bu Bakım Ziyaretini iptal etmeden önce Malzeme Ziyareti {0} iptal edin, +Can only make payment against unbilled {0},Yalnızca faturalandırılmamış {0} karşılığında ödeme yapılabilir, +Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total',Yalnızca ücret türü 'Önceki Satırdaki Tutar' veya 'Önceki Satır Toplamı' ise satıra başvurulabilir, +"Can't change valuation method, as there are transactions against some items which does not have it's own valuation method",Kendi değerleme yöntemi olmayan bazı kalemlere karşı işlemler olduğundan değerleme yöntemi değiştirilemiyor, +Can't create standard criteria. Please rename the criteria,Standart ölçütler oluşturulamıyor. Lütfen kriterleri yeniden adlandırın, +Cancel,İptal etmek, +Cancel Material Visit {0} before cancelling this Warranty Claim,Bu Garanti Talebini iptal etmeden önce Malzeme Ziyaretini İptal Edin {0}, +Cancel Material Visits {0} before cancelling this Maintenance Visit,Bu Bakım Ziyaretini iptal etmeden önce {0} Malzeme Ziyaretlerini İptal Edin, Cancel Subscription,Aboneliği iptal et, -Cancel the journal entry {0} first,Önce {0} yevmiye kaydını iptal et, +Cancel the journal entry {0} first,Önce {0} yevmiye kaydını iptal edin, Canceled,İptal edildi, -"Cannot Submit, Employees left to mark attendance","Gönderilemiyor, çalışanlar katılmak için ayrılmış", -Cannot be a fixed asset item as Stock Ledger is created.,Stok Defteri oluşturulduğu sabit bir varlık kalemi olamaz., -Cannot cancel because submitted Stock Entry {0} exists,Sunulan Stok Giriş {0} varolduğundan iptal edilemiyor, -Cannot cancel transaction for Completed Work Order.,Tamamlanmış İş Emri için işlemi iptal edemez., -Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3},"{0} {1} tarihinde iptal edilemedi, çünkü Seri No {2} depoya {3} ait değil.", -Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,Hisse senetlerini oluşturduktan sonra değiştiremezsiniz. Yeni Bir Öğe Yapın ve Stokları Yeni Öğe Taşı, -Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved.,Mali Yıl Başlangıç Tarihi ve Mali Yılı kaydedildikten sonra Mali Yıl Sonu Tarihi değiştiremezsiniz., -Cannot change Service Stop Date for item in row {0},{0} satır satırdaki öğe için Hizmet Durdurma Tarihi değiştirilemez, -Cannot change Variant properties after stock transaction. You will have to make a new Item to do this.,Stok yapıldıktan sonra Varyant özellikleri değiştirilemez. Bunu yapmak için yeni bir öğe almanız gerekir., -"Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency.","Mevcut işletimlerinden, genel genel para birimini değiştiremezsiniz. İşlemler Varsayılan para birimini değiştirmek için iptal edilmelidir.", -Cannot change status as student {0} is linked with student application {1},öğrenci olarak değiştirilemez {0} öğrenci uygulaması ile bağlantılı {1}, -Cannot convert Cost Center to ledger as it has child nodes,Çocuk düğümleri nedeniyle Maliyet Merkezi ana deftere dönüştürülemez, -Cannot covert to Group because Account Type is selected.,Hesap Türü görünümünden Grup gizli olamaz., -Cannot create Retention Bonus for left Employees,Sol çalışanlar için Tutma Bonusu oluşturamıyor, -Cannot create a Delivery Trip from Draft documents.,Taslak belgelerden Teslimat Gezisi oluşturulamaz., -Cannot deactivate or cancel BOM as it is linked with other BOMs,Devre dışı hizmet veya diğer ürün ağaçları ile bağlantılı olarak BOM iptal edilemiyor, -"Cannot declare as lost, because Quotation has been made.",Kayıp olarak Kotasyon yapıldığı için ilan edilemez., -Cannot deduct when category is for 'Valuation' or 'Valuation and Total',Kategori 'Değerleme' veya 'Toplam ve Değerleme' olduğu zaman çıkarılamaz, -Cannot deduct when category is for 'Valuation' or 'Vaulation and Total',Kategori 'Değerleme' veya 'Değerlendirme ve Toplam' için olduğunda düşülemez, -"Cannot delete Serial No {0}, as it is used in stock transactions","{0} Seri Numarası stok işlemlerinde kullanıldığından silinemiyor", -Cannot enroll more than {0} students for this student group.,Bu öğrenci grubu için {0} gelen göre daha fazla kayıt olamaz., -Cannot produce more Item {0} than Sales Order quantity {1},Satış Sipariş Miktarı {1} den fazla Ürün {0} üretilemez, -Cannot promote Employee with status Left,Çalışan durumu sata tanıtılamaz, -Cannot refer row number greater than or equal to current row number for this Charge type,Kolon sırası bu Ücret tipi için kolon numarasından büyük veya eşit olamaz, -Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row,İlk satır için ücret tipi 'Önceki satırları kullanır' veya 'Önceki satır toplamında' olarak seçilemez, -Cannot set as Lost as Sales Order is made.,Satış Siparişi verildiği için Kayıp olarak ayarlanamaz., -Cannot set authorization on basis of Discount for {0},{0} için İndirim bazında yetkilendirme ayarlanamıyor, -Cannot set multiple Item Defaults for a company.,Bir şirket için birden fazla Öğe Varsayılanı belirlenemiyor., -Cannot set quantity less than delivered quantity,Teslim edilen miktardan daha az miktar belirlenemiyor, -Cannot set quantity less than received quantity,Alınan miktardan daha az miktar ayarlanamaz, -Cannot set the field {0} for copying in variants,Değişkenlere kopyalamak için {0} alanı ayarlanamıyor, -Cannot transfer Employee with status Left,Çalışan durumu Sola aktarılamıyor, -Cannot {0} {1} {2} without any negative outstanding invoice,{0} {1} {2} olmadan herhangi bir olumsuz ödenmemiş fatura Can, +"Cannot Submit, Employees left to mark attendance","Gönderilemiyor, Çalışanların katılımı işaretlemesi bırakıldı", +Cannot be a fixed asset item as Stock Ledger is created.,Stok Defteri oluşturulduğu için sabit kıymet kalemi olamaz., +Cannot cancel because submitted Stock Entry {0} exists,Gönderilen Stok Girişi {0} mevcut olduğundan iptal edilemiyor, +Cannot cancel transaction for Completed Work Order.,Tamamlanan İş Emri için işlem iptal edilemiyor., +Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3},{2} Seri Numarası {3} deposuna ait olmadığından {0} {1} iptal edilemiyor, +Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,Hisse senedi işleminden sonra Nitelikler değiştirilemez. Yeni bir Öğe oluşturun ve stoğu yeni Öğeye aktarın, +Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved.,Mali Yıl kaydedildikten sonra Mali Yıl Başlangıç Tarihi ve Mali Yıl Bitiş Tarihi değiştirilemez., +Cannot change Service Stop Date for item in row {0},{0}. satırdaki öğenin Hizmet Durdurma Tarihi değiştirilemiyor, +Cannot change Variant properties after stock transaction. You will have to make a new Item to do this.,Stok işleminden sonra Varyant özellikleri değiştirilemiyor. Bunu yapmak için yeni bir Öğe oluşturmanız gerekecek., +"Cannot change company's default currency, because there are existing transactions. Transactions must be cancelled to change the default currency.",Mevcut işlemler olduğundan şirketin varsayılan para birimi değiştirilemiyor. Varsayılan para birimini değiştirmek için işlemlerin iptal edilmesi gerekir., +Cannot change status as student {0} is linked with student application {1},"{0} öğrencisi, {1} öğrenci uygulamasına bağlı olduğundan durum değiştirilemiyor", +Cannot convert Cost Center to ledger as it has child nodes,Alt düğümleri olduğundan Maliyet Merkezi genel muhasebeye dönüştürülemiyor, +Cannot covert to Group because Account Type is selected.,Hesap Türü seçili olduğundan Gruba geçiş yapılamıyor., +Cannot create Retention Bonus for left Employees,Kalan Çalışanlar için Elde Tutma İkramiyesi oluşturulamıyor, +Cannot create a Delivery Trip from Draft documents.,Taslak belgelerden Teslimat Gezisi oluşturulamıyor., +Cannot deactivate or cancel BOM as it is linked with other BOMs,Diğer Malzeme Listeleri ile bağlantılı olduğundan Malzeme Listesi devre dışı bırakılamaz veya iptal edilemez, +"Cannot declare as lost, because Quotation has been made.",Teklif verildiği için kayıp olarak beyan edilemez., +Cannot deduct when category is for 'Valuation' or 'Valuation and Total',Kategori 'Değerleme' veya 'Değerleme ve Toplam' için olduğunda düşülemez, +Cannot deduct when category is for 'Valuation' or 'Vaulation and Total',Kategori 'Değerleme' veya 'Değerleme ve Toplam' için olduğunda düşülemez, +"Cannot delete Serial No {0}, as it is used in stock transactions",{0} Seri Numarası hisse senedi işlemlerinde kullanıldığından silinemiyor, +Cannot enroll more than {0} students for this student group.,Bu öğrenci grubuna en fazla {0} öğrenci kaydedilebilir., +Cannot produce more Item {0} than Sales Order quantity {1},Satış Siparişi miktarından {1} daha fazla Ürün {0} üretilemez, +Cannot promote Employee with status Left,Durumu Sol olan Çalışan terfi ettirilemiyor, +Cannot refer row number greater than or equal to current row number for this Charge type,Bu Ücret türü için mevcut satır numarasından büyük veya bu satır numarasına eşit satır numarasına başvurulamaz, +Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row,İlk satır için masraf türü 'Önceki Satırdaki Tutar' veya 'Önceki Satırdaki Toplam' olarak seçilemiyor, +Cannot set as Lost as Sales Order is made.,Satış Siparişi yapıldığı için Kayıp olarak ayarlanamıyor., +Cannot set authorization on basis of Discount for {0},{0} için İndirim esasına göre yetkilendirme ayarlanamıyor, +Cannot set multiple Item Defaults for a company.,Bir şirket için birden fazla Öğe Varsayılanı ayarlanamaz., +Cannot set quantity less than delivered quantity,"Miktar, teslim edilen miktardan daha azına ayarlanamıyor", +Cannot set quantity less than received quantity,"Miktar, alınan miktardan daha azına ayarlanamıyor", +Cannot set the field {0} for copying in variants,Varyantlarda kopyalama için {0} alanı ayarlanamıyor, +Cannot transfer Employee with status Left,Durumu Sol olan Çalışan aktarılamıyor, +Cannot {0} {1} {2} without any negative outstanding invoice,Ödenmemiş negatif fatura olmadan {0} {1} {2} yapılamaz, Capital Equipments,Sermaye Ekipmanları, Capital Stock,Öz Sermaye, Capital Work in Progress,Sermaye Yarı Mamul, @@ -466,51 +466,51 @@ Central Tax,Merkezi Vergi, Certification,Belgeleme, Cess,Cess, Change Amount,Değişim Tutarı, -Change Item Code,Öğe Kodunu Değiştir, -Change Release Date,Yayın Tarihi Değiştir, +Change Item Code,Ürün Kodunu Değiştir, +Change Release Date,Yayın Tarihini Değiştir, Change Template Code,Şablon Kodunu Değiştir, -Changing Customer Group for the selected Customer is not allowed.,Seçilen Müşteri için Müşteri Grubunu değiştirmeye izin verilmiyor., +Changing Customer Group for the selected Customer is not allowed.,Seçilen Müşteri için Müşteri Grubunun değiştirilmesine izin verilmiyor., Chapter,Bölüm, Chapter information.,Bölüm bilgileri., -Charge of type 'Actual' in row {0} cannot be included in Item Rate,Satır {0}'daki 'Gerçek' ücret biçimi Ürün Br.Fiyatına dahil edilemez, -Chargeble,Masrafa tabi, -Charges are updated in Purchase Receipt against each item,Masraflar her kalem için Satınalma Fişinde güncellenir, -"Charges will be distributed proportionately based on item qty or amount, as per your selection","Masraflar, seçiminize göre ürün miktarına veya tutarına göre orantılı olarak dağıtılacaktır.", -Chart of Cost Centers,Maliyet Merkezlerinin Grafikleri, +Charge of type 'Actual' in row {0} cannot be included in Item Rate,{0}. satırdaki 'Gerçek' türündeki ücret Ürün Fiyatına dahil edilemez, +Chargeble,Ücretli, +Charges are updated in Purchase Receipt against each item,"Ücretler, Satın Alma Makbuzu'nda her ürüne göre güncellenir", +"Charges will be distributed proportionately based on item qty or amount, as per your selection","Ücretler, seçiminize göre ürün miktarına veya miktarına göre orantılı olarak dağıtılacaktır.", +Chart of Cost Centers,Maliyet Merkezleri Tablosu, Check all,Tümünü kontrol et, -Checkout,Çıkış yap, +Checkout,Çıkış yapmak, Chemical,Kimyasal, -Cheque,Çek, -Cheque/Reference No,Çek / Referans No, -Cheques Required,Çekler Gerekli, -Cheques and Deposits incorrectly cleared,Çekler ve Mevduat yanlış temizlendi, -Child Task exists for this Task. You can not delete this Task.,Bu Görev için Alt Görev var. Bu görevi silemezsiniz., -Child nodes can be only created under 'Group' type nodes,Çocuk düğümleri sadece 'Grup' tür düğüm altında oluşturulabilir, -Child warehouse exists for this warehouse. You can not delete this warehouse.,Bu depoya ait alt depo bulunmaktadır. Bu depoyu silemezsiniz., +Cheque,Kontrol etmek, +Cheque/Reference No,Çek/Referans No, +Cheques Required,Kontroller Gerekli, +Cheques and Deposits incorrectly cleared,Çekler ve Mevduatlar hatalı şekilde temizlendi, +Child Task exists for this Task. You can not delete this Task.,Bu Görev için Alt Görev mevcut. Bu Görevi silemezsiniz., +Child nodes can be only created under 'Group' type nodes,Alt düğümler yalnızca 'Grup' türü düğümler altında oluşturulabilir, +Child warehouse exists for this warehouse. You can not delete this warehouse.,Bu depo için alt depo mevcut. Bu depoyu silemezsiniz., Circular Reference Error,Dairesel Referans Hatası, -City,İl, -City/Town,İl / İlçe, +City,Şehir, +City/Town,Şehir/Kasaba, Clay,Kil, Clear filters,Filtreleri temizle, Clear values,Değerleri temizle, -Clearance Date,Ödeme Tarihi, -Clearance Date not mentioned,Ödeme Tarihi belirtilmedi, -Clearance Date updated,Ödeme Tarihi güncellendi, -Client,Client, -Client ID,Client ID, -Client Secret,Client Secret, -Clinical Procedure,Klinik Prosedürü, +Clearance Date,Gümrükleme Tarihi, +Clearance Date not mentioned,Gümrükleme Tarihi belirtilmedi, +Clearance Date updated,Gümrükleme Tarihi güncellendi, +Client,Müşteri, +Client ID,Müşteri Kimliği, +Client Secret,Müşteri Sırrı, +Clinical Procedure,Klinik Prosedür, Clinical Procedure Template,Klinik Prosedür Şablonu, -Close Balance Sheet and book Profit or Loss.,Bilançoyu Kapat ve Kar veya Zararı ayır., +Close Balance Sheet and book Profit or Loss.,Bilançoyu kapatın ve Kar veya Zararı kaydedin., Close Loan,Krediyi Kapat, -Close the POS,POSu kapat, -Closed,Kapandı, -Closed order cannot be cancelled. Unclose to cancel.,Kapalı sipariş iptal edilemez. İptal etmek için açın., +Close the POS,POS'u kapat, +Closed,Kapalı, +Closed order cannot be cancelled. Unclose to cancel.,Kapatılan emir iptal edilemez. İptal etmek için kapatmayı açın., Closing (Cr),Kapanış (Alacak), Closing (Dr),Kapanış (Borç), Closing (Opening + Total),Kapanış (Açılış + Toplam), Closing Account {0} must be of type Liability / Equity,"Kapanış Hesabı {0}, Borç / Özkaynak türünde olmalıdır", -Closing Balance,Kapanış bakiyesi, +Closing Balance,Kapanış Bakiyesi, Code,Kod, Collapse All,Tümünü Daralt, Color,Renk, @@ -525,7 +525,7 @@ Community Forum,Topluluk Forumu, Company (not Customer or Supplier) master.,Şirket (değil Müşteri veya alanı) usta., Company Abbreviation,Şirket Kısaltması, Company Abbreviation cannot have more than 5 characters,Şirket Kısaltması 5 karakterden uzun olamaz, -Company Name,Firma Adı, +Company Name,Şirket Adı, Company Name cannot be Company,Şirket Adı olamaz, Company currencies of both the companies should match for Inter Company Transactions.,Her iki şirketin şirket para birimleri Inter Şirket İşlemleri için eşleşmelidir., Company is manadatory for company account,Şirket hesabı için şirket, @@ -668,7 +668,7 @@ Currency is required for Price List {0},Döviz Fiyat Listesi için gereklidir {0 Currency of the Closing Account must be {0},Kapanış Hesap Dövizi olmalıdır {0}, Currency of the price list {0} must be {1} or {2},{0} fiyat listesi para birimi {1} veya {2} olmalıdır., Currency should be same as Price List Currency: {0},"Para birimi, Fiyat Listesi Para Birimi ile aynı olmalıdır: {0}", -Current Assets,Mevcut Varlıklar, +Current Assets,Dönen Varlıklar, Current BOM and New BOM can not be same,Cari BOM ve Yeni BOM aynı olamaz, Current Liabilities,Cari Borçlar/Pasif, Current Qty,Güncel Mik, @@ -779,11 +779,11 @@ Difference Account,Fark Hesabı, Difference Amount,Farklı ayrılıklar, Difference Amount must be zero,Fark Tutar sıfır olmalıdır, Different UOM for items will lead to incorrect (Total) Net Weight value. Make sure that Net Weight of each item is in the same UOM.,Ürünler için farklı Ölçü Birimi yanlış (Toplam) net değer değerine yol açacaktır. Net etki değerinin aynı olduğundan emin olun., -Direct Expenses,Doğrudan Giderler, -Direct Income,doğrudan gelir, +Direct Expenses,Direkt Giderler, +Direct Income,Direkt Gelir, Disable,Devre Dışı Bırak, Disabled template must not be default template,Engelli kalıpları varsayılan kalıpları, -Disburse Loan,Kredi Kredisi, +Disburse Loan,Kredi Ödemesi, Disbursed,Önceki dönemlerde toplananlar, Disc,İnd., Discharge,Tediye, @@ -796,7 +796,7 @@ Dispatch Notification,Sevk Bildirimi, Dispatch State,Sevk Durumu, Distance,Mesafe, Distribution,Dağıtım, -Distributor,Dağıtımcı, +Distributor,Distribütör, Dividends Paid,Ödenen Temettüler, Do you really want to restore this scrapped asset?,Eğer gerçekten bu hurdaya ait varlığın geri yüklenmesini istiyor musunuz?, Do you really want to scrap this asset?,Bu varlığı gerçekten hurdalamak istiyor musunuz?, @@ -805,7 +805,7 @@ Doc Date,Belge Tarihi, Doc Name,Belge Adı, Doc Type,Belge Türü, Docs Search,Belge Ara, -Document Name,Belge adı, +Document Name,Belge Adı, Document Type,Belge Türü, Domain,Domain, Domains,Domains, @@ -873,7 +873,7 @@ End Date can not be less than Start Date,"Bitiş Tarihi, Başlangıç Tarihinden End Date cannot be before Start Date.,"Bitiş Tarihi, Başlangıç Tarihi'nden önce olamaz.", End Year,bitiş yılı, End Year cannot be before Start Year,Yıl Sonu Başlangıç Yıl önce olamaz, -End on,Bitiş tarihi, +End on,Bitiş Tarihi, Ends On date cannot be before Next Contact Date.,"Bitiş Tarihi, Sonraki İletişim Tarihi'nden önce olamaz.", Energy,Enerji, Engineer,Mühendis, @@ -956,8 +956,8 @@ Financial Services,Finansal Hizmetler, Financial Statements,Finansal Tablolar, Financial Year,Mali Yıl, Finish,Bitiş, -Finished Good,Mamul Ürün, -Finished Good Item Code,Mamul Ürün Kodu, +Finished Good,Mamül Ürün, +Finished Good Item Code,Mamül Ürün Kodu, Finished Goods,Mamüller, Finished Item {0} must be entered for Manufacture type entry,Öğe sonlandırıldı {0} imalat tipi giriş için girilmelidir, Finished product quantity {0} and For Quantity {1} cannot be different,Bitmiş ürün miktarı {0} ve Miktar {1} için farklı olamaz, @@ -1005,13 +1005,13 @@ From Date cannot be greater than To Date,Tarihten bugüne kadardan ileride olama From Date must be before To Date,Tarihten itibaren bugüne kadardan önce olmalıdır, From Date should be within the Fiscal Year. Assuming From Date = {0},Tarihten Mali'den yıl içinde olmalıdır Tarihten itibaren = {0} varsayılır, From Datetime,Başlama Zamanı, -From Delivery Note,Baş. Satış İrsaliyesi, +From Delivery Note,Satış İrsaliyesinden Al, From Fiscal Year,Baş. Mali Yılı, From GSTIN,GSTIN'den, From Party Name,Baş. Cari Adı, From Pin Code,Baş. Pin Kodu, From Place,Baş. Yeri, -From Range has to be less than To Range,Menzil az olmak zorundadır Kimden daha Range için, +From Range has to be less than To Range,Menzilden Hedef Aralığa kadar olan değerden küçük olmalıdır, From State,Başlangıç Durumu, From Time,Başlama Tarihi, From Time Should Be Less Than To Time,Zaman Zamandan Daha Az Olmalı, @@ -1020,13 +1020,13 @@ From Time cannot be greater than To Time.,Zaman zaman daha büyük olamaz., From and To dates required,tarih aralığı gerekli, From value must be less than to value in row {0},"Değerden, {0} bilgisindeki değerden az olmalıdır", From {0} | {1} {2},Gönderen {0} | {1} {2}, -Fulfillment,Yerine Getirme, +Fulfillment,Gereksinim, Full Name,Tam Adı, Fully Depreciated,Tamamen Amortismanlı, Furnitures and Fixtures,Döşeme ve demirbaşlar, -"Further accounts can be made under Groups, but entries can be made against non-Groups","Ek hesaplar Gruplar altında yapılabilir, ancak girişler olmayan Gruplar karşı yapılabilir", -Further cost centers can be made under Groups but entries can be made against non-Groups,"Daha fazla masraf Gruplar altında yapılabilir, ancak girişleri olmayan Gruplar karşı yapılabilir", -Further nodes can be only created under 'Group' type nodes,Ek kısımlar ancak 'Grup' tipi kısımlar altında oluşturulabilir, +"Further accounts can be made under Groups, but entries can be made against non-Groups","Gruplar altında başka hesaplar da açılabilir, ancak Grup olmayanlara karşı da giriş yapılabilir", +Further cost centers can be made under Groups but entries can be made against non-Groups,Gruplar altında daha fazla masraf yerleri yapılabilir ancak Grup dışı kişilere karşı da giriş yapılabilir, +Further nodes can be only created under 'Group' type nodes,Daha fazla düğüm yalnızca 'Grup' tipi düğümler altında oluşturulabilir, GSTIN,GSTIN, GSTR3B-Form,GSTR3B-Formu, Gain/Loss on Asset Disposal,Varlık Bertaraf Kâr / Zarar, @@ -1058,7 +1058,7 @@ GoCardless payment gateway settings,GoCardless ödeme ağ özellikleri ayarları Goal and Procedure,Hedef ve Prosedür, Goals cannot be empty,Hedefler boş olamaz, Goods In Transit,Transit Ürünler, -Goods Transferred,Edilen Mallar'ı transfer et, +Goods Transferred,Edilen Mallar'ı Transfer et, Goods and Services Tax (GST India),Mal ve Hizmet Vergisi (GST Hindistan), Goods are already received against the outward entry {0},{0} dış girişine karşı ürünler zaten alınmış, Government,Kamu / Devlet, @@ -1153,11 +1153,11 @@ In Value,Giriş Maliyeti, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Çok katmanlı program söz konusu olduğunda, Müşteriler harcanan esasa göre ilgili kademeye otomatik olarak atanacaktır.", Inactive,Pasif, Incentives,Teşvikler, -Include Default FB Entries,Varsayılan Defter Girişlerini Dahil et, +Include Default FB Entries,Vars. Defter Girişleri Dahil, Include Exploded Items,Patlatılmış Öğeleri Dahil et, Include POS Transactions,POS İşlemlerini Dahil et, Include UOM,Birimi Dahil et, -Included in Gross Profit,Brüt Kâr Dahil, +Included in Gross Profit,Brüt Kâra Dahil, Income,Gelir, Income Account,Gelir Hesabı, Income Tax,Gelir Vergisi, @@ -1175,7 +1175,7 @@ Inpatient Record,Yatan Hasta Kaydı, Installation Note,Kurulum Notları, Installation Note {0} has already been submitted,Kurulum Notu {0} zaten gönderildi, Installation date cannot be before delivery date for Item {0},Kurulum tarih Ürün için teslim tarihinden önce olamaz {0}, -Installing presets,Önayarları yükleniyor, +Installing presets,Ön ayarlar yükleniyor, Institute Abbreviation,Enstitü Kısaltma, Institute Name,Kurum İsmi, Instructor,Eğitmen, @@ -1317,7 +1317,7 @@ Lead Owner,Aday Sahibi, Lead Owner cannot be same as the Lead,Müşteri Aday Kaydı Sahibi Müşteri Adayı olamaz, Lead Time Days,Teslim zamanı Günü, Lead to Quotation,Müşteri Adayından Teklif Oluştur, -"Leads help you get business, add all your contacts and more as your leads","Potansiyel müşteriler iş almanıza, tüm kişilerinizi ve daha fazlasını potansiyel müşteri adayı olarak eklemenize yardımcı olur", +"Leads help you get business, add all your contacts and more as your leads","Potansiyel müşteriler iş almanıza, tüm kişilerinizi eklemenize ve daha fazlasını potansiyel müşterileriniz olarak eklemenize yardımcı olur", Learn,Öğren, Leave Management,İzin Yönetimi, Leave and Attendance,Puantaj ve İzin, @@ -1341,7 +1341,7 @@ Loan Start Date and Loan Period are mandatory to save the Invoice Discounting,Fa Loans (Liabilities),Krediler (Borçlar), Loans and Advances (Assets),Krediler ve Avanslar (Varlıklar), Local,Yerel, -Logs for maintaining sms delivery status,Sms teslim durumunu korumak için günlükleri, +Logs for maintaining sms delivery status,SMS teslim durumunu korumaya yönelik log kayıtları, Lost,Kaybedildi, Lost Reasons,Kayıp Nedenleri, Low,Düşük, @@ -1362,7 +1362,7 @@ Maintenance Schedule is not generated for all the items. Please click on 'Genera Maintenance Schedule {0} exists against {1},{1} ile ilgili Bakım Çizelgesi {0} var, Maintenance Schedule {0} must be cancelled before cancelling this Sales Order,Bakım Programı {0} bu Satış Emri iptal edilmeden önce iptal edilmelidir, Maintenance Status has to be Cancelled or Completed to Submit,Bakım Durumu İptal Edildi veya Gönderilmesi Tamamlandı, -Maintenance User,Bakımcı Kullanıcı, +Maintenance User,Bakım Kullanıcısı, Maintenance Visit,Bakım Ziyareti, Maintenance Visit {0} must be cancelled before cancelling this Sales Order,Bakım Ziyareti {0} bu Satış Emri iptal edilmeden önce iptal edilmelidir, Maintenance start date can not be before delivery date for Serial No {0},Seri No {0} için bakım başlangıç tarihi teslim tarihinden önce olamaz, @@ -1421,10 +1421,10 @@ Max: {0},Maks: {0}, Maximum Samples - {0} can be retained for Batch {1} and Item {2}.,"Maksimum Örnekler - {0}, Toplu İş {1} ve Madde {2} için tutulabilir.", Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}.,"Maksimum Örnekler - {0}, Toplu İş {1} ve Öğe {2} için Toplu İş Alma İşlemi {3} içinde zaten tutulmuştur.", Maximum discount for Item {0} is {1}%,{0} Öğesi için maksimum indirim %{1}, -Medical Code,Tıbbi kod, +Medical Code,Tıbbi Kod, Medical Code Standard,Tıbbi Kod Standardı, Medical Department,Tıbbi Bölüm, -Medical Record,Tıbbi kayıt, +Medical Record,Tıbbi Kayıt, Medium,Orta, Member Activity,Üye Etkinliği, Member ID,Üye ID, @@ -1452,7 +1452,7 @@ Minimum Lead Age (Days),Minimum Müşteri Aday Kayı Yaşı (Gün), Miscellaneous Expenses,Çeşitli Giderler, Missing Currency Exchange Rates for {0},Eksik Döviz Kurları {0}, Missing email template for dispatch. Please set one in Delivery Settings.,Sevk için e-posta şablonu eksik. Lütfen Teslimat Ayarları'nda bir tane ayarlayın., -"Missing value for Password, API Key or Shopify URL","Şifre, API Anahtarı veya Shopify URL için eksik değer", +"Missing value for Password, API Key or Shopify URL","Şifre, API Key veya Shopify URL için eksik değer", Mode of Payment,Ödeme Şekli, Mode of Payments,Ödemeler Şekli, Mode of Transport,Ulaşım Şekli, @@ -1484,7 +1484,7 @@ Needs Analysis,İhtiyaç Analizi, Negative Quantity is not allowed,Negatif Miktara izin verilmez, Negative Valuation Rate is not allowed,Negatif Değerleme Oranına izin verilmez, Negotiation/Review,Müzakere / İnceleme, -Net Asset value as on,Net Aktif değeri olarak, +Net Asset value as on,Net Varlık değeri şu şekildedir:, Net Cash from Financing,Finansmandan Gelen Net Nakit, Net Cash from Investing,Yatırımdan Gelen Net Nakit, Net Cash from Operations,Faaliyetlerden Gelen Net Nakit, @@ -1509,7 +1509,7 @@ New Customers,Yeni Müşteriler, New Department,Yeni Departman, New Employee,Yeni Çalışan, New Location,Yeni Konum, -New Quality Procedure,Yeni Kalite hükümleri, +New Quality Procedure,Yeni Kalite Prosedürü, New Sales Invoice,Yeni Satış Faturası, New Sales Person Name,Yeni Satış Kişi Adı, New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt,Yeni Seri Deposuz olamaz. Depo Stok Hareketi ile veya alım makbuzuyla ayarlanmalıdır, @@ -1522,9 +1522,9 @@ Next,Sonraki, Next Contact By cannot be same as the Lead Email Address,Sonraki İletişim Sorumlusu Müşteri Aday Kaydının E-posta Adresi ile aynı olamaz, Next Contact Date cannot be in the past,Sonraki İletişim Tarihi olamaz, Next Steps,Sonraki Adımlar, -No Action,İşlem yok, +No Action,İşlem Yok, No Customers yet!,Henüz Müşteri yok!, -No Data,Hiç Veri yok, +No Data,Veri Yok, No Delivery Note selected for Customer {},Müşteri için {} dağıtım Notu çalıştırmadı, No Item with Barcode {0},Barkodlu Ürün Yok {0}, No Item with Serial No {0},Seri Numaralı Ürün Yok {0}, @@ -1534,16 +1534,16 @@ No Items to pack,Ambalaj Ürün Yok Olacak, No Items with Bill of Materials to Manufacture,Malzeme Listesine Öğe Yok İmalat için, No Items with Bill of Materials.,Malzeme Listesi ile Öğe Yok., No Permission,İzin yok, -No Remarks,Remark yok, +No Remarks,Açıklama yok, No Result to submit,Gönderilecek Sonuç Yok, -No Student Groups created.,Hiçbir Öğrenci Grupları mevcut., -No Students in,İçinde öğrenci yok, -No Tax Withholding data found for the current Fiscal Year.,Mevcut Mali Yılı için Vergi Stopajı verileri bulunamadı., +No Student Groups created.,Hiçbir Öğrenci Grubu oluşturulmadı., +No Students in,Öğrenci Yok, +No Tax Withholding data found for the current Fiscal Year.,Mevcut Mali Yıl için Vergi Stopajı verisi bulunamadı., No Work Orders created,İş emri oluşturulmadı, -No accounting entries for the following warehouses,Şu depolar için muhasebe girdisi yok, -No contacts with email IDs found.,E-posta kimlikleri olan hiç kişi bulunamadı., -No data for this period,Bu süre için veri yok, -No description given,Açıklama verilmemiştir, +No accounting entries for the following warehouses,Aşağıdaki depolar için muhasebe girişi yok, +No contacts with email IDs found.,E-posta kimliğine sahip kişi bulunamadı., +No data for this period,Bu dönem için veri yok, +No description given,Açıklama verilmedi, No employees for the mentioned criteria,Sözü edilen ölçütler için çalışan yok, No gain or loss in the exchange rate,Döviz kurunda kazanç veya kayıp yok, No items listed,Listelenen öğe yok, @@ -1647,8 +1647,8 @@ Opportunities by lead source,Aday kaynağına göre fırsatlar, Opportunity,Fırsat, Opportunity Amount,Fırsat Tutarı, "Optional. Sets company's default currency, if not specified.","İsteğe bağlı. Eğer belirtilmemişse, şirketin genel para birimini belirler.", -Optional. This setting will be used to filter in various transactions.,İsteğe bağlı. Bu çeşitli ayar işlemlerini yapmak için kullanmaktır, -Options,Sepetler, +Optional. This setting will be used to filter in various transactions.,İsteğe bağlı. Bu ayar çeşitli işlemlerde filtreleme yapmak için kullanılacaktır., +Options,Seçenekler, Order Count,Sipariş Sayısı, Order Entry,Sipariş Kaydı, Order Value,Sipariş Değeri, @@ -1685,8 +1685,8 @@ POS Profile is required to use Point-of-Sale,"POS Profili, Satış Noktasını K POS Profile required to make POS Entry,POS Profil POS Girişi yapmak için gerekli, POS Settings,POS Ayarları, Packed quantity must equal quantity for Item {0} in row {1},{1} Paketli miktar satır {1} deki Ürün {0} a eşit olmalıdır, -Packing Slip,Paketleme Fişi, -Packing Slip(s) cancelled,Paketleme Fişi iptal edildi, +Packing Slip,Çeki Listesi, +Packing Slip(s) cancelled,Çeki Listesi iptal edildi, Paid,Ödendi, Paid Amount,Ödenen Tutar, Paid Amount cannot be greater than total negative outstanding amount {0},"Ödenen Tutar, toplam negatif ödenmemiş miktardan daha fazla olamaz {0}", @@ -1726,7 +1726,7 @@ Payment Failed. Please check your GoCardless Account for more details,Ödeme ba Payment Gateway,Ödeme Ağ Geçidi, "Payment Gateway Account not created, please create one manually.","Ödeme Ağ Geçidi Hesabı oluşturulmaz, bir tane oluşturun lütfen.", Payment Gateway Name,Ödeme Ağ Geçidi Adı, -Payment Mode,Ödeme Modu, +Payment Mode,Ödeme Şekli, Payment Receipt Note,Ödeme Makbuzu Dekontu, Payment Request,Ödeme Talebi, Payment Request for {0},{0} için Ödeme İsteği, @@ -1943,12 +1943,12 @@ Prescription Duration,Reçete Süresi, Prescriptions,Reçeteler, Prev,Önceki, Preview,Önizleme, -Previous Financial Year is not closed,Geçmiş Mali Yıl kapatılmamış, +Previous Financial Year is not closed,Önceki Mali Yıl kapatılmamış, Price,Fiyat, Price List,Fiyat Listesi, -Price List Currency not selected,Fiyat Listesi para birimini seçmiş, +Price List Currency not selected,Fiyat Listesi Para Birimi seçilmedi, Price List Rate,Fiyat Listesi Oranı, -Price List master.,Fiyat Listesi ustası., +Price List master.,Fiyat Listesi master., Price List must be applicable for Buying or Selling,Fiyat Listesi Alış veya Satış için geçerli olmalıdır, Price List {0} is disabled or does not exist,Fiyat Listesi {0} devre dışı veya yok, Price or product discount slabs are required,Fiyat veya ürün indirimi levhaları gereklidir, @@ -1966,8 +1966,8 @@ Print Report Card,Kartı Rapor Yazdır, Print Settings,Yazdırma Ayarları, Print and Stationery,Baskı ve Kırtasiye, Print settings updated in respective print format,"Yazdırma ayarları, ilgili baskı ağırlığı güncellendi", -Print taxes with zero amount,Sıfır etkileme vergileri yazdırın, -Printing and Branding,Baskı ve Markalaşma, +Print taxes with zero amount,Vergileri sıfır tutarla yazdır, +Printing and Branding,Yazdırma ve Markalaşma, Private Equity,Özel Sermaye, Procedure,Prosedür, Process Day Book Data,Günlük Defter Verisini İşle, @@ -2021,7 +2021,7 @@ Purchase Date,Satınalma Tarihi, Purchase Invoice,Satınalma Faturası, Purchase Invoice {0} is already submitted,Satınalma Faturası {0} zaten teslim edildi, Purchase Manager,Satınalma Yöneticisi, -Purchase Master Manager,Satınalma Ana Yöneticisi, +Purchase Master Manager,Satınalma Master Yönetici, Purchase Order,Satınalma Siparişi, Purchase Order Amount,Satınalma Siparişi Tutarı, Purchase Order Amount(Company Currency),Satınalma Siparişi Tutarı (Şirket Para Birimi), @@ -2158,9 +2158,9 @@ Request for Quotations,Teklif Talepleri, Request for Raw Materials,Hammadde Talebi, Request for purchase.,Satınalma Talebi, Request for quotation.,Teklif Talebi., -Requested Qty,İstenen Miktar, +Requested Qty,Talep Miktarı, "Requested Qty: Quantity requested for purchase, but not ordered.","İstenen Miktar: Satın almak için istenen, ancak sipariş edilmeyen miktar", -Requesting Site,Site Talep ediyor, +Requesting Site,Talep eden Site, Requesting payment against {0} {1} for amount {2},"karşı ödeme talep {0}, {1} miktarda {2}", Requestor,Talep eden, Required On,İhtiyaç Tarihi, @@ -2259,7 +2259,7 @@ Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2},S Row {0}: Debit entry can not be linked with a {1},Satır {0}: Banka girişi ile bağlantılı olamaz bir {1}, Row {0}: Depreciation Start Date is required,Satır {0}: Amortisman Başlangıç Tarihi gerekli, Row {0}: Enter location for the asset item {1},Satır {0}: {1} varlık varlığı için yer girin, -Row {0}: Exchange Rate is mandatory,Satır {0}: Döviz Kuru cezaları, +Row {0}: Exchange Rate is mandatory,Satır {0}: Döviz Kuru zorunludur, Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount,Satır {0}: Faydalı Ömürden Sonra Beklenen Değer Brüt Alım Tutarından daha az olmalıdır, Row {0}: From Time and To Time is mandatory.,Satır {0}: From Time ve Zaman için bakımları., Row {0}: From Time and To Time of {1} is overlapping with {2},Satır {0}: Zaman ve zaman {1} ile örtüşen {2}, @@ -2271,9 +2271,9 @@ Row {0}: Party Type and Party is required for Receivable / Payable account {1},S Row {0}: Payment against Sales/Purchase Order should always be marked as advance,Satır {0}: Satış / Satınalma Siparişi karşı Ödeme hep avans olarak işaretlenmiş olmalıdır, Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.,Satır {0}: Kontrol edin Hesabı karşı 'Advance mı' {1} Bu bir avans girişi ise., Row {0}: Please set at Tax Exemption Reason in Sales Taxes and Charges,{0} Satırı: Lütfen Satış Vergileri ve Masraflarında Vergi Muafiyeti Nedeni ayarını yapın, -Row {0}: Please set the Mode of Payment in Payment Schedule,{0} Satırı: Lütfen Ödeme Planında Ödeme Modu ayarı, -Row {0}: Please set the correct code on Mode of Payment {1},{0} Satırı: Lütfen {1} Ödeme Modunda doğru kodu ayarı, -Row {0}: Qty is mandatory,Satır {0}: Miktar cezaları, +Row {0}: Please set the Mode of Payment in Payment Schedule,Satır {0} : Lütfen Ödeme Planında Ödeme Şeklini ayarlayın, +Row {0}: Please set the correct code on Mode of Payment {1},Satır {0} : Lütfen {1} Ödeme Şeklinde doğru kodu ayarlayın, +Row {0}: Qty is mandatory,Satır {0}: Miktar zorunludur, Row {0}: Quality Inspection rejected for item {1},{0} Satırı: {1} kalem için Kalite Denetimi reddedildi, Row {0}: UOM Conversion Factor is mandatory,Satır {0}: Ölçü Birimi Dönüşüm Faktörü Hizmetleri, Row {0}: select the workstation against the operation {1},{0} bilgisi: {1} işlemine karşı iş istasyonunu seçin, @@ -2301,9 +2301,9 @@ Sales Invoice {0} must be cancelled before cancelling this Sales Order,Satış F Sales Manager,Satış Yöneticisi, Sales Master Manager,Satış Master Yönetici, Sales Order,Satış Siparişi, -Sales Order Item,Sipariş Satış Kalemi, -Sales Order required for Item {0},Ürün {0}için Satış Sipariş gerekli, -Sales Order to Payment,Ödeme Satış Sipariş, +Sales Order Item,Satış Siparişi Kalemi, +Sales Order required for Item {0},{0} ürünü için Satış Siparişi gerekli, +Sales Order to Payment,Satış Siparişinden Ödemeye, Sales Order {0} is not submitted,Satış Sipariş {0} teslim edilmedi, Sales Order {0} is not valid,Satış Sipariş {0} geçerli değildir, Sales Order {0} is {1},Satış Sipariş {0} {1}, @@ -2406,7 +2406,7 @@ Send mass SMS to your contacts,Kişilerinize toplu SMS Gönder, Sensitivity,Hassasiyet, Sent,Gönderildi, Serial No and Batch,Seri No ve Parti (Batch), -Serial No is mandatory for Item {0},Ürün {0} için Seri no cezaları, +Serial No is mandatory for Item {0},Ürün {0} için Seri no zorunludur, Serial No {0} does not belong to Batch {1},"{0} Seri Numarası, {1} Batch'a ait değil", Serial No {0} does not belong to Delivery Note {1},Seri No {0} İrsaliye {1} e ait değil, Serial No {0} does not belong to Item {1},Seri No {0} Ürün {1} e ait değil, @@ -2536,7 +2536,7 @@ Start Year,Başlangıç yılı, Start date should be less than end date for Item {0},Başlangıç tarihi Ürün {0} için bitiş çizgisi daha az olmalıdır, Start date should be less than end date for task {0},{0} görevi için başlangıç tarihi bitiş süreleri daha az olmalıdır, Start day is greater than end day in task '{0}',"Başlangıç gününde, '{0}' Görev bitiş tarihinden daha büyük", -Start on,Başla, +Start on,Başlama tarihi, State,Eyalet, State/UT Tax,Eyalet / UT Vergisi, Statement of Account,Hesap Beyanı, @@ -2563,7 +2563,7 @@ Stock Received But Not Billed,Stok Alındı Ancak Faturalandırılmadı, Stock Reports,Stok Raporları, Stock Summary,Stok Özeti, Stock Transactions,Stok İşlemleri, -Stock UOM,Stok Ölçü Birimi, +Stock UOM,Stok Birimi, Stock Value,Stok Değeri, Stock balance in Batch {0} will become negative {1} for Item {2} at Warehouse {3},Toplu stok bakiyesi {0} olacak olumsuz {1} Warehouse Ürün {2} için {3}, Stock cannot be updated against Delivery Note {0},Stok İrsaliye {0} karşısı güncellenmez, @@ -2586,8 +2586,8 @@ Student Group,Çğrenci grubu, Student Group Strength,Öğrenci Grubu Gücü, Student Group is already updated.,Öğrenci Grubu zaten güncellendi., Student Group: ,Öğrenci Grubu: , -Student ID,Öğrenci Kimliği, -Student ID: ,Öğrenci Kimliği:, +Student ID,Öğrenci No, +Student ID: ,Öğrenci No: , Student LMS Activity,Öğrenci LMS Etkinliği, Student Mobile No.,Öğrenci Cep No, Student Name,Öğrenci Adı, @@ -2623,7 +2623,7 @@ Sunday,Pazar, Suplier,Tedarikçi, Supplier,Tedarikçi, Supplier Group,Tedarikçi Grubu, -Supplier Group master.,Tedarikçi Grubu yöneticisi., +Supplier Group master.,Tedarikçi Grubu ustası., Supplier Id,Tedarikçi kimliği, Supplier Invoice Date cannot be greater than Posting Date,"Tedarikçi Fatura Tarihi, postalama tarihinden büyük olamaz", Supplier Invoice No,Tedarikçi Fatura No, @@ -2656,7 +2656,7 @@ Target,Hedef, Target ({}),Hedef ({}), Target On,Hedef yeri, Target Warehouse,Hedef Depo, -Target warehouse is mandatory for row {0},Satır {0} için hedef depo cezaları, +Target warehouse is mandatory for row {0},Satır {0} için hedef depo zorunludur, Task,Görev, Tasks,Görevler, Tasks have been created for managing the {0} disease (on row {1}),{0} hastalığını izlemek için yazışmalar (satır {1}), @@ -2666,7 +2666,7 @@ Tax Category,Vergi Kategorisi, Tax Category for overriding tax rates.,Vergi oranlarını geçersiz kılmak için Vergi Kategorisi., "Tax Category has been changed to ""Total"" because all the Items are non-stock items","Tüm Maddeler stokta bulunmayan maddeler olduklarında, Vergi Kategorisi "Toplam" olarak değiştirildi", Tax ID,Vergi Numarası, -Tax Id: ,Vergi numarası:, +Tax Id: ,Vergi Numarası:, Tax Rate,Vergi Oranı, Tax Rule Conflicts with {0},Vergi Kural Çatışmalar {0}, Tax Rule for transactions.,Işlemler için vergi hesaplama kuralı., @@ -2737,7 +2737,7 @@ This Month's Summary,Bu Ayın Özeti, This Week's Summary,Bu Haftanın Özeti, This action will stop future billing. Are you sure you want to cancel this subscription?,"Bu işlemi, faturalandırmayı durduracak. Bu aboneliği iptal etmek istediğinizden emin misiniz?", This covers all scorecards tied to this Setup,"Bu, bu Kurulum ile bağlantılı tüm puan kartlarını kapsayan", -This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?,Bu belge ile sınırı üzerinde {0} {1} öğe için {4}. aynı karşı başka {3} {2}?, +This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?,"Bu belge, {4} öğesi için sınırı {0} {1} kadar aştı. Aynı {2}'ye karşı başka bir {3} mı yapıyorsunuz?", This is a root account and cannot be edited.,Bu bir kök hesabıdır ve düzenlenemez., This is a root customer group and cannot be edited.,Bu bir kök müşteri grubudur ve düzenlenemez., This is a root department and cannot be edited.,Bu bir kök devlettir ve düzenlenemez., @@ -2751,7 +2751,7 @@ This is based on logs against this Vehicle. See timeline below for details,"Bu, This is based on stock movement. See {0} for details,Bu stok hareketleri devam ediyor. Bkz. {0} ayrıntılar için, This is based on the Time Sheets created against this project,"Bu, bu projeye karşı potansiyel Zaman postalarını yönlendiriyor", This is based on the attendance of this Student,"Bu, bu Öğrencinin katılımıyla artar", -This is based on transactions against this Customer. See timeline below for details,"Bu, bu Müşteriye karşı işlemlere ayrılmıştır. Ayrıntılar için aşağıdaki zaman geçişini bakın", +This is based on transactions against this Customer. See timeline below for details,"Bu, bu Müşteri ile ilgili işlemlere dayanmaktadır. Ayrıntılar için zaman hattına bakın", This is based on transactions against this Healthcare Practitioner.,"Bu, bu Sağlık Personeline yapılan işlemlere bağlıdır.", This is based on transactions against this Patient. See timeline below for details,"Bu, bu Hastaya karşı işlemlere göre yapılır. Ayrıntılar için aşağıdaki zaman aralarına bakın", This is based on transactions against this Sales Person. See timeline below for details,"Bu, bu Satış Kişisine karşı yapılan işlemlere göre yapılır. Ayrıntılar için aşağıdaki zaman aralarına bakın", @@ -2775,16 +2775,16 @@ To Address 1,Adres 1'ye, To Address 2,Adres 2'ye, To Bill,Faturalanacak, To Date,Bitiş Tarihi, -To Date cannot be before From Date,Bitiş tarihi başlatma cezaları önce bitirme, -To Date cannot be less than From Date,"Tarihe, Başlangıç Tarihinden daha az olamaz", -To Date must be greater than From Date,"Tarihe, Tarihten büyük olmalı", -To Date should be within the Fiscal Year. Assuming To Date = {0},Tarih Mali Yıl içinde olmalıdır. İlgili Tarih = {0}, -To Datetime,DateTime için, +To Date cannot be before From Date,Bitiş Tarihi Başlangıç Tarihinden önce olamaz, +To Date cannot be less than From Date,Bitiş Tarihi Başlangıç Tarihinden küçük olamaz, +To Date must be greater than From Date,Bitiş Tarihi Başlangıç Tarihinden büyük olmalıdır, +To Date should be within the Fiscal Year. Assuming To Date = {0},Bitiş Tarihi Mali Yıl içinde olmalıdır. Varsayalım Bitiş Tarihi = {0}, +To Datetime,Bitiş TarihZaman, To Deliver,Teslim edilecek, To Deliver and Bill,Teslim edilecek ve Faturalanacak, -To Fiscal Year,Mali Yıl, +To Fiscal Year,Bitiş Mali Yılı, To GSTIN,GSTIN'e, -To Party Name,Cari Adı bitişi, +To Party Name,Cari Adı Bitişi, To Pin Code,PIN Koduna, To Place,Yerleştirilecek, To Receive,Alınacak, @@ -2917,7 +2917,7 @@ Update stock must be enable for the purchase invoice {0},Satınalma faturası {0 Updating Variants...,Varyantlar Güncelleniyor..., Upload your letter head and logo. (you can edit them later).,Mektup baş ve logo yükleyin. (Daha sonra bunları düzenleyebilirsiniz)., Upper Income,Üst Gelir, -Use Sandbox,Kullanım Sandbox, +Use Sandbox,Sandbox Kullan, User,Kullanıcı, User ID,Kullanıcı ID, User ID not set for Employee {0},Çalışan {0} için kullanıcı sıfatı ayarlanmamış, @@ -3076,7 +3076,7 @@ disabled user,kullanıcı devredışı, "e.g. Bank, Cash, Credit Card","Örnek: Banka, Nakit, Kredi Kartı", hidden,gizli, modified,düzenlendi, -old_parent,eski_ebeveyn, +old_parent,old_parent, on,üzerinde, {0} '{1}' is disabled,{0} '{1}' devre dışı, {0} '{1}' not in Fiscal Year {2},{0} '{1}' mali yıl {2} içinde değil, @@ -3109,17 +3109,17 @@ on,üzerinde, {0} hours,{0} saat, {0} in row {1},{1} bilgisinde {0}, {0} is blocked so this transaction cannot proceed,"{0} engellendi, bu işleme devam edilemiyor", -{0} is mandatory,{0} yaşam alanı, -{0} is mandatory for Item {1},{0} Ürün {1} için cezalar, -{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.,{0} yaptırımlar. {1} ve {2} için Döviz kaydı oluşturulabilir., +{0} is mandatory,{0} zorunludur, +{0} is mandatory for Item {1},{0} ögesi {1} için zorunludur, +{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.,{0} zorunludur. Belki {1} ile {2} arasında Döviz Değişim kaydı oluşturulmamıştır., {0} is not a stock Item,{0} bir stok ürünü değildir., -{0} is not a valid Batch Number for Item {1},{0} Ürün {1} için geçerli bir parti numarası değildir, +{0} is not a valid Batch Number for Item {1},{{0} {1} Öğesi için geçerli bir Parti Numarası değil, {0} is not added in the table,Tabloya {0} eklenmedi, -{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect.,{0} varsayılan Mali Yıldır. değiştirmek için tarayıcınızı yenileyiniz, +{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect.,{0} varsayılan Mali Yıldır. Değiştirmek için tarayıcınızı yenileyiniz, {0} is on hold till {1},"{0}, {1} geçen zamana kadar beklemede", {0} item found.,{0} öğe bulundu., {0} items found.,{0} öğe bulundu., -{0} items in progress,{0} ürün devam ediyor, +{0} items in progress,{0} öge işlemde, {0} items produced,{0} ürün üretildi, {0} must appear only once,{0} sadece bir kez yer almalıdır, {0} must be negative in return document,{0} iade belgesinde negatif olmalı, @@ -3161,7 +3161,7 @@ on,üzerinde, {0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.,{0} {1}: Kar/zarar hesabı {2} için Masraf Merkezi tanımlanmalıdır. Lütfen aktif şirket için kapsamlı bir Masraf Merkezi tanımlayın., {0} {1}: Cost Center {2} does not belong to Company {3},{0} {1}: Maliyet Merkezi {2} Şirkete ait olmayan {3}, {0} {1}: Customer is required against Receivable account {2},{0} {1}: Alacak hesabı {2} için müşteri tanımlanmalıdır., -{0} {1}: Either debit or credit amount is required for {2},{0} {1}: {2} için borç ya da alacak alacaklısı girilmelidir, +{0} {1}: Either debit or credit amount is required for {2},{0} {1}: {2} için borç veya alacak tutarı gerekiyor, {0} {1}: Supplier is required against Payable account {2},{0} {1}: Borç hesabı {2} için tedarikçi tanımlanmalıdır, {0}% Billed,%{0} Faturalandırıldı, {0}% Delivered,{0}% Teslim Edildi, @@ -3187,7 +3187,7 @@ Likes,Beğeniler, Merge with existing,Varolan ile Birleştir, Orientation,Oryantasyon, Parent,Ana Kalem, -Payment Failed,Ödeme başarısız, +Payment Failed,Ödeme Başarısız, Personal,Kişisel, Post,Gönder, Postal Code,Posta Kodu, @@ -3232,10 +3232,10 @@ From Date,Başlama Tarihi, Group By,Gruplama Ölçütü, Invalid URL,Geçersiz URL, Landscape,Landscape, -Naming Series,Adlandırma Serisi, +Naming Series,Seri Numarası, No data to export,Verilecek veri yok, Portrait,Portrait, -Print Heading,Baskı Başlığı, +Print Heading,Yazdırma Başlığı, Scheduler Inactive,Zamanlayıcı Etkin Değil, Scheduler is inactive. Cannot import data.,Zamanlayıcı etkin değil. Veri alınamıyor., Show Document,Belgeyi Göster, @@ -3283,7 +3283,7 @@ Asset Id,Varlık Kimliği, Asset Value,Varlık Değeri, Asset Value Adjustment cannot be posted before Asset's purchase date {0}.,"Varlık Değer Ayarlaması, Varlığın satınalma yollarından önce {0} yayınlanamaz .", Asset {0} does not belongs to the custodian {1},"{0} varlık, {1} saklama deposuna ait değil", -Asset {0} does not belongs to the location {1},"{0} öğesi, {1} sunumu ait değil", +Asset {0} does not belongs to the location {1},"{0} öğesi, {1} konumuna ait değil", At least one of the Applicable Modules should be selected,Uygulanabilir Modüllerden en az biri seçilmelidir, Atleast one asset has to be selected.,En az bir varlık seçilmelidir., Authentication Failed,Kimlik doğrulaması başarısız oldu, @@ -3380,7 +3380,7 @@ Doctype,BelgeTipi, Document {0} successfully uncleared,{0} dokümanı başarıyla temizlendi, Download Template,Şablonu İndir, Dr,Borç, -Due Date,Bitiş tarihi, +Due Date,Vade Tarihi, Duplicate,Kopyala, Duplicate Project with Tasks,Projeyi Görev ile Çoğalt, Duplicate project has been created,Yinelenen proje oluşturuldu, @@ -3414,7 +3414,7 @@ Expired,Süresi Bitti, Export,Dışarı Aktar, Export not allowed. You need {0} role to export.,İhracata izin verilmiyor. Vermek {0} rolü gerekir., Failed to add Domain,Etki alanı eklenemedi, -Fetch Items from Warehouse,Depodan Eşya Al, +Fetch Items from Warehouse,Depodan Ürünleri Getir, Fetching...,Getiriliyor..., Field,Alan, Filters,Filtreler, @@ -3591,7 +3591,7 @@ Purchase Invoices,Satınalma Faturaları, Purchase Orders,Satınalma Siparişleri, Purchase Receipt doesn't have any Item for which Retain Sample is enabled.,"Satınalma Fişinde, Örneği Tut'un etkinleştirildiği bir Öğe yoktur.", Purchase Return,Satınalma İadesi, -Qty of Finished Goods Item,Mamul Mal Miktarı, +Qty of Finished Goods Item,Mamül Kalem Miktarı, Quality Inspection required for Item {0} to submit,{0} Ürününün gönderilmesi için Kalite Kontrol gerekli, Quantity to Manufacture,Üretim Miktarı, Quantity to Manufacture can not be zero for the operation {0},{0} işlemi için Üretim Miktarı sıfır olamaz, @@ -3679,8 +3679,8 @@ Show Stock Ageing Data,Stok Yaşlandırmayı Göster, Show Warehouse-wise Stock,Depo bazında Stoğu Göster, Size,Boyut, Something went wrong while evaluating the quiz.,Sınavı değerlendirirken bir şey ters gitti., -Sr,Sr, -Start,Başlangıç, +Sr,No, +Start,Başlat, Start Date cannot be before the current date,"Başlangıç Tarihi, geçerli karşılaştırma önce olamaz", Start Time,Başlangıç Zamanı, Status,Durumu, @@ -3754,7 +3754,7 @@ Vendor Name,Satıcı Adı, Verify Email,E-mail'i dogrula, View,Göster, View all issues from {0},{0} 'daki tüm sorunları görüntüle, -View call log,Arama gününü görüntüle, +View call log,Arama günlüğünü görüntüle, Warehouse,Depo, Warehouse not found against the account {0},{0} hesabına karşı depo bulunamadı, Welcome to {0},Hoşgeldiniz {0}, @@ -3802,8 +3802,8 @@ Clear,Açık, Comments,Yorumlar, DocType,Belge Türü, Download,İndir, -Left,Ayrıldı, -Link,Bağlantı, +Left,Sol, +Link,Link, New,Yeni, Print,Yazdır, Reference Name,Referans Adı, @@ -3820,7 +3820,7 @@ No students Found,Öğrenci Bulunamadı, Not in Stock,Stokta yok, Please select a Customer,Lütfen bir müşteri seçin, Received From,Alındığı Yer, -Sales Person,Satış Elemanı, +Sales Person,Satış Temsilcisi, To date cannot be before From date,Bitiş tarihi başlatma cezaları önce bitirme, Write Off,Şüpheli Alacak, {0} Created,{0} Oluşturuldu, @@ -3876,7 +3876,7 @@ Cards,Kartlar, Percentage,Yüzde, Failed to setup defaults for country {0}. Please contact support@erpnext.com,{0} ülke için varsayılanlar ayarlanamadı. Lütfen support@erpnext.com ile iletişim geçin, Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.,Satır # {0}: {1} öğe bir Seri / Toplu İş Öğesi değil. Seri No / Parti No'ya karşı olamaz., -Please set {0},Lütfen {0} ayarınız, +Cancelled,İptal edildi, Please setup Instructor Naming System in Education > Education Settings,Lütfen Eğitim> Eğitim Yönetimi bölümü Eğitmen Adlandırma Sistemini kurun, Please set Naming Series for {0} via Setup > Settings > Naming Series,Lütfen Kurulum> Ayarlar> Adlandırma Serisi aracılığıyla {0} için Adlandırma Serisini ayarlayın, UOM Conversion factor ({0} -> {1}) not found for item: {2},{2} bileşeni için UOM Dönüşüm faktörü ({0} -> {1}) bulunamadı., @@ -4095,7 +4095,7 @@ Over Billing Allowance (%),Fazla Fatura Ödeneği (%), Credit Controller,Kredi Kontrolü, Check Supplier Invoice Number Uniqueness,Tedarikçi Fatura Numarasının Benzersizliğini Kontrol et, Make Payment via Journal Entry,Devmiye Kayıtları yoluyla Ödeme Yap, -Unlink Payment on Cancellation of Invoice,Fatura İptaline İlişkin Ödeme süresini kaldır, +Unlink Payment on Cancellation of Invoice,Fatura İptalinde Ödeme Bağlantısını Kaldır, Book Asset Depreciation Entry Automatically,Varlık Amortisman Kaydını Otomatik olarak Kaydet, Automatically Add Taxes and Charges from Item Tax Template,Öğe Vergisi Şablonundan Otomatik Olarak Vergi ve Masraf Ekleme, Automatically Fetch Payment Terms,Ödeme Şifrelerini Otomatik Olarak Al, @@ -4156,7 +4156,7 @@ Statement Header Mapping,Deyim Üstbilgisi Eşlemesi, Statement Headers,Bildirim Başlıkları, Transaction Data Mapping,İşlem Verileri Eşlemesi, Mapped Items,Eşleştirilmiş Öğeler, -Bank Statement Settings Item,Banka Ekstrem ayar öğesi, +Bank Statement Settings Item,Banka Ekstresi Ayarları Ögesi, Mapped Header,Eşlenen Üstbilgi, Bank Header,Banka Başlığı, Bank Statement Transaction Entry,Banka ekstresi işlem girişi, @@ -4166,9 +4166,9 @@ Match Transaction to Invoices,İşlemlerin Faturalara Eşleştirilmesi, Create New Payment/Journal Entry,Yeni Ödeme / Yevmiye Kaydı Oluştur, Submit/Reconcile Payments,Ödemeleri Gönderme / Mutabakat, Matching Invoices,Eşleşen Faturalar, -Payment Invoice Items,Ödeme Faturası Öğeleri, +Payment Invoice Items,Ödeme Faturası Kalemleri, Reconciled Transactions,Mutabık Kılınan İşlemler, -Bank Statement Transaction Invoice Item,Banka Ekstrem İşlem Fatura Öğesi, +Bank Statement Transaction Invoice Item,Banka Ekstresi İşlem Fatura Kalemi, Payment Description,Ödeme Açıklaması, Invoice Date,Fatura Tarihi, invoice,Fatura, @@ -4181,7 +4181,7 @@ Mapped Data Type,Eşlenen Veri Türü, Mapped Data,Eşlenmiş Veri, Bank Transaction,banka işlemi, ACC-BTN-.YYYY.-,ACC-BTN-.YYYY.-, -Transaction ID,İşlem Kimliği, +Transaction ID,İşlem ID, Unallocated Amount,Ayrılmamış Tutar, Field in Bank Transaction,Banka İşlemindeki Alan, Column in Bank File,Banka Dosyasındaki Sütün, @@ -4263,25 +4263,25 @@ Closed Document,Kapalı Belge, Track separate Income and Expense for product verticals or divisions.,Ayrı Gelir izlemek ve ürün dikey veya bölüm için Gider., Cost Center Name,Maliyet Merkezi Adı, Parent Cost Center,Ana Maliyet Merkezi, -lft,lft, -rgt,rgt, +lft,sol, +rgt,sağ, Coupon Code,Kupon Kodu, Coupon Name,Kupon Adı, "e.g. ""Summer Holiday 2019 Offer 20""",veya. "Yaz Tatili 2019 Teklifi 20", Coupon Type,Kupon Türü, -Promotional,tanıtım, -Gift Card,hediye kartı, +Promotional,Promosyonel, +Gift Card,Hediye Kartı, unique e.g. SAVE20 To be used to get discount,Örnek örnekleme SAVE20 İndirim almak için kullanmak, Validity and Usage,Kullanım ve Kullanım, -Valid From,Başlangıç Tarihi, -Valid Upto,Şu tarihe kadar geçerli, -Maximum Use,Maksimum kullanım, +Valid From,Geçerlilik Başlangıcı, +Valid Upto,Geçerlilik Bitişi, +Maximum Use,Maksimum Kullan, Used,Kullanılmış, -Coupon Description,Kupon çevirisi, +Coupon Description,Kupon Açıklaması, Discounted Invoice,İndirimli Fatura, -Debit to,Şuraya borçlandır, +Debit to,Şuna borçlandır, Exchange Rate Revaluation,Döviz Kuru Yeniden Değerleme, -Get Entries,Girişleri Alın, +Get Entries,Kayıtları Getir, Exchange Rate Revaluation Account,Döviz Kuru Yeniden Değerleme Hesabı, Total Gain/Loss,Toplam Kazanç / Zarar, Balance In Account Currency,Hesap Döviz Bakiyesi, @@ -4300,9 +4300,9 @@ Auto Created,Otomatik Yapılandırıldı, Stock User,Stok Kullanıcısı, Fiscal Year Company,Mali Yıl Şirketi, Debit Amount,Borç Tutarı, -Credit Amount,Kredi Tutarı, -Debit Amount in Account Currency,Hesap Para Bankamatik Tutarı, -Credit Amount in Account Currency,Hesap Para Birimi Kredi Tutarı, +Credit Amount,Alacak Tutarı, +Debit Amount in Account Currency,Hesap Para Birimine göre Borç Tutarı, +Credit Amount in Account Currency,Hesap Para Birimine göre Alacak Tutarı, Voucher Detail No,Fiş Detay No, Is Opening,Açılış mı, Is Advance,Avans mı, @@ -4327,7 +4327,7 @@ Item Tax Template Detail,Öğe Vergisi Şablon Ayrıntısı, Entry Type,Kayıt Türü, Inter Company Journal Entry,Inter Şirket Yevmiye Kaydı, Bank Entry,Banka Kaydı, -Cash Entry,Nakit Kaydı, +Cash Entry,Kasa Kaydı, Credit Card Entry,Kredi Kartı Kaydı, Contra Entry,Ters Kayıt, Excise Entry,Tüketim Kaydı, @@ -4397,13 +4397,13 @@ Monthly Distribution Percentages,Aylık Dağılımı Yüzdeler, Monthly Distribution Percentage,Aylık Dağılımı Yüzde, Percentage Allocation,Yüzde Tahsisi, Create Missing Party,Eksik Cariyi Oluştur, -Create missing customer or supplier.,Kayıp müşteri veya tedarikçi koruması., +Create missing customer or supplier.,Eksik müşteri veya tedarikçi oluşturun., Opening Invoice Creation Tool Item,Fatura Oluşturma Aracı Öğesini Açma, Temporary Opening Account,Geçici Açılış Hesabı, Party Account,Cari Hesap, Type of Payment,Ödeme Türü, ACC-PAY-.YYYY.-,ACC-PAY-.YYYY.-, -Receive,Tahsilat yap, +Receive,Tahsilat, Internal Transfer,İç transfer, Payment Order Status,Ödeme Emri Durumu, Payment Ordered,Ödeme Siparişi, @@ -4417,7 +4417,7 @@ Received Amount,alınan Tutar, Received Amount (Company Currency),alınan Tutar (Şirket Para Birimi), Get Outstanding Invoice,Öden Faturalamamış Alın, Payment References,Ödeme Referansları, -Writeoff,Hurdaya çıkarmak, +Writeoff,Gider kaydı, Total Allocated Amount,Toplam Ayrılan Tutar, Total Allocated Amount (Company Currency),Toplam Ayrılan Tutar (Şirket Para Birimi), Set Exchange Gain / Loss,Değişim Kazanç Seti / Zarar, @@ -4444,10 +4444,10 @@ To Invoice Date,Bitiş Fatura Tarihi, Minimum Invoice Amount,Asgari Fatura Tutarı, Maximum Invoice Amount,Maksimum Fatura Tutarı, System will fetch all the entries if limit value is zero.,"Eğer limit değeri sıfırsa, sistem tüm kayıtlarını alır.", -Get Unreconciled Entries,Mutabık olmayan girdileri alın, +Get Unreconciled Entries,Mutabakatı Yapılmamış Girişleri Alın, Unreconciled Payment Details,Mutabakatı Yapılmamış Ödeme Ayrıntıları, Invoice/Journal Entry Details,Fatura / Yevmiye Kaydı Detayları, -Payment Reconciliation Invoice,Ödeme Mutabakat Faturası, +Payment Reconciliation Invoice,Fatura Ödeme Mutabakatı, Invoice Number,Fatura Numarası, Payment Reconciliation Payment,Ödeme Mutabakat Ödemesi, Reference Row,Referans Satır, @@ -4477,8 +4477,8 @@ Day(s) after the end of the invoice month,Fatura ayının bitiminden sonra kaç Month(s) after the end of the invoice month,Fatura ayının bitiminden sonra kaç ay, Credit Days,Alacak Günü, Credit Months,Alacak Ayı, -Allocate Payment Based On Payment Terms,Ödeme Hücrelerine Göre Ödemeyi Tahsis Et, -"If this checkbox is checked, paid amount will be splitted and allocated as per the amounts in payment schedule against each payment term","Bu onay kutusu işaretlenirse, kiracıları bölünecek ve her ödeme süresine göre ödeme planındaki tutarlara göre tahsis edilecektir.", +Allocate Payment Based On Payment Terms,Ödeme Vadesine göre Ödeme Tahsisi Yap, +"If this checkbox is checked, paid amount will be splitted and allocated as per the amounts in payment schedule against each payment term","Bu kutucuğun işaretlenmesi durumunda ödenen tutar, her ödeme dönemine göre ödeme planındaki tutarlara göre bölünerek tahsis edilecektir.", Payment Terms Template Detail,Ödeme Protokolleri Şablon Ayrıntısı, Closing Fiscal Year,Mali Yılı Kapanış, Closing Account Head,Kapanış Hesap Başkanı, @@ -4487,7 +4487,7 @@ POS Customer Group,POS Müşteri Grubu, POS Field,POS Alanı, POS Item Group,POS Ürün Grubu, Company Address,Şirket Adresi, -Update Stock,Stok Güncelle, +Update Stock,Stoğu Güncelle, Ignore Pricing Rule,Fiyatlandırma Kuralını Yoksay, Applicable for Users,Kullanıcılar için geçerlidir, Sales Invoice Payment,Satış Fatura Ödeme, @@ -4500,7 +4500,7 @@ Write Off Cost Center,Şüpheli Alacak Maliyet Merkezi, Account for Change Amount,Değişim Miktarı Hesabı, Taxes and Charges,Vergi ve Harçlar, Apply Discount On,İndirim buna göre Uygula, -POS Profile User,POS Profil Kullanıcıları, +POS Profile User,POS Profil Kullanıcısı, Apply On,Buna Uygula, Price or Product Discount,Fiyat veya Ürün İndirimi, Apply Rule On Item Code,Ürün Koduna Kural Uygula, @@ -4550,14 +4550,14 @@ Price Discount Slabs,Fiyat İndirim Levhaları, Promotional Scheme Price Discount,Promosyon Şeması Fiyat İndirimi, Product Discount Slabs,Ürün İndirimli Döşeme, Promotional Scheme Product Discount,Promosyon Programı Ürün İndirimi, -Min Amount,Min Miktarı, -Max Amount,Maksimum Tutar, +Min Amount,Min Tutar, +Max Amount,Max Tutar, Discount Type,İndirim Türü, ACC-PINV-.YYYY.-,ACC-PINV-.YYYY.-, Tax Withholding Category,Vergi Stopajı Kategorisi, Edit Posting Date and Time,İşlem Tarihi ve Saatini Düzenle, Is Paid,Ödendi mi, -Is Return (Debit Note),Iade mi (Borç dekontu), +Is Return (Debit Note),Iade mi (Borç Dekontu), Apply Tax Withholding Amount,Vergi Stopaj Tutarını Uygula, Accounting Dimensions ,Muhasebe Boyutları, Supplier Invoice Details,Tedarikçi Fatura Ayrıntıları, @@ -4569,7 +4569,7 @@ Select Shipping Address,Teslimat Adresi Seç, Currency and Price List,Fiyat Listesi ve Para Birimi, Price List Currency,Fiyat Listesi Para Birimi, Price List Exchange Rate,Fiyat Listesi Döviz Kuru, -Set Accepted Warehouse,Kabül edilen Depoyu Ayarla, +Set Accepted Warehouse,Kabul Edilen Depoyu Ayarla, Rejected Warehouse,Reddedilen Depo, Warehouse where you are maintaining stock of rejected items,Reddetilen Ürün stoklarını muhafaza ettiği depo, Raw Materials Supplied,Tedarik edilen Hammaddeler, @@ -4622,7 +4622,7 @@ Start date of current invoice's period,Cari dönem faturanın Başlangıç tarih End date of current invoice's period,Cari dönem faturanın bitiş tarihi, Update Auto Repeat Reference,Otomatik Tekrar Referansı Güncelle, Purchase Invoice Advance,Satınalma Faturası Avansı, -Purchase Invoice Item,Satınalma Faturası Ürünleri, +Purchase Invoice Item,Satınalma Faturası Kalemi, Quantity and Rate,Miktarı ve Oranı, Received Qty,Alınan Miktar, Accepted Qty,Kabul edilen Miktar, @@ -4660,7 +4660,7 @@ Purchase Receipt Detail,Satınalma Makbuzu Ayrıntısı, Item Weight Details,Öğe Ağırlık Ayrıntıları, Weight Per Unit,Birim Ağırlık, Total Weight,Toplam Ağırlık, -Weight UOM,Ağırlık Ölçü Birimi, +Weight UOM,Ağırlık Birimi, Page Break,Sayfa Sonu, Consider Tax or Charge for,Vergi veya Ücret, Valuation and Total,Değerleme ve Toplam, @@ -4687,7 +4687,7 @@ Customer PO Details,Müşteri Satınalma Siparişi Ayrıntıları, Customer's Purchase Order,Müşterinin Satınalma Siparişi, Customer's Purchase Order Date,Müşterinin Satınalma Sipariş Tarihi, Customer Address,Müşteri Adresi, -Shipping Address Name,Teslimat Adresi İsmi, +Shipping Address Name,Teslimat Adresi Adı, Company Address Name,Şirket Adresi Adı, Rate at which Customer Currency is converted to customer's base currency,Müşteri Para Biriminin Müşterinin temel birimine dönüştürme oranı, Rate at which Price list currency is converted to customer's base currency,Fiyat listesi para biriminin temel verileri para birimine dönüştürme oranı, @@ -4723,7 +4723,7 @@ Sales Team1,Satış Ekibi1, Against Income Account,Karşılık Gelir Hesabı, Sales Invoice Advance,Satış Fatura Avansı, Advance amount,Avans Tutarı, -Sales Invoice Item,Satış Faturası Ürünü, +Sales Invoice Item,Satış Faturası Kalemi, Customer's Item Code,Müşterinin Ürün Kodu, Brand Name,Marka Adı, Qty as per Stock UOM,Stok Birimi için Miktar, @@ -4739,7 +4739,7 @@ Stock Details,Stok Detayları, Customer Warehouse (Optional),Müşteri Deposu (İsteğe bağlı), Available Batch Qty at Warehouse,Depodaki Mevcut Parti Miktarı, Available Qty at Warehouse,Depodaki mevcut miktar, -Delivery Note Item,İrsaliye Ürünleri, +Delivery Note Item,İrsaliye Kalemi, Base Amount (Company Currency),Esas Tutar (Şirket Para Birimi), Sales Invoice Timesheet,Satış Faturası Çizelgesi, Time Sheet,Mesai Kartı, @@ -4975,7 +4975,7 @@ Written Down Value,Yazılı Değer, Expected Value After Useful Life,Kullanım süresi sonunda beklenen değer, Rate of Depreciation,Amortisman Oranı, In Percentage,yüzde olarak, -Maintenance Team,bakım ekibi, +Maintenance Team,Bakım Ekibi, Maintenance Manager Name,Bakım Yöneticisi Adı, Maintenance Tasks,Bakım Görevleri, Manufacturing User,Üretim Kullanıcısı, @@ -5001,7 +5001,7 @@ Maintenance Team Name,Bakım Takım Adı, Maintenance Team Members,Bakım Ekibi Üyeleri, Purpose,Amaç, Stock Manager,Stok Yöneticisi, -Asset Movement Item,Varlık Hareketi Öğesi, +Asset Movement Item,Varlık Hareket Kalemi, Source Location,Kaynak Konum, From Employee,Talep eden Personel, Target Location,Hedef Konum, @@ -5073,7 +5073,7 @@ Blanket Order Rate,Açık Sipariş Oranı, Returned Qty,İade edilen Miktar, Purchase Order Item Supplied,Tedarik Edilen Satınalma Siparişi Ürünü, BOM Detail No,BOM Detay yok, -Stock Uom,Stok Ölçü Birimi, +Stock Uom,Stok Birimi, Raw Material Item Code,Hammadde Malzeme Kodu, Supplied Qty,verilen Adet, Purchase Receipt Item Supplied,Tedarik edilen satınalma makbuzu ürünü, @@ -5093,7 +5093,7 @@ Name and Type,Adı ve Türü, SUP-.YYYY.-,SUP-.YYYY.-, Default Bank Account,Varsayılan Banka Hesabı, Is Transporter,Nakliyeci mi, -Represents Company,Şirketi Temsil Ediyor, +Represents Company,Firmayı Temsil Ediyor, Supplier Type,Tedarikçi Türü, Allow Purchase Invoice Creation Without Purchase Order,Satınalma Siparişi olmadan Satınalma Faturası Oluşturmaya İzin Ver, Allow Purchase Invoice Creation Without Purchase Receipt,Satınalma İrsaliye olmadan Satınalma Faturası Oluşturmaya İzin Ver, @@ -5114,7 +5114,7 @@ Statutory info and other general information about your Supplier,Tedarikçiniz h PUR-SQTN-.YYYY.-,PUR-SQTN-.YYYY.-, Supplier Address,Tedarikçi Adresi, Link to material requests,Malzeme taleplerine bağlantı, -Rounding Adjustment (Company Currency,Yuvarlama Ayarı (Şirket Kuru, +Rounding Adjustment (Company Currency,Yuvarlama Ayarı (Firma Kuru, Auto Repeat Section,Otomatik Tekrar Bölümü, Is Subcontracted,Taşerona verildi, Lead Time in days,Teslimat Süresi gün olarak, @@ -5185,7 +5185,7 @@ Timeslots,Zaman dilimleri, Communication Medium Timeslot,İletişim Orta Zaman Çizelgesi, Employee Group,Personel Grubu, Appointment,Randevu, -Scheduled Time,Planlanmış Zaman, +Scheduled Time,Planlanan Süre, Unverified,Doğrulanmamış, Customer Details,Müşteri Detayları, Phone Number,Telefon Numarası, @@ -5265,7 +5265,7 @@ Request for Information,Bilgi Talebi, Suggestions,Öneriler, Blog Subscriber,Blog Abonesi, LinkedIn Settings,LinkedIn Ayarları, -Company ID,Şirket ID, +Company ID,Firma ID, OAuth Credentials,OAuth Kimlik Bilgileri, Consumer Key,Consumer Key, Consumer Secret,Consumer Secret, @@ -5304,11 +5304,11 @@ Twitter Post Id,Twitter Gönderim Kimliği, LinkedIn Post Id,LinkedIn Gönderim Kimliği, Tweet,Tweet, Twitter Settings,Twitter Ayarları, -API Secret Key,API Gizli Anahtarı, +API Secret Key,API Secret Key, Term Name,Dönem Adı, Term Start Date,Dönem Başlangıç Tarihi, Term End Date,Dönem Bitiş Tarihi, -Academics User,Akademik Kullanıcı, +Academics User,Akademi Kullanıcısı, Academic Year Name,Akademik Yıl Adı, Article,Makale, LMS User,LMS Kullanıcısı, @@ -5414,7 +5414,7 @@ EDU-INS-.YYYY.-,EDU-INS-.YYYY.-, Instructor Log,Eğitmen Günlüğü, Other details,Diğer Detaylar, Option,Seçenek, -Is Correct,Doğru, +Is Correct,Doğru mu, Program Name,Programın Adı, Program Abbreviation,Program Kısaltma, Courses,Dersler, @@ -5446,11 +5446,11 @@ New Academic Term,Yeni Akademik Dönem, Program Enrollment Tool Student,Programı Kaydı Öğrenci Aracı, Student Batch Name,Öğrenci Toplu Adı, Program Fee,Program Ücreti, -Question,soru, +Question,Soru, Single Correct Answer,Tek Doğru Cevap, Multiple Correct Answer,Çokluk Doğru Cevap, Quiz Configuration,Sınav Yapılandırması, -Passing Score,Geçme puanı, +Passing Score,Geçme Puanı, Score out of 100,100 üzerinden puan, Max Attempts,Max Girişimleri, Enter 0 to waive limit,Sınırdan feragat etmek için 0 girin, @@ -5467,7 +5467,7 @@ Correct,Doğru, Wrong,Yanlış, Room Name,Oda Adı, Room Number,Oda Numarası, -Seating Capacity,oturma kapasitesi, +Seating Capacity,Oturma Kapasitesi, House Name,Evin Adı, EDU-STU-.YYYY.-,EDU-STU-.YYYY.-, Student Mobile Number,Öğrenci Cep Numarası, @@ -5476,11 +5476,11 @@ A+,A+, A-,A-, B+,B+, B-,B-, -O+,0+, -O-,0-, +O+,0 RH+, +O-,0 RH-, AB+,AB+, AB-,AB-, -Nationality,Milliyet, +Nationality,Uyruğu, Home Address,Ev Adresi, Guardian Details,Veli Detayları, Guardians,Veliler, @@ -5508,18 +5508,18 @@ Students HTML,Öğrenciler HTML, Group Based on,Ona Dayalı Grup, Student Group Name,Öğrenci Grubu Adı, Max Strength,Maksimum Güç, -Set 0 for no limit,hiçbir sınırı 0 olarak ayarlamak, -Instructors,ders, +Set 0 for no limit,Sınırsız için 0'ı ayarlayın, +Instructors,Eğitmenler, Student Group Creation Tool,Öğrenci Grubu Oluşturma Aracı, Leave blank if you make students groups per year,Öğrenci gruplarını yılda bir kere boş bırakın., -Get Courses,Kursları alın, +Get Courses,Kursları Getir, Separate course based Group for every Batch,Her Toplu İş için Ayrılmış Kurs Tabanlı Grup, Leave unchecked if you don't want to consider batch while making course based groups. ,"Kurs temelli gruplar yaparken toplu düşünmeyi sonlandırın, gecen.", Student Group Creation Tool Course,Öğrenci Grubu Oluşturma Aracı Kursu, -Course Code,Kurs kodu, +Course Code,Kurs Kodu, Student Group Instructor,Öğrenci Grubu Eğitimi, Student Group Student,Öğrenci Öğrenci Grubu, -Group Roll Number,Grup Rulosu Numarası, +Group Roll Number,Grup Kayıt Numarası, Student Guardian,Öğrenci Velisi, Relation,İlişki, Mother,Anne, @@ -5601,7 +5601,7 @@ Scope,Kapsam, Authorization Settings,Yetkilendirme Ayarları, Authorization Endpoint,Yetkilendirme Bitiş Noktası, Authorization URL,Yetkilendirme URL'si, -Quickbooks Company ID,Quickbooks Şirket Kimliği, +Quickbooks Company ID,Quickbooks Firma ID, Company Settings,Firma Ayarları, Default Shipping Account,Varsayılan Kargo Hesabı, Default Warehouse,Varsayılan Depo, @@ -5621,7 +5621,7 @@ Webhooks,Webhooks, Customer Settings,Müşteri Ayarları, Default Customer,Varsayılan Müşteri, Customer Group will set to selected group while syncing customers from Shopify,"Müşteri Grubu, Shopify'tan müşteriler senkronize iken seçilmiş grup ayarlanacak", -For Company,Şirket için, +For Company,Firma için, Cash Account will used for Sales Invoice creation,Satış Faturası oluşturmak için Nakit Hesabı kullanımtır, Update Price from Shopify To ERPNext Price List,ERPNext Fiyat Listesinden Shopify Güncelleme Fiyatı, Default Warehouse to to create Sales Order and Delivery Note,Satış Siparişi ve İrsaliye Oluşturma İçin Varsayılan Depo, @@ -5649,7 +5649,7 @@ Company Name as per Imported Tally Data,İçe Aktarılan Tally Verilerine göre Default UOM,varsayılan ölçü birimi, UOM in case unspecified in imported data,İçe aktarılan bilgilerde belirtilmemiş olması durumunda UOM, ERPNext Company,ERPNext Şirketi, -Your Company set in ERPNext,Şirketiniz ERPNext'te ayarlandı, +Your Company set in ERPNext,Firmanız ERPNext'te ayarlandı, Processed Files,İşlenmiş Dosyalar, Parties,Taraflar, UOMs,Ölçü Birimleri, @@ -5667,16 +5667,16 @@ API consumer key,API işletim anahtarı, API consumer secret,API şifreleme sırrı, Tax Account,Vergi Hesabı, Freight and Forwarding Account,Yük ve Nakliyat Hesabı, -Creation User,Yaratıcı Kullanıcısı, +Creation User,Kullanıcısı Oluşturma, "The user that will be used to create Customers, Items and Sales Orders. This user should have the relevant permissions.","Müşteriler, Öğeler ve Satış Siparişleri oluşturmak için kullanıcı. Bu kullanıcı ilgili izinlere sahip olmalıdır.", "This warehouse will be used to create Sales Orders. The fallback warehouse is ""Stores"".",Bu depo Müşteri Siparişlerini oluşturmak için kullanmaktır. Yedek depo "Mağazalar" dir., "The fallback series is ""SO-WOO-"".",Geri dönüş serisi "SO-WOO-", -This company will be used to create Sales Orders.,Bu şirket Satış Siparişlerini oluşturmak için kullanmaktır., +This company will be used to create Sales Orders.,Bu Firma Satış Siparişlerini oluşturmak için kullanılacaktır., Delivery After (Days),Teslimat Sonrası (Gün), This is the default offset (days) for the Delivery Date in Sales Orders. The fallback offset is 7 days from the order placement date.,"Bu, Müşteri Siparişlerindeki Teslim Tarihi için varsayılan ofsettir (gün). Yedek ofset, sipariş yerleşim servisleri 7 dosyadan itibaren.", "This is the default UOM used for items and Sales orders. The fallback UOM is ""Nos"".","Bu, sunucu ve Satış siparişleri için kullanılan varsayılan UOM'dir. Geri dönüş UOM'si ""Nos"".", -Endpoints,uç noktalar, -Endpoint,son nokta, +Endpoints,Endpoints, +Endpoint,Endpoint, Antibiotic Name,Antibiyotik adı, Healthcare Administrator,Sağlık Yöneticisi, Laboratory User,Laboratuar Kullanıcısı, @@ -5688,20 +5688,20 @@ HLC-CPR-.YYYY.-,HLC-CPR-.YYYY.-, Procedure Template,gözenek şablonu, Procedure Prescription,Cezalar Reçete, Service Unit,Servis Ünitesi, -Consumables,sarf, -Consume Stock,Stok tüketimi, +Consumables,Sarf Malzeme, +Consume Stock,Stok Tüket, Invoice Consumables Separately,Fatura Sarf Malzemelerini Ayrı Ayrı, Consumption Invoiced,Faturalandırılan Tüketim, Consumable Total Amount,Sarf Malzemesi Toplam Miktarı, Consumption Details,Tüketim Ayrıntıları, -Nursing User,hemşirelik kullanıcı, -Clinical Procedure Item,Klinik görüntü öğesi, +Nursing User,Hemşire Kullanıcısı, +Clinical Procedure Item,Klinik Prosedür Öğesi, Invoice Separately as Consumables,Sarf Malzemeleri Olarak Ayrı Olarak Fatura, Transfer Qty,Miktarı Aktar, Actual Qty (at source/target),Fiili Miktar (kaynak / hedef), Is Billable,Faturalandırılabilir mi, Allow Stock Consumption,Stok Tüketimine İzin Ver, -Sample UOM,Örnek UOM, +Sample UOM,Örnek Birim, Collection Details,Koleksiyon Ayrıntıları, Change In Item,Öğede Değişim, Codification Table,Kodlama Tablosu, @@ -5966,7 +5966,7 @@ Hotel Room,Otel Odası, Hotel Room Type,Otel Oda Tipi, Capacity,Kapasite, Extra Bed Capacity,Ekstra Yatak Kapasitesi, -Hotel Manager,otel yöneticisi, +Hotel Manager,Otel Yöneticisi, Hotel Room Amenity,Otel Odası İmkanları, Billable,Faturalandırılabilir, Hotel Room Package,Otel Oda Paketi, @@ -6194,13 +6194,13 @@ Against Document No,Karşılık Belge No., Against Document Detail No,Karşılık Belge Detay No., MFG-BLR-.YYYY.-,MFG-BLR-.YYYY.-, Order Type,Sipariş Türü, -Blanket Order Item,Battaniye sipariş öğesi, +Blanket Order Item,Açık Sipariş Kalemi, Ordered Quantity,Sipariş Miktarı, Item to be manufactured or repacked,Üretilecek veya yeniden paketlenecek Ürün, Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Belirli miktarlarda ham maddeden üretim / yeniden paketleme sonrasında elde edilen ürün miktarı, Set rate of sub-assembly item based on BOM,BOM'a dayalı alt montaj malzemesinin ayarlarını ayarlama, Allow Alternative Item,Alternatif Öğeye İzin Ver, -Item UOM,Ürün Ölçü Birimi, +Item UOM,Stok Birimi, Conversion Rate,Dönüşüm Oranı, Rate Of Materials Based On,Malzeme Fiyatı Şuna göre, With Operations,Operasyonlar ile, @@ -6268,8 +6268,8 @@ Started Time,Başlangıç Zamanı, Current Time,Şimdiki Zaman, Job Card Item,İş Kartı Öğesi, Job Card Time Log,İş Kartı Zaman günlüğü, -Time In Mins,Süre dakika, -Completed Qty,Tamamlanan Adet, +Time In Mins,Süre (dakika), +Completed Qty,Tamamlanan Miktar, Manufacturing Settings,Üretim Ayarları, Raw Materials Consumption,Hammadde Tüketimi, Allow Multiple Material Consumption,Çoklu Malzeme Tüketimine İzin Ver, @@ -6281,8 +6281,8 @@ Allow Overtime,Fazla Mesaiye izin ver, Allow Production on Holidays,Tatilde Üretime izin ver, Capacity Planning For (Days),Kapasite Planlama (Gün), Default Warehouses for Production,Varsayılan Üretim Depoları, -Default Work In Progress Warehouse,Varsayılan Yarı Mamul Deposu, -Default Finished Goods Warehouse,Varsayılan Mamul Deposu, +Default Work In Progress Warehouse,Varsayılan Yarı Mamül Deposu, +Default Finished Goods Warehouse,Varsayılan Mamül Deposu, Default Scrap Warehouse,Varsayılan Hurda Deposu, Overproduction Percentage For Sales Order,Satış Siparişi İçin Fazla Üretim Yüzdesi, Overproduction Percentage For Work Order,İş Emri İçin Fazla Üretim Yüzdesi, @@ -6296,7 +6296,7 @@ Minimum Order Quantity,Minimum Sipariş Miktarı, Default Workstation,Varsayılan İş İstasyonu, Production Plan,Üretim Planı, MFG-PP-.YYYY.-,MFG-PP-.YYYY.-, -Get Items From,Öğeleri Al, +Get Items From,Öğeleri Burdan Al, Get Sales Orders,Satış Şiparişlerini Getir, Material Request Detail,Malzeme Talep Ayrıntısı, Get Material Request,Malzeme Talebini Getir, @@ -6329,7 +6329,7 @@ Material Transferred for Manufacturing,Üretim için Aktarılan Malzeme, Manufactured Qty,Üretilen Miktar, Use Multi-Level BOM,Çok Seviyeli BOM Kullan, Plan material for sub-assemblies,Alt-montaj için Malzeme Planla, -Skip Material Transfer to WIP Warehouse,Yarı Mamul Deposuna Malzeme Transferini Atla, +Skip Material Transfer to WIP Warehouse,Yarı Mamül Deposuna Malzeme Transferini Atla, Check if material transfer entry is not required,Malzeme transfer girişinin gerekli olup olmadığını kontrol et, Backflush Raw Materials From Work-in-Progress Warehouse,Devam eden depodaki hammaddelerin geri bilgileri, Update Consumed Material Cost In Project,Projede Tüketilen Malzeme Maliyetini Güncelle, @@ -6355,7 +6355,7 @@ Available Qty at Source Warehouse,Kaynak Depodaki Mevcut Miktar, Available Qty at WIP Warehouse,WIP Ambarında Mevcut Miktar, Work Order Operation,İş Emri Operasyonu, Operation Description,Operasyon Tanımı, -Operation completed for how many finished goods?,Kaç mamul için operasyon tamamlandı?, +Operation completed for how many finished goods?,Kaç Mamül için operasyon tamamlandı?, Work in Progress,Devam ediyor, Estimated Time and Cost,Tahmini Süre ve Maliyet, Planned Start Time,Planlanan Başlangıç Zamanı, @@ -6392,15 +6392,15 @@ Name of Consultant,Danışmanın Adı, Certification Validity,Belgelendirme geçerliliği, Discuss ID,Kimliği tartışmak, GitHub ID,GitHub Kimliği, -Non Profit Manager,Kâr Dışı Müdür, -Chapter Head,Bölüm Başkanı, +Non Profit Manager,Vakıf / Dernek Yöneticisi, +Chapter Head,Bölüm Başlığı, Meetup Embed HTML,Tanışma HTML Göm, chapters/chapter_name\nleave blank automatically set after saving chapter.,bölüm kaydedildikten sonra bölüm otomatik olarak ayarlanır., Chapter Members,Bölüm Üyeleri, Members,Üyeler, Chapter Member,Bölüm Üyesi, Website URL,Web Sitesi URL'si, -Leave Reason,Nedenini Bırak, +Leave Reason,İzin Nedeni, Donor Name,Donör Adı, Donor Type,Donör Türü, Withdrawn,Çekilmiş, @@ -6411,7 +6411,7 @@ Has any past Grant Record,Geçmiş Hibe Kayıtları var mı, Show on Website,Web sitesinde göster, Assessment Mark (Out of 10),Değerlendirme Markası (10''), Assessment Manager,Değerlendirme Yöneticisi, -Email Notification Sent,Gönderilen E-posta Bildirimi, +Email Notification Sent,E-posta Bildirimi Gönderildi, NPO-MEM-.YYYY.-,NPO-MEM-.YYYY.-, Membership Expiry Date,Üyelik Sona Erme Tarihi, Razorpay Details,Razorpay Ayrıntıları, @@ -6483,7 +6483,7 @@ Activity Cost,Faaliyet Maliyeti, Billing Rate,Fatura Oranı, Costing Rate,Maliyet Oranı, title,Başlık, -Projects User,Projeler Kullanıcısı, +Projects User,Proje Kullanıcısı, Default Costing Rate,Varsayılan Maliyetlendirme Oranı, Default Billing Rate,Varsayılan Fatura Oranı, Dependent Task,Bağımlı Görev, @@ -6496,7 +6496,6 @@ From Template,Proje Şablonundan, Project will be accessible on the website to these users,Şu kullanıcılar projeye web sitesinden erişebilecek, Copied From,Şurdan Kopyalanacak, Start and End Dates,Başlangıç ve Tarihler Sonu, -Actual Time in Hours (via Timesheet),Gerçek Zaman (Saat olarak), Costing and Billing,Maliyet ve Faturalandırma, Total Costing Amount (via Timesheet),Toplam Maliyetleme Tutarı (Çalışma Sayfası Tablosu Üzerinden), Total Expense Claim (via Expense Claim),Toplam Gider İddiası (Gider Talepleri yoluyla), @@ -6537,11 +6536,12 @@ Is Milestone,Kilometre taşı, Task Description,Görev Tanımı, Dependencies,Bağımlılıklar, Dependent Tasks,Bağımlı Görevler, -Depends on Tasks,Görevler bağlıdır, +Depends on Tasks,Görevlere Bağlı, Actual Start Date (via Timesheet),Gerçek başlangış tarihi (Zaman Tablosu'ndan), +Actual Time in Hours (via Timesheet),Gerçek Zaman (Saat olarak), Actual End Date (via Timesheet),Gerçek bitiş tarihi (Zaman Tablosu'ndan), Total Expense Claim (via Expense Claim),(Gider İstem yoluyla) Toplam Gider İddiası, -Review Date,inceleme tarihi, +Review Date,İnceleme Tarihi, Closing Date,Kapanış Tarihi, Task Depends On,Görev Bağlıdır, Task Type,Görev Türü, @@ -6553,12 +6553,13 @@ Total Billed Hours,Toplam Faturalı Saat, Total Costing Amount,Toplam Maliyet Tutarı, Total Billable Amount,Toplam Faturalandırılabilir Tutar, Total Billed Amount,Toplam Faturalı Tutar, -% Amount Billed,% Faturalanan Tutar, +% Amount Billed,Faturalandırma Oranı, +% Returned,İade Oranı, Hrs,Saat, Costing Amount,Maliyet Tutarı, Corrective/Preventive,Önleyici / Düzeltici, Corrective,Düzeltici, -Preventive,koruyucu, +Preventive,Koruyucu, Resolution,Karar, Resolutions,Kararlar, Quality Action Resolution,Kalite Eylem Çözünürlüğü, @@ -6571,7 +6572,7 @@ Objectives,Hedefler, Quality Goal Objective,Kalite Hedef Amaç, Objective,Amaç, Agenda,Gündem, -Minutes,Dakikalar, +Minutes,Dakika, Quality Meeting Agenda,Kalite Toplantı Gündemi, Quality Meeting Minutes,Kalite Toplantı Tutanakları, Minute,Dakika, @@ -6630,15 +6631,15 @@ Rate Of TDS As Per Certificate,Sertifikaya Göre TDS Oranı, Certificate Limit,Sertifika Limiti, Invoice Series Prefix,Fatura Serisi Öneki, Active Menu,Aktif Menü, -Restaurant Menu,Restoran menüsü, +Restaurant Menu,Restoran Menüsü, Price List (Auto created),Fiyat Listesi (Otomatik kaydı), -Restaurant Manager,restoran yöneticisi, -Restaurant Menu Item,Restaurant Menü Öğesi, -Restaurant Order Entry,Restoran Siparişi Girişi, +Restaurant Manager,Restoran Yöneticisi, +Restaurant Menu Item,Restoran Menü Öğesi, +Restaurant Order Entry,Restoran Sipariş Kaydı, Restaurant Table,Restoran Masası, -Click Enter To Add,Ekle Gir'i tıklayın, +Click Enter To Add,Eklemek için Enter'a tıklayın, Last Sales Invoice,Son Satış Faturası, -Current Order,Tamamlayıcı Sipariş, +Current Order,Mevcut Sipariş, Restaurant Order Entry Item,Restaurant Sipariş Girişi Maddesi, Served,teslim, Restaurant Reservation,Restoran Rezervasyonu, @@ -6667,7 +6668,7 @@ Customer Primary Contact,Müşteri Birincil İletişim, Customer Primary Address,Müşteri Birincil Adres, "Reselect, if the chosen address is edited after save",Seçilen adres kaydedildikten sonra düzenlenirse yeniden seçin, Primary Address,Birincil Adres, -Mention if non-standard receivable account ,Varsayılan dışı alacak hesabı varsa belirtiniz, +Mention if non-standard receivable account,Standart dışı alacak hesabı varsa belirtin, Credit Limit and Payment Terms,Kredi Limiti ve Ödeme Vadeleri, Additional information regarding the customer.,Müşteri ile ilgili ek bilgi., Sales Partner and Commission,Satış Ortağı ve Komisyon, @@ -6704,12 +6705,12 @@ Rate at which Price list currency is converted to company's base currency,Fiyat Additional Discount and Coupon Code,Ek İndirim ve Kupon Kodu, Referral Sales Partner,Referans Satış Ortağı, In Words will be visible once you save the Quotation.,fiyat tekliflerini saklayacağınızda görünür olacaktır, -Term Details,Dönem Ayrıntıları, +Term Details,Koşul ve Hükümler, Quotation Item,Teklif Kalemi, Against Doctype,Belge Türüne Karşı, Against Docname,Belge Adına Karşı, Additional Notes,Ek Notlar, -SAL-ORD-.YYYY.-,SAL-ORD-.YYYY.-, +SAL-ORD-.YYYY.-,SAT-SİP-.YYYY.-, Skip Delivery Note,Teslim Notunu Atlası, In Words will be visible once you save the Sales Order.,Satış emrini saklayacağınızda görünür olacaktır., Track this Sales Order against any Project,Bu satış emrini bütün Projelere karşı takip et, @@ -6718,7 +6719,7 @@ Not Delivered,Teslim Edilmedi, Fully Delivered,Tamamen Teslim Edildi, Partly Delivered,Kısmen Teslim Edildi, Not Applicable,Uygulanamaz, -% Delivered,% Teslim Edildi, +% Delivered,Teslimat Oranı, % of materials delivered against this Sales Order,% malzeme bu satış emri karşılığında teslim edildi, % of materials billed against this Sales Order,% malzemenin faturası bu Satış Emri karşılığında oluşturuldu, Not Billed,Faturalanmamış, @@ -6754,7 +6755,7 @@ All Supplier Contact,Tüm Tedarikçi İrtibatları, All Sales Partner Contact,Tüm Satış Ortağı İrtibatları, All Lead (Open),Tüm Müşteri Adayları (Açık), All Employee (Active),Tüm Çalışanlar (Aktif), -All Sales Person,Bütün Satıcılar, +All Sales Person,Tüm Satıcılar, Create Receiver List,Alıcı Listesi Oluştur, Receiver List,Alıcı Listesi, Messages greater than 160 characters will be split into multiple messages,160 karakterden daha büyük mesajlar birden fazla mesaja bölünecektir, @@ -6768,10 +6769,10 @@ Itemwise Discount,Ürün İndirimi, Customer or Item,Müşteri veya Ürün, Customer / Item Name,Müşteri / Ürün Adı, Authorized Value,Yetkilendirilmiş Değer, -Applicable To (Role),(Role) uygulanabilir, -Applicable To (Employee),(Çalışana) uygulanabilir, +Applicable To (Role),(Role) Uygulanabilir, +Applicable To (Employee),(Personele) Uygulanabilir, Applicable To (User),(Kullanıcıya) Uygulanabilir, -Applicable To (Designation),(Görev) için uygulanabilir, +Applicable To (Designation),(Göreve) Uygulanabilir, Approving Role (above authorized value),(Yetkili değerin üstünde) Rolü onaylanması, Approving User (above authorized value),(Yetkili değerin üstünde) Kullanıcı onaylanması, Brand Defaults,Marka Varsayılanları, @@ -6818,7 +6819,7 @@ Series for Asset Depreciation Entry (Journal Entry),Varlık Amortisman Girişi S Gain/Loss Account on Asset Disposal,Varlık Elden Çıkarma Kazanç/Zarar Hesabı, Asset Depreciation Cost Center,Varlık Değer Kaybı Maliyet Merkezi, Budget Detail,Bütçe Detayı, -Exception Budget Approver Role,İstisna Bütçe Onaylayan Rolü, +Exception Budget Approver Role,İstisna Bütçesi Onaylayıcı Rolü, Company Info,Şirket Bilgisi, For reference only.,Yalnız Referans için., Company Logo,Şirket Logosu, @@ -6830,7 +6831,7 @@ Registration Details,Kayıt Detayları, Company registration numbers for your reference. Tax numbers etc.,Referans için şirket kayıt numaraları. vergi numaraları vb., Delete Company Transactions,Şirket İşlemleri Sil, Currency Exchange,Döviz Kuru, -Specify Exchange Rate to convert one currency into another,Döviz Kuru içine başka bir para birimini kullandığınız, +Specify Exchange Rate to convert one currency into another,Bir para birimini diğerine dönüştürmek için Döviz Kurunu belirtin, From Currency,Para Biriminden, To Currency,Para Birimine, For Buying,Alış için, @@ -6935,7 +6936,7 @@ Website Item Group,Web Sitesi Ürün Grubu, Cross Listing of Item in multiple groups,Öğenin birden çok grupta Çapraz Listelenmesi, Default settings for Shopping Cart,Alışveriş Sepeti Varsayılan ayarları, Enable Shopping Cart,Alışveriş Sepeti etkinleştirin, -Display Settings,Görüntü Ayarları, +Display Settings,Ekran Ayarları, Show Public Attachments,Genel Ekleri Göster, Show Price,Fiyatı Göster, Show Stock Availability,Stok Uygunluğunu Göster, @@ -7017,15 +7018,15 @@ Delivery Details,Teslim Bilgileri, Driver Email,Sürücü E-postası, Driver Address,Sürücü Adresi, Total Estimated Distance,Toplam Tahmini Mesafe, -Distance UOM,uzak UOM, -Departure Time,hareket saati, +Distance UOM,Mesafe Birimi, +Departure Time,Hareket Saati, Delivery Stops,Teslimat Durakları, Calculate Estimated Arrival Times,Tahmini Varış Sürelerini Hesaplayın, Use Google Maps Direction API to calculate estimated arrival times,Tahmini tahmini kullanım kullanımlarını hesaplamak için Google Haritalar Yönü API'sini kullanın, Optimize Route,Rotayı Optimize Et, Use Google Maps Direction API to optimize route,Rotayı optimize etmek için Google Haritalar Yönü API'sini kullanın, -In Transit,transit olarak, -Fulfillment User,Yerine getirme kullanıcı, +In Transit,Transit olarak, +Fulfillment User,Fulfillment Kullanıcısı, "A Product or a Service that is bought, sold or kept in stock.","Bir Ürün veya satın alınan, satılan veya stokta verileri bir hizmet.", STO-ITEM-.YYYY.-,STO-MADDE-.YYYY.-, Variant Of,Varyantı, @@ -7064,7 +7065,7 @@ Serial Number Series,Seri Numarası Serisi, "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.","Örnek:. Seri ayarladı ve Seri No belirtilen istenen ABCD ##### \n, daha sonra otomatik seri numarası bu seriye dayalı olarak oluşturulur. Her zaman geniş bu öğe için seri No konuşmak istiyorum. Bu boş bırakın.", Variants,Varyantlar, Has Variants,Varyanta Sahip, -"If this item has variants, then it cannot be selected in sales orders etc.","Bu ürünün varyantları varsa satış siparişlerinde vb. seçilemez.", +"If this item has variants, then it cannot be selected in sales orders etc.",Bu ürünün varyantları varsa satış siparişlerinde vb. seçilemez., Variant Based On,Varyant Tabanlı, Item Attribute,Ürün Özelliği, "Sales, Purchase, Accounting Defaults","Satış, Satınalma, Muhasebe Varsayılanları", @@ -7081,7 +7082,7 @@ Supplier Items,Tedarikçi Öğeleri, Foreign Trade Details,Dış Ticaret Detayları, Country of Origin,Menşei ülke, Sales Details,Satış Ayrıntıları, -Default Sales Unit of Measure,Varsayılan Öğe Satış Birimi, +Default Sales Unit of Measure,Varsayılan Satış Birimi, Is Sales Item,Satış Kalemi mi, Max Discount (%),Maksimum İndirim (%), No of Months,Ay Sayısı, @@ -7192,24 +7193,24 @@ To Warehouse (Optional),Depo (İsteğe bağlı), Actual Batch Quantity,Gerçek Parti Miktarı, Prevdoc DocType,Önceki Doküman, Parent Detail docname,Ana Detay belgesi adı, -"Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Teslim edilecek paketler için Paketleme Fişi oluşturun. Paket numarasını, paket içeriğini ve ağırlığını bildirmek için kullanılır.", +"Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Teslim edilecek paketler için Çeki Listesi oluşturun. Paket numarasını, paket içeriğini ve ağırlığını bildirmek için kullanılır.", Indicates that the package is a part of this delivery (Only Draft),Paketin bu teslimatın bir parçası olduğunu belirtir (Yalnızca Taslak), MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-, -From Package No.,Başlangıç Paket No., +From Package No.,Baş. Paket No., Identification of the package for the delivery (for print),Teslimat için paketin tanımlanması (baskı için), To Package No.,Bitiş Paket No., If more than one package of the same type (for print),Aynı türden birden fazla paket varsa (baskı için), Package Weight Details,Paket Ağırlığı Detayları, The net weight of this package. (calculated automatically as sum of net weight of items),Bu paketin net ağırlığı (Ürünlerin net toplamından otomatik olarak çıkarılması), -Net Weight UOM,Net Ağırlık Ölçü Birimi, +Net Weight UOM,Net Ağırlık Birimi, Gross Weight,Brüt Ağırlık, The gross weight of the package. Usually net weight + packaging material weight. (for print),Paketin brüt ağırlığı. Genellikle net ağırlık + ambalaj malzemesi ağırlığı. (baskı için), Gross Weight UOM,Brüt Ağırlık Birimi, -Packing Slip Item,Paketleme Fişi Kalemi, +Packing Slip Item,Çeki Listesi Kalemi, DN Detail,DN Detay, STO-PICK-.YYYY.-,STO-PICK-.YYYY.-, Material Transfer for Manufacture,Üretim için Malzeme Transferi, -Qty of raw materials will be decided based on the qty of the Finished Goods Item,"Hammadde miktarına, Mamul Madde miktarına göre karar verecek.", +Qty of raw materials will be decided based on the qty of the Finished Goods Item,"Hammadde miktarına, Mamül Kalem miktarına göre karar verecek.", Parent Warehouse,Ana Depo, Items under this warehouse will be suggested,Bu depodaki ürünler önerilecek, Get Item Locations,Malzeme Konumlarını Getir, @@ -7329,8 +7330,8 @@ Actual Qty After Transaction,İşlem sonrası gerçek Adet, Stock Value Difference,Stok Değer Farkı, Stock Queue (FIFO),Stok Kuyruğu (FIFO), Is Cancelled,İptal edildi mi, -Stock Reconciliation,Stok Mutabakatı, -This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.,"Bu araç, güncellemek veya sistem stok miktarı ve değerleme düzeltmeleri için yardımcı olur. Genellikle sistem değerleri ve ne aslında depolarda var eşitlemek için kullanılır.", +Stock Reconciliation,Stok Sayımı, +This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.,"Bu araç, sistemdeki stok miktarını ve değerlemesini güncellemenize veya düzeltmenize yardımcı olur. Genellikle sistem değerlerini ve depolarınızda gerçekte var olanları senkronize etmek için kullanılır.", MAT-RECO-.YYYY.-,MAT-Reco-.YYYY.-, Reconciliation JSON,Mutabakat JSON, Stock Reconciliation Item,Stok Mutabakat Kalemi, @@ -7450,7 +7451,7 @@ Absent Student Report,Öğrenci Devamsızlık Raporu, Assessment Plan Status,Değerlendirme Planı Durumu, Asset Depreciation Ledger,Varlık Değer Kaybı Defteri, Asset Depreciations and Balances,Varlık Değer Kayıpları ve Hesapları, -Available Stock for Packing Items,Ambalajlama Ürünleri İçin Kullanılabilir Stok, +Available Stock for Packing Items,Packing Kalemleri İçin Kullanılabilir Stok, Bank Clearance Summary,Banka Gümrükleme Özeti, Batch Item Expiry Status,Parti Öğesi Süre Sonu Durumu, Batch-Wise Balance History,Parti bazlı Bakiye Geçmişi, @@ -7522,7 +7523,7 @@ Material Requests for which Supplier Quotations are not created,Tedarikçi Tekli Open Work Orders,İş Emirlerini Aç, Qty to Deliver,Teslim Edilecek Miktar, Patient Appointment Analytics,Hasta Randevu Analizi, -Payment Period Based On Invoice Date,Fatura Tarihine göre Ödeme Dönemi, +Payment Period Based On Invoice Date,Fatura Tarihine Göre Ödeme Dönemi, Pending SO Items For Purchase Request,Satınalma Talebi Bekleyen PO Ürünleri, Procurement Tracker,Tedarik Takibi, Product Bundle Balance,Ürün Bundle Bakiyesi, @@ -7669,7 +7670,7 @@ ACC-PSINV-.YYYY.-,ACC-PSTERS-.YYYY.-, Consolidated Sales Invoice,Konsolide Satış Faturası, Return Against POS Invoice,POS Fatura Karşılığı İadesi, Consolidated,konsolide, -POS Invoice Item,POS Fatura Öğesi, +POS Invoice Item,POS Fatura Kalemi, POS Invoice Merge Log,POS Fatura Birleştirme Günlüğü, POS Invoices,POS Faturaları, Consolidated Credit Note,Konsolide Alacak Dekontu, @@ -7827,8 +7828,8 @@ Sandbox Mode,Korumalı Alan Modu, Enable Tax Calculation,Vergi Hesaplamasını Etkinleştir, Create TaxJar Transaction,TaxJar İşlemi Oluşturma, Credentials,Kimlik Bilgileri, -Live API Key,Canlı API Anahtarı, -Sandbox API Key,Sandbox API Anahtarı, +Live API Key,Canlı API Key, +Sandbox API Key,Sandbox API Key, Configuration,Yapılandırma, Tax Account Head,Vergi Hesap Başkanı, Shipping Account Head,Sevkiyat Hesap Başkanı, @@ -7838,6 +7839,8 @@ Set the Item Code which will be used for billing the Clinical Procedure.,Klinik Select an Item Group for the Clinical Procedure Item.,Klinik göz Öğesi için bir Öğe Grubu seçin., Clinical Procedure Rate,Klinik çevre Oranı, Check this if the Clinical Procedure is billable and also set the rate.,Klinik bölümlerin faturalandırılabilir olup olmadığı kontrol edin ve maliyet de ayarı., +Check this if the Clinical Procedure utilises consumables. Click ,Klinik çevre sarf malzemelerini kullanansa bunu kontrol edin. Daha fazlasını öğrenmek için tıklayın, + to know more,daha fazlasını bilmek, "You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.","Ayrıca şablon için Tıp Departmanını da ayarlayabilirsiniz. Belgeyi kaydettikten sonra, bu Klinik davanın faturalandırılması için otomatik olarak bir Öğe oluşturulacaktır. Daha sonra Hastalar için Klinik gözlemler oluştururken bu şablonu kullanabilirsiniz. Şablonlar sizi her fırsatta gereksiz verileri doldurmaktan kurtarır. Ayrıca Laboratuar Testleri, Terapi Oturumları vb. Gibi diğer yapılar için şablonlar oluşturabilirsiniz.", Descriptive Test Result,Tanımlayıcı Test Sonucu, Allow Blank,Boşluğa İzin Ver, @@ -7922,7 +7925,7 @@ Tobacco Consumption (Past),Tütün Tüketimi (Geçmiş), Tobacco Consumption (Present),Tütün Tüketimi (Günümüzde), Alcohol Consumption (Past),Alkol Tüketimi (Geçmiş), Alcohol Consumption (Present),Alkol Tüketimi (Günümüzde), -Billing Item,Fatura Öğesi, +Billing Item,Fatura Kalemi, Medical Codes,Tıbbi Kodlar, Clinical Procedures,Klinik Prosedürleri, Order Admission,Sipariş Kabulü, @@ -7953,7 +7956,7 @@ Select variant item code for the template item {0},{0} kalıp öğeleri için d Downtime Entry,Kesinti/Arıza Süresi Girişi, DT-,DT-, Workstation / Machine,İş İstasyonu / Makine, -Operator,Şebeke, +Operator,Operatör, In Mins,Dakika, Downtime Reason,Kesinti Nedeni, Stop Reason,Nedeni Durdur, @@ -7967,10 +7970,10 @@ Operation Row Number,Operasyon Satır Numarası, Operation {0} added multiple times in the work order {1},"Operasyon {0}, iş emrine birden çok kez eklendi {1}", "If ticked, multiple materials can be used for a single Work Order. This is useful if one or more time consuming products are being manufactured.","İşaretliyse, tek bir İş Emri için birden fazla malzeme kullanılabilir. Bu, bir veya daha fazla zaman alan ürün üretiliyorsa kullanışlıdır.", Backflush Raw Materials,Ters Yıkamalı Hammaddeler, -"The Stock Entry of type 'Manufacture' is known as backflush. Raw materials being consumed to manufacture finished goods is known as backflushing.

When creating Manufacture Entry, raw-material items are backflushed based on BOM of production item. If you want raw-material items to be backflushed based on Material Transfer entry made against that Work Order instead, then you can set it under this field.","'Üretim' türündeki Stok Hareketi, ters yıkama olarak bilinir. Mamul malları üretmek için tüketilen hammaddeler, ters yıkama olarak bilinir.

Üretim Girişi yaratılırken, hammadde kalemleri, üretim defterinin ürün reçetelerine göre ters yıkanır. Hammadde kalemlerinin bunun yerine o İş Emrine karşı Yapılan Malzeme Transferi girişine göre yıkanmış tersini istiyorsanız bu alan altında ayarlayabilirsiniz.", +"The Stock Entry of type 'Manufacture' is known as backflush. Raw materials being consumed to manufacture finished goods is known as backflushing.

When creating Manufacture Entry, raw-material items are backflushed based on BOM of production item. If you want raw-material items to be backflushed based on Material Transfer entry made against that Work Order instead, then you can set it under this field.","'Üretim' türündeki Stok Hareketi, ters yıkama olarak bilinir. Mamül kalemleri üretmek için tüketilen hammaddeler, ters yıkama olarak bilinir.

Üretim Girişi yaratılırken, hammadde kalemleri, üretim defterinin ürün reçetelerine göre ters yıkanır. Hammadde kalemlerinin bunun yerine o İş Emrine karşı Yapılan Malzeme Transferi girişine göre yıkanmış tersini istiyorsanız bu alan altında ayarlayabilirsiniz.", Work In Progress Warehouse,Devam Eden Çalışma Deposu, This Warehouse will be auto-updated in the Work In Progress Warehouse field of Work Orders.,"Bu Depo, İş Emirlerinin Devam Eden İşler Deposu alanında otomatik olarak güncellenecektir.", -Finished Goods Warehouse,Mamul Mal Deposu, +Finished Goods Warehouse,Mamül Mal Deposu, This Warehouse will be auto-updated in the Target Warehouse field of Work Order.,"Bu Depo, İş Emrinin Hedef Depo alanında otomatik olarak güncellenecektir.", "If ticked, the BOM cost will be automatically updated based on Valuation Rate / Price List Rate / last purchase rate of raw materials.","İşaretlenirse, ürün reçetesi maliyeti, Değerleme Oranı / Fiyat Listesi Oranı / hammaddelerin son satınalma oranlarına göre otomatik olarak güncellenecektir.", Source Warehouses (Optional),Kaynak Depolar (Opsiyonel), @@ -8026,7 +8029,7 @@ Choose between FIFO and Moving Average Valuation Methods. Click ,FIFO ve Hareket to know more about them.,Onlar hakkında daha fazla bilgi edinmek için., Show 'Scan Barcode' field above every child table to insert Items with ease.,Öğeleri bulmak için her alt tablonun üzerinde 'Barkod Tara' yaklaşmak., "Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.","Stok seri numaraları, Satınalma / Satış Faturaları, Sevk irsaliyeleri vb. İşlemlerde ilk giren ilk çıkarma esas tesisi girilen Kalemlere göre otomatik olarak ayarlanacaktır.", -"If blank, parent Warehouse Account or company default will be considered in transactions","Boş ise, işlemlerde ana Depo Hesabı veya şirket temerrüdü dikkate alınmalıdır.", +"If blank, parent Warehouse Account or company default will be considered in transactions",Boş bırakılırsa işlemlerde ana Depo Hesabı veya şirket temerrüdü dikkate alınacaktır., Service Level Agreement Details,Hizmet Seviyesi Sözleşme Ayrıntıları, Service Level Agreement Status,Hizmet Seviyesi Sözleşme Şartları, On Hold Since,O zaman beri beklemede, @@ -8337,7 +8340,7 @@ Material Requests Required,Gerekli Malzeme Talepleri, Items to Manufacture are required to pull the Raw Materials associated with it.,Üretilecek Öğelerin yanında bulunan ilk madde ve malzemeleri çekmesi gerekir., Items Required,Gerekli Öğeler, Operation {0} does not belong to the work order {1},"{0} işlemi, {1} iş emrine ait değil", -Print UOM after Quantity,Miktardan Sonra Birimi Yazdır, +Print UOM after Quantity,Birimi Miktardan Sonra Yazdır, Set default {0} account for perpetual inventory for non stock items,Stokta olmayan sunucular için kalıcı envanter için yerleşik {0} hesabını ayarladı, Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save,"Satır # {0}: Alt Öğe, Ürün Paketi paketi. Lütfen {1} Öğesini yükleme ve Kaydedin", Credit limit reached for customer {0},{0} müşterisi için kredi limitine ulaşıldı, @@ -8376,7 +8379,7 @@ Email Sent to Supplier {0},Tedarikçiye Gönderilen E-posta {0}, "The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings.",Portaldan Teklif İsteğine Erişim Devre Dışı Bırakıldı. Erişime İzin Vermek için Portal Ayarlarında etkinleştirin., Supplier Quotation {0} Created,tedarikçi Teklifi {0} Oluşturuldu, Valid till Date cannot be before Transaction Date,Tarihe kadar geçerli İşlem Tarihinden önce olamaz, -Unlink Advance Payment on Cancellation of Order,Sipariş İptali Üzerine Peşin Ödeme Bağlantısını Kaldır, +Unlink Advance Payment on Cancellation of Order,Sipariş İptalinde Avans Ödeme Bağlantısını Kaldır, "Simple Python Expression, Example: territory != 'All Territories'","Basit Python ifadesi, Örnek: bölge! = 'Tüm Bölgeler'", Sales Contributions and Incentives,Satış Katkıları ve Teşvikler, Sourced by Supplier,tedarikçi Kaynaklı, @@ -8412,8 +8415,8 @@ Enrollment Date cannot be before the Start Date of the Academic Year {0},"Kayıt Enrollment Date cannot be after the End Date of the Academic Term {0},Kayıt Tarihi Akademik Dönemin Bitiş Tarihinden sonra olamaz {0}, Enrollment Date cannot be before the Start Date of the Academic Term {0},"Kayıt Tarihi, Akademik Dönemin Başlangıç Tarihinden önce olamaz {0}", Future Posting Not Allowed,Hayatına Göndermeye İzin Verilmiyor, -"To enable Capital Work in Progress Accounting, ","Yarı Mamul Muhasebesini etkinleştirmek için,", -you must select Capital Work in Progress Account in accounts table,hesaplarda Sermaye Yarı Mamul Hesabını seçmelisiniz, +"To enable Capital Work in Progress Accounting, ","Yarı Mamül Muhasebesini etkinleştirmek için,", +you must select Capital Work in Progress Account in accounts table,hesaplar tablosunda Sermaye Devam Eden İş Hesabı'nı seçmelisiniz, You can also set default CWIP account in Company {},"Ayrıca, Şirket içinde genel CWIP hesabı da ayarlayabilirsiniz {}", The Request for Quotation can be accessed by clicking on the following button,Teklif Talebine aşağıdaki butona tıklanarak erişim sağlanır., Please click on the following button to set your new password,Yeni şifrenizi belirlemek için lütfen aşağıdaki düğmeyi tıklayın, @@ -8503,8 +8506,8 @@ Is Delivery Note Required for Sales Invoice Creation?,Satış Faturası Oluştur How often should Project and Company be updated based on Sales Transactions?,Satış İşlemlerine göre Proje ve Şirket hangi sıklıkta güncellenmelidir?, Allow User to Edit Price List Rate in Transactions,Kullanıcının İşlemlerde Fiyat Listesi Oranını Düzenlemesine İzin Ver, Allow Item to Be Added Multiple Times in a Transaction,Bir İşlemde Öğenin Birden Fazla Kez Eklenmesi İzin Ver, -Allow Multiple Sales Orders Against a Customer's Purchase Order,Müşterinin Satınalma Siparişine Karşı Birden Fazla Satış Siparişine İzin Ver, -Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Öğenin Satış Fiyatını Satınalma Oranına veya Değerleme Oranına Karşı Doğrula, +Allow Multiple Sales Orders Against a Customer's Purchase Order,Müşterinin Satınalma Siparişine karşın birden fazla Satış Siparişine izin ver, +Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Öğenin Satış Fiyatını Satınalma veya Değerleme Oranına Karşı Doğrula, Hide Customer's Tax ID from Sales Transactions,Müşterinin Vergi Numarasını Satış İşlemlerinden Gizle, "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.","Sipariş edilen miktara göre daha fazla alma veya teslimat yapmanıza izin verilen yüzde. Örneğin, 100 birim sipariş ettiyseniz ve Ödeneğiniz %10 ise, 110 birim almanıza izin verilir.", Action If Quality Inspection Is Not Submitted,Kalite Denetimi Gönderilmezse Yapılacak İşlem, @@ -8635,8 +8638,8 @@ Row #{}: Serial No. {} has already been transacted into another POS Invoice. Ple Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.,Satır # {}: Seri Numaraları {} zaten başka bir POS Faturasına dönüştürüldü. Lütfen geçerli bir seri numarası seçin., Item Unavailable,Öğe Mevcut Değil, Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {},Satır # {}: Orijinal faturada işlem görmediğinden Seri Numarası {} iade etmeyen {}, -Please set default Cash or Bank account in Mode of Payment {},Lütfen Ödeme Modunda varsayılan Nakit veya Banka hesabını ayarlayın {}, -Please set default Cash or Bank account in Mode of Payments {},Lütfen Ödeme Modu'nda varsayılan Nakit veya Banka hesabını ayarlayın {}, +Please set default Cash or Bank account in Mode of Payment {},Lütfen Ödeme Şeklinde varsayılan Kasa veya Banka hesabını ayarlayın {}, +Please set default Cash or Bank account in Mode of Payments {},Lütfen Ödeme Şeklinde varsayılan Kasa veya Banka hesabını ayarlayın {}, Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.,Lütfen {} hesabının bir Bilanço hesabı olduğundan emin olun. Ana hesabı bir Bilanço hesabı olarak dağıtma veya farklı bir hesaptan çalıştırma., Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.,Lütfen {} hesabının Alacaklı bir hesabı olduğundan emin olun. Hesap açma Borçlu olarak taşıma veya farklı bir hesap seçin., Row {}: Expense Head changed to {} ,Satır {}: Gider Başlığı {} olarak değiştirilir, @@ -8706,7 +8709,7 @@ Qualified on,Yeterlilik Tarihi, Qualification Status,Yeterlilik Durumu, Is Template,Şablon mu, Price List Defaults,Fiyat Listesi Varsayılanları, -Auto Insert Item Price If Missing,Eksikse Öğe Fiyatını Otomatik Ekle, +Auto Insert Item Price If Missing,Eksikse Ürün Fiyatını Otomatik Ekle, Update Existing Price List Rate,Mevcut Fiyat Listesi Oranını Güncelle, Stock Transactions Settings,Stok İşlem Ayarları, Role Allowed to Over Deliver/Receive,Aşırı Teslim Etmeye/Almaya İzin Verilen Rol, @@ -8714,7 +8717,7 @@ Let's Set Up the Assets Module.,Varlık Modülünü Kuralım!, "Assets, Depreciations, Repairs, and more.","Varlıklar, Amortismanlar, Onarımlar ve daha fazlası.", Review Fixed Asset Accounts,Sabit Kıymet Hesaplarını İnceleyin, Define Asset Category,Varlık Kategorisini Tanımla, -Create an Asset Item,Bir Varlık Öğesi Oluşturun, +Create an Asset Item,Bir Varlık Öğesi Oluştur, Purchase an Asset,Bir Varlık Satın Alın, Add an Existing Asset,Mevcut Bir Varlık Ekle, Settings & Configurations,Ayarlar ve Yapılandırmalar, @@ -8723,7 +8726,7 @@ Let's Set Up the Selling Module.,Haydi Satış Modülünü Kuralım!, Let's Set Up the Buying Module.,Haydi Satınalma Modülünü Kuralım!, "Products, Purchases, Analysis, and more.","Ürünler, Satın Almalar, Analizler ve daha fazlası.", Track Material Request,Malzeme Talebini Takip et, -Create first Purchase Order,İlk Satınalma Siparişini oluşturun, +Create first Purchase Order,İlk Satınalma Siparişini Oluştur, Opening & Closing,Açılış & Kapanış, Connections,Bağlantılar, Items & Pricing,Ürünler & Fiyatlandırma, @@ -8852,8 +8855,8 @@ Action If Same Rate is Not Maintained,Aynı Oran Sağlanmazsa Yapılacak İşlem Calculate Product Bundle Price based on Child Items' Rates,Bundle Ürün Fiyatını Alt Öğelerin Oranlarına Göre Hesapla, Transaction Settings,İşlem Ayarları, Sales Update Frequency in Company and Project,Şirket ve Projede Satış Güncelleme Sıklığı, -Allow Item to be Added Multiple Times in a Transaction,Bir İşlemde Birden Fazla Öğe Eklenmesine İzin Ver, -Enable Discount Accounting for Selling,Satış için İskonto Muhasebesini Etkinleştirin, +Allow Item to be Added Multiple Times in a Transaction,Bir İşlemde Öğenin birden çok kez eklenmesine izin ver, +Enable Discount Accounting for Selling,Satış için İskonto Muhasebesini Etkinleştir, "If enabled, additional ledger entries will be made for discounts in a separate Discount Account","Etkinleştirilirse, indirimler için ayrı bir İndirim Hesabında ek defter girişleri yapılır", Setting the account as a Company Account is necessary for Bank Reconciliation,Hesabın Şirket Hesabı olarak ayarlanması Banka Mutabakatı için gereklidir, Is Finished Item,Bitmiş Ürün mü, @@ -8881,7 +8884,7 @@ Item Reference,Öğe Referansı, Bank Reconciliation Tool,Banka Uzlaştırma Aracı, Sales Order Reference,Satış Siparişi Referansı, Contact & Address,İletişim ve Adres, -FG Warehouse,Mamul Deposu, +FG Warehouse,Mamül Deposu, Tax Detail,Vergi Detayı, Other Info,Diğer Bilgiler, Transit Entry,Transit Kaydı, @@ -8892,12 +8895,12 @@ Stock Closing,Stok Kapanışı, Stock Validations,Stok Doğrulamaları, Serial & Batch Item,Seri ve Parti Öğesi, Picked Qty (in Stock UOM),Toplanan Mik (Stok Birimi), -Inter Transfer Reference,Inter Transfer Referansı, +Inter Transfer Reference,Transferler Arası Referansı, Update Rate as per Last Purchase,Son Alışa göre Fiyatı Güncelle, Create Job Card based on Batch Size,Parti Büyüklüğüne göre İş Kartı Oluştur, Is Corrective Operation,Düzeltici İşlem mi, Sub Operations,Alt Operasyonlar, -Sales Order Status,Satış Sipariş Durumu, +Sales Order Status,Satış Siparişi Durumu, Order Status,Sipariş Durumu, Terms & Conditions,Vade & Koşullar, From Delivery Date,Teslimat Tarihi Baş. , @@ -8976,7 +8979,7 @@ Chart Of Accounts,Hesap Planı, Permitted Data Type,İzin Verilen Veri Türü, Reports & Masters,Raporlar & Ana Veriler, Masters & Reports,Ana Veriler ve Raporlar, -"ERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to business and legal requirements.","ERPNext, oluşturduğunuz her Şirket için basit bir hesap planı oluşturur, ancak bunu ticari ve yasal gereksinimlere göre değiştirebilirsiniz." +"ERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to business and legal requirements.","ERPNext, oluşturduğunuz her Şirket için basit bir hesap planı oluşturur, ancak bunu ticari ve yasal gereksinimlere göre değiştirebilirsiniz.", Watch Tutorial,Eğitimi izleyin, "An individual who works and is recognized for his rights and duties in your company is your Employee. You can manage the Employee master. It captures the demographic, personal and professional details, joining and leave details, etc.","Şirketinizde çalışan, hakları ve görevleri ile tanınan bir kişi Çalışanınızdır. Çalışan yöneticisini yönetebilirsiniz. Demografik, kişisel ve mesleki ayrıntıları, katılma ve ayrılma ayrıntılarını vb. yakalar.", Payroll,Bordro, @@ -9027,11 +9030,11 @@ Clinical Procedures Status,Klinik Prosedür Durumu, Let's Set Up the Healthcare Module.,Haydi Sağlık Modülünü Kuralım., "Patients, Practitioner Schedules, Settings, and more.","Hastalar, Pratisyen Programları, Ayarlar ve daha fazlası.", Create Patient,Hasta Oluştur, -Create Practitioner Schedule,Pratisyen Programı Oluşturun, +Create Practitioner Schedule,Pratisyen Hekim Programı Oluştur, Introduction to Healthcare Practitioner,Sağlık Personeline Giriş, -Create Healthcare Practitioner,Sağlık Personeli Oluşturun, -Explore Healthcare Settings,Sağlık Hizmeti Ayarlarını Keşfedin, -Explore Clinical Procedure Templates,Klinik Prosedür Şablonlarını Keşfedin, +Create Healthcare Practitioner,Sağlık Personeli Oluştur, +Explore Healthcare Settings,Sağlık Hizmeti Ayarlarını Keşfet, +Explore Clinical Procedure Templates,Klinik Prosedür Şablonlarını Keşfet, Show Hasta List,{0} Listesini Göster, Total Patients,Toplam Hasta, Total Patients Admitted,Kabul Edilen Toplam Hasta, @@ -9065,7 +9068,7 @@ Items Catalogue,Ürün Kataloğu, Get Started,Başlarken, Visit LMS Portal,LMS Portalını ziyaret edin, Create a Course,Kurs Oluştur, -Setup a Home Page,Bir Ana Sayfa Oluşturun, +Setup a Home Page,Bir Ana Sayfa Oluştur, LMS Setting,LMS Ayarları, Documentation,Dokümantasyon, Video Tutorials,Video Eğitimleri, @@ -9087,10 +9090,12 @@ Evaluation Request,Değerlendirme Talebi, Quiz Submission,Sınav Gönderimi, Let's begin your journey with ERPNext,Haydi ERPNext ile yolculuğa başlayalım!, "Item, Customer, Supplier and Quotation","Ürün, Müşteri, Tedarikçi ve Teklif", -Create an Item,Bir Öğe Oluşturun, -Create a new Item ,Bir Öğe Oluşturun, -Create a Customer,Müşteri Oluşturun, +Create an Item,Ürün Oluştur, +Create a new Item ,Yeni Ürün Oluştur , +Create a Customer,Müşteri Oluştur, Create Your First Sales Invoice ,İlk Satış Faturanızı Oluşturun, +"Item is a product or a service offered by your company, or something you buy as a part of your supplies or raw materials.","Ürün/Malzeme, şirketiniz tarafından sunulan bir ürün veya hizmettir ya da malzeme veya hammaddelerinizin bir parçası olarak satın aldığınız bir şeydir.", +"# Create an Item\n\nItem is a product or a service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets, etc.\n","# Bir Öğe Oluşturun\n\nÖğe, şirketiniz tarafından sunulan bir ürün veya hizmettir ya da sarf malzemelerinizin veya hammaddelerinizin bir parçası olarak satın aldığınız bir şeydir.\n\nÖğeler, faturalandırmadan, faturalandırmaya, ERPNext'te yaptığınız her şeyin ayrılmaz bir parçasıdır. satın almadan envanteri yönetmeye kadar satın aldığınız veya sattığınız her şey, ister fiziksel bir ürün ister bir hizmet olsun, Öğeler stok, stok dışı, varyantlar, serileştirilmiş, toplu, varlıklar vb. olabilir.\n", Check Stock Ledger,Stok Defterini Kontrol Edin, Learn Project Management,Proje Yönetimini Öğren, Users List,Kullanıcı Listesi, @@ -9099,7 +9104,7 @@ Partnership,Ortaklık, Proprietorship,Sahiplik, Internal Customer,Dahili Müşteri, Allowed Items,İzin Verilen Öğeler, -Party Specific Item,Partiye Özel Öğe, +Party Specific Item,Cariye Özel Ürün, Portal Users,Portal Kullanıcıları, Customer Portal Users,Müşteri Portalı Kullanıcıları, Show Title in Link Fields,Bağlantı Alanlarında Başlığı Göster, @@ -9117,10 +9122,10 @@ Set Operating Cost / Scrape Items From Sub-assemblies,İşletim Maliyetini Ayarl Create and send emails to a specific group of subscribers periodically.,Belirli aralıklarla belirli bir abone grubuna e-posta oluşturun ve gönderin., Enable Provisional Accounting For Non Stock Items,Stok Dışı Kalemler için Geçici Muhasebeyi Etkinleştir, Book Advance Payments in Separate Party Account,Avans Ödemelerini Ayrı Taraf Hesabında Ayırın, -"Enabling this option will allow you to record -

1. Advances Received in a Liability Account instead of the Asset Account

2. Advances Paid in an Asset Account instead of the Liability Account","Bu seçeneğin etkinleştirilmesi aşağıdakileri kaydetmenize olanak tanır -

1. Varlık Hesabı yerine Pasif Hesabından Alınan Avanslar

2. Pasif Hesabı yerine Varlık Hesabına Ödenen Avanslar", -"# Buying Settings\n\n\nBuying module\u2019s features are highly configurable as per your business needs. Buying Settings is the place where you can set your preferences for:\n\n- Supplier naming and default values\n- Billing and shipping preference in buying transactions\n\n\n","Satın Alma Ayarları\n\n\nSatın Alma modülünün özellikleri iş ihtiyaçlarınıza göre son derece yapılandırılabilir. Satın Alma Ayarları, aşağıdaki tercihlerinizi ayarlayabileceğiniz yerdir:\n\n- Tedarikçi adı ve varsayılan değerler\n- Faturalandırma ve satınalma işlemlerinde gönderim tercihi\n\n\n", +Enabling this option will allow you to record -

1. Advances Received in a Liability Account instead of the Asset Account

2. Advances Paid in an Asset Account instead of the Liability Account,Bu seçeneğin etkinleştirilmesi aşağıdakileri kaydetmenize olanak tanır -

1. Varlık Hesabı yerine Pasif Hesabından Alınan Avanslar

2. Pasif Hesabı yerine Varlık Hesabına Ödenen Avanslar, +# Buying Settings\n\n\nBuying module\u2019s features are highly configurable as per your business needs. Buying Settings is the place where you can set your preferences for:\n\n- Supplier naming and default values\n- Billing and shipping preference in buying transactions\n\n\n,"Satın Alma Ayarları\n\n\nSatın Alma modülünün özellikleri iş ihtiyaçlarınıza göre son derece yapılandırılabilir. Satın Alma Ayarları, aşağıdaki tercihlerinizi ayarlayabileceğiniz yerdir:\n\n- Tedarikçi adı ve varsayılan değerler\n- Faturalandırma ve satınalma işlemlerinde gönderim tercihi\n\n\n", Create a new Item,Yeni bir Ürün Oluştur, -"# Create an Item\n\nItem is a product or a service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets, etc.\n","Bir Öğe Oluşturun\n\nÖğe, şirketiniz tarafından sunulan bir ürün veya hizmettir ya da sarf malzemelerinizin veya hammaddelerinizin bir parçası olarak satın aldığınız bir şeydir.\n\nÖğeler, faturalandırmadan satın alma işlemine kadar ERPNext'te yaptığınız her şeyin ayrılmaz bir parçasıdır. envanteri yönetmek. İster fiziksel bir ürün ister hizmet olsun, satın aldığınız veya sattığınız her şey bir Öğedir. Öğeler stok, stok dışı, varyantlar, serileştirilmiş, toplu, varlıklar vb. olabilir.\n" +"# Create an Item\n\nItem is a product or a service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets, etc.\n","Bir Öğe Oluşturun\n\nÖğe, şirketiniz tarafından sunulan bir ürün veya hizmettir ya da sarf malzemelerinizin veya hammaddelerinizin bir parçası olarak satın aldığınız bir şeydir.\n\nÖğeler, faturalandırmadan satın alma işlemine kadar ERPNext'te yaptığınız her şeyin ayrılmaz bir parçasıdır. envanteri yönetmek. İster fiziksel bir ürün ister hizmet olsun, satın aldığınız veya sattığınız her şey bir Öğedir. Öğeler stok, stok dışı, varyantlar, serileştirilmiş, toplu, varlıklar vb. olabilir.\n", Exchange Rate Revaluation Settings,Döviz Kuru Yeniden Değerleme Ayarları, Add Columns in Transaction Currency,İşlem Para Biriminde Sütun Ekle, Ignore Exchange Rate Revaluation Journals,Döviz Kuru Yeniden Değerleme Günlüklerini Yoksay, @@ -9190,8 +9195,8 @@ Is setup complete,Kurulum tamamlandı mı, Is name setup skipped,Ad kurulumu atlandı mı, Service Level Name,Hizmet Seviyesi Adı, enabled,etkinleştirildi, -Assignment Conditions,Atama Koşulları -Default SLA,Varsayılan SLA +Assignment Conditions,Atama Koşulları, +Default SLA,Varsayılan SLA, "Simple Python Expression, Example: doc.status == 'Open' and doc.ticket_type == 'Bug'","Basit Python İfadesi, Örnek: doc.status == 'Aç' ve doc.ticket_type =='Bug'", condition,koşul, Response and Resolution,Yanıt ve Çözüm, @@ -9230,10 +9235,10 @@ leave application,İzin Uygulaması, Leave Application,İzin Uygulaması, Compensatory Leave Request,Telafi İzin Talebi, Employee Grade,Personel Derecesi, -Create Holiday List,Tatil Listesi Oluşturun, +Create Holiday List,Tatil Listesi Oluştur, Create Leave Type,İzin Türü Oluştur, -Create Leave Allocation,İzin Tahsisi Oluşturun, -Create Leave Application,İzin Başvurusu Oluşturun, +Create Leave Allocation,İzin Tahsisi Oluştur, +Create Leave Application,İzin Başvurusu Oluştur, Monthly Attendance Sheet,Aylık Devam Tablosu, Recruitment Analytics,İşe Alım Analitiği, Employee Advance Summary,Personel Avansı Özeti, @@ -9271,15 +9276,15 @@ Customer Service Representative,Müşteri Hizmetleri Temsilcisi, Executive Assistant,Yönetici Asistanı, Finance Manager,Finans Yöneticisi, Managing Director,Genel Müdür, -Marketing Manager,Pazarlama Müdürü, +Marketing Manager,Pazarlama Yöneticisi, Marketing Specialist,Pazarlama Uzmanı, President,Başkan, -Product Manager,Ürün Müdürü, +Product Manager,Ürün Yöneticisi, Sales Representative,Satış Temsilcisi, Vice President,Başkan Vekili, Attendance & Leaves,Devam ve İzinler, Joining,Katılma, -Log Type,Kayıt Türü, +Log Type,Log Türü, Location / Device ID,Konum / Cihaz Kimliği, Skip Auto Attendance,Otomatik Katılımı Atla, Cost to Company (CTC),Şirkete Maliyeti (CTC), @@ -9291,7 +9296,7 @@ Education Details,Eğitim detayları, Institution Name,Kurum Adı, Degree Type,Derece Türü, Field of Major/Study,Anadal/Çalışma Alanı, -Work Experience Details,İş tecrübesi detayları +Work Experience Details,İş tecrübesi detayları, Work Experience,İş deneyimi, Hide my Private Information from others,Özel Bilgilerimi başkalarından gizle, Private Information includes your Grade and Work Environment Preferences,Özel Bilgiler Notunuzu ve Çalışma Ortamı Tercihlerinizi içerir, @@ -9312,11 +9317,11 @@ Casual Wear,Rahat kıyafet, Formal Wear,Resmi Kıyafet, Collaboration Preference,İşbirliği Tercihi, collaboration,işbirliği, -Role Preference,Rol Tercihi +Role Preference,Rol Tercihi, Clearly Defined Role,Açıkça Tanımlanmış Rol, Location Preference,Konum Tercihi, Travel,Seyahat, -Individual Work,Bireysel Çalışma +Individual Work,Bireysel Çalışma, Team Work,Takım Çalışması, Both Individual and Team Work,Hem Bireysel Hem Takım Çalışması, Unstructured Role,Yapılandırılmamış Rol, @@ -9375,9 +9380,8 @@ Expense Taxes and Charges,Masraf Vergileri ve Harçları, Sanctioned Amount,Onaylanan Tutar, Expenses & Advances,Masraflar ve Avanslar, Unclaimed Amount,Talep Edilmeyen Tutar, -Employee Settings,Çalışan Ayarları, -Employee Naming By,Çalışan İsimlendirmesi, -Adlandırma Serisi,Adlandırma Serisi, +Employee Settings,Personel Ayarları, +Employee Naming By,Personel Adlandırma, Employee records are created using the selected option,Seçilen seçenek kullanılarak çalışan kayıtları oluşturulur, Standard Working Hours,Standart Çalışma Saatleri, Retirement Age (In Years),Emeklilik Yaşı (Yıl Olarak), @@ -9406,7 +9410,7 @@ Leave Type Name,İzin Türü Adı, Maximum Leave Allocation Allowed,İzin Verilen Maksimum İzin Tahsisi, Applicable After (Working Days),Şu Süreden Sonra Geçerlidir (İş Günleri), Maximum Consecutive Leaves Allowed,İzin Verilen Maksimum Ardışık İzinler, -Attendance for the following dates will be skipped/overwritten on submission, +Attendance for the following dates will be skipped/overwritten on submission,, Attendance Warnings,Katılım Uyarıları, Action on Submission,Teslim Edildiğinde Yapılacak İşlem, Existing Record,Mevcut Kayıt, @@ -9432,7 +9436,7 @@ Blanket Order Allowance (%),Blanket Sipariş Ödeneği (%), Update frequency of Project,Projenin güncelleme sıklığı, Use Transaction Date Exchange Rate,İşlem Tarihi Döviz Kurunu Kullanın, "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.","Satınalma Siparişinden Satınalma Faturası oluştururken, Satın Alma Siparişinden devralmak yerine, Faturanın işlem tarihindeki Döviz Kurunu kullanın. Yalnızca Satınalma Faturası için geçerlidir.", -How often should Project be updated of Total Purchase Cost ?,Projenin Toplam Satınalma Maliyeti ne sıklıkta güncellenmeli? +How often should Project be updated of Total Purchase Cost ?,Projenin Toplam Satınalma Maliyeti ne sıklıkta güncellenmeli?, Delete Accounting and Stock Ledger Entries on deletion of Transaction,İşlem silinirken Muhasebe ve Stok Kayıtlarını da Sil, Invoicing Features,Faturalama Özellikleri, Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field,Tedarikçi Fatura No alanında her Satınalma Faturasının benzersiz bir değere sahip olmasını sağla, @@ -9453,7 +9457,7 @@ Tax Amount will be rounded on a row(items) level,Vergi Tutarı satır (öğeler) Invoice and Billing,Fatura ve Faturalandırma, Credit Limit Settings,Kredi Limiti Ayarları, Role Allowed to Over Bill ,Fazla Faturalandırmaya İzin Verilen Rol, -Users with this role are allowed to over bill above the allowance percentage,Bu role sahip kullanıcıların tahsisat yüzdesinin üzerinde fazla faturalandırma yapmasına izin verilir. +Users with this role are allowed to over bill above the allowance percentage,Bu role sahip kullanıcıların tahsisat yüzdesinin üzerinde fazla faturalandırma yapmasına izin verilir., Role allowed to bypass Credit Limit,Kredi Limitini Aşmasına İzin verilen Rol, POS Setting,POS Ayarları, Create Ledger Entries for Change Amount,Değişiklik Tutarı için Defter Girişlerini Oluştur, @@ -9505,7 +9509,7 @@ Deduction Reports,Kesinti Raporları, Income Tax Deductions,Gelir Vergisi Kesintileri, Accounting Reports,Muhasebe Raporları, Employee Incentive,Personel Teşviki, -Retention Bonus,Elde Tutma Bonusu, +Retention Bonus,Birikim Bonusu, Transactions & Reports,İşlemler & Raporlar, Salary Payout,Maaş Ödemesi, Tax & Benefits,Vergi ve Avantajlar, @@ -9513,7 +9517,7 @@ Benefits,Faydalar, Employee Benefit Application,Personellere Sağlanan Fayda Uygulaması, Employee Benefit Claim,Personellere Sağlanan Fayda Talebi, Exemption,Muafiyet, -Employee Tax Exemption Declaration,Personel Vergi Muafiyeti Beyannamesi +Employee Tax Exemption Declaration,Personel Vergi Muafiyeti Beyannamesi, Employee Tax Exemption Proof Submission,Personel Vergi Muafiyeti Belgesinin İbrazı, Tax Setup,Vergi Kurulumu, Employee Tax Exemption Sub Category,Personel Vergi Muafiyeti Alt Kategorisi, @@ -9569,13 +9573,13 @@ Appointment Letter Template,Randevu Mektubu Şablonu, Appointment Letter,Randevu Mektubu, Appraisal,Değerlendirme, Appraisal Overview,Değerlendirmeye Genel Bakış, -Appraisal Cycle,Değerlendirme Döngüsü +Appraisal Cycle,Değerlendirme Döngüsü, Employee Performance Feedback,Çalışan Performans Geri Bildirimi, Employee Feedback Criteria,Çalışan Geri Bildirim Kriterleri, Promotion,Promosyon, Summarized View,Özet Görünüm, Trainings (This Week),Eğitimler (Bu Hafta), -Interviews (This Week),Mülakatlar (Bu Hafta) +Interviews (This Week),Mülakatlar (Bu Hafta), Exits (This Month),Çıkışlar (Bu Ay), New Hires (This Month),Yeni Alımlar (Bu Ay), Onboardings (This Month),İşe Alımlar (Bu Ay), @@ -9587,8 +9591,8 @@ Y-O-Y Transfers,Y-O-Y Transferler, Y-O-Y Promotions,Y-O-Y Promosyonlar, Trainer Name,Eğitmen Adı, Trainer Email,Eğitmen E-postası, -Contact Number,İletişim numarası, -Event Name,Etkinlik adı, +Contact Number,İletişim Numarası, +Event Name,Etkinlik Adı, Event Status,Etkinlik Durumu, Has Certificate,Sertifikası Var, Attendees,Katılımcılar, @@ -9625,3 +9629,64 @@ Dec,Aralık, Advance,Peşinat, Advanced,Gelişmiş, Reference No,Referans No, +Card Links,Kart Linkleri, +Edit Links,Linkleri Düzenle, +Link Type,Link Tipi, +Hidden,Gizli, +Activity Duration,Faaliyet Süresi, +Task DocType,Görev Belge Tipi, +Acceptance for Terms and/or Policies,Şartların ve/veya Politikaların Kabulü, +Expected Time Required (In Mins),Gerekli Beklenen Süre (Dakika), +Actual Time,Fiili Süre, +Delivery Note Packed Item,İrsaliye Paketlenen Kalemi, +Stock Reservation Entry,Stok Rezervasyon Kaydı, +Skip Available Sub Assembly Items,Mevcut Alt Montaj Öğelerini Atla, +Sub Assembly Warehouse,Alt Montaj Deposu, +Is Group Warehouse,Grup Deposu, +Is Rejected Warehouse,Reddedilen Depo, +Item cannot be added to its own descendants,Öğe kendi alt öğelerine eklenemez, +"If yes, then this warehouse will be used to store rejected materials","Cevabınız evet ise bu depo, reddedilen malzemeleri depolamak için kullanılacaktır", +"By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a Naming Series choose the 'Naming Series' option.","Varsayılan olarak Ürün Adı, girilen Ürün Koduna göre ayarlanır. Öğelerin bir Adlandırma Serisine göre adlandırılmasını istiyorsanız 'Adlandırma Serisi' seçeneğini seçin.", +Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.,Envanter İşlemleri için Varsayılan Depo Ayarlayın. Bu Öge yöneticisindeki Varsayılan Depoya getirilecektir., +"If this checkbox is enabled, then the system won’t run the MRP for the available sub-assembly items.","Bu onay kutusu etkinleştirilirse, sistem mevcut alt montaj öğeleri için MİP'yi çalıştırmaz.", +"When a parent warehouse is chosen, the system conducts stock checks against the associated child warehouses","Bir ana depo seçildiğinde sistem, ilgili alt depolara karşı stok kontrolleri gerçekleştirir", +Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions.,Şirket ile ilgili işlemler zaten mevcut! Hesap Planı yalnızca işlem yapmayan bir Şirket için içe aktarılabilir., +Try the new Print Format Builder,Yeni Yazdırma Formatı Oluşturucu'yu Deneyin, +Consider Minimum Order Qty,Minimum Sipariş Miktarını Dikkate al, +Ignore Available Stock,Mevcut Stokları Yoksay, +"If enabled the system will create material requests even if the stock exists in the 'Raw Materials Warehouse'.","Etkinleştirildiğinde 'Hammadde Deposu'nda stok mevcut olsa bile sistem malzeme talepleri oluşturacaktır.", +No {0} found with matching filters. Clear filters to see all {0}.,Eşleşen filtrelerle {0} bulunamadı. {0} öğesinin tamamını görmek için filtreleri temizleyin., +Invalid date,Geçersiz Tarih, +Setup Series for transactions,İşlemler için Kurulum Serisi, +Set Naming Series options on your transactions.,İşlemlerinizde Seri Adlandırma seçeneklerini ayarlayın., +Update Series Counter,Seri Sayacı Güncelle, +Amended Documents,Değiştirilen Belgeler, +Workspace Manager,Workspace Yöneticisi, +Operation & Workstation,Operasyon ve İş İstasyonu, +Serial and Batch Bundle,Seri ve Toplu Bundle, +Delivery Term,Teslimat Koşulları, +Proforma Date,Proforma Tarihi, +Price Description,Fiyat Açıklaması, +Shipping Description,Nakliye Açıklaması, +Is Shipping Included,Nakliye Dahil mi, +Is Tax Included,Vergi Dahil mi, +HR & Payroll,İK ve Bordro, +HR & Payroll Settings,İK ve Bordro Ayarları, +UnReconcile,Mutabakatı Kaldır, +Sistem Notifications,System Bildirimleri, +Quality Inspection(s),Kalite Kontrol, +Named Place,Adlandırılmış Yer, +VAT %20,KDV %20, +Disables auto-fetching of existing quantity,Mevcut miktarın otomatik olarak getirilmesini devre dışı bırakır, +Scan Mode,Tarama Modu, +Unreconcile Payment,Ödemeyi Uzlaştırma, +Unreconcile Payment Entries,Ödeme Girişlerinin Uzlaştırma, +Job Card Scheduled Time,İş Kartının Planlanan Zamanı, +Document Naming Settings,Belge Adlandırma Ayarları, +Asset Depreciation Schedule,Varlık Amortisman Planı, +Try a Naming Series,Bir Adlandırma Serisini Deneyin, +Get a preview of generated names with a series.,Bir seriyle oluşturulan adların önizlemesini alın., +Default Amendment Naming,Varsayılan Değişikliğin Adlandırılması, +Update Amendment Naming,Değişiklik Adlandırmasını Güncelle, +Default Naming,Varsayılan Adlandırma, +Amend Counter,Sayacı Değiştir,