Compare commits

..

118 Commits

Author SHA1 Message Date
coderabbitai[bot]
b7ae191478 📝 Add docstrings to fix/warehouse-source-reference
Docstrings generation was requested by @matteoarosti.

* https://github.com/frappe/erpnext/pull/50058#issuecomment-3399282664

The following files were modified:

* `erpnext/manufacturing/report/production_planning_report/production_planning_report.py`
2025-10-13 22:40:48 +00:00
El-Shafei H.
6cacead726 fix(Supplier Quotation Comparison): add a missing translate function (#49497)
* Update supplier_quotation_comparison.py

* refactor: text cleaning
2025-10-13 23:58:57 +05:30
akhilakr113
2d8513de4e fix: extend quotation filters to exclude 'Ordered' quotations in 'Get Items From' on Sales Order (#50029)
* feat: show item name in update items dialog for sales and purchase order

* feat: remove the ordered quotation from listing the quotation in sales order get items from

* chore: remove this pr from this
2025-10-13 23:45:26 +05:30
Mihir Kandoi
992027fe89 Merge pull request #50025 from thomasantony12/dev-batch_order_fix
fix: Batch ordering based on the method mentioned in settings
2025-10-13 19:45:41 +05:30
rohitwaghchaure
4eb045d927 Merge pull request #50047 from vorasmit/compex-fetch-rm-items
fix: enhance sub-assembly item handling in raw material request calculations
2025-10-13 19:36:07 +05:30
Mihir Kandoi
1717a7c983 refactor: move value inline 2025-10-13 19:26:34 +05:30
rohitwaghchaure
2de4b2ea56 Merge pull request #50048 from rohitwaghchaure/fixed-unhide-field
fix: show qty in popup while making additional transfer entry
2025-10-13 17:27:43 +05:30
Rohit Waghchaure
b08e0014f7 fix: show qty in popup while making additional transfer entry 2025-10-13 16:46:42 +05:30
Smit Vora
f912c8419a fix: enhance sub-assembly item handling in raw material request calculations 2025-10-13 16:16:01 +05:30
Khushi Rawat
f757adc7f7 Merge pull request #50040 from khushi8112/add-composite-indexes-advance-payment-ledger
perf: add composite indexes to Advance Payment Ledger Entry
2025-10-13 16:07:07 +05:30
rohitwaghchaure
df65fbbc4a Merge pull request #50043 from rohitwaghchaure/fixed-delivered-qty-in-sre
fix: delivered qty in reservation entry
2025-10-13 15:32:40 +05:30
khushi8112
59bd35c64d fix: revert unrelated manual modified timestamp change 2025-10-13 15:15:48 +05:30
Rohit Waghchaure
bd03bcdcb2 fix: delivered qty in reservation entry 2025-10-13 14:52:04 +05:30
khushi8112
7fcf277055 perf: add composite indexes to Advance Payment Ledger Entry table 2025-10-13 13:56:30 +05:30
ruthra kumar
1e2bcde0f5 Merge pull request #50017 from aerele/service_stop_date_comparison
fix(deferred revenue): validate service stop date
2025-10-13 12:37:15 +05:30
Diptanil Saha
f23d6911f3 Merge pull request #50034 from diptanilsaha/gh_49941
fix: set default roles on Role Profiles during reinstallation
2025-10-13 12:06:34 +05:30
diptanilsaha
12c1b8a910 fix: set default roles on role_profile during reinstallation 2025-10-13 02:20:48 +05:30
Khushi Rawat
f4c37f1f20 Merge pull request #49508 from khushi8112/print-format-for-sales-invoice
feat: print format for sales invoice
2025-10-12 20:06:53 +05:30
MochaMind
a799af7f9f fix: sync translations from crowdin (#49959) 2025-10-12 16:10:52 +02:00
MochaMind
f697679b37 chore: update POT file (#50030) 2025-10-12 16:03:02 +02:00
khushi8112
f14b3ed723 refactor: add permission check and minor fixes 2025-10-12 19:22:26 +05:30
thomasantony12
fab7f9ee53 chore: use get_single_value instead of get_cached_doc 2025-10-12 18:05:29 +05:30
khushi8112
6e07aac5b7 fix: add filter query for address field 2025-10-12 13:11:04 +05:30
khushi8112
a4fe0fb809 refactor: use query builder to set company address 2025-10-12 13:11:04 +05:30
khushi8112
533257c4f3 refactor: use get_value to improve performance 2025-10-12 13:11:04 +05:30
khushi8112
33110951b3 refactor: replace get_doc with get_value 2025-10-12 13:11:04 +05:30
khushi8112
12ebab1657 refactor: change print format type html to custom 2025-10-12 13:11:04 +05:30
khushi8112
50eb6786bf feat: condition based item code column 2025-10-12 13:11:04 +05:30
khushi8112
590207419a style: slight spacing and alignment fix 2025-10-12 13:11:04 +05:30
khushi8112
92f69ae484 fix: validate email address 2025-10-12 13:11:04 +05:30
khushi8112
610dcbb974 chore: remove frappe.db.commit 2025-10-12 13:11:04 +05:30
khushi8112
e3ca318e93 fix: small ui changes 2025-10-12 13:11:04 +05:30
khushi8112
98838b1dd5 feat: input website, email, phone_no if not already set in company 2025-10-12 13:11:04 +05:30
khushi8112
abc7bf2fd6 style: add company and customer name on bill_to and bill_from section 2025-10-12 13:11:04 +05:30
khushi8112
8a19dc4a20 style: format and display the address for improved visual clarity 2025-10-12 13:11:04 +05:30
khushi8112
301b294da9 style: fix layout issues with extended data 2025-10-12 13:11:04 +05:30
khushi8112
bf6c331ac4 fix: show tax breakup in print format 2025-10-12 13:11:04 +05:30
khushi8112
780d3f5ba4 fix: better sub total section with tax breakup 2025-10-12 13:11:04 +05:30
khushi8112
dbf9faa87c feat: prompt user to input company logo and address if missing in print preview 2025-10-12 13:11:04 +05:30
khushi8112
6494fc42c6 refactor: create_letter_head for readability 2025-10-12 13:11:04 +05:30
khushi8112
3abdfcb269 fix: app path correctly 2025-10-12 13:11:04 +05:30
khushi8112
0d58dfd0fa feat: add default letterhead with HTML template via after_install 2025-10-12 13:11:04 +05:30
khushi8112
f6ebf2d0b3 feat: letterhead for print format 2025-10-12 13:11:04 +05:30
khushi8112
842a3645dc refactor: remove tax breakup table from the print format 2025-10-12 13:11:04 +05:30
khushi8112
a6d92e5ec7 refactor: small changes for better readability 2025-10-12 13:11:04 +05:30
khushi8112
ce19514a2c refactor: update letterhead styles for wkhtmltopdf compatibility 2025-10-12 13:11:04 +05:30
khushi8112
e223731924 refactor: remove flex usage for better wkhtmltopdf support 2025-10-12 13:11:04 +05:30
khushi8112
39b6aab714 fix: update styles to work with wkhtmltopdf rendering 2025-10-12 13:11:04 +05:30
khushi8112
6703610596 fix: do not make letterhead default 2025-10-12 13:11:04 +05:30
khushi8112
f4f2d11fa4 style: always show border even when logo is missing 2025-10-12 13:11:04 +05:30
khushi8112
5c4f778223 style: center-align logo within its container div 2025-10-12 13:11:04 +05:30
khushi8112
5f97bec2b3 refactor: revert debugging changes 2025-10-12 13:11:04 +05:30
khushi8112
f1a2e1b725 refactor: remove img tag for testing 2025-10-12 13:11:03 +05:30
khushi8112
17397ae652 test: add in_install condition for debugging 2025-10-12 13:11:03 +05:30
khushi8112
6b83309750 test: just debugging 2025-10-12 13:11:03 +05:30
khushi8112
e08f82909c fix: radius of the items/tax table thead 2025-10-12 13:11:03 +05:30
khushi8112
4cc2afbd83 feat: print format design two 2025-10-12 13:11:03 +05:30
khushi8112
c780796284 fix: remove border if company logo not available 2025-10-12 13:11:03 +05:30
khushi8112
ddf4a83cf8 fix: condition based address display 2025-10-12 13:11:03 +05:30
khushi8112
b5c739d1cc fix: broken img tag in letterhead 2025-10-12 13:11:03 +05:30
khushi8112
2bc19783cb fix: letterhead styling 2025-10-12 13:11:03 +05:30
khushi8112
1adbf90d8c fix: css changes in letterhead 2025-10-12 13:11:03 +05:30
khushi8112
7386270fce feat: add letterhead fixture 2025-10-12 13:11:03 +05:30
khushi8112
156dda8157 style: change padding 2025-10-12 13:11:03 +05:30
khushi8112
af974fbccd feat: add css 2025-10-12 13:11:03 +05:30
khushi8112
e85238383f feat: default print format for sales invoice 2025-10-12 13:11:03 +05:30
rohitwaghchaure
b380b60486 Merge pull request #50024 from rohitwaghchaure/fixed-adjustment-sle-entry
fix: stock ledger adjustment entry
2025-10-12 11:40:41 +05:30
thomasantony12
7fa800b874 fix: Batch ordering based on the method mentioned in settings 2025-10-12 11:28:05 +05:30
Rohit Waghchaure
8b6e58d02a fix: stock ledger adjustment entry 2025-10-12 11:12:04 +05:30
ravibharathi656
58203a89f1 fix(deferred revenue): validate service stop date 2025-10-11 12:28:42 +05:30
rohitwaghchaure
eb5899c786 Merge pull request #50007 from rohitwaghchaure/fixed-expense-account-in-company
feat: service expense account in the company
2025-10-10 16:49:32 +05:30
Rohit Waghchaure
4605051903 feat: service expense account in the company 2025-10-10 16:21:03 +05:30
rohitwaghchaure
15397b17f3 Merge pull request #49985 from mihir-kandoi/gh-49622
fix: call onload of buying controller in purchase_receipt.js
2025-10-10 14:11:50 +05:30
rohitwaghchaure
b63566681b Merge pull request #50002 from rohitwaghchaure/fixed-posting-date-in-serial-no
fix: posting date in serial no
2025-10-10 14:08:22 +05:30
rohitwaghchaure
a5e49ea8a1 Merge pull request #49993 from mihir-kandoi/gh-46943
fix: incorrect PR status when using set landed cost based on PI rate
2025-10-10 14:08:02 +05:30
Rohit Waghchaure
98f186b0e0 fix: posting date in serial no 2025-10-10 12:46:33 +05:30
Diptanil Saha
eac6e6a7dd Merge pull request #49940 from ljain112/perf-status-updater
perf: optimize validate_qty method to eliminate N+1 query problem
2025-10-10 11:26:46 +05:30
Khushi Rawat
2de3f63478 Merge pull request #49980 from aerele/fixed-asset-register-show-opening-entries
fix: fixed asset register showing opening entries
2025-10-10 11:12:39 +05:30
Khushi Rawat
334bb609f0 Merge pull request #49995 from rehanrehman389/report-show-asset-name
feat: add asset name to Asset Depreciations and Balances report
2025-10-10 11:04:40 +05:30
Rehan Ansari
b4cf6a1fb9 feat: add asset name to Asset Depreciations and Balances report 2025-10-09 23:40:56 +05:30
Mihir Kandoi
4a26810871 fix: fix: incorrect PR status when using set landed cost based on PI rate 2025-10-09 20:47:00 +05:30
rohitwaghchaure
96cd8cdb38 Merge pull request #49991 from rohitwaghchaure/fixed-consider-negative-batches
fix: consider negative qty in batch qty calculation
2025-10-09 20:37:06 +05:30
Rohit Waghchaure
912ffc2d64 fix: consider negative qty in batch qty calculation 2025-10-09 19:42:54 +05:30
ljain112
f1f61ff61b refactor: replace SQL query with Query Builder in fetch_items_with_pending_qty method 2025-10-09 17:51:51 +05:30
Diptanil Saha
aaca906a0f Merge pull request #49764 from elshafei-developer/add-employee-name-to-session-user
feat: Cache employee name in session data on boot
2025-10-09 15:30:20 +05:30
ruthra kumar
94b75e80b9 fix: use naming series configuration for Sales Partner 2025-10-09 15:16:34 +05:30
Mihir Kandoi
67f7341721 fix: call onload of buying controller in purchase_receipt.js 2025-10-09 15:12:56 +05:30
rohitwaghchaure
b11d064a2a Merge pull request #49975 from rohitwaghchaure/fixed-sales-return-issue
fix: sales return for product bundle items
2025-10-09 15:08:57 +05:30
Murtaza Ghadiali
959c311795 refactor: use naming series configuration for Sales Partner ID
Replaced hardcoded ID assignment with Naming Series configuration so that Sales Partner IDs can be managed via Setup > Naming Series. Fixes #49623
2025-10-09 14:53:45 +05:30
Rohit Waghchaure
1d57bbca11 test: test case for sales return for product bundle 2025-10-09 13:56:04 +05:30
ravibharathi656
c9d98eb4f0 fix: fixed asset register showing opening entries 2025-10-09 13:51:29 +05:30
Soham Kulkarni
fc7a33ebf8 Merge pull request #49979 from sokumon/format-url
fix: format workstation link correctly
2025-10-09 13:05:02 +05:30
sokumon
b48bff2029 fix: format workstation link correctly 2025-10-09 13:01:46 +05:30
Diptanil Saha
a82c0c20f0 Merge pull request #49939 from aerele/retain-address
fix: preserve address if present
2025-10-09 11:09:08 +05:30
El-Shafei H.
83d575206b feat: cache employee name in session data on boot 2025-10-09 08:25:58 +03:00
El-Shafei H.
e2d4ce74d9 Merge branch 'frappe:develop' into add-employee-name-to-session-user 2025-10-09 08:22:38 +03:00
Rohit Waghchaure
13ce7279a8 fix: sales return for product bundle items 2025-10-09 10:15:42 +05:30
ruthra kumar
4672c2c383 Merge pull request #49848 from Jaswanth-Sriram-Veturi/perf/transaction-set-dynamic-labels
perf: avoid unnecessary set_dynamic_labels updates
2025-10-09 10:15:01 +05:30
Jaswanth Sriram
946073cfd9 perf: avoid unnecessary set_dynamic_labels updates 2025-10-09 10:13:49 +05:30
rohitwaghchaure
1d97b7cc2b Merge pull request #49973 from rohitwaghchaure/fixed-support-47931
fix: Reset Raw Materials Table button not working
2025-10-08 22:34:17 +05:30
Rohit Waghchaure
128e243945 fix: Reset Raw Materials Table button not working 2025-10-08 21:46:17 +05:30
rohitwaghchaure
a6a04e8245 Merge pull request #49969 from rohitwaghchaure/fixed-incorrect-qty-in-stock-levels
fix: incorrect qty in stock levels
2025-10-08 19:28:24 +05:30
rohitwaghchaure
6c8e909599 Merge pull request #49967 from rohitwaghchaure/fixed-batch-qty-for-expired-batches
fix: batch qty for expired batches
2025-10-08 19:18:06 +05:30
Rohit Waghchaure
aab6271b14 fix: incorrect qty in stock levels 2025-10-08 19:06:53 +05:30
Rohit Waghchaure
ff2faf36a7 fix: batch qty for expired batches 2025-10-08 18:55:03 +05:30
rohitwaghchaure
231356a005 Merge pull request #49966 from rohitwaghchaure/fixed-incorrect-field
fix: incorrect field valuation_rate
2025-10-08 18:45:22 +05:30
Rohit Waghchaure
630d873214 fix: incorrect field valuation_rate 2025-10-08 18:27:44 +05:30
Diptanil Saha
6cc421eec6 Merge pull request #49957 from diptanilsaha/psoa_fixes
fix: process statement of accounts
2025-10-08 14:55:34 +05:30
El-Shafei H.
5d0958c5b1 feat: Cache employee name in session data on boot 2025-10-08 09:51:13 +03:00
El-Shafei H.
1d7a8dda26 Merge branch 'frappe:develop' into add-employee-name-to-session-user 2025-10-08 09:49:56 +03:00
diptanilsaha
4a4c2188ec fix(process statement of accounts): naming of reports 2025-10-07 16:04:23 +05:30
ljain112
f00a63b69d perf: optimize validate_qty method to eliminate N+1 query problem 2025-10-07 12:46:30 +05:30
ravibharathi656
0678638106 fix: preserve address if present 2025-10-07 12:46:09 +05:30
diptanilsaha
d610d1dccd feat(process statement of accounts): added more frequency options for auto email 2025-10-07 12:29:24 +05:30
diptanilsaha
dbab718aaa fix(process statement of accounts): allow renaming 2025-10-07 11:18:30 +05:30
El-Shafei H.
3578ee1195 Merge branch 'frappe:develop' into add-employee-name-to-session-user 2025-09-28 08:37:11 +03:00
El-Shafei H.
6730960f56 Merge branch 'frappe:develop' into add-employee-name-to-session-user 2025-09-24 11:26:38 +03:00
El-Shafei H.
4b7cb6bfad feat: add employee name to session user 2025-09-08 13:46:52 +03:00
66 changed files with 64900 additions and 1631 deletions

View File

@@ -46,7 +46,8 @@ def validate_service_stop_date(doc):
if (
old_stop_dates
and old_stop_dates.get(item.name)
and item.service_stop_date != old_stop_dates.get(item.name)
and item.service_stop_date
and getdate(item.service_stop_date) != getdate(old_stop_dates.get(item.name))
):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))

View File

@@ -82,7 +82,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-07-29 11:37:42.678556",
"modified": "2025-10-13 15:11:58.300836",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Payment Ledger Entry",

View File

@@ -34,3 +34,15 @@ class AdvancePaymentLedgerEntry(Document):
and not frappe.flags.is_reverse_depr_entry
):
update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None)
def on_doctype_update():
frappe.db.add_index(
"Advance Payment Ledger Entry",
["against_voucher_type", "against_voucher_no"],
)
frappe.db.add_index(
"Advance Payment Ledger Entry",
["voucher_type", "voucher_no"],
)

View File

@@ -13,7 +13,7 @@
</div>
{% endif %}
</div>
<h2 class="text-center">{{ _("STATEMENT OF ACCOUNTS") }}</h2>
<h2 class="text-center">{{ _("GENERAL LEDGER") }}</h2>
<div>
{% if filters.party[0] == filters.party_name[0] %}
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{ filters.party_name[0] }}</b></h5>

View File

@@ -1,5 +1,6 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2020-05-22 16:46:18.712954",
"doctype": "DocType",
@@ -69,7 +70,7 @@
"fieldname": "frequency",
"fieldtype": "Select",
"label": "Frequency",
"options": "Weekly\nMonthly\nQuarterly"
"options": "Daily\nWeekly\nBiweekly\nMonthly\nQuarterly"
},
{
"fieldname": "company",
@@ -416,7 +417,7 @@
}
],
"links": [],
"modified": "2025-09-03 14:24:43.608565",
"modified": "2025-10-07 12:19:20.719898",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",

View File

@@ -8,7 +8,7 @@ import frappe
from frappe import _
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
from frappe.utils import add_days, add_months, format_date, getdate, today
from frappe.utils import add_days, add_months, add_to_date, format_date, getdate, today
from frappe.utils.jinja import validate_template
from frappe.utils.pdf import get_pdf
from frappe.www.printview import get_print_style
@@ -55,7 +55,7 @@ class ProcessStatementOfAccounts(Document):
enable_auto_email: DF.Check
filter_duration: DF.Int
finance_book: DF.Link | None
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
frequency: DF.Literal["Daily", "Weekly", "Biweekly", "Monthly", "Quarterly"]
from_date: DF.Date | None
ignore_cr_dr_notes: DF.Check
ignore_exchange_rate_revaluation_journals: DF.Check
@@ -555,8 +555,9 @@ def send_emails(document_name, from_scheduler=False, posting_date=None):
if doc.enable_auto_email and from_scheduler:
new_to_date = getdate(posting_date or today())
if doc.frequency == "Weekly":
new_to_date = add_days(new_to_date, 7)
if doc.frequency in ("Daily", "Weekly", "Biweekly"):
frequency = {"Daily": 1, "Weekly": 7, "Biweekly": 14}
new_to_date = add_days(new_to_date, frequency[doc.frequency])
else:
new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3)
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)

View File

@@ -23,7 +23,7 @@
{% endif %}
</div>
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
<h2 class="text-center" style="margin-top:0">{{ _("STATEMENT OF ACCOUNTS") }}</h2>
<h4 class="text-center">
{{ filters.customer_name }}
</h4>

View File

@@ -2629,6 +2629,38 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
@IntegrationTestCase.change_settings(
"Buying Settings", {"maintain_same_rate": 0, "set_landed_cost_based_on_purchase_invoice_rate": 1}
)
def test_pr_status_rate_adjusted_from_pi(self):
pr = make_purchase_receipt(qty=5, rate=100)
pi = create_purchase_invoice_from_receipt(pr.name)
pi.submit()
pr.reload()
# Inital check
self.assertEqual(pr.status, "Completed")
pi.reload()
pi.cancel()
pi = create_purchase_invoice_from_receipt(pr.name)
pi.items[0].rate = 80
pi.submit()
pr.reload()
# Test 1 : Adjustment amount is negative
self.assertEqual(pr.status, "Completed")
pi.reload()
pi.cancel()
pi = create_purchase_invoice_from_receipt(pr.name)
pi.items[0].rate = 120
pi.submit()
pr.reload()
# Test 2 : Adjustment amount is positive
self.assertEqual(pr.status, "Completed")
def test_opening_invoice_rounding_adjustment_validation(self):
pi = make_purchase_invoice(do_not_save=1)
pi.items[0].rate = 99.98

View File

@@ -232,7 +232,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, IntegrationTestCase):
company.save()
test_cc = company.cost_center
default_expense_account = company.default_expense_account
default_expense_account = company.service_expense_account
item = make_item(properties={"is_stock_item": 0})

View File

@@ -282,22 +282,40 @@ class SalesInvoice(SellingController):
def before_print(self, settings=None):
from frappe.contacts.doctype.address.address import get_address_display_list
super().before_print(settings)
company_details = frappe.get_value(
"Company", self.company, ["company_logo", "website", "phone_no", "email"], as_dict=True
)
address_display_list = get_address_display_list("Company", self.company)
address_line = address_display_list[0] if address_display_list else ""
required_fields = [
company_details.get("company_logo"),
company_details.get("website"),
company_details.get("phone_no"),
company_details.get("email"),
self.company_address,
address_line,
]
if not all(required_fields) and not frappe.has_permission("Company", "write", throw=False):
frappe.msgprint(
_(
"Some required Company details are missing. You don't have permission to update them. Please contact your System Manager."
)
)
return
if not self.company_address and not frappe.has_permission("Sales Invoice", "write", throw=False):
frappe.msgprint(
_(
"Company Address is missing. You don't have permission to update it. Please contact your System Manager."
)
)
return
address_display_list = get_address_display_list("Company", self.company)
address_line = address_display_list[0].get("address_line1") if address_display_list else ""
required_fields.append(self.company_address)
required_fields.append(address_line)
if not all(required_fields):
frappe.publish_realtime(
"sales_invoice_before_print",
@@ -2845,13 +2863,13 @@ def save_company_master_details(name, company, details):
details = frappe.parse_json(details)
if details.get("email"):
validate_email_address(details.email, throw=True)
validate_email_address(details.get("email"), throw=True)
company_fields = ["company_logo", "website", "phone_no", "email"]
updated_fields = {field: details.get(field) for field in company_fields if details.get(field)}
company_fields_to_update = {field: details.get(field) for field in company_fields if details.get(field)}
if updated_fields:
frappe.db.set_value("Company", company, updated_fields)
if company_fields_to_update:
frappe.db.set_value("Company", company, company_fields_to_update)
company_address = details.get("company_address")
if details.get("address_line1"):
@@ -2861,6 +2879,7 @@ def save_company_master_details(name, company, details):
"address_title": details.get("address_title"),
"address_type": details.get("address_type"),
"address_line1": details.get("address_line1"),
"address_line2": details.get("address_line2"),
"city": details.get("city"),
"state": details.get("state"),
"pincode": details.get("pincode"),
@@ -2869,20 +2888,22 @@ def save_company_master_details(name, company, details):
"links": [{"link_doctype": "Company", "link_name": company}],
}
)
address_doc.insert(ignore_permissions=True)
address_doc.insert()
company_address = address_doc.name
if company_address:
current_display = frappe.db.get_value("Sales Invoice", name, "company_address_display")
if not current_display or details.get("address_line1"):
frappe.db.set_value(
"Sales Invoice",
name,
{
"company_address": company_address,
"company_address_display": get_address_display(company_address),
},
)
company_address_display = frappe.db.get_value("Sales Invoice", name, "company_address_display")
if not company_address_display or details.get("address_line1"):
from frappe.query_builder import DocType
SalesInvoice = DocType("Sales Invoice")
(
frappe.qb.update(SalesInvoice)
.set(SalesInvoice.company_address, company_address)
.set(SalesInvoice.company_address_display, get_address_display(company_address))
.where(SalesInvoice.name == name)
).run()
return True

View File

@@ -16,12 +16,9 @@
width: 100px;
text-align: center;
position: relative;
}
.logo-container {
width: 90px;
border: 1px solid #EDEDED;
border-radius: 15px;
display: block;
}
.logo-container img {
@@ -72,38 +69,37 @@
{{ doc.company }}
</div>
{% if doc.company_address %}
{% set company_address_display = frappe.get_doc("Address", doc.company_address) %}
{{ company_address_display.address_line1 or "" }}
{% if company_address_display.address_line2 %}{{ company_address_display.address_line2 }}{% endif %}<br>
{{ company_address_display.city or "" }} {{ company_address_display.state or "" }} {{ company_address_display.pincode or "" }} {{ company_address_display.country or "" }}<br>
{% set company_address = frappe.db.get_value("Address", doc.company_address, ["address_line1", "address_line2", "city", "state", "pincode", "country"], as_dict=True) %}
{{ company_address.get("address_line1") or "" }}<br>
{% if company_address.get("address_line2") %}{{ company_address.get("address_line2") }}<br>{% endif %}
{{ company_address.get("city") or "" }}, {{ company_address.get("state") or "" }} {{ company_address.get("pincode") or "" }}, {{ company_address.get("country") or "" }}<br>
{% endif %}
</td>
<td class="invoice-info-cell">
{% set website = frappe.db.get_value("Company", doc.company, "website") %}
{% set email = frappe.db.get_value("Company", doc.company, "email") %}
{% set phone_no = frappe.db.get_value("Company", doc.company, "phone_no") %}
{% set company_details = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
<div class="invoice-info">
<span class="invoice-label">{{ _("Invoice:") }}</span>
<span>{{ doc.name }}</span>
</div>
{% if website %}
{% if company_details.website %}
<div class="invoice-info">
<span class="invoice-label">{{ _("Website:") }}</span>
<span>{{ website }}</span>
<span>{{ company_details.website }}</span>
</div>
{% endif %}
{% if email %}
{% if company_details.email %}
<div class="invoice-info">
<span class="invoice-label">{{ _("Email:") }}</span>
<span>{{ email }}</span>
<span>{{ company_details.email }}</span>
</div>
{% endif %}
{% if phone_no %}
{% if company_details.phone_no %}
<div class="invoice-info">
<span class="invoice-label">{{ _("Contact:") }}</span>
<span>{{ phone_no }}</span>
<span>{{ company_details.phone_no }}</span>
</div>
{% endif %}
</td>

View File

@@ -22,15 +22,14 @@
vertical-align: top;
}
.logo {
.letter-head .logo {
width: 90px;
display: block;
margin-bottom: 10px;
}
.logo img {
.letter-head .logo img {
border-radius: 15px;
border: 1px solid #ededed;
}
.company-name {
@@ -83,12 +82,12 @@
{% endif %}
<div class="company-name">{{ doc.company }}</div>
<div class="company-address">
{% if doc.company_address %} {% set company_address_display = frappe.get_doc("Address",
doc.company_address) %} {{ company_address_display.address_line1 or "" }} {% if
company_address_display.address_line2 %}{{ company_address_display.address_line2 }}<br />{%
endif %} {{ company_address_display.city or "" }} {{ company_address_display.state or ""
}} {{ company_address_display.pincode or "" }} {{ company_address_display.country or ""
}}<br />
{% if doc.company_address %}
{% set company_address = frappe.db.get_value("Address", doc.company_address, ["address_line1", "address_line2", "city", "state", "pincode", "country"], as_dict=True) %}
{{ company_address.address_line1 or "" }}<br />
{% if company_address.address_line2 %} {{ company_address.address_line2 }}<br /> {% endif %}
{{ company_address.city or "" }}, {{ company_address.state or "" }}
{{ company_address.pincode or "" }}, {{ company_address.country or ""}}<br />
{% endif %}
</div>
</td>
@@ -100,23 +99,24 @@
<br />
</div>
<div style="text-align: left; float: right" class="other-details">
{% set website = frappe.db.get_value("Company", doc.company, "website") %} {% set email =
frappe.db.get_value("Company", doc.company, "email") %} {% set phone_no =
frappe.db.get_value("Company", doc.company, "phone_no") %} {% if website %}
<div>
<span class="contact-title">{{ _("Website:") }}</span
><span class="contact-value">{{ website }}</span>
</div>
{% endif %} {% if email %}
<div>
<span class="contact-title">{{ _("Email:") }}</span
><span class="contact-value">{{ email }}</span>
</div>
{% endif %} {% if phone_no %}
<div>
<span class="contact-title">{{ _("Contact:") }}</span
><span class="contact-value">{{ phone_no }}</span>
</div>
{% set company_details = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
{% if company_details.website %}
<div>
<span class="contact-title">{{ _("Website:") }}</span
><span class="contact-value">{{ company_details.website }}</span>
</div>
{% endif %}
{% if company_details.email %}
<div>
<span class="contact-title">{{ _("Email:") }}</span
><span class="contact-value">{{ company_details.email }}</span>
</div>
{% endif %}
{% if company_details.phone_no %}
<div>
<span class="contact-title">{{ _("Contact:") }}</span
><span class="contact-value">{{ company_details.phone_no }}</span>
</div>
{% endif %}
</div>
</td>

View File

@@ -1,332 +0,0 @@
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None,
print_heading_template=None) -%}
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
{% if print_heading_template %}
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
{% endif %}
{%- endmacro -%}
{% for page in layout %}
<div class="page-break invoice-page">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
</div>
<style>
.letter-head {
padding-top: 12px !important;
padding-bottom: 12px !important;
}
.invoice-page {
font-family: Inter, sans-serif;
color: #171717;
}
.print-format-body {
padding: 30px 12px 12px 12px !important;
}
.title {
color: #7c7c7c !important;
}
.text-muted {
color: #7c7c7c !important;
}
.text-dark {
color: #171717 !important;
}
.small-text {
font-size: 11px;
color: #666;
}
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-bold {
font-weight: 700;
}
.mt-15 {
margin-top: 15px;
}
.mt-16 {
margin-top: 16px;
}
.mt-40 {
margin-top: 40px;
}
.mw-400 {
max-width: 400px;
}
.items-table,
.tax-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
border-radius: 10px;
overflow: hidden;
}
.items-table td,
.tax-table td {
border-bottom: 1px solid #ededed;
padding: 8px;
}
.items-table thead td,
.tax-table thead td {
color: #7c7c7c !important;
}
.items-table thead tr:first-of-type td,
.tax-table thead tr:first-of-type td {
border-bottom: none;
}
.items-table thead tr:first-child td:first-child,
.tax-table thead tr:first-child td:first-child {
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}
.items-table thead tr:first-child td:last-child,
.tax-table thead tr:first-child td:last-child {
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.in-words {
color: #171717 !important;
max-width: 400px;
word-wrap: break-word;
line-height: 1.5;
font-size: 12px;
}
.row-divider {
border-bottom: 1px solid #e9e9e9;
}
.highlight-bg {
background: #f8f8f8;
}
.info-card {
margin-top: 40px;
color: #7c7c7c;
}
.info-text {
margin-bottom: 12px;
}
.print-format {
color: #171717;
font-size: 14px;
font-style: normal;
font-weight: 420;
line-height: 21px;
padding: 0px !important;
margin-left: 0mm !important;
margin-right: 0mm !important;
}
.letter-head-footer {
margin-top: 0px !important;
}
.print-format-body td {
padding: 8px 8px !important;
}
.tax-table td,
.items-table td {
height: auto !important;
}
.tax-table thead td,
.items-table thead td {
height: 32px;
}
.item-img td {
border: none !important;
padding: 0px !important;
}
</style>
<div class="print-format-body">
<table class="info-table col-xs-12" style="width: 100%; margin-left: 12px">
<tr>
<td class="col-xs-6" style="padding: 0 !important">
<div class="title col-xs-5 p-0">
<div class="info-text">Customer Name:</div>
<div class="info-text">Bill to:</div>
</div>
<div class="col-xs-7">
<div class="info-text">{{ doc.customer_name }}</div>
<div class="info-text">
{% if doc.customer_address %}
{% set customer_address = frappe.get_doc("Address", doc.customer_address) %}
{{ customer_address.address_line1 or "" }}<br>
{% if customer_address.address_line2 %}{{ customer_address.address_line2 }}<br>{% endif %}
{{ customer_address.city or "" }} {{ customer_address.state or "" }} {{ customer_address.pincode or "" }} {{ customer_address.country or "" }}<br>
{% endif %}
</div>
</div>
</td>
<td class="col-xs-6" style="padding: 0 !important">
<div class="title col-xs-5 p-0">
<div class="info-text">Invoice Number:</div>
</div>
<div class="col-xs-7">
<div class="info-text">{{ doc.name }}</div>
</div>
<div class="title col-xs-5 p-0">
<div class="info-text">Invoice Date:</div>
</div>
<div class="col-xs-7">
<div class="info-text">{{ frappe.utils.format_date(doc.posting_date) }}</div>
</div>
<div class="title col-xs-5 p-0">
<div class="info-text">Payment Due Date:</div>
</div>
<div class="col-xs-7">
<div class="info-text">{{ frappe.utils.format_date(doc.due_date) }}</div>
</div>
</td>
</tr>
</table>
<!-- Items Table -->
<table class="items-table mt-15">
<colgroup>
<col style="width: 5%" />
<col style="width: 35%" />
<col style="width: 10%" />
<col style="width: 20%" />
<col style="width: 15%" />
</colgroup>
<thead class="highlight-bg">
<tr>
<td class="text-center">{{ _("No") }}</td>
<td class="text-left">{{ _("Item") }}</td>
<td class="text-right">{{ _("Quantity") }}</td>
<td class="text-right">{{ _("Rate") }}</td>
<td class="text-right">{{ _("Amount") }}</td>
</tr>
</thead>
<tbody>
{% for item in doc.items %}
<tr>
<td class="text-center">{{ loop.index }}</td>
<td class="text-left">
<table class="item-img" style="border-collapse: collapse">
<tr>
{% if item.image %}
<td class="no-style" style="border-radius: 6px; vertical-align: middle">
<img
src="{{ item.image }}"
alt="{{ item.item_name }}"
style="
border-radius: 6px;
max-height: 24px;
min-height: 24px;
max-width: 24px;
min-width: 24px;
margin-right: 8px;
vertical-align: middle;
"
/>
</td>
{% endif %}
<td
class="no-style"
style="vertical-align: middle !important; padding: 0 !important"
>
{{ item.item_name }}
</td>
</tr>
</table>
</td>
<td class="text-right">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
<td class="text-right">{{ item.get_formatted("net_rate", doc) }}</td>
<td class="text-right">{{ item.get_formatted("net_amount", doc) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="test" style="float: right; margin-top: 15px; margin-bottom: 5px;">
<table style="width: 100%; border-collapse: collapse">
<tr class="row-divider">
<td class="text-right text-muted" style="padding-right: 30px !important;">{{ _("Sub Total:") }}</td>
<td class="text-right">{{ doc.get_formatted("total", doc) }}</td>
</tr>
{%- if doc.apply_discount_on == "Net Total" -%}
<tr class="row-divider">
<td class="text-right text-muted" style="padding-right: 30px !important;">
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
</td>
<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
</tr>
{%- endif -%}
{%- for tax in doc.taxes -%}
{%- if (tax.tax_amount or print_settings.print_taxes_with_zero_amount) and (not tax.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
<tr class="row-divider">
<td class="text-right text-muted" style="padding-right: 30px !important;">{{ tax.get_formatted("description") }} ({{ tax.get_formatted("rate") }}%):</td>
<td class="text-right">{{ tax.get_formatted("tax_amount") }}</td>
</tr>
{%- endif -%}
{%- endfor -%}
{%- if doc.apply_discount_on == "Grand Total" -%}
<tr class="row-divider">
<td class="text-right text-muted" style="padding-right: 30px !important;">
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
</td>
<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
</tr>
{%- endif -%}
</table>
</div>
<div style="border-bottom: 1px solid #ededed;">
<table class="highlight-bg" style="width: 100%; border-collapse: collapse; border-radius: 12px; margin-bottom: 10px;">
<tr>
<td class="text-left mw-400">
<div class="text-capitalize">
<span class="title"> {{ _("In Words: ") }}</span>{{ doc.in_words }}
</div>
</td>
<td class="text-right"><b>{{ _("Grand Total:") }}</b></td>
<td class="text-right">
<span style="font-weight: 700">
{{ doc.get_formatted("grand_total", doc) }}
</span>
</td>
</tr>
</table>
</div>
<!-- Terms -->
{% if doc.terms %}
<div class="info-card">
<div class="title">{{ _("Terms and Conditions") }}</div>
{{ doc.terms}}
</div>
{% endif %}
</div>
</div>
{% endfor %}

View File

@@ -1,32 +0,0 @@
{
"absolute_value": 0,
"align_labels_right": 0,
"creation": "2025-09-15 16:31:00.732539",
"custom_format": 0,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font_size": 14,
"idx": 0,
"line_breaks": 0,
"margin_bottom": 15.0,
"margin_left": 15.0,
"margin_right": 15.0,
"margin_top": 15.0,
"modified": "2025-09-15 16:31:00.732539",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Print Format Sales Invoice",
"owner": "Administrator",
"page_number": "Hide",
"pdf_generator": "wkhtmltopdf",
"print_format_builder": 0,
"print_format_builder_beta": 0,
"print_format_for": "DocType",
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

View File

@@ -1,312 +0,0 @@
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None,
print_heading_template=None) -%}
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
{% if print_heading_template %}
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
{% else %}
{% endif %}
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
<div class="text-center" document-status="cancelled">
<h4 style="margin: 0px">{{ _("CANCELLED") }}</h4>
</div>
{%- endif -%}
{%- endmacro -%} {% for page in layout %}
<div class="page-break invoice-wrapper">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
</div>
<style>
.letter-head {
margin-top: 12px !important;
}
.invoice-wrapper {
font-family: "Inter", sans-serif;
color: var(--black-overlay-700);
}
.print-format-body {
padding: 30px 12px 12px 12px !important;
}
body {
margin-bottom: 0mm !important;
padding: 0px !important;
}
table.info-table,
table.items-table,
table.tax-table {
border-collapse: separate;
border-spacing: 0;
border: 1px solid #ededed;
border-radius: 10px;
overflow: hidden;
}
table.info-table td {
border-bottom: 1px solid #ededed;
padding: 8px 10px !important;
}
table.items-table td,
table.tax-table td {
border-bottom: 1px solid #ededed;
padding: 8px;
}
table.info-table td:not(:first-of-type),
table.items-table td:not(:first-of-type),
table.tax-table td:not(:first-of-type) {
border-left: 1px solid #ededed;
}
table.info-table tbody tr:last-of-type td,
table.items-table tbody tr:last-of-type td,
table.tax-table tbody tr:last-of-type td {
border-bottom: none;
}
thead.table-header {
background: #f8f8f8;
}
thead.table-header td {
color: #7c7c7c;
}
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-muted {
color: #7c7c7c;
}
.text-dark {
color: #525252;
}
.text-bold {
font-weight: bold;
}
.small-text {
color: #666;
}
.mt-10 {
margin-top: 10px;
}
.mt-15 {
margin-top: 15px;
}
.mt-20 {
margin-top: 20px;
}
.mt-40 {
margin-top: 40px;
}
.mt-80 {
margin-top: 80px;
}
.mb-20 {
margin-bottom: 20px;
}
.pb-8 {
padding-bottom: 8px;
}
.amount-width {
width: 110px;
}
.totals-table {
border-radius: 5px;
width: 100%;
}
.totals-table td {
padding: 4px 8px;
}
.totals-table tr {
border-bottom: 1px solid #ededed;
}
.totals-table tr:last-child {
border-bottom: none;
}
.totals-table .grand-total td {
padding: 6px 8px;
font-weight: bold;
}
.words-box {
background: #f8f8f8;
border-radius: 8px;
padding: 8px;
line-height: 21px;
letter-spacing: 0.21px;
font-size: small;
}
.info-card {
color: #7c7c7c;
}
.title {
color: #7c7c7c !important;
}
.heading {
color: #525252 !important;
font-weight: 300;
}
.print-format {
color: #171717;
font-size: 14px;
font-style: normal;
font-weight: 420;
line-height: 21px;
padding: 0px;
letter-spacing: 0.14px;
margin-left: 0mm !important;
margin-right: 0mm !important;
}
.letter-head-footer {
margin-top: 0px !important;
}
.print-heading {
margin-top: 0px !important;
margin-bottom: 0px !important;
padding-bottom: 0px !important;
}
</style>
<div class="print-format-body">
<table class="info-table mb-20" style="width: 100%">
<tr>
<td style="width: 50%">
<span class="heading">{{ _("Customer Name") }}:</span> {{doc.customer_name }}
</td>
<td style="width: 50%">
<span class="heading">{{ _("Payment Due Date") }}:</span> {{
frappe.utils.format_date(doc.due_date) }}
</td>
</tr>
<tr>
<td><span class="heading">{{ _("Invoice Number") }}:</span> {{ doc.name }}</td>
<td>
<span class="heading">{{ _("Invoice Date") }}:</span> {{
frappe.utils.format_date(doc.posting_date) }}
</td>
</tr>
<tr>
<td><span class="heading">{{ _("Bill From") }}:</span><br />
{% if doc.customer_address %}
{% set customer_address = frappe.get_doc("Address", doc.customer_address) %}
{{ doc.customer_name }}<br>
{{ customer_address.address_line1 or "" }}
{% if customer_address.address_line2 %}{{ customer_address.address_line2 }}{% endif %}<br>
{{ customer_address.city or "" }} {{ customer_address.state or "" }} {{ customer_address.pincode or "" }} {{ customer_address.country or "" }}<br>
{% endif %}
</td>
<td><span class="heading">{{ _("Bill To") }}:</span><br />
{% if doc.company_address %}
{% set company_address_display = frappe.get_doc("Address", doc.company_address) %}
{{ doc.company }}<br>
{{ company_address_display.address_line1 or "" }}
{% if company_address_display.address_line2 %}{{ company_address_display.address_line2 }}{% endif %}<br>
{{ company_address_display.city or "" }} {{ company_address_display.state or "" }} {{ company_address_display.pincode or "" }} {{ company_address_display.country or "" }}<br>
{% endif %}
</td>
</tr>
</table>
<!-- Items Table -->
<table class="items-table mt-15" style="width: 100%">
<colgroup>
<col style="width: 5%" />
<col style="width: 40%" />
<col style="width: 15%" />
<col style="width: 20%" />
<col style="width: 20%" />
</colgroup>
<thead class="table-header">
<tr>
<td class="text-center">{{ _("No") }}</td>
<td class="text-left">{{ _("Item") }}</td>
<td class="text-right">{{ _("Quantity") }}</td>
<td class="text-right">{{ _("Rate") }}</td>
<td class="text-right">{{ _("Amount") }}</td>
</tr>
</thead>
<tbody>
{% for item in doc.items %}
<tr>
<td class="text-center text-dark">{{ loop.index }}</td>
<td>{{ item.item_name }}</td>
<td class="text-right text-dark">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
<td class="text-right text-dark">{{ item.get_formatted("net_rate", doc) }}</td>
<td class="text-right" style="color: #171717">
{{ item.get_formatted("net_amount", doc) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Totals Section -->
<table style="width:100%; margin-top: 15px">
<td style="vertical-align: bottom !important;">
<p class="title">{{ _("Total in words") }}</p>
<div class="words-box text-uppercase">{{ doc.in_words }}</div>
</td>
<td>
<table class="totals-table">
<tr>
<td class="text-right text-muted">{{ _("Sub Total:") }}</td>
<td class="text-right amount-width">{{ doc.get_formatted("total", doc) }}</td>
</tr>
{%- if doc.apply_discount_on == "Net Total" -%}
<tr>
<td class="text-right text-muted">
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
</td>
<td class="text-right amount-width">{{ doc.get_formatted("discount_amount", doc) }}</td>
</tr>
{%- endif -%}
{%- for tax in doc.taxes -%}
{%- if (tax.tax_amount or print_settings.print_taxes_with_zero_amount) and (not tax.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
<tr>
<td class="text-right text-muted">{{ tax.get_formatted("description") }} ({{ tax.get_formatted("rate") }}%):</td>
<td class="text-right amount-width">{{ tax.get_formatted("tax_amount") }}</td>
</tr>
{%- endif -%}
{%- endfor -%}
{%- if doc.apply_discount_on == "Grand Total" -%}
<tr>
<td class="text-right text-muted">
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
</td>
<td class="text-right amount-width">{{ doc.get_formatted("discount_amount", doc) }}</td>
</tr>
{%- endif -%}
<tr class="grand-total">
<td class="text-right">{{ _("Grand Total:") }}</td>
<td class="text-right amount-width">{{ doc.get_formatted("grand_total", doc) }}</td>
</tr>
</table>
</td>
</table>
<!-- Terms -->
<div class="terms-section">
{% if doc.terms %}
<div class="info-card mt-40">
<div>{{ _("Terms and Conditions") }}</div>
{{ doc.terms}}
</div>
{% endif %}
</div>
</div>
{% endfor %}

View File

@@ -1,32 +0,0 @@
{
"absolute_value": 0,
"align_labels_right": 0,
"creation": "2025-08-28 04:03:36.284420",
"custom_format": 0,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font_size": 14,
"idx": 0,
"line_breaks": 0,
"margin_bottom": 15.0,
"margin_left": 15.0,
"margin_right": 15.0,
"margin_top": 15.0,
"modified": "2025-09-01 17:47:14.710435",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Print Format",
"owner": "Administrator",
"page_number": "Hide",
"pdf_generator": "wkhtmltopdf",
"print_format_builder": 0,
"print_format_builder_beta": 0,
"print_format_for": "DocType",
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -354,7 +354,7 @@ def get_asset_details_for_grouped_by_category(filters):
# nosemgrep
return frappe.db.sql(
f"""
SELECT a.name,
SELECT a.name, a.asset_name,
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.net_purchase_amount
@@ -583,6 +583,14 @@ def get_columns(filters):
"width": 120,
}
)
columns.append(
{
"label": _("Asset Name"),
"fieldname": "asset_name",
"fieldtype": "Data",
"width": 140,
}
)
columns += [
{

View File

@@ -319,6 +319,7 @@ def get_asset_value_adjustment_map(filters, finance_book):
.select(asset.name.as_("asset"), Sum(gle.debit - gle.credit).as_("adjustment_amount"))
.where(gle.account == aca.fixed_asset_account)
.where(gle.is_cancelled == 0)
.where(gle.is_opening == "No")
.where(company.name == filters.company)
.where(asset.docstatus == 1)
)

View File

@@ -284,15 +284,15 @@ def get_columns(filters):
def get_message():
return """<span class="indicator">
Valid till : &nbsp;&nbsp;
return f"""<span class="indicator">
{_("Valid Till")}:&nbsp;&nbsp;
</span>
<span class="indicator orange">
Expires in a week or less
{_("Expires in a week or less")}
</span>
&nbsp;&nbsp;
<span class="indicator red">
Expires today / Already Expired
{_("Expires today or already expired")}
</span>"""

View File

@@ -854,13 +854,14 @@ def available_serial_batch_for_return(field, doctype, reference_ids, is_rejected
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)
return get_serial_batches_based_on_bundle(doctype, field, _bundle_ids)
def get_serial_batches_based_on_bundle(field, _bundle_ids):
def get_serial_batches_based_on_bundle(doctype, field, _bundle_ids):
available_dict = frappe._dict({})
batch_serial_nos = frappe.get_all(
"Serial and Batch Bundle",
@@ -872,6 +873,7 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
"`tabSerial and Batch Bundle`.`voucher_detail_no`",
"`tabSerial and Batch Bundle`.`voucher_type`",
"`tabSerial and Batch Bundle`.`voucher_no`",
"`tabSerial and Batch Bundle`.`item_code`",
],
filters=[
["Serial and Batch Bundle", "name", "in", _bundle_ids],
@@ -885,6 +887,16 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
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 doctype == "Packed Item":
if key is None:
key = frappe.get_cached_value("Packed Item", row.voucher_detail_no, field)
if row.voucher_type == "Delivery Note":
key = frappe.get_cached_value("Delivery Note Item", key, "dn_detail")
elif row.voucher_type == "Sales Invoice":
key = frappe.get_cached_value("Sales Invoice Item", key, "sales_invoice_item")
key = (row.item_code, key)
if row.voucher_type in ["Sales Invoice", "Delivery Note"]:
row.qty = -1 * row.qty
@@ -913,6 +925,8 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
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")}
if doctype == "Packed Item":
filters = get_filters_for_packed_item(field, reference_ids)
pluck_field = "serial_and_batch_bundle"
if is_rejected:
@@ -926,10 +940,14 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
pluck=pluck_field,
)
if _bundle_ids and doctype == "Packed Item":
return _bundle_ids
if not _bundle_ids:
return {}
del filters["name"]
if "name" in filters:
del filters["name"]
filters[field] = ("in", reference_ids)
@@ -972,10 +990,29 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
return _bundle_ids
def get_filters_for_packed_item(field, reference_ids):
names = []
filters = {"docstatus": 1, "dn_detail": ("in", reference_ids)}
if dns := frappe.get_all("Delivery Note Item", filters=filters, pluck="name"):
names.extend(dns)
filters = {"docstatus": 1, "sales_invoice_item": ("in", reference_ids)}
if sis := frappe.get_all("Sales Invoice Item", filters=filters, pluck="name"):
names.extend(sis)
if names:
reference_ids.extend(names)
return {"docstatus": 1, field: ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")}
def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field=None):
if not qty_field:
qty_field = "stock_qty"
if not hasattr(row, qty_field):
qty_field = "qty"
if not warehouse_field:
warehouse_field = "warehouse"
@@ -1065,6 +1102,9 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f
if not qty_field:
qty_field = "stock_qty"
if not hasattr(child_doc, qty_field):
qty_field = "qty"
warehouse = child_doc.get(warehouse_field)
if parent_doc.get("is_internal_customer"):
warehouse = child_doc.get("target_warehouse")

View File

@@ -519,8 +519,15 @@ class SellingController(StockController):
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
continue
item_details = frappe.get_cached_value(
"Item", d.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
)
if not self.get("return_against") or (
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
get_valuation_method(d.item_code) == "Moving Average"
and self.get("is_return")
and not item_details.has_serial_no
and not item_details.has_batch_no
):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty"))
@@ -999,6 +1006,9 @@ def set_default_income_account_for_item(obj):
def get_serial_and_batch_bundle(child, parent, delivery_note_child=None):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
if parent.get("is_return") and parent.get("packed_items"):
return
if child.get("use_serial_batch_fields"):
return

View File

@@ -265,6 +265,8 @@ class StatusUpdater(Document):
# if target_ref_field is not specified, the programmer does not want to validate qty / amount
continue
items_to_validate = []
# get unique transactions to update
for d in self.get_all_children():
if hasattr(d, "qty") and d.qty < 0 and not self.get("is_return"):
@@ -286,31 +288,63 @@ class StatusUpdater(Document):
)
if d.doctype == args["source_dt"] and d.get(args["join_field"]):
args["name"] = d.get(args["join_field"])
is_from_pp = (
hasattr(d, "production_plan_sub_assembly_item")
and frappe.db.get_value(
"Production Plan Sub Assembly Item",
d.production_plan_sub_assembly_item,
"type_of_manufacturing",
items_to_validate.append(
frappe._dict(
{
"name": d.get(args["join_field"]),
"production_plan_sub_assembly_item": d.get(
"production_plan_sub_assembly_item"
),
"idx": d.idx,
"child_doc": d,
}
)
== "Subcontract"
)
args["item_code"] = "production_item" if is_from_pp else "item_code"
# get all qty where qty > target_field
item = frappe.db.sql(
"""select `{item_code}` as item_code, `{target_ref_field}`,
`{target_field}`, parenttype, parent from `tab{target_dt}`
where `{target_ref_field}` < `{target_field}`
and name=%s and docstatus=1""".format(**args),
args["name"],
as_dict=1,
if items_to_validate:
pp_sub_assembly_items = [
item.production_plan_sub_assembly_item
for item in items_to_validate
if item.production_plan_sub_assembly_item
]
pp_subcontract_items = []
if pp_sub_assembly_items:
pp_subcontract_items = frappe.db.get_all(
"Production Plan Sub Assembly Item",
filters={
"name": ("in", pp_sub_assembly_items),
"type_of_manufacturing": "Subcontract",
},
pluck="name",
)
regular_items = []
pp_items = []
for item in items_to_validate:
if item.production_plan_sub_assembly_item in pp_subcontract_items:
pp_items.append(item.name)
else:
regular_items.append(item.name)
item_details = []
# Query regular items with item_code field
if regular_items:
item_details.extend(self.fetch_items_with_pending_qty(args, "item_code", regular_items))
# Query production plan items with production_item field
if pp_items:
item_details.extend(self.fetch_items_with_pending_qty(args, "production_item", pp_items))
item_lookup = {item.name: item for item in item_details}
for child_item in items_to_validate:
item = item_lookup.get(child_item.name)
if item:
item = item[0]
item["idx"] = d.idx
item["idx"] = child_item.idx
item["target_ref_field"] = args["target_ref_field"].replace("_", " ")
# if not item[args['target_ref_field']]:
@@ -323,6 +357,28 @@ class StatusUpdater(Document):
elif item[args["target_ref_field"]]:
self.check_overflow_with_allowance(item, args)
def fetch_items_with_pending_qty(self, args, item_field, items):
doctype = frappe.qb.DocType(args["target_dt"])
item_field = doctype[item_field]
target_ref_field = doctype[args["target_ref_field"]]
target_field = doctype[args["target_field"]]
return (
frappe.qb.from_(doctype)
.select(
doctype.name,
item_field.as_("item_code"),
target_ref_field,
target_field,
doctype.parenttype,
doctype.parent,
)
.where(target_ref_field < target_field)
.where(doctype.name.isin(items))
.where(doctype.docstatus == 1)
.run(as_dict=True)
)
def check_overflow_with_allowance(self, item, args):
"""
Checks if there is overflow condering a relaxation allowance

View File

@@ -360,10 +360,20 @@ class StockController(AccountsController):
return
child_doctype = self.doctype + " Item"
if table_name == "packed_items":
field = "parent_detail_docname"
child_doctype = "Packed 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)):
value = row.get(field)
if table_name == "packed_items" and row.get("parent_detail_docname"):
value = self.get_value_for_packed_item(row)
if not value:
continue
if data := available_dict.get(value):
data = filter_serial_batches(self, data, row)
bundle = make_serial_batch_bundle_for_return(data, row, self)
row.db_set(
@@ -379,6 +389,14 @@ class StockController(AccountsController):
"incoming_rate", frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
)
def get_value_for_packed_item(self, row):
parent_items = self.get("items", {"name": row.parent_detail_docname})
if parent_items:
ref = parent_items[0].get("dn_detail")
return (row.item_code, ref)
return None
def get_reference_ids(self, table_name, qty_field=None, bundle_field=None) -> tuple[str, list[str]]:
field = {
"Sales Invoice": "sales_invoice_item",
@@ -413,6 +431,12 @@ class StockController(AccountsController):
):
reference_ids.append(row.get(field))
if table_name == "packed_items" and row.get("parent_detail_docname"):
parent_rows = self.get("items", {"name": row.parent_detail_docname}) or []
for d in parent_rows:
if d.get(field) and not d.get(bundle_field):
reference_ids.append(d.get(field))
return field, reference_ids
@frappe.request_cache

View File

@@ -202,6 +202,9 @@ class SubcontractingController(StockController):
self.set(self.raw_material_table, [])
return
if not self.get(self.raw_material_table):
return
item_dict = self.__get_data_before_save()
if not item_dict:
return True
@@ -657,6 +660,9 @@ class SubcontractingController(StockController):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward
from erpnext.stock.get_item_details import get_filtered_serial_nos
if self.is_return:
return
for row in self.supplied_items:
item_details = frappe.get_cached_value(
"Item", row.rm_item_code, ["has_batch_no", "has_serial_no"], as_dict=1

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2025-10-05 09:35+0000\n"
"PO-Revision-Date: 2025-10-06 10:13\n"
"PO-Revision-Date: 2025-10-09 11:15\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Persian\n"
"MIME-Version: 1.0\n"
@@ -4127,7 +4127,7 @@ msgstr "همه این آیتم‌ها قبلاً صورتحساب/بازگردا
#: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js:85
#: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js:92
msgid "Allocate"
msgstr "اختصاص دهید"
msgstr ""
#. Label of the allocate_advances_automatically (Check) field in DocType 'POS
#. Invoice'
@@ -5628,7 +5628,7 @@ msgstr "اسلات رزرو قرار"
#: erpnext/crm/doctype/appointment/appointment.py:95
msgid "Appointment Confirmation"
msgstr "تایید قرار ملاقات"
msgstr "تأیید قرار ملاقات"
#: erpnext/www/book_appointment/index.js:237
msgid "Appointment Created Successfully"
@@ -5661,7 +5661,7 @@ msgstr "ملاقات با"
#: erpnext/crm/doctype/appointment/appointment.py:101
msgid "Appointment was created. But no lead was found. Please check the email to confirm"
msgstr "قرار ملاقات ایجاد شد. اما سرنخی پیدا نشد. لطفا برای تایید ایمیل را بررسی کنید"
msgstr "قرار ملاقات ایجاد شد. اما سرنخی پیدا نشد. لطفا برای تأیید ایمیل را بررسی کنید"
#. Label of the approving_role (Link) field in DocType 'Authorization Rule'
#: erpnext/setup/doctype/authorization_rule/authorization_rule.json
@@ -5675,7 +5675,7 @@ msgstr "نقش تأیید نمی‌تواند با نقشی که قانون بر
#. Label of the approving_user (Link) field in DocType 'Authorization Rule'
#: erpnext/setup/doctype/authorization_rule/authorization_rule.json
msgid "Approving User (above authorized value)"
msgstr "تایید کاربر (بالاتر از مقدار مجاز)"
msgstr "تأیید کاربر (بالاتر از مقدار مجاز)"
#: erpnext/setup/doctype/authorization_rule/authorization_rule.py:77
msgid "Approving User cannot be same as user the rule is Applicable To"
@@ -9434,7 +9434,7 @@ msgstr "برنامه های کمپین"
#: erpnext/setup/doctype/authorization_control/authorization_control.py:60
msgid "Can be approved by {0}"
msgstr "قابل تایید توسط {0}"
msgstr "قابل تأیید توسط {0}"
#: erpnext/manufacturing/doctype/work_order/work_order.py:2172
msgid "Can not close Work Order. Since {0} Job Cards are in Work In Progress state."
@@ -10655,7 +10655,7 @@ msgstr "هنگامی که فایل فشرده به سند پیوست شد، رو
#: erpnext/templates/emails/confirm_appointment.html:3
msgid "Click on the link below to verify your email and confirm the appointment"
msgstr "برای تایید ایمیل خود و تایید قرار ملاقات روی لینک زیر کلیک کنید"
msgstr "برای تأیید ایمیل خود و تأیید قرار ملاقات روی لینک زیر کلیک کنید"
#: erpnext/selling/page/point_of_sale/pos_item_cart.js:485
msgid "Click to add email / phone"
@@ -12000,7 +12000,7 @@ msgstr ""
#. Label of the final_confirmation_date (Date) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "Confirmation Date"
msgstr "تاریخ تایید"
msgstr "تاریخ تأیید"
#. Label of the connections_tab (Tab Break) field in DocType 'Purchase Invoice'
#. Label of the connections_tab (Tab Break) field in DocType 'Sales Invoice'
@@ -23223,7 +23223,7 @@ msgstr "درصد سود ناخالص"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:171
msgid "Gross Profit Ratio"
msgstr ""
msgstr "نسبت سود ناخالص"
#. Label of the gross_purchase_amount (Currency) field in DocType 'Asset
#. Depreciation Schedule'
@@ -24763,7 +24763,7 @@ msgstr "در تولید"
#: erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.json
#: erpnext/telephony/doctype/call_log/call_log.json
msgid "In Progress"
msgstr "در حال پیش رفت"
msgstr "در حال انجام"
#: erpnext/stock/report/available_serial_no/available_serial_no.py:112
#: erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py:82
@@ -26030,7 +26030,7 @@ msgstr "تنظیمات موجودی"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:214
msgid "Inventory Turnover Ratio"
msgstr ""
msgstr "نسبت گردش موجودی"
#: erpnext/setup/setup_wizard/data/industry_type.txt:29
msgid "Investment Banking"
@@ -32558,7 +32558,7 @@ msgstr "سود خالص"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:172
msgid "Net Profit Ratio"
msgstr ""
msgstr "نسبت سود خالص"
#: erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py:180
msgid "Net Profit/Loss"
@@ -34796,12 +34796,12 @@ msgstr "سفارش توسط"
#. Order'
#: erpnext/buying/doctype/purchase_order/purchase_order.json
msgid "Order Confirmation Date"
msgstr "تاریخ تایید سفارش"
msgstr "تاریخ تأیید سفارش"
#. Label of the order_confirmation_no (Data) field in DocType 'Purchase Order'
#: erpnext/buying/doctype/purchase_order/purchase_order.json
msgid "Order Confirmation No"
msgstr "شماره تایید سفارش"
msgstr "شماره تأیید سفارش"
#: erpnext/crm/report/campaign_efficiency/campaign_efficiency.py:23
#: erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py:29
@@ -38140,7 +38140,7 @@ msgstr "لطفاً شناسه مشتری Plaid و مقادیر مخفی خود
#: erpnext/crm/doctype/appointment/appointment.py:98
#: erpnext/www/book_appointment/index.js:235
msgid "Please check your email to confirm the appointment"
msgstr "لطفا ایمیل خود را برای تایید قرار ملاقات بررسی کنید"
msgstr "لطفا ایمیل خود را برای تأیید قرار ملاقات بررسی کنید"
#: erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py:374
msgid "Please click on 'Generate Schedule'"
@@ -38263,7 +38263,7 @@ msgstr "لطفاً حساب را برای تغییر مبلغ وارد کنید"
#: erpnext/setup/doctype/authorization_rule/authorization_rule.py:75
msgid "Please enter Approving Role or Approving User"
msgstr "لطفاً نقش تأیید یا تأیید کاربر را وارد کنید"
msgstr "لطفاً نقش تأیید یا کاربر تأیید را وارد کنید"
#: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py:945
msgid "Please enter Cost Center"
@@ -59037,11 +59037,11 @@ msgstr "تا زمان"
#. Option for the 'Status' (Select) field in DocType 'Appointment'
#: erpnext/crm/doctype/appointment/appointment.json
msgid "Unverified"
msgstr "تایید نشده"
msgstr "تأیید نشده"
#: erpnext/erpnext_integrations/utils.py:22
msgid "Unverified Webhook Data"
msgstr "داده های وب هوک تایید نشده"
msgstr "داده های وب هوک تأیید نشده"
#: erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.js:17
msgid "Up"
@@ -60173,7 +60173,7 @@ msgstr "تأیید انجام نشد لطفاً پیوند را بررسی کن
#. Label of the verified_by (Data) field in DocType 'Quality Inspection'
#: erpnext/stock/doctype/quality_inspection/quality_inspection.json
msgid "Verified By"
msgstr "تایید شده توسط"
msgstr "تأیید شده توسط"
#: erpnext/templates/emails/confirm_appointment.html:6
#: erpnext/www/book_appointment/verify/index.html:4
@@ -62198,7 +62198,7 @@ msgstr "نام شما (الزامی)"
#: erpnext/www/book_appointment/verify/index.html:11
msgid "Your email has been verified and your appointment has been scheduled"
msgstr "ایمیل شما تایید شده و قرار ملاقات شما تعیین شده است"
msgstr "ایمیل شما تأیید شده و قرار ملاقات شما تعیین شده است"
#: erpnext/patches/v11_0/add_default_dispatch_notification_template.py:22
#: erpnext/setup/setup_wizard/operations/install_fixtures.py:320

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2025-10-05 09:35+0000\n"
"PO-Revision-Date: 2025-10-06 10:14\n"
"PO-Revision-Date: 2025-10-08 10:44\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
@@ -41601,7 +41601,7 @@ msgstr "Analyse des bons de commande"
#: erpnext/buying/report/procurement_tracker/procurement_tracker.py:76
msgid "Purchase Order Date"
msgstr "Date du de la Commande d'Achat"
msgstr "Date de la commande d'achat"
#. Label of the po_detail (Data) field in DocType 'Purchase Invoice Item'
#. Label of the purchase_order_item (Data) field in DocType 'Sales Invoice

File diff suppressed because it is too large Load Diff

63249
erpnext/locale/my.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2025-10-05 09:35+0000\n"
"PO-Revision-Date: 2025-10-06 10:13\n"
"PO-Revision-Date: 2025-10-12 11:20\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Polish\n"
"MIME-Version: 1.0\n"
@@ -2525,7 +2525,7 @@ msgstr "Zajęcia"
#: erpnext/projects/doctype/task/task_dashboard.py:8
#: erpnext/support/doctype/issue/issue_dashboard.py:5
msgid "Activity"
msgstr ""
msgstr "Aktywność"
#. Name of a DocType
#. Label of a Link in the Projects Workspace
@@ -7829,7 +7829,7 @@ msgstr ""
#: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
#: erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
msgid "Barcode"
msgstr ""
msgstr "Kod kreskowy"
#. Label of the barcode_type (Select) field in DocType 'Item Barcode'
#: erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -9425,7 +9425,7 @@ msgstr ""
#: erpnext/setup/setup_wizard/data/marketing_source.txt:9
#: erpnext/stock/doctype/delivery_note/delivery_note.json
msgid "Campaign"
msgstr ""
msgstr "Kampania"
#. Name of a report
#. Label of a Link in the CRM Workspace
@@ -10162,7 +10162,7 @@ msgstr ""
#. Label of the category (Link) field in DocType 'UOM Conversion Factor'
#: erpnext/setup/doctype/uom_conversion_factor/uom_conversion_factor.json
msgid "Category"
msgstr ""
msgstr "Kategoria"
#. Label of the category_details_section (Section Break) field in DocType 'Tax
#. Withholding Category'
@@ -10932,7 +10932,7 @@ msgstr "Poziom kolekcji"
#: erpnext/setup/doctype/holiday_list/holiday_list.json
#: erpnext/setup/doctype/vehicle/vehicle.json
msgid "Color"
msgstr ""
msgstr "Kolor"
#: erpnext/setup/setup_wizard/operations/install_fixtures.py:263
msgid "Colour"
@@ -14219,7 +14219,7 @@ msgstr ""
#: erpnext/stock/doctype/price_list/price_list.json
#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
msgid "Currency"
msgstr ""
msgstr "Waluta"
#. Label of a Link in the Accounting Workspace
#. Name of a DocType
@@ -14461,7 +14461,7 @@ msgstr "Opieka"
#. Exchange Settings'
#: erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
msgid "Custom"
msgstr ""
msgstr "Niestandardowy"
#. Label of the custom_remarks (Check) field in DocType 'Payment Entry'
#: erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -18261,7 +18261,7 @@ msgstr ""
#: erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json
#: erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
msgid "DocType"
msgstr ""
msgstr "DocType"
#: erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py:77
msgid "DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."
@@ -19057,7 +19057,7 @@ msgstr ""
#: erpnext/selling/page/point_of_sale/pos_item_cart.js:939
#: erpnext/setup/doctype/company/company.json
msgid "Email"
msgstr ""
msgstr "E-mail"
#. Label of a Card Break in the Settings Workspace
#: erpnext/setup/workspace/settings/settings.json
@@ -19071,7 +19071,7 @@ msgstr ""
#: erpnext/setup/workspace/settings/settings.json
#: erpnext/support/doctype/issue/issue.json
msgid "Email Account"
msgstr ""
msgstr "Konto e-mail"
#. Label of the email_id (Data) field in DocType 'Warehouse'
#. Label of a field in the addresses Web Form
@@ -20838,7 +20838,7 @@ msgstr "Ujmujący..."
#: erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.js:106
#: erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.js:155
msgid "Field"
msgstr ""
msgstr "Pole"
#. Label of the field_mapping_section (Section Break) field in DocType
#. 'Inventory Dimension'
@@ -20869,12 +20869,12 @@ msgstr ""
#: erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
#: erpnext/portal/doctype/website_filter_field/website_filter_field.json
msgid "Fieldname"
msgstr ""
msgstr "Nazwa pola"
#. Label of the fields (Table) field in DocType 'Item Variant Settings'
#: erpnext/stock/doctype/item_variant_settings/item_variant_settings.json
msgid "Fields"
msgstr ""
msgstr "Pola"
#. Description of the 'Do not update variants on save' (Check) field in DocType
#. 'Item Variant Settings'
@@ -20894,7 +20894,7 @@ msgstr "Plik to zmiany nazwy"
#: erpnext/edi/doctype/code_list/code_list_import.js:65
msgid "Filter"
msgstr ""
msgstr "Filtr"
#: erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js:16
#: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js:16
@@ -23294,7 +23294,7 @@ msgstr ""
#: erpnext/accounts/doctype/payment_terms_template/payment_terms_template_dashboard.py:17
msgid "Group"
msgstr ""
msgstr "Grupa"
#: erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.js:30
#: erpnext/accounts/report/gross_profit/gross_profit.js:36
@@ -23620,7 +23620,7 @@ msgstr ""
#: erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
#: erpnext/templates/pages/help.html:3 erpnext/templates/pages/help.html:5
msgid "Help"
msgstr ""
msgstr "Pomoc"
#. Label of the help_section (Tab Break) field in DocType 'Pricing Rule'
#: erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -23806,7 +23806,7 @@ msgstr ""
#. Name of a Workspace
#: erpnext/setup/workspace/home/home.json
msgid "Home"
msgstr ""
msgstr "Strona główna"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -28854,7 +28854,7 @@ msgstr ""
#: erpnext/accounts/doctype/pos_field/pos_field.json
#: erpnext/stock/doctype/item_website_specification/item_website_specification.json
msgid "Label"
msgstr ""
msgstr "Etykieta"
#. Label of the taxes (Table) field in DocType 'Landed Cost Voucher'
#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json
@@ -28928,7 +28928,7 @@ msgstr "Krajobraz"
#. Label of the language (Link) field in DocType 'Dunning Letter Text'
#: erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json
msgid "Language"
msgstr ""
msgstr "Język"
#. Option for the 'Fulfilment Status' (Select) field in DocType 'Contract'
#: erpnext/crm/doctype/contract/contract.json
@@ -30348,7 +30348,7 @@ msgstr ""
#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.js:254
#: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js:101
msgid "Mandatory"
msgstr ""
msgstr "Obowiązkowy"
#: erpnext/accounts/doctype/pos_profile/pos_profile.py:99
msgid "Mandatory Accounting Dimension"
@@ -31393,7 +31393,7 @@ msgstr ""
#: erpnext/projects/doctype/project/project.json
#: erpnext/selling/doctype/sms_center/sms_center.json
msgid "Message"
msgstr ""
msgstr "Wiadomość"
#. Label of the message_examples (HTML) field in DocType 'Payment Gateway
#. Account'
@@ -32310,7 +32310,7 @@ msgstr ""
#: erpnext/selling/doctype/quotation/quotation.js:274
#: erpnext/setup/doctype/employee_group/employee_group.json
msgid "Name"
msgstr ""
msgstr "Nazwa"
#. Label of the name_and_employee_id (Section Break) field in DocType 'Sales
#. Person'
@@ -32946,7 +32946,7 @@ msgstr ""
#: erpnext/www/book_appointment/index.html:34
msgid "Next"
msgstr ""
msgstr "Następny"
#. Label of the next_depreciation_date (Date) field in DocType 'Asset'
#: erpnext/assets/doctype/asset/asset.json
@@ -33010,7 +33010,7 @@ msgstr "Kolejny e-mali zostanie wysłany w dniu:"
#: erpnext/stock/doctype/shipment/shipment.json
#: erpnext/stock/doctype/stock_entry/stock_entry.json
msgid "No"
msgstr ""
msgstr "Nie"
#: erpnext/setup/doctype/company/test_company.py:99
msgid "No Account matched these filters: {}"
@@ -33694,7 +33694,7 @@ msgstr ""
#. Label of a Link in the Settings Workspace
#: erpnext/setup/workspace/settings/settings.json
msgid "Notification Settings"
msgstr ""
msgstr "Ustawienia powiadomień"
#: erpnext/stock/doctype/delivery_trip/delivery_trip.js:45
msgid "Notify Customers via Email"
@@ -34530,7 +34530,7 @@ msgstr ""
#: erpnext/manufacturing/workspace/manufacturing/manufacturing.json
#: erpnext/public/js/bom_configurator/bom_configurator.bundle.js:328
msgid "Operation"
msgstr ""
msgstr "Operacja"
#. Label of the production_section (Section Break) field in DocType 'Job Card'
#: erpnext/manufacturing/doctype/job_card/job_card.json
@@ -34803,7 +34803,7 @@ msgstr ""
#. Label of the options (Text) field in DocType 'POS Field'
#: erpnext/accounts/doctype/pos_field/pos_field.json
msgid "Options"
msgstr ""
msgstr "Opcje"
#. Option for the 'Color' (Select) field in DocType 'Supplier Scorecard Scoring
#. Standing'
@@ -36537,7 +36537,7 @@ msgstr ""
#: erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.json
#: erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.json
msgid "Path"
msgstr ""
msgstr "Ścieżka"
#. Option for the 'Status' (Select) field in DocType 'Job Card Operation'
#: erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.js:96
@@ -37687,7 +37687,7 @@ msgstr "Farmaceutyczne"
#: erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
#: erpnext/utilities/web_form/addresses/addresses.json
msgid "Phone"
msgstr ""
msgstr "Telefon"
#. Label of the phone_ext (Data) field in DocType 'Lead'
#. Label of the phone_ext (Data) field in DocType 'Opportunity'
@@ -40258,7 +40258,7 @@ msgstr ""
#. Label of a Card Break in the Settings Workspace
#: erpnext/setup/workspace/settings/settings.json
msgid "Printing"
msgstr ""
msgstr "Drukowanie"
#. Label of the printing_details (Section Break) field in DocType 'Material
#. Request'
@@ -48391,7 +48391,7 @@ msgstr ""
#: erpnext/public/js/call_popup/call_popup.js:169
#: erpnext/selling/page/point_of_sale/pos_payment.js:62
msgid "Save"
msgstr ""
msgstr "Zapisz"
#. Option for the 'Action on New Invoice' (Select) field in DocType 'POS
#. Profile'
@@ -50398,7 +50398,7 @@ msgstr ""
#: erpnext/stock/workspace/stock/stock.json
#: erpnext/support/workspace/support/support.json
msgid "Settings"
msgstr ""
msgstr "Ustawienia"
#. Description of a DocType
#: erpnext/crm/doctype/crm_settings/crm_settings.json
@@ -51259,7 +51259,7 @@ msgstr ""
#: erpnext/stock/doctype/delivery_note/delivery_note.json
#: erpnext/templates/form_grid/stock_entry_grid.html:29
msgid "Source"
msgstr ""
msgstr "Źródło"
#. Label of the source_doctype (Link) field in DocType 'Support Search Source'
#: erpnext/support/doctype/support_search_source/support_search_source.json
@@ -52007,7 +52007,7 @@ msgstr "Stan / prowincja"
#: erpnext/templates/pages/task_info.html:69
#: erpnext/templates/pages/timelog_info.html:40
msgid "Status"
msgstr ""
msgstr "Status"
#. Label of the status_details (Section Break) field in DocType 'Service Level
#. Agreement'
@@ -53161,7 +53161,7 @@ msgstr ""
#: erpnext/support/web_form/issues/issues.json
#: erpnext/templates/pages/task_info.html:44
msgid "Subject"
msgstr ""
msgstr "Temat"
#: erpnext/accounts/doctype/payment_order/payment_order.js:139
#: erpnext/accounts/doctype/pos_invoice/pos_invoice.js:323
@@ -54289,7 +54289,7 @@ msgstr ""
#: erpnext/utilities/doctype/video/video.json
#: erpnext/utilities/doctype/video_settings/video_settings.json
msgid "System Manager"
msgstr ""
msgstr "Menedżer systemu"
#. Label of a Link in the Settings Workspace
#. Label of a shortcut in the Settings Workspace
@@ -54384,7 +54384,7 @@ msgstr ""
#: erpnext/quality_management/doctype/quality_review_objective/quality_review_objective.json
#: erpnext/templates/form_grid/stock_entry_grid.html:36
msgid "Target"
msgstr ""
msgstr "Cel"
#. Label of the target_amount (Float) field in DocType 'Target Detail'
#: erpnext/setup/doctype/target_detail/target_detail.json
@@ -55205,7 +55205,7 @@ msgstr ""
#: erpnext/quality_management/doctype/quality_feedback/quality_feedback.json
#: erpnext/stock/doctype/item/item_list.js:20
msgid "Template"
msgstr ""
msgstr "Szablon"
#: erpnext/manufacturing/doctype/bom/bom.js:376
msgid "Template Item"
@@ -56557,7 +56557,7 @@ msgstr "Szczeliny czasowe"
#: erpnext/utilities/doctype/video/video.json
#: erpnext/utilities/report/youtube_interactions/youtube_interactions.py:23
msgid "Title"
msgstr ""
msgstr "Tytuł"
#. Label of the email_to (Data) field in DocType 'Payment Request'
#. Label of the to_uom (Link) field in DocType 'UOM Conversion Factor'
@@ -57082,7 +57082,7 @@ msgstr "Zbyt wiele kolumn. Wyeksportować raport i wydrukować go za pomocą ark
#: erpnext/setup/doctype/email_digest/email_digest.json
#: erpnext/stock/workspace/stock/stock.json
msgid "Tools"
msgstr ""
msgstr "Narzędzia"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -58491,7 +58491,7 @@ msgstr ""
#: erpnext/stock/report/bom_search/bom_search.py:43
#: erpnext/telephony/doctype/call_log/call_log.json
msgid "Type"
msgstr ""
msgstr "Typ"
#. Label of the type_of_call (Link) field in DocType 'Call Log'
#: erpnext/telephony/doctype/call_log/call_log.json
@@ -58875,7 +58875,7 @@ msgstr ""
#: erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js:10
#: erpnext/projects/doctype/project/project_dashboard.html:7
msgid "Unknown"
msgstr ""
msgstr "Nieznany"
#: erpnext/public/js/call_popup/call_popup.js:110
msgid "Unknown Caller"
@@ -59541,7 +59541,7 @@ msgstr "Używane do Planu Produkcji"
#: erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
#: erpnext/utilities/doctype/portal_user/portal_user.json
msgid "User"
msgstr ""
msgstr "Użytkownik"
#. Label of the section_break_5 (Section Break) field in DocType 'POS Closing
#. Entry'
@@ -59618,7 +59618,7 @@ msgstr ""
#: erpnext/projects/doctype/project/project.json
#: erpnext/projects/doctype/project_update/project_update.json
msgid "Users"
msgstr ""
msgstr "Użytkownicy"
#. Description of the 'Track Semi Finished Goods' (Check) field in DocType
#. 'BOM'
@@ -59963,7 +59963,7 @@ msgstr ""
#: erpnext/stock/report/stock_analytics/stock_analytics.js:26
#: erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py:101
msgid "Value"
msgstr ""
msgstr "Wartość"
#: erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js:58
msgid "Value (G - D)"
@@ -60214,7 +60214,7 @@ msgstr ""
#. Label of the version (Data) field in DocType 'Code List'
#: erpnext/edi/doctype/code_list/code_list.json
msgid "Version"
msgstr ""
msgstr "Wersja"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -61156,7 +61156,7 @@ msgstr ""
#: erpnext/setup/workspace/settings/settings.json
#: erpnext/stock/doctype/manufacturer/manufacturer.json
msgid "Website"
msgstr ""
msgstr "Strona internetowa"
#. Name of a DocType
#: erpnext/portal/doctype/website_attribute/website_attribute.json
@@ -61197,7 +61197,7 @@ msgstr ""
#. Label of a Link in the Settings Workspace
#: erpnext/setup/workspace/settings/settings.json
msgid "Website Settings"
msgstr "Ustawienia witryny"
msgstr ""
#. Label of the sb_web_spec (Section Break) field in DocType 'BOM'
#: erpnext/manufacturing/doctype/bom/bom.json
@@ -62018,7 +62018,7 @@ msgstr ""
#: erpnext/stock/doctype/shipment/shipment.json
#: erpnext/stock/doctype/stock_entry/stock_entry.json
msgid "Yes"
msgstr ""
msgstr "Tak"
#: erpnext/edi/doctype/code_list/code_list_import.js:29
msgid "You are importing data for the code list:"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2025-10-05 09:35+0000\n"
"PO-Revision-Date: 2025-10-06 10:13\n"
"PO-Revision-Date: 2025-10-08 10:44\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Serbian (Cyrillic)\n"
"MIME-Version: 1.0\n"
@@ -14063,7 +14063,7 @@ msgstr "Ограничење потраживања премашено за ку
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:213
msgid "Creditor Turnover Ratio"
msgstr ""
msgstr "Коефицијент обрта добављача"
#: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py:87
#: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py:118
@@ -14460,7 +14460,7 @@ msgstr "Тренутна количина"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:152
msgid "Current Ratio"
msgstr ""
msgstr "Рацио опште ликвидности"
#. Label of the current_serial_and_batch_bundle (Link) field in DocType 'Stock
#. Reconciliation Item'
@@ -15752,11 +15752,11 @@ msgstr "Дугује-Потражује нису у равнотежи"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:170
msgid "Debt Equity Ratio"
msgstr ""
msgstr "Рацио структуре капитала"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:212
msgid "Debtor Turnover Ratio"
msgstr ""
msgstr "Коефицијент обрта купаца"
#: erpnext/accounts/party.py:617
msgid "Debtor/Creditor"
@@ -21451,7 +21451,7 @@ msgstr "Регистар основних средстава"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:211
msgid "Fixed Asset Turnover Ratio"
msgstr ""
msgstr "Коефицијент обрта основних средстава"
#: erpnext/manufacturing/doctype/bom/bom.py:668
msgid "Fixed Asset item {0} cannot be used in BOMs."
@@ -23320,7 +23320,7 @@ msgstr "Проценат бруто профита"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:171
msgid "Gross Profit Ratio"
msgstr ""
msgstr "Стопа пословног добитка"
#. Label of the gross_purchase_amount (Currency) field in DocType 'Asset
#. Depreciation Schedule'
@@ -26130,7 +26130,7 @@ msgstr "Поставке инвентара"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:214
msgid "Inventory Turnover Ratio"
msgstr ""
msgstr "Коефицијент обрта залиха"
#: erpnext/setup/setup_wizard/data/industry_type.txt:29
msgid "Investment Banking"
@@ -29648,7 +29648,7 @@ msgstr "Линк за дневник позива"
#: erpnext/accounts/report/financial_ratios/financial_ratios.js:55
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:150
msgid "Liquidity Ratios"
msgstr ""
msgstr "Показатељи ликвидности"
#. Description of the 'Items' (Section Break) field in DocType 'Product Bundle'
#: erpnext/selling/doctype/product_bundle/product_bundle.json
@@ -32659,7 +32659,7 @@ msgstr "Нето профит"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:172
msgid "Net Profit Ratio"
msgstr ""
msgstr "Стопа нето добитка"
#: erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py:180
msgid "Net Profit/Loss"
@@ -42969,7 +42969,7 @@ msgstr "Брзи налог књижења"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:152
msgid "Quick Ratio"
msgstr ""
msgstr "Рацио редуциране ликвидности"
#. Name of a DocType
#. Label of a Link in the Stock Workspace
@@ -45949,11 +45949,11 @@ msgstr "Повраћај компоненти"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:173
msgid "Return on Asset Ratio"
msgstr ""
msgstr "Стопа приноса на имовину"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:174
msgid "Return on Equity Ratio"
msgstr ""
msgstr "Стопа приноса на капитал"
#. Option for the 'Tracking Status' (Select) field in DocType 'Shipment'
#: erpnext/selling/page/point_of_sale/pos_past_order_summary.js:138
@@ -51297,7 +51297,7 @@ msgstr "Продато од"
#: erpnext/accounts/report/financial_ratios/financial_ratios.js:55
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:168
msgid "Solvency Ratios"
msgstr ""
msgstr "Показатељи солвентности"
#: erpnext/www/book_appointment/index.js:248
msgid "Something went wrong please try again"
@@ -58524,7 +58524,7 @@ msgstr "Уторак"
#: erpnext/accounts/report/financial_ratios/financial_ratios.js:55
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:198
msgid "Turnover Ratios"
msgstr ""
msgstr "Показатељи обртаја"
#. Option for the 'Frequency To Collect Progress' (Select) field in DocType
#. 'Project'

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2025-10-05 09:35+0000\n"
"PO-Revision-Date: 2025-10-06 10:13\n"
"PO-Revision-Date: 2025-10-08 10:45\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Serbian (Latin)\n"
"MIME-Version: 1.0\n"
@@ -14063,7 +14063,7 @@ msgstr "Ograničenje potraživanja premašeno za kupca {0}"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:213
msgid "Creditor Turnover Ratio"
msgstr ""
msgstr "Koeficijent obrta dobavljača"
#: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py:87
#: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py:118
@@ -14460,7 +14460,7 @@ msgstr "Trenutna količina"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:152
msgid "Current Ratio"
msgstr ""
msgstr "Racio opšte likvidnosti"
#. Label of the current_serial_and_batch_bundle (Link) field in DocType 'Stock
#. Reconciliation Item'
@@ -15752,11 +15752,11 @@ msgstr "Duguje-Potražuje nisu u ravnoteži"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:170
msgid "Debt Equity Ratio"
msgstr ""
msgstr "Racio strukture kapitala"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:212
msgid "Debtor Turnover Ratio"
msgstr ""
msgstr "Koeficijent obrta kupaca"
#: erpnext/accounts/party.py:617
msgid "Debtor/Creditor"
@@ -21451,7 +21451,7 @@ msgstr "Registar osnovnih sredstava"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:211
msgid "Fixed Asset Turnover Ratio"
msgstr ""
msgstr "Koeficijent obrta osnovnih sredstava"
#: erpnext/manufacturing/doctype/bom/bom.py:668
msgid "Fixed Asset item {0} cannot be used in BOMs."
@@ -23320,7 +23320,7 @@ msgstr "Procenat bruto profita"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:171
msgid "Gross Profit Ratio"
msgstr ""
msgstr "Stopa poslovnog dobitka"
#. Label of the gross_purchase_amount (Currency) field in DocType 'Asset
#. Depreciation Schedule'
@@ -26130,7 +26130,7 @@ msgstr "Postavke inventara"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:214
msgid "Inventory Turnover Ratio"
msgstr ""
msgstr "Koeficijent obrta zaliha"
#: erpnext/setup/setup_wizard/data/industry_type.txt:29
msgid "Investment Banking"
@@ -29648,7 +29648,7 @@ msgstr "Link za dnevnik poziva"
#: erpnext/accounts/report/financial_ratios/financial_ratios.js:55
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:150
msgid "Liquidity Ratios"
msgstr ""
msgstr "Pokazatelji likvidnosti"
#. Description of the 'Items' (Section Break) field in DocType 'Product Bundle'
#: erpnext/selling/doctype/product_bundle/product_bundle.json
@@ -32659,7 +32659,7 @@ msgstr "Neto profit"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:172
msgid "Net Profit Ratio"
msgstr ""
msgstr "Stopa neto dobitka"
#: erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py:180
msgid "Net Profit/Loss"
@@ -42969,7 +42969,7 @@ msgstr "Brzi nalog knjiženja"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:152
msgid "Quick Ratio"
msgstr ""
msgstr "Racio reducirane likvidnosti"
#. Name of a DocType
#. Label of a Link in the Stock Workspace
@@ -45949,11 +45949,11 @@ msgstr "Povraćaj komponenti"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:173
msgid "Return on Asset Ratio"
msgstr ""
msgstr "Stopa prinosa na imovinu"
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:174
msgid "Return on Equity Ratio"
msgstr ""
msgstr "Stopa prinosa na kapital"
#. Option for the 'Tracking Status' (Select) field in DocType 'Shipment'
#: erpnext/selling/page/point_of_sale/pos_past_order_summary.js:138
@@ -51297,7 +51297,7 @@ msgstr "Prodato od"
#: erpnext/accounts/report/financial_ratios/financial_ratios.js:55
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:168
msgid "Solvency Ratios"
msgstr ""
msgstr "Pokazatelji solventnosti"
#: erpnext/www/book_appointment/index.js:248
msgid "Something went wrong please try again"
@@ -58524,7 +58524,7 @@ msgstr "Utorak"
#: erpnext/accounts/report/financial_ratios/financial_ratios.js:55
#: erpnext/accounts/report/financial_ratios/financial_ratios.py:198
msgid "Turnover Ratios"
msgstr ""
msgstr "Pokazatelji obrtaja"
#. Option for the 'Frequency To Collect Progress' (Select) field in DocType
#. 'Project'

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2025-10-05 09:35+0000\n"
"PO-Revision-Date: 2025-10-06 10:13\n"
"PO-Revision-Date: 2025-10-11 11:22\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@@ -50930,7 +50930,7 @@ msgstr "Visa Bokföring Register Saldo"
#: erpnext/accounts/report/trial_balance/trial_balance.js:116
msgid "Show Group Accounts"
msgstr "Visa Gruppkonton"
msgstr "Visa Grupp Konto"
#. Label of the show_in_website (Check) field in DocType 'Sales Partner'
#: erpnext/setup/doctype/sales_partner/sales_partner.json
@@ -58449,19 +58449,19 @@ msgstr "Kvalitet Procedur Träd"
#: erpnext/accounts/workspace/accounting/accounting.json
#: erpnext/accounts/workspace/financial_reports/financial_reports.json
msgid "Trial Balance"
msgstr "Aktuell Saldo"
msgstr "Brutto Saldo"
#. Name of a report
#: erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json
msgid "Trial Balance (Simple)"
msgstr "Aktuell Saldo (Enkel)"
msgstr "Brutto Saldo (Enkel)"
#. Name of a report
#. Label of a Link in the Financial Reports Workspace
#: erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.json
#: erpnext/accounts/workspace/financial_reports/financial_reports.json
msgid "Trial Balance for Party"
msgstr "Aktuell Saldo för Parti"
msgstr "Brutto Saldo för Parti"
#. Label of the trial_period_end (Date) field in DocType 'Subscription'
#: erpnext/accounts/doctype/subscription/subscription.json
@@ -58485,7 +58485,7 @@ msgstr "Prov Period Start Datum kan inte vara efter Prenumeration Start Datum"
#: erpnext/accounts/doctype/subscription/subscription.json
#: erpnext/accounts/doctype/subscription/subscription_list.js:4
msgid "Trialing"
msgstr "Prövning"
msgstr "Prov Period"
#. Description of the 'General Ledger' (Int) field in DocType 'Accounts
#. Settings'
@@ -62170,7 +62170,7 @@ msgstr "Du kan inte göra några ändringar i Jobbkort eftersom Arbetsorder är
#: erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py:187
msgid "You can't process the serial number {0} as it has already been used in the SABB {1}. {2} if you want to inward same serial number multiple times then enabled 'Allow existing Serial No to be Manufactured/Received again' in the {3}"
msgstr "Du kan inte behandla serienummer {0} eftersom det redan har använts i Serienummer och Parti Paket {1}. {2} Om du vill leverera in samma serienummer flera gånger aktiverar du \"Tillåt att befintligt serienummer produceras/tas emot igen\" i {3}"
msgstr "Du kan inte behandla serienummer {0} eftersom det redan har använts i Serienummer och Parti Paket {1}. {2} Om du vill leverera in samma serienummer flera gånger aktiverar du \"Tillåt att befintligt serienummer Produceras/Tas Emot igen\" i {3}"
#: erpnext/accounts/doctype/loyalty_program/loyalty_program.py:192
msgid "You can't redeem Loyalty Points having more value than the Total Amount."

View File

@@ -1624,6 +1624,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
include_safety_stock = doc.get("include_safety_stock")
so_item_details = frappe._dict()
existing_sub_assembly_items = set()
sub_assembly_items = defaultdict(int)
if doc.get("skip_available_sub_assembly_item") and doc.get("sub_assembly_items"):
@@ -1659,7 +1660,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
and doc.get("sub_assembly_items")
):
item_details = get_raw_materials_of_sub_assembly_items(
so_item_details[doc.get("sales_order")].keys() if so_item_details else [],
existing_sub_assembly_items,
item_details,
company,
bom_no,
@@ -2044,6 +2045,7 @@ def get_raw_materials_of_sub_assembly_items(
sub_assembly_items,
planned_qty=planned_qty,
)
existing_sub_assembly_items.add(item.item_code)
else:
if not item.conversion_factor and item.purchase_uom:
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)

View File

@@ -1859,11 +1859,17 @@ class TestProductionPlan(IntegrationTestCase):
def test_calculation_of_sub_assembly_items(self):
make_item("Sub Assembly Item ", properties={"is_stock_item": 1})
make_item("Sub Assembly Item 2", properties={"is_stock_item": 1})
make_item("RM Item 1", properties={"is_stock_item": 1})
make_item("RM Item 2", properties={"is_stock_item": 1})
make_item("_Test FG Item 3", properties={"is_stock_item": 1})
make_item("_Test FG Item 4", properties={"is_stock_item": 1})
make_bom(item="Sub Assembly Item", raw_materials=["RM Item 1", "RM Item 2"])
make_bom(item="Sub Assembly Item 2", raw_materials=["RM Item 2"])
make_bom(item="_Test FG Item", raw_materials=["Sub Assembly Item", "RM Item 1"])
make_bom(item="_Test FG Item 2", raw_materials=["Sub Assembly Item"])
make_bom(item="_Test FG Item 3", raw_materials=["RM Item 1"])
make_bom(item="_Test FG Item 4", raw_materials=["Sub Assembly Item 2"])
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -1899,13 +1905,40 @@ class TestProductionPlan(IntegrationTestCase):
"warehouse": "_Test Warehouse - _TC",
},
)
# Assembly item with similar RM item
plan.append(
"po_items",
{
"use_multi_level_bom": 1,
"item_code": "_Test FG Item 3",
"bom_no": frappe.db.get_value("Item", "_Test FG Item 3", "default_bom"),
"planned_qty": 10,
"planned_start_date": now_datetime(),
"stock_uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
},
)
# Sub-assembly item with similar RM item
plan.append(
"po_items",
{
"use_multi_level_bom": 1,
"item_code": "_Test FG Item 4",
"bom_no": frappe.db.get_value("Item", "_Test FG Item 4", "default_bom"),
"planned_qty": 10,
"planned_start_date": now_datetime(),
"stock_uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
},
)
plan.save()
plan.ignore_existing_ordered_qty = 1
plan.get_sub_assembly_items()
self.assertEqual(plan.sub_assembly_items[0].qty, 20)
self.assertEqual(plan.sub_assembly_items[1].qty, 50)
self.assertEqual(plan.sub_assembly_items[0].qty, 20) # Sub Assembly For FG 1
self.assertEqual(plan.sub_assembly_items[1].qty, 50) # Sub Assembly For FG 2
self.assertEqual(plan.sub_assembly_items[2].qty, 10) # Sub Assembly For FG 4
from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_items_for_material_requests,
@@ -1913,8 +1946,11 @@ class TestProductionPlan(IntegrationTestCase):
mr_items = get_items_for_material_requests(plan.as_dict())
self.assertEqual(mr_items[0].get("quantity"), 80)
self.assertEqual(mr_items[1].get("quantity"), 70)
# RM Item 1 (FG1 (100 + 100) + FG2 (50) + FG3 (10) - 90 in stock - 80 sub assembly stock)
self.assertEqual(mr_items[0].get("quantity"), 90)
# RM Item 2 (FG1 (100) + FG2 (50) + FG4 (10) - 80 sub assembly stock)
self.assertEqual(mr_items[1].get("quantity"), 80)
def test_stock_reservation_against_production_plan(self):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt

View File

@@ -767,12 +767,24 @@ erpnext.work_order = {
frm.add_custom_button(
__("Additional Material Transfer"),
function () {
erpnext.work_order.make_se(
frm,
"Material Transfer for Manufacture",
qty,
1
);
let purpose = "Material Transfer for Manufacture";
erpnext.work_order
.show_prompt_for_qty_input(frm, purpose, qty, 1)
.then((data) => {
return frappe.xcall(
"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry",
{
work_order_id: frm.doc.name,
purpose: purpose,
qty: data.qty,
is_additional_transfer_entry: 1,
}
);
})
.then((stock_entry) => {
frappe.model.sync(stock_entry);
frappe.set_route("Form", stock_entry.doctype, stock_entry.name);
});
},
__("Make")
);
@@ -940,8 +952,8 @@ erpnext.work_order = {
return flt(max, precision("qty"));
},
show_prompt_for_qty_input: function (frm, purpose) {
let max = this.get_max_transferable_qty(frm, purpose);
show_prompt_for_qty_input: function (frm, purpose, qty, additional_transfer_entry) {
let max = !additional_transfer_entry ? this.get_max_transferable_qty(frm, purpose) : qty;
let fields = [
{
@@ -951,7 +963,10 @@ erpnext.work_order = {
description: __("Max: {0}", [max]),
default: max,
},
{
];
if (!additional_transfer_entry) {
fields.push({
fieldtype: "Check",
label: __("Consider Process Loss"),
fieldname: "consider_process_loss",
@@ -963,8 +978,8 @@ erpnext.work_order = {
frm.qty_prompt.set_value("qty", max);
}
},
},
];
});
}
if (purpose === "Disassemble") {
fields.push({

View File

@@ -1336,6 +1336,9 @@ class WorkOrder(Document):
self.set_available_qty()
def update_transferred_qty_for_required_items(self):
if self.skip_transfer:
return
ste = frappe.qb.DocType("Stock Entry")
ste_child = frappe.qb.DocType("Stock Entry Detail")

View File

@@ -31,6 +31,16 @@ class ProductionPlanReport:
return self.columns, self.data
def get_open_orders(self):
"""
Load open orders into self.orders according to the report filters.
Builds and executes a query determined by self.filters.based_on and self.filters.order_by:
- For "Work Order": selects production fields for active work orders and optionally orders by planned start date or filters by docnames.
- For other doctypes: joins with the corresponding "<Doctype> Item" child table to select BOM/item-based production lines; includes special handling for "Sales Order" (filters undelivered/open orders, optional ordering by delivery date or total amount) and "Material Request" (filters manufacture-type requests with outstanding quantity, optional ordering by required date).
The query always restricts to submitted documents (docstatus == 1) and applies a company filter when provided.
After execution, assigns the query result (list of dicts) to self.orders.
"""
doctype, order_by = self.filters.based_on, self.filters.order_by
parent = frappe.qb.DocType(doctype)
@@ -113,6 +123,13 @@ class ProductionPlanReport:
self.orders = query.run(as_dict=True)
def get_raw_materials(self):
"""Retrieve raw materials and source warehouses for production orders.
This method collects BOM or Work Order items depending on the selected
filter and updates `self.raw_materials_dict`, `self.warehouses`,
and `self.item_codes` accordingly.
"""
if not self.orders:
return
self.warehouses = [d.warehouse for d in self.orders]
@@ -135,7 +152,7 @@ class ProductionPlanReport:
)
or []
)
self.warehouses.extend([d.source_warehouse for d in raw_materials])
self.warehouses.extend([d.warehouse for d in raw_materials])
else:
bom_nos = []
@@ -426,4 +443,4 @@ class ProductionPlanReport:
"width": 140,
},
]
)
)

View File

@@ -4,6 +4,9 @@ import frappe
def execute():
# Update the reference_name, reference_doctype fields for Serial No where it is null
if not frappe.db.has_column("Serial and Batch Bundle", "posting_date"):
return
sabb = frappe.qb.DocType("Serial and Batch Bundle")
sabb_entry = frappe.qb.DocType("Serial and Batch Entry")
serial_no = frappe.qb.DocType("Serial No").as_("sn")
@@ -16,7 +19,7 @@ def execute():
.on(sabb.name == sabb_entry.parent)
.set(serial_no.reference_name, serial_no.purchase_document_no)
.set(serial_no.reference_doctype, sabb.voucher_type)
.set(serial_no.posting_date, sabb.posting_datetime)
.set(serial_no.posting_date, sabb.posting_date)
.where(
(sabb.voucher_no == serial_no.purchase_document_no)
& (sabb.is_cancelled == 0)

View File

@@ -174,13 +174,15 @@ erpnext.buying = {
shipping_address: this.frm.doc.shipping_address,
},
callback: (r) => {
this.frm.set_value("billing_address", r.message.primary_address || "");
if (!this.frm.doc.billing_address)
this.frm.set_value("billing_address", r.message.primary_address || "");
if (!frappe.meta.has_field(this.frm.doc.doctype, "shipping_address")) return;
this.frm.set_value(
"shipping_address",
r.message.shipping_address || this.frm.doc.shipping_address || ""
);
if (
!frappe.meta.has_field(this.frm.doc.doctype, "shipping_address") ||
this.frm.doc.shipping_address
)
return;
this.frm.set_value("shipping_address", r.message.shipping_address || "");
},
});
erpnext.utils.set_letter_head(this.frm);

View File

@@ -1170,7 +1170,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (
frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype)
["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype) &&
!this.frm.doc.shipping_address
) {
let is_drop_ship = me.frm.doc.items.some((item) => item.delivered_by_supplier);
@@ -1692,6 +1693,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
);
var company_currency = this.get_company_currency();
if (
this._last_company_currency === company_currency &&
this._last_price_list_currency === this.frm.doc.price_list_currency
) {
return;
}
this._last_company_currency = company_currency;
this._last_price_list_currency = this.frm.doc.price_list_currency;
this.change_form_labels(company_currency);
this.change_grid_labels(company_currency);
this.frm.refresh_fields();

View File

@@ -20,7 +20,6 @@ frappe.realtime.on("sales_invoice_before_print", (data) => {
label: "Website",
fieldname: "website",
fieldtype: "Data",
reqd: data.website ? 0 : 1,
hidden: data.website ? 1 : 0,
},
{
@@ -59,12 +58,18 @@ frappe.realtime.on("sales_invoice_before_print", (data) => {
hidden: data.address_line ? 1 : 0,
},
{
label: "Address Line",
label: "Address Line 1",
fieldname: "address_line1",
fieldtype: "Data",
reqd: data.address_line ? 0 : 1,
hidden: data.address_line ? 1 : 0,
},
{
label: "Address Line 2",
fieldname: "address_line2",
fieldtype: "Data",
hidden: data.address_line ? 1 : 0,
},
{
label: "City",
fieldname: "city",
@@ -99,6 +104,7 @@ frappe.realtime.on("sales_invoice_before_print", (data) => {
options: "Address",
get_query: function () {
return {
query: "frappe.contacts.doctype.address.address.address_query",
filters: {
link_doctype: "Company",
link_name: data.company,

View File

@@ -5,7 +5,7 @@
<span class="workstation-status-title" style="font-size:10px">{{row.status}}</span>
</span>
</div>
<div class="workstation-image {{row.workstation_off}}" onclick="location.href='{{row.workstation_link}}'">
<div class="workstation-image {{row.workstation_off}}" onclick="location.href='{{ frappe.utils.get_form_link('Workstation', row.name)}}'">
<div class="workstation-image-container flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
{% if(row.status_image) { %}
<img class="workstation-image-cls" src="{{row.status_image}}">

View File

@@ -1011,7 +1011,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
get_query_filters: {
company: me.frm.doc.company,
docstatus: 1,
status: ["!=", "Lost"],
status: ["not in", ["Lost", "Ordered"]],
},
allow_child_item_selection: true,
child_fieldname: "items",

View File

@@ -289,6 +289,7 @@ erpnext.company.setup_queries = function (frm) {
["default_provisional_account", { root_type: ["in", ["Liability", "Asset"]] }],
["default_advance_received_account", { root_type: "Liability", account_type: "Receivable" }],
["default_advance_paid_account", { root_type: "Asset", account_type: "Payable" }],
["service_expense_account", { root_type: "Expense" }],
],
function (i, v) {
erpnext.company.set_custom_query(frm, v);

View File

@@ -108,6 +108,7 @@
"transactions_annual_history",
"purchase_expense_section",
"purchase_expense_account",
"service_expense_account",
"column_break_ereg",
"purchase_expense_contra_account",
"stock_tab",
@@ -869,6 +870,13 @@
"fieldtype": "Link",
"label": "Purchase Expense Contra Account",
"options": "Account"
},
{
"description": "For service item",
"fieldname": "service_expense_account",
"fieldtype": "Link",
"label": "Service Expense Account",
"options": "Account"
}
],
"icon": "fa fa-building",
@@ -876,7 +884,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2025-10-01 17:34:10.971627",
"modified": "2025-10-10 15:12:37.941251",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -100,6 +100,7 @@ class Company(NestedSet):
round_off_for_opening: DF.Link | None
sales_monthly_history: DF.SmallText | None
series_for_depreciation_entry: DF.Data | None
service_expense_account: DF.Link | None
stock_adjustment_account: DF.Link | None
stock_received_but_not_billed: DF.Link | None
submit_err_jv: DF.Check
@@ -570,6 +571,21 @@ class Company(NestedSet):
self.db_set("disposal_account", disposal_acct)
if not self.service_expense_account:
service_expense_acct = frappe.db.get_value(
"Account",
{
"account_name": _("Marketing Expenses"),
"company": self.name,
"is_group": 0,
"root_type": "Expense",
},
"name",
)
if service_expense_acct:
self.db_set("service_expense_account", service_expense_acct)
def _set_default_account(self, fieldname, account_type):
if self.get(fieldname):
return

View File

@@ -44,7 +44,7 @@ class SalesPartner(WebsiteGenerator):
load_address_and_contact(self)
def autoname(self):
self.name = self.partner_name
pass
def validate(self):
if not self.route:

View File

@@ -253,6 +253,20 @@ def update_roles():
def create_default_role_profiles():
for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items():
if frappe.db.exists("Role Profile", role_profile_name):
role_profile = frappe.get_doc("Role Profile", role_profile_name)
existing_roles = [row.role for row in role_profile.roles]
role_profile.roles = [row for row in role_profile.roles if row.role in roles]
for role in roles:
if role not in existing_roles:
role_profile.append("roles", {"role": role})
role_profile.save(ignore_permissions=True)
continue
role_profile = frappe.new_doc("Role Profile")
role_profile.role_profile = role_profile_name
for role in roles:
@@ -286,8 +300,8 @@ def create_letter_head():
base_path = frappe.get_app_path("erpnext", "accounts", "letterhead")
letterheads = {
"Letterhead with background colour": "letterhead_with_background_colour.html",
"Letterhead Plain": "letterhead_plain.html",
"Company Letterhead": "company_letterhead.html",
"Company Letterhead - Grey": "company_letterhead_grey.html",
}
for name, filename in letterheads.items():

View File

@@ -81,6 +81,8 @@ def update_page_info(bootinfo):
def bootinfo(bootinfo):
if bootinfo.get("user") and bootinfo["user"].get("name"):
bootinfo["user"]["employee"] = ""
frappe.session.data.employee = ""
employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name")
if employee:
bootinfo["user"]["employee"] = employee
frappe.session.data.employee = employee

View File

@@ -158,7 +158,9 @@ class Batch(Document):
@frappe.whitelist()
def recalculate_batch_qty(self):
batches = get_batch_qty(batch_no=self.name, item_code=self.item)
batches = get_batch_qty(
batch_no=self.name, item_code=self.item, for_stock_levels=True, consider_negative_batches=True
)
batch_qty = 0.0
if batches:
for row in batches:
@@ -260,6 +262,7 @@ def get_batch_qty(
"warehouse": warehouse,
"creation": creation,
"batch_no": batch_no,
"based_on": frappe.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"),
"ignore_voucher_nos": ignore_voucher_nos,
"for_stock_levels": for_stock_levels,
"consider_negative_batches": consider_negative_batches,

View File

@@ -2659,6 +2659,127 @@ class TestDeliveryNote(IntegrationTestCase):
status = frappe.db.get_value("Serial No", row, "status")
self.assertEqual(status, "Active")
def test_sales_return_for_product_bundle(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
from erpnext.stock.doctype.item.test_item import make_item
rm_items = []
for item_code, properties in {
"_Packed Service Item": {"is_stock_item": 0},
"_Packed FG Item New 1": {
"is_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "SN-PACKED-1-.#####",
},
"_Packed FG Item New 2": {
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BATCH-PACKED-2-.#####",
},
"_Packed FG Item New 3": {
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BATCH-PACKED-3-.#####",
"has_serial_no": 1,
"serial_no_series": "SN-PACKED-3-.#####",
},
}.items():
if not frappe.db.exists("Item", item_code):
make_item(item_code, properties)
if item_code != "_Packed Service Item":
rm_items.append(item_code)
for rate in [100, 200]:
make_stock_entry(item=item_code, target="_Test Warehouse - _TC", qty=5, rate=rate)
make_product_bundle("_Packed Service Item", rm_items)
dn = create_delivery_note(
item_code="_Packed Service Item",
warehouse="_Test Warehouse - _TC",
qty=5,
)
serial_batch_map = {}
for row in dn.packed_items:
self.assertTrue(row.serial_and_batch_bundle)
if row.item_code not in serial_batch_map:
serial_batch_map[row.item_code] = frappe._dict(
{
"serial_nos": [],
"batches": defaultdict(int),
"serial_no_valuation": defaultdict(float),
"batch_no_valuation": defaultdict(float),
}
)
doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
for entry in doc.entries:
if entry.serial_no:
serial_batch_map[row.item_code].serial_nos.append(entry.serial_no)
serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no] = entry.incoming_rate
if entry.batch_no:
serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty
serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no] = entry.incoming_rate
dn1 = make_sales_return(dn.name)
dn1.items[0].qty = -2
dn1.submit()
dn1.reload()
for row in dn1.packed_items:
doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
for entry in doc.entries:
if entry.serial_no:
self.assertTrue(entry.serial_no in serial_batch_map[row.item_code].serial_nos)
self.assertEqual(
entry.incoming_rate,
serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no],
)
serial_batch_map[row.item_code].serial_nos.remove(entry.serial_no)
serial_batch_map[row.item_code].serial_no_valuation.pop(entry.serial_no)
elif entry.batch_no:
serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty
self.assertTrue(entry.batch_no in serial_batch_map[row.item_code].batches)
self.assertEqual(entry.qty, 2.0)
self.assertEqual(
entry.incoming_rate,
serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no],
)
dn2 = make_sales_return(dn.name)
dn2.items[0].qty = -3
dn2.submit()
dn2.reload()
for row in dn2.packed_items:
doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
for entry in doc.entries:
if entry.serial_no:
self.assertTrue(entry.serial_no in serial_batch_map[row.item_code].serial_nos)
self.assertEqual(
entry.incoming_rate,
serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no],
)
serial_batch_map[row.item_code].serial_nos.remove(entry.serial_no)
serial_batch_map[row.item_code].serial_no_valuation.pop(entry.serial_no)
elif entry.batch_no:
serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty
self.assertEqual(serial_batch_map[row.item_code].batches[entry.batch_no], 0.0)
self.assertTrue(entry.batch_no in serial_batch_map[row.item_code].batches)
self.assertEqual(entry.qty, 3.0)
self.assertEqual(
entry.incoming_rate,
serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no],
)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")

View File

@@ -200,6 +200,7 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
}
onload() {
super.onload();
this.frm.set_query("supplier", function () {
return {
filters: {

View File

@@ -1215,7 +1215,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
buying_settings = frappe.get_single("Buying Settings")
over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance")
total_amount, total_billed_amount = 0, 0
total_amount, total_billed_amount, pi_landed_cost_amount = 0, 0, 0
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
if adjust_incoming_rate:
@@ -1255,6 +1255,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
) * item.qty
adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount"))
pi_landed_cost_amount += adjusted_amt
item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
elif amount and item.billed_amt > amount:
per_over_billed = (flt(item.billed_amt / amount, 2) * 100) - 100
@@ -1265,6 +1266,9 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
)
)
if pi_landed_cost_amount < 0:
total_billed_amount += abs(pi_landed_cost_amount)
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
pr_doc.db_set("per_billed", percent_billed)

View File

@@ -638,6 +638,17 @@ class SerialandBatchBundle(Document):
if not rate and self.voucher_detail_no and self.voucher_no:
rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
is_packed_item = False
if rate is None and child_table in ["Delivery Note Item", "Sales Invoice Item"]:
rate = frappe.db.get_value(
"Packed Item",
self.voucher_detail_no,
"incoming_rate",
)
if rate is not None:
is_packed_item = True
stock_queue = []
batches = []
if prev_sle and prev_sle.stock_queue:
@@ -659,6 +670,9 @@ class SerialandBatchBundle(Document):
elif (d.incoming_rate == rate) and not stock_queue and d.qty and d.stock_value_difference:
continue
if is_packed_item and d.incoming_rate:
rate = d.incoming_rate
d.incoming_rate = flt(rate)
if d.qty:
d.stock_value_difference = flt(d.qty) * d.incoming_rate
@@ -2337,15 +2351,15 @@ def get_reserved_batches_for_sre(kwargs) -> dict:
if kwargs.batch_no:
if isinstance(kwargs.batch_no, list):
query = query.where(sb_entry.batch_no.notin(kwargs.batch_no))
query = query.where(sb_entry.batch_no.isin(kwargs.batch_no))
else:
query = query.where(sb_entry.batch_no != kwargs.batch_no)
query = query.where(sb_entry.batch_no == kwargs.batch_no)
if kwargs.warehouse:
if isinstance(kwargs.warehouse, list):
query = query.where(sre.warehouse.notin(kwargs.warehouse))
query = query.where(sre.warehouse.isin(kwargs.warehouse))
else:
query = query.where(sre.warehouse != kwargs.warehouse)
query = query.where(sre.warehouse == kwargs.warehouse)
if kwargs.ignore_voucher_nos:
query = query.where(sre.name.notin(kwargs.ignore_voucher_nos))

View File

@@ -706,7 +706,6 @@
"depends_on": "eval:doc.purpose == \"Material Transfer for Manufacture\"",
"fieldname": "is_additional_transfer_entry",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Additional Transfer Entry",
"read_only": 1
}
@@ -716,7 +715,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-09-29 15:56:21.344296",
"modified": "2025-10-13 15:09:23.905118",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@@ -2106,10 +2106,13 @@ class StockEntry(StockController):
if not frappe.get_cached_value("Work Order", self.work_order, "reserve_stock"):
return
skip_transfer = frappe.get_cached_value("Work Order", self.work_order, "skip_transfer")
if (
self.purpose not in ["Material Transfer for Manufacture"]
and frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")
!= "BOM"
and not skip_transfer
):
return
@@ -2172,6 +2175,10 @@ class StockEntry(StockController):
self.append("items", new_row)
sorted_items = sorted(self.items, key=lambda x: x.item_code)
if self.purpose == "Manufacture":
# ensure finished item at last
sorted_items = sorted(sorted_items, key=lambda x: (x.t_warehouse))
idx = 0
for row in sorted_items:
idx += 1

View File

@@ -613,10 +613,10 @@ class StockReservationEntry(Document):
data = row_wise_serial_batch[row]
if entry.serial_no in data.serial_nos:
entry.delivered_qty = 1
entry.delivered_qty = flt(1)
elif entry.batch_no in data.batch_nos:
entry.delivered_qty = data.batch_nos[entry.batch_no]
entry.delivered_qty = flt(data.batch_nos[entry.batch_no])
entry.db_update()

View File

@@ -502,6 +502,9 @@ def get_basic_details(ctx: ItemDetailsCtx, item, overwrite_warehouse=True) -> It
}
)
if not item.is_stock_item and not out.expense_account:
out.expense_account = frappe.get_cached_value("Company", ctx.company, "service_expense_account")
default_supplier = get_default_supplier(ctx, item_defaults, item_group_defaults, brand_defaults)
if default_supplier:
out.supplier = default_supplier

View File

@@ -1293,6 +1293,12 @@ class SerialBatchCreation:
if self.get("voucher_type"):
voucher_type = self.get("voucher_type")
posting_date = frappe.db.get_value(
voucher_type,
voucher_no,
"posting_date",
)
for _i in range(abs(cint(self.actual_qty))):
serial_no = make_autoname(self.serial_no_series, "Serial No")
sr_nos.append(serial_no)
@@ -1312,6 +1318,7 @@ class SerialBatchCreation:
"Active",
voucher_type,
voucher_no,
posting_date,
self.batch_no,
)
)
@@ -1332,6 +1339,7 @@ class SerialBatchCreation:
"status",
"reference_doctype",
"reference_name",
"posting_date",
"batch_no",
]

View File

@@ -892,9 +892,8 @@ class update_entries_after:
sle.stock_value = self.wh_data.stock_value
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
if not sle.is_adjustment_entry:
sle.stock_value_difference = stock_value_difference
elif sle.is_adjustment_entry and not self.args.get("sle_id"):
sle.stock_value_difference = stock_value_difference
if sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0:
sle.stock_value_difference = (
get_stock_value_difference(
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no
@@ -1074,16 +1073,15 @@ class update_entries_after:
for d in sabb_data:
incoming_rate = get_incoming_rate_for_serial_and_batch(self.item_code, d, sn_obj)
if flt(incoming_rate, self.currency_precision) == flt(
d.valuation_rate, self.currency_precision
) and not getattr(d, "stock_queue", None):
continue
amount = incoming_rate * flt(d.qty)
tot_amt += flt(amount)
total_qty += flt(d.qty)
if flt(incoming_rate, self.currency_precision) == flt(
d.incoming_rate, self.currency_precision
) and not getattr(d, "stock_queue", None):
continue
values_to_update = {
"incoming_rate": incoming_rate,
"stock_value_difference": amount,

View File

@@ -336,6 +336,10 @@ frappe.ui.form.on("Subcontracting Receipt", {
reset_raw_materials_table: (frm) => {
frm.clear_table("supplied_items");
frm.doc.__unsaved = true;
if (!frm.doc.set_posting_time) {
frm.set_value("posting_time", frappe.datetime.now_time());
}
frm.call({
method: "reset_raw_materials",

View File

@@ -649,6 +649,7 @@
"label": "Raw Materials Actions"
},
{
"description": "Click this button if you encounter a negative stock error for a serial or batch item. The system will fetch the available serials or batches automatically.",
"fieldname": "reset_raw_materials_table",
"fieldtype": "Button",
"label": "Reset Raw Materials Table"
@@ -678,7 +679,7 @@
"in_create": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-12-06 15:24:38.384232",
"modified": "2025-10-08 21:43:27.065640",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",
@@ -739,6 +740,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "status, posting_date, supplier",
"show_name_in_global_search": 1,
"sort_field": "creation",
@@ -747,4 +749,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}